From b8bbe141a03fd490ed6f100c81a47f9f75d32b43 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 16 Nov 2025 09:18:32 +0100 Subject: [PATCH 001/325] Update prebuild.js (#6266) --- webclient/prebuild.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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`], ]; From 73ce5e051c2c445de73d1f109ab036955529b403 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 16 Nov 2025 15:09:26 +0100 Subject: [PATCH 002/325] Add min os and arch inspection (#6259) --- .ci/compile.sh | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) 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::" From 722344967f64373e43fdf7da85f19612bdc922df Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sun, 16 Nov 2025 17:35:27 +0100 Subject: [PATCH 003/325] [Home Tab] Don't add connect button with stretch (#6333) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 4 minutes Co-authored-by: Lukas Brübach --- cockatrice/src/interface/widgets/general/home_widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, From 9a3104c5ac9d98abd169f7ac2c5426ae2da72209 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 16 Nov 2025 08:56:57 -0800 Subject: [PATCH 004/325] [CardInfo] refactor some fields into a UiAttributes struct (#6322) * refactor CardInfo * refactor everything else --- .../src/game/board/abstract_card_item.cpp | 3 +- cockatrice/src/game/board/arrow_item.cpp | 4 +- cockatrice/src/game/player/player_actions.cpp | 16 ++--- .../src/game/zones/logic/table_zone_logic.cpp | 2 +- .../card_picture_loader.cpp | 2 +- .../cards/card_info_picture_widget.cpp | 2 +- .../widgets/dialogs/dlg_edit_tokens.cpp | 4 +- .../libcockatrice/card/card_info.cpp | 18 ++--- .../libcockatrice/card/card_info.h | 66 +++++++------------ .../card/database/parser/cockatrice_xml_3.cpp | 19 ++++-- .../card/database/parser/cockatrice_xml_4.cpp | 18 +++-- oracle/src/oracleimporter.cpp | 6 +- 12 files changed, 73 insertions(+), 87 deletions(-) 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/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_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/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/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(); From 537e29d937136978bb368655f71875af884d568f Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sun, 16 Nov 2025 18:04:42 +0100 Subject: [PATCH 005/325] [Game Selector] Add button to join game as judge as well as convenience filters and doxygen (#6325) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add button to join game as judge as well as convenience filters. Took 1 hour 11 minutes * Change button to filter to games created by buddies, set default filter settings to be very permissive. Took 45 minutes * Remove debug. Took 3 minutes * Update game_selector.cpp * Add spacers, rearrange. Took 20 minutes Took 20 seconds * Add explanation tooltip. Took 39 seconds * Try layouting. Took 14 minutes * Set min size, set spacing for mac os Took 3 minutes * Try without the labels. Took 3 minutes * Don't use labels. Took 5 minutes * Fine-tune. Took 2 minutes * AsJudge Took 4 minutes * Clear up comment. Took 37 seconds * Remove shift hotkey. Took 4 minutes * Spectate as judge. Took 8 minutes * Add checkBox to create game as judge. Took 7 minutes * Fix crash. Took 12 minutes * Rename, fix returns. Took 19 minutes --------- Co-authored-by: Lukas Brübach --- cockatrice/CMakeLists.txt | 1 + .../widgets/dialogs/dlg_create_game.cpp | 18 +- .../widgets/dialogs/dlg_create_game.h | 2 +- .../widgets/dialogs/dlg_filter_games.cpp | 8 +- .../widgets/dialogs/dlg_filter_games.h | 2 +- .../widgets/server/game_selector.cpp | 74 ++++++- .../interface/widgets/server/game_selector.h | 159 +++++++++++-- .../game_selector_quick_filter_toolbar.cpp | 110 +++++++++ .../game_selector_quick_filter_toolbar.h | 39 ++++ .../interface/widgets/server/games_model.cpp | 26 ++- .../interface/widgets/server/games_model.h | 208 ++++++++++++++++-- .../settings/game_filters_settings.cpp | 26 +-- .../settings/game_filters_settings.h | 4 +- 13 files changed, 594 insertions(+), 83 deletions(-) create mode 100644 cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp create mode 100644 cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h 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/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_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/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/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); From a4057582226fee3dabb53fe2ab511473f6c7342c Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 16 Nov 2025 23:51:27 +0100 Subject: [PATCH 006/325] doxygen config changes (#6330) --- .github/workflows/documentation-build.yml | 3 +++ Doxyfile | 11 ++++++----- DoxygenLayout.xml => doc/doxygen/DoxygenLayout.xml | 0 3 files changed, 9 insertions(+), 5 deletions(-) rename DoxygenLayout.xml => doc/doxygen/DoxygenLayout.xml (100%) 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/DoxygenLayout.xml b/doc/doxygen/DoxygenLayout.xml similarity index 100% rename from DoxygenLayout.xml rename to doc/doxygen/DoxygenLayout.xml From a8ee0d76481a7ddcf8ede051c1e925e29a33fc8c Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Mon, 17 Nov 2025 10:19:53 +0100 Subject: [PATCH 007/325] [Doxygen] PrintingSelector Extra Pages (#6334) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Doxygen] PrintingSelector extra pages Took 46 minutes * Update Doxyfile * Subgroup it. Took 5 minutes * Update editing_decks_printings.md --------- Co-authored-by: Lukas Brübach --- .../deck_management/editing_decks.md | 4 +- .../editing_decks_printings.md | 123 +++++++++++++++++- doc/doxygen-images/printing_selector.png | Bin 0 -> 66058 bytes .../printing_selector_disable.png | Bin 0 -> 28557 bytes .../printing_selector_enable.png | Bin 0 -> 22974 bytes .../printing_selector_navigation.png | Bin 0 -> 3840 bytes .../printing_selector_options.png | Bin 0 -> 42754 bytes .../printing_selector_pre_providerid.png | Bin 0 -> 95828 bytes 8 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 doc/doxygen-images/printing_selector.png create mode 100644 doc/doxygen-images/printing_selector_disable.png create mode 100644 doc/doxygen-images/printing_selector_enable.png create mode 100644 doc/doxygen-images/printing_selector_navigation.png create mode 100644 doc/doxygen-images/printing_selector_options.png create mode 100644 doc/doxygen-images/printing_selector_pre_providerid.png 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 0000000000000000000000000000000000000000..c06ac90bcdbef86cbe554078c943dca0acbf68fa GIT binary patch literal 66058 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:p<(<-=>-2xw9#9^KrSjJS-g%SU=l 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 YDHJ8uyc`(i;9KA%L}f+Fh4lUZ7j_!^nE(I) literal 0 HcmV?d00001 diff --git a/doc/doxygen-images/printing_selector_disable.png b/doc/doxygen-images/printing_selector_disable.png new file mode 100644 index 0000000000000000000000000000000000000000..8bdd8f2927d71dc6f0ff280110ea49d2e8141ba8 GIT binary patch literal 28557 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 L^5UhU??3)O<$COu literal 0 HcmV?d00001 diff --git a/doc/doxygen-images/printing_selector_enable.png b/doc/doxygen-images/printing_selector_enable.png new file mode 100644 index 0000000000000000000000000000000000000000..2c7d05afa53271d218e3931139ddf2d55f39b29b GIT binary patch literal 22974 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 WbIVEBee-Xzg*;H#RjW|34E`^c__F-~ literal 0 HcmV?d00001 diff --git a/doc/doxygen-images/printing_selector_navigation.png b/doc/doxygen-images/printing_selector_navigation.png new file mode 100644 index 0000000000000000000000000000000000000000..a91ed45110fef1a8b5bdb13143c975111ee61d22 GIT binary patch literal 3840 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 literal 0 HcmV?d00001 diff --git a/doc/doxygen-images/printing_selector_options.png b/doc/doxygen-images/printing_selector_options.png new file mode 100644 index 0000000000000000000000000000000000000000..2079b70ac733dd453738fcba9691e06a344d222b GIT binary patch literal 42754 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 QxBvhE07*qoM6N<$f<*@+IsgCw literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a1be993eeac9c481d50d3b71f0c65a5d5e8236b4 GIT binary patch literal 95828 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 t;aMe&tWZKmwD}v}WQbybYi6q|i$x!KXK- Date: Mon, 17 Nov 2025 03:49:45 -0800 Subject: [PATCH 008/325] [DeckLoader] Refactor to make some methods static (#6336) * Make forEachCard const * Make some DeckLoader methods static * Update usages * Update method param documentation in deck_loader.cpp --------- Co-authored-by: BruebachL <44814898+BruebachL@users.noreply.github.com> --- .../parsers/interface_json_deck_parser.h | 4 +- .../src/interface/deck_loader/deck_loader.cpp | 39 +++++++----- .../src/interface/deck_loader/deck_loader.h | 59 ++++++++++--------- .../dialogs/dlg_load_deck_from_clipboard.cpp | 10 ++-- .../dialogs/dlg_load_deck_from_website.cpp | 2 +- .../dialogs/dlg_select_set_for_cards.cpp | 6 +- .../widgets/tabs/abstract_tab_deck_editor.cpp | 13 ++-- .../deck_preview/deck_preview_widget.cpp | 8 +-- .../libcockatrice/deck_list/deck_list.cpp | 2 +- .../libcockatrice/deck_list/deck_list.h | 2 +- 10 files changed, 80 insertions(+), 65 deletions(-) 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/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/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/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/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 From 8788a7aada33c8705043a44b7cdbf2b1ce8034cc Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:16:39 +0100 Subject: [PATCH 009/325] [DeckLoader] Disable copy constructor (#6338) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 1 hour 19 minutes Co-authored-by: Lukas Brübach --- cockatrice/src/game/player/player.cpp | 2 +- cockatrice/src/interface/deck_loader/deck_loader.cpp | 6 ------ cockatrice/src/interface/deck_loader/deck_loader.h | 7 +++++-- .../widgets/dialogs/dlg_load_deck_from_clipboard.cpp | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index 300300de3..1ff2eb381 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -265,7 +265,7 @@ void Player::deleteCard(CardItem *card) void Player::setDeck(const DeckLoader &_deck) { - deck = new DeckLoader(_deck); + deck = new DeckLoader(this, _deck.getDeckList()); emit deckChanged(); } diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index ebc951c6e..3d739cade 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -36,12 +36,6 @@ DeckLoader::DeckLoader(QObject *parent, DeckList *_deckList) deckList->setParent(this); } -DeckLoader::DeckLoader(const DeckLoader &other) - : QObject(), deckList(other.deckList), lastFileName(other.lastFileName), lastFileFormat(other.lastFileFormat), - lastRemoteDeckId(other.lastRemoteDeckId) -{ -} - void DeckLoader::setDeckList(DeckList *_deckList) { deckList = _deckList; diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h index a83542ce9..be4601cd3 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.h +++ b/cockatrice/src/interface/deck_loader/deck_loader.h @@ -53,8 +53,11 @@ private: public: DeckLoader(QObject *parent); DeckLoader(QObject *parent, DeckList *_deckList); + DeckLoader(const DeckLoader &) = delete; + DeckLoader &operator=(const DeckLoader &) = delete; + void setDeckList(DeckList *_deckList); - DeckLoader(const DeckLoader &other); + const QString &getLastFileName() const { return lastFileName; @@ -106,7 +109,7 @@ public: bool convertToCockatriceFormat(QString fileName); - DeckList *getDeckList() + DeckList *getDeckList() const { return deckList; } 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 3385dd41e..6ba05f0b0 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 @@ -142,7 +142,7 @@ DlgEditDeckInClipboard::DlgEditDeckInClipboard(const DeckLoader &deckList, bool { setWindowTitle(tr("Edit deck in clipboard")); - deckLoader = new DeckLoader(deckList); + deckLoader = new DeckLoader(this, deckList.getDeckList()); deckLoader->setParent(this); DlgEditDeckInClipboard::actRefresh(); From 9957cb20e24781f39d12aba441c2b15731c528e2 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 20 Nov 2025 12:52:14 +0100 Subject: [PATCH 010/325] [Refactor] Move AbstractGraphicsItem and GraphicsItemType to game_graphics/board (#6342) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Refactor] Move AbstractGraphicsItem and GraphicsItemType to game_graphics/board folder. Took 3 minutes * Update CMakeLists.txt Took 12 minutes * Update CMakeLists.txt Took 12 minutes Took 2 minutes Took 16 seconds --------- Co-authored-by: Lukas Brübach --- cockatrice/CMakeLists.txt | 2 +- cockatrice/src/game/board/abstract_card_item.h | 2 +- cockatrice/src/game/board/arrow_target.h | 2 +- cockatrice/src/game/board/counter_general.cpp | 2 +- cockatrice/src/game/hand_counter.h | 4 ++-- cockatrice/src/game/phases_toolbar.h | 2 +- cockatrice/src/game/player/player.h | 2 +- cockatrice/src/game/player/player_area.h | 2 +- cockatrice/src/game/player/player_target.h | 2 +- cockatrice/src/game/zones/card_zone.h | 4 ++-- .../{game => game_graphics}/board/abstract_graphics_item.cpp | 0 .../{game => game_graphics}/board/abstract_graphics_item.h | 0 .../src/{game => game_graphics}/board/graphics_item_type.h | 0 13 files changed, 12 insertions(+), 12 deletions(-) rename cockatrice/src/{game => game_graphics}/board/abstract_graphics_item.cpp (100%) rename cockatrice/src/{game => game_graphics}/board/abstract_graphics_item.h (100%) rename cockatrice/src/{game => game_graphics}/board/graphics_item_type.h (100%) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index d77d70d36..03a64ba3a 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -53,7 +53,6 @@ set(cockatrice_SOURCES src/game/board/abstract_card_drag_item.cpp src/game/board/abstract_card_item.cpp src/game/board/abstract_counter.cpp - src/game/board/abstract_graphics_item.cpp src/game/board/arrow_item.cpp src/game/board/arrow_target.cpp src/game/board/card_drag_item.cpp @@ -113,6 +112,7 @@ set(cockatrice_SOURCES src/game/zones/table_zone.cpp src/game/zones/view_zone.cpp src/game/zones/view_zone_widget.cpp + src/game_graphics/board/abstract_graphics_item.cpp src/interface/card_picture_loader/card_picture_loader.cpp src/interface/card_picture_loader/card_picture_loader_local.cpp src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.cpp diff --git a/cockatrice/src/game/board/abstract_card_item.h b/cockatrice/src/game/board/abstract_card_item.h index a935c97b6..dad73dc14 100644 --- a/cockatrice/src/game/board/abstract_card_item.h +++ b/cockatrice/src/game/board/abstract_card_item.h @@ -7,8 +7,8 @@ #ifndef ABSTRACTCARDITEM_H #define ABSTRACTCARDITEM_H +#include "../../game_graphics/board/graphics_item_type.h" #include "arrow_target.h" -#include "graphics_item_type.h" #include #include diff --git a/cockatrice/src/game/board/arrow_target.h b/cockatrice/src/game/board/arrow_target.h index 8cc5d55dc..55f4ef678 100644 --- a/cockatrice/src/game/board/arrow_target.h +++ b/cockatrice/src/game/board/arrow_target.h @@ -7,7 +7,7 @@ #ifndef ARROWTARGET_H #define ARROWTARGET_H -#include "abstract_graphics_item.h" +#include "../../game_graphics/board/abstract_graphics_item.h" #include diff --git a/cockatrice/src/game/board/counter_general.cpp b/cockatrice/src/game/board/counter_general.cpp index c71077324..d68486a1b 100644 --- a/cockatrice/src/game/board/counter_general.cpp +++ b/cockatrice/src/game/board/counter_general.cpp @@ -1,7 +1,7 @@ #include "counter_general.h" +#include "../../game_graphics/board/abstract_graphics_item.h" #include "../../interface/pixel_map_generator.h" -#include "abstract_graphics_item.h" #include diff --git a/cockatrice/src/game/hand_counter.h b/cockatrice/src/game/hand_counter.h index cd2b0fe81..2c0175ecc 100644 --- a/cockatrice/src/game/hand_counter.h +++ b/cockatrice/src/game/hand_counter.h @@ -7,8 +7,8 @@ #ifndef HANDCOUNTER_H #define HANDCOUNTER_H -#include "board/abstract_graphics_item.h" -#include "board/graphics_item_type.h" +#include "../game_graphics/board/abstract_graphics_item.h" +#include "../game_graphics/board/graphics_item_type.h" #include diff --git a/cockatrice/src/game/phases_toolbar.h b/cockatrice/src/game/phases_toolbar.h index 57ab6de66..215a97dd1 100644 --- a/cockatrice/src/game/phases_toolbar.h +++ b/cockatrice/src/game/phases_toolbar.h @@ -8,7 +8,7 @@ #ifndef PHASESTOOLBAR_H #define PHASESTOOLBAR_H -#include "board/abstract_graphics_item.h" +#include "../game_graphics/board/abstract_graphics_item.h" #include #include diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h index 92125530a..151a7d30c 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player.h @@ -7,8 +7,8 @@ #ifndef PLAYER_H #define PLAYER_H +#include "../../game_graphics/board/abstract_graphics_item.h" #include "../../interface/widgets/menus/tearoff_menu.h" -#include "../board/abstract_graphics_item.h" #include "../dialogs/dlg_create_token.h" #include "menu/player_menu.h" #include "player_actions.h" diff --git a/cockatrice/src/game/player/player_area.h b/cockatrice/src/game/player/player_area.h index ec065fb4b..ec0d23cb6 100644 --- a/cockatrice/src/game/player/player_area.h +++ b/cockatrice/src/game/player/player_area.h @@ -7,7 +7,7 @@ #ifndef COCKATRICE_PLAYER_AREA_H #define COCKATRICE_PLAYER_AREA_H -#include "../board/graphics_item_type.h" +#include "../../game_graphics/board/graphics_item_type.h" #include "QGraphicsItem" /** diff --git a/cockatrice/src/game/player/player_target.h b/cockatrice/src/game/player/player_target.h index bf26838c3..aaa0f0610 100644 --- a/cockatrice/src/game/player/player_target.h +++ b/cockatrice/src/game/player/player_target.h @@ -7,9 +7,9 @@ #ifndef PLAYERTARGET_H #define PLAYERTARGET_H +#include "../../game_graphics/board/graphics_item_type.h" #include "../board/abstract_counter.h" #include "../board/arrow_target.h" -#include "../board/graphics_item_type.h" #include #include diff --git a/cockatrice/src/game/zones/card_zone.h b/cockatrice/src/game/zones/card_zone.h index f489915e2..6fe8157e4 100644 --- a/cockatrice/src/game/zones/card_zone.h +++ b/cockatrice/src/game/zones/card_zone.h @@ -7,8 +7,8 @@ #ifndef CARDZONE_H #define CARDZONE_H -#include "../board/abstract_graphics_item.h" -#include "../board/graphics_item_type.h" +#include "../../game_graphics/board/abstract_graphics_item.h" +#include "../../game_graphics/board/graphics_item_type.h" #include "logic/card_zone_logic.h" #include diff --git a/cockatrice/src/game/board/abstract_graphics_item.cpp b/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp similarity index 100% rename from cockatrice/src/game/board/abstract_graphics_item.cpp rename to cockatrice/src/game_graphics/board/abstract_graphics_item.cpp diff --git a/cockatrice/src/game/board/abstract_graphics_item.h b/cockatrice/src/game_graphics/board/abstract_graphics_item.h similarity index 100% rename from cockatrice/src/game/board/abstract_graphics_item.h rename to cockatrice/src/game_graphics/board/abstract_graphics_item.h diff --git a/cockatrice/src/game/board/graphics_item_type.h b/cockatrice/src/game_graphics/board/graphics_item_type.h similarity index 100% rename from cockatrice/src/game/board/graphics_item_type.h rename to cockatrice/src/game_graphics/board/graphics_item_type.h From ab5d6db8a2ac6ec6273ae4c8299f94541ee64c79 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 20 Nov 2025 13:20:09 +0100 Subject: [PATCH 011/325] [DeckList] Disable copy constructor (#6339) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [DeckList] Disable copy constructor Took 1 hour 44 minutes Took 1 minute # Commit time for manual adjustment: # Took 28 seconds Took 33 seconds * Revert member to pointer. Took 19 minutes * Revert pulling up setters/getters now that getDeckList is no longer const. Took 6 minutes * Revert more. Took 2 minutes * One more fix. Took 1 minute * Update cockatrice/src/interface/deck_loader/deck_loader.cpp Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com> --------- Co-authored-by: Lukas Brübach Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com> --- cockatrice/src/game/deckview/deck_view.cpp | 2 +- cockatrice/src/game/player/player.cpp | 3 +- cockatrice/src/game/player/player.h | 2 +- .../src/interface/deck_loader/deck_loader.cpp | 9 ++---- .../src/interface/deck_loader/deck_loader.h | 4 +-- .../deck_editor_deck_dock_widget.cpp | 6 ++-- .../dialogs/dlg_load_deck_from_clipboard.cpp | 6 ++-- .../dialogs/dlg_load_deck_from_clipboard.h | 2 +- .../widgets/tabs/abstract_tab_deck_editor.cpp | 2 +- .../widgets/tabs/tab_deck_storage.cpp | 6 ++-- .../interface/widgets/tabs/tab_supervisor.cpp | 4 +-- .../tab_deck_storage_visual.cpp | 6 ++-- .../deck_preview_deck_tags_display_widget.cpp | 29 +++++++++---------- .../deck_preview_deck_tags_display_widget.h | 6 ++-- .../deck_preview/deck_preview_widget.cpp | 2 +- .../libcockatrice/deck_list/deck_list.cpp | 14 --------- .../libcockatrice/deck_list/deck_list.h | 5 ++-- .../models/deck_list/deck_list_model.cpp | 10 +++---- 18 files changed, 49 insertions(+), 69 deletions(-) diff --git a/cockatrice/src/game/deckview/deck_view.cpp b/cockatrice/src/game/deckview/deck_view.cpp index 48c906100..64e9abc46 100644 --- a/cockatrice/src/game/deckview/deck_view.cpp +++ b/cockatrice/src/game/deckview/deck_view.cpp @@ -330,7 +330,7 @@ void DeckViewScene::setDeck(const DeckList &_deck) if (deck) delete deck; - deck = new DeckList(_deck); + deck = new DeckList(_deck.writeToString_Native()); rebuildTree(); applySideboardPlan(deck->getCurrentSideboardPlan()); rearrangeItems(); diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index 1ff2eb381..ffb26c26e 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -263,7 +263,8 @@ void Player::deleteCard(CardItem *card) } } -void Player::setDeck(const DeckLoader &_deck) +// TODO: Does a player need a DeckLoader? +void Player::setDeck(DeckLoader &_deck) { deck = new DeckLoader(this, _deck.getDeckList()); diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h index 151a7d30c..0751abb14 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player.h @@ -130,7 +130,7 @@ public: return playerMenu; } - void setDeck(const DeckLoader &_deck); + void setDeck(DeckLoader &_deck); [[nodiscard]] DeckLoader *getDeck() const { diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index 3d739cade..320302142 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -33,13 +33,6 @@ DeckLoader::DeckLoader(QObject *parent) DeckLoader::DeckLoader(QObject *parent, DeckList *_deckList) : QObject(parent), deckList(_deckList), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1) { - deckList->setParent(this); -} - -void DeckLoader::setDeckList(DeckList *_deckList) -{ - deckList = _deckList; - deckList->setParent(this); } bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest) @@ -159,12 +152,14 @@ bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt) break; case CockatriceFormat: result = deckList->saveToFile_Native(&file); + qCInfo(DeckLoaderLog) << "Saving to " << fileName << "-" << result; break; } if (result) { lastFileName = fileName; lastFileFormat = fmt; + qCInfo(DeckLoaderLog) << "Deck was saved -" << result; } file.flush(); diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h index be4601cd3..57305b7de 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.h +++ b/cockatrice/src/interface/deck_loader/deck_loader.h @@ -56,8 +56,6 @@ public: DeckLoader(const DeckLoader &) = delete; DeckLoader &operator=(const DeckLoader &) = delete; - void setDeckList(DeckList *_deckList); - const QString &getLastFileName() const { return lastFileName; @@ -109,7 +107,7 @@ public: bool convertToCockatriceFormat(QString fileName); - DeckList *getDeckList() const + DeckList *getDeckList() { return deckList; } 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 7a4820dd5..11818a912 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 @@ -109,7 +109,7 @@ void DeckEditorDeckDockWidget::createDeckDock() &DeckEditorDeckDockWidget::setBannerCard); bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); - deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList()); + deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader); deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible()); activeGroupCriteriaLabel = new QLabel(this); @@ -382,7 +382,7 @@ void DeckEditorDeckDockWidget::setDeck(DeckLoader *_deck) deckView->expandAll(); deckView->expandAll(); - deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList()); + deckTagsDisplayWidget->connectDeckList(); emit deckChanged(); } @@ -412,7 +412,7 @@ void DeckEditorDeckDockWidget::cleanDeck() emit deckModified(); emit deckChanged(); updateBannerCardComboBox(); - deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList()); + deckTagsDisplayWidget->connectDeckList(); } void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index) 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 6ba05f0b0..2a276479f 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 @@ -133,16 +133,16 @@ void DlgLoadDeckFromClipboard::actOK() /** * Creates the dialog window for the "Edit deck in clipboard" action * - * @param deckList The existing deck in the deck editor. Copies the instance + * @param _deckLoader The existing deck in the deck editor. Copies the instance * @param _annotated Whether to add annotations to the text that is loaded from the deck * @param parent The parent widget */ -DlgEditDeckInClipboard::DlgEditDeckInClipboard(const DeckLoader &deckList, bool _annotated, QWidget *parent) +DlgEditDeckInClipboard::DlgEditDeckInClipboard(DeckLoader *_deckLoader, bool _annotated, QWidget *parent) : AbstractDlgDeckTextEdit(parent), annotated(_annotated) { setWindowTitle(tr("Edit deck in clipboard")); - deckLoader = new DeckLoader(this, deckList.getDeckList()); + deckLoader = new DeckLoader(this, _deckLoader->getDeckList()); deckLoader->setParent(this); DlgEditDeckInClipboard::actRefresh(); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h index d93a020cc..982840228 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h @@ -88,7 +88,7 @@ private: bool annotated; public: - explicit DlgEditDeckInClipboard(const DeckLoader &deckList, bool _annotated, QWidget *parent = nullptr); + explicit DlgEditDeckInClipboard(DeckLoader *_deckLoader, bool _annotated, QWidget *parent = nullptr); [[nodiscard]] DeckLoader *getDeckList() const override { 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 65e1e4e71..8a26b3a6a 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -486,7 +486,7 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard() */ void AbstractTabDeckEditor::editDeckInClipboard(bool annotated) { - DlgEditDeckInClipboard dlg(*getDeckLoader(), annotated, this); + DlgEditDeckInClipboard dlg(getDeckLoader(), annotated, this); if (!dlg.exec()) return; diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp index bf74b73fb..b483f0616 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp @@ -242,11 +242,11 @@ void TabDeckStorage::actOpenLocalDeck() continue; QString filePath = localDirModel->filePath(curLeft); - DeckLoader deckLoader(this); - if (!deckLoader.loadFromFile(filePath, DeckLoader::CockatriceFormat, true)) + auto deckLoader = new DeckLoader(this); + if (!deckLoader->loadFromFile(filePath, DeckLoader::CockatriceFormat, true)) continue; - emit openDeckEditor(&deckLoader); + emit openDeckEditor(deckLoader); } } diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp index 6d550be01..902b1f589 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp @@ -869,7 +869,7 @@ TabDeckEditor *TabSupervisor::addDeckEditorTab(DeckLoader *deckToOpen) { auto *tab = new TabDeckEditor(this); if (deckToOpen) - tab->openDeck(new DeckLoader(this, deckToOpen->getDeckList())); + tab->openDeck(deckToOpen); connect(tab, &AbstractTabDeckEditor::deckEditorClosing, this, &TabSupervisor::deckEditorClosed); connect(tab, &AbstractTabDeckEditor::openDeckEditor, this, &TabSupervisor::addDeckEditorTab); myAddTab(tab); @@ -882,7 +882,7 @@ TabDeckEditorVisual *TabSupervisor::addVisualDeckEditorTab(DeckLoader *deckToOpe { auto *tab = new TabDeckEditorVisual(this); if (deckToOpen) - tab->openDeck(new DeckLoader(this, deckToOpen->getDeckList())); + tab->openDeck(deckToOpen); connect(tab, &AbstractTabDeckEditor::deckEditorClosing, this, &TabSupervisor::deckEditorClosed); connect(tab, &AbstractTabDeckEditor::openDeckEditor, this, &TabSupervisor::addVisualDeckEditorTab); myAddTab(tab); diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp index b4749f408..bfb498909 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp @@ -27,11 +27,11 @@ TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor) void TabDeckStorageVisual::actOpenLocalDeck(const QString &filePath) { - DeckLoader deckLoader(this); - if (!deckLoader.loadFromFile(filePath, DeckLoader::getFormatFromName(filePath), true)) { + auto deckLoader = new DeckLoader(this); + if (!deckLoader->loadFromFile(filePath, DeckLoader::getFormatFromName(filePath), true)) { QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(filePath)); return; } - emit openDeckEditor(&deckLoader); + emit openDeckEditor(deckLoader); } diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp index eb105697f..e5cbc0adb 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp @@ -14,8 +14,8 @@ #include #include -DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList) - : QWidget(_parent), deckList(nullptr) +DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckLoader *_deckLoader) + : QWidget(_parent), deckLoader(_deckLoader) { setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); @@ -28,21 +28,20 @@ DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_par flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); - if (_deckList) { - connectDeckList(_deckList); - } + connectDeckList(); layout->addWidget(flowWidget); } -void DeckPreviewDeckTagsDisplayWidget::connectDeckList(DeckList *_deckList) +void DeckPreviewDeckTagsDisplayWidget::connectDeckList() { - if (deckList) { - disconnect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags); + if (deckLoader) { + disconnect(deckLoader->getDeckList(), &DeckList::deckTagsChanged, this, + &DeckPreviewDeckTagsDisplayWidget::refreshTags); } - deckList = _deckList; - connect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags); + connect(deckLoader->getDeckList(), &DeckList::deckTagsChanged, this, + &DeckPreviewDeckTagsDisplayWidget::refreshTags); refreshTags(); } @@ -51,7 +50,7 @@ void DeckPreviewDeckTagsDisplayWidget::refreshTags() { flowWidget->clearLayout(); - for (const QString &tag : deckList->getTags()) { + for (const QString &tag : deckLoader->getDeckList()->getTags()) { flowWidget->addWidget(new DeckPreviewTagDisplayWidget(this, tag)); } @@ -98,7 +97,7 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() if (qobject_cast(parentWidget())) { auto *deckPreviewWidget = qobject_cast(parentWidget()); QStringList knownTags = deckPreviewWidget->visualDeckStorageWidget->tagFilterWidget->getAllKnownTags(); - QStringList activeTags = deckList->getTags(); + QStringList activeTags = deckLoader->getDeckList()->getTags(); bool canAddTags = true; @@ -149,7 +148,7 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() DeckPreviewTagDialog dialog(knownTags, activeTags); if (dialog.exec() == QDialog::Accepted) { QStringList updatedTags = dialog.getActiveTags(); - deckList->setTags(updatedTags); + deckLoader->getDeckList()->setTags(updatedTags); deckPreviewWidget->deckLoader->saveToFile(deckPreviewWidget->filePath, DeckLoader::CockatriceFormat); } } @@ -175,12 +174,12 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() knownTags.removeDuplicates(); } - QStringList activeTags = deckList->getTags(); + QStringList activeTags = deckLoader->getDeckList()->getTags(); DeckPreviewTagDialog dialog(knownTags, activeTags); if (dialog.exec() == QDialog::Accepted) { QStringList updatedTags = dialog.getActiveTags(); - deckList->setTags(updatedTags); + deckLoader->getDeckList()->setTags(updatedTags); deckEditor->setModified(true); } } diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h index 4270e38e5..645c769bd 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h @@ -20,10 +20,10 @@ class DeckPreviewDeckTagsDisplayWidget : public QWidget Q_OBJECT public: - explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList); - void connectDeckList(DeckList *_deckList); + explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckLoader *_deckLoader); + void connectDeckList(); void refreshTags(); - DeckList *deckList; + DeckLoader *deckLoader; FlowWidget *flowWidget; public slots: 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 8b2bec10b..ba111100d 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 @@ -82,7 +82,7 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess) setFilePath(deckLoader->getLastFileName()); colorIdentityWidget = new ColorIdentityWidget(this, getColorIdentity()); - deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeckList()); + deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader); bannerCardLabel = new QLabel(this); bannerCardLabel->setObjectName("bannerCardLabel"); diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index a12c6fe0e..be26c51a3 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -80,20 +80,6 @@ DeckList::DeckList() root = new InnerDecklistNode; } -// TODO: https://qt-project.org/doc/qt-4.8/qobject.html#no-copy-constructor-or-assignment-operator -DeckList::DeckList(const DeckList &other) - : QObject(), name(other.name), comments(other.comments), bannerCard(other.bannerCard), - lastLoadedTimestamp(other.lastLoadedTimestamp), tags(other.tags), cachedDeckHash(other.cachedDeckHash) -{ - root = new InnerDecklistNode(other.getRoot()); - - QMapIterator spIterator(other.getSideboardPlans()); - while (spIterator.hasNext()) { - spIterator.next(); - sideboardPlans.insert(spIterator.key(), new SideboardPlan(spIterator.key(), spIterator.value()->getMoveList())); - } -} - DeckList::DeckList(const QString &nativeString) { root = new InnerDecklistNode; diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index b724efc0f..824d62f45 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -215,8 +215,9 @@ public slots: public: /// @brief Construct an empty deck. explicit DeckList(); - /// @brief Deep-copy constructor. - DeckList(const DeckList &other); + /// @brief Delete copy constructor. + DeckList(const DeckList &) = delete; + DeckList &operator=(const DeckList &) = delete; /// @brief Construct from a serialized native-format string. explicit DeckList(const QString &nativeString); ~DeckList() override; 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 52e3b678c..ed77b55ca 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -533,14 +533,14 @@ void DeckListModel::cleanList() } /** - * @param _deck The deck. Takes ownership of the object + * @param _deck The deck. */ void DeckListModel::setDeckList(DeckList *_deck) { - deckList->deleteLater(); - deckList = _deck; - deckList->setParent(this); - rebuildTree(); + if (deckList != _deck) { + deckList = _deck; + rebuildTree(); + } } QList DeckListModel::getCards() const From c46f6d1178aca700ffc240a08079066b86efa825 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 20 Nov 2025 05:54:23 -0800 Subject: [PATCH 012/325] Support `flavorName` in PrintingInfo and cache the altNames in CardInfo (#6335) * Support flavorName property and cache altNames * update oracleimporter * update cards.xsd --- doc/carddatabase_v4/cards.xsd | 1 + .../libcockatrice/card/card_info.cpp | 25 +++++++++++++-- .../libcockatrice/card/card_info.h | 31 ++++++++++++++++--- .../card/database/card_database.cpp | 5 +-- .../card/printing/printing_info.cpp | 5 +++ .../card/printing/printing_info.h | 7 +++++ oracle/src/oracleimporter.cpp | 7 +++++ 7 files changed, 72 insertions(+), 9 deletions(-) diff --git a/doc/carddatabase_v4/cards.xsd b/doc/carddatabase_v4/cards.xsd index 9a879302b..92d30b94f 100644 --- a/doc/carddatabase_v4/cards.xsd +++ b/doc/carddatabase_v4/cards.xsd @@ -30,6 +30,7 @@ + diff --git a/libcockatrice_card/libcockatrice/card/card_info.cpp b/libcockatrice_card/libcockatrice/card/card_info.cpp index 58aa83848..3054a10cf 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.cpp +++ b/libcockatrice_card/libcockatrice/card/card_info.cpp @@ -32,7 +32,7 @@ CardInfo::CardInfo(const QString &_name, { simpleName = CardInfo::simplifyName(name); - refreshCachedSetNames(); + refreshCachedSets(); } CardInfoPtr CardInfo::newInstance(const QString &_name) @@ -84,7 +84,7 @@ void CardInfo::addToSet(const CardSetPtr &_set, const PrintingInfo _info) setsToPrintings[_set->getShortName()].append(_info); } - refreshCachedSetNames(); + refreshCachedSets(); } void CardInfo::combineLegalities(const QVariantHash &props) @@ -98,6 +98,12 @@ void CardInfo::combineLegalities(const QVariantHash &props) } } +void CardInfo::refreshCachedSets() +{ + refreshCachedSetNames(); + refreshCachedAltNames(); +} + void CardInfo::refreshCachedSetNames() { QStringList setList; @@ -113,6 +119,21 @@ void CardInfo::refreshCachedSetNames() setsNames = setList.join(", "); } +void CardInfo::refreshCachedAltNames() +{ + altNames.clear(); + + // update the altNames with the flavorNames + for (const auto &printings : setsToPrintings) { + for (const auto &printing : printings) { + QString flavorName = printing.getFlavorName(); + if (!flavorName.isEmpty()) { + altNames.insert(flavorName); + } + } + } +} + QString CardInfo::simplifyName(const QString &name) { static const QRegularExpression spaceOrSplit("(\\s+|\\/\\/.*)"); diff --git a/libcockatrice_card/libcockatrice/card/card_info.h b/libcockatrice_card/libcockatrice/card/card_info.h index a52c0553a..ab1d51a49 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.h +++ b/libcockatrice_card/libcockatrice/card/card_info.h @@ -77,8 +77,9 @@ private: QList reverseRelatedCards; ///< Cards that refer back to this card. 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. UiAttributes uiAttributes; ///< Attributes that affect display and game logic + QString setsNames; ///< Cached, human-readable list of set names. + QSet altNames; ///< Cached set of alternate names, used when searching ///@} public: @@ -114,7 +115,8 @@ 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), uiAttributes(other.uiAttributes) + setsToPrintings(other.setsToPrintings), uiAttributes(other.uiAttributes), setsNames(other.setsNames), + altNames(other.altNames) { } @@ -185,6 +187,10 @@ public: { return simpleName; } + const QSet &getAltNames() + { + return altNames; + }; const QString &getText() const { return text; @@ -303,11 +309,11 @@ public: void combineLegalities(const QVariantHash &props); /** - * @brief Refreshes the cached, human-readable list of set names. + * @brief Refreshes all cached fields that are calculated from the contained sets and printings. * - * Typically called after adding or modifying set memberships. + * Typically called after adding or modifying set memberships or printings. */ - void refreshCachedSetNames(); + void refreshCachedSets(); /** * @brief Simplifies a name for fuzzy matching. @@ -319,6 +325,21 @@ public: */ static QString simplifyName(const QString &name); +private: + /** + * @brief Refreshes the cached, human-readable list of set names. + * + * Typically called after adding or modifying set memberships. + */ + void refreshCachedSetNames(); + + /** + * @brief Refreshes the cached list of alt names for the card. + * + * Typically called after adding or modifying the contained printings. + */ + void refreshCachedAltNames(); + signals: /** * @brief Emitted when a pixmap for this card has been updated or finished loading. diff --git a/libcockatrice_card/libcockatrice/card/database/card_database.cpp b/libcockatrice_card/libcockatrice/card/database/card_database.cpp index 7e23b4663..57b3ce06e 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database.cpp @@ -194,8 +194,9 @@ void CardDatabase::markAllSetsAsKnown() void CardDatabase::notifyEnabledSetsChanged() { // refresh the list of cached set names - for (const CardInfoPtr &card : cards) - card->refreshCachedSetNames(); + for (const CardInfoPtr &card : cards) { + card->refreshCachedSets(); + } // inform the carddatabasemodels that they need to re-check their list of cards emit cardDatabaseEnabledSetsChanged(); diff --git a/libcockatrice_card/libcockatrice/card/printing/printing_info.cpp b/libcockatrice_card/libcockatrice/card/printing/printing_info.cpp index 9de563d78..340998565 100644 --- a/libcockatrice_card/libcockatrice/card/printing/printing_info.cpp +++ b/libcockatrice_card/libcockatrice/card/printing/printing_info.cpp @@ -12,4 +12,9 @@ PrintingInfo::PrintingInfo(const CardSetPtr &_set) : set(_set) QString PrintingInfo::getUuid() const { return properties.value("uuid").toString(); +} + +QString PrintingInfo::getFlavorName() const +{ + return properties.value("flavorName").toString(); } \ No newline at end of file diff --git a/libcockatrice_card/libcockatrice/card/printing/printing_info.h b/libcockatrice_card/libcockatrice/card/printing/printing_info.h index 37d7a15e2..4329ca2f4 100644 --- a/libcockatrice_card/libcockatrice/card/printing/printing_info.h +++ b/libcockatrice_card/libcockatrice/card/printing/printing_info.h @@ -110,6 +110,13 @@ public: * @return A string representing the providerID. */ QString getUuid() const; + + /** + * @brief Returns the flavorName for this printing. + * + * @return The flavorName, or empty if it isn't present. + */ + QString getFlavorName() const; }; #endif // COCKATRICE_PRINTING_INFO_H diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 41335d72d..f316e439d 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -293,6 +293,13 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList printingInfo.setProperty(xmlPropertyName, propertyValue); } + // handle flavorNames specially due to double-faced cards + QString faceFlavorName = getStringPropertyFromMap(card, "faceFlavorName"); + QString flavorName = !faceFlavorName.isEmpty() ? faceFlavorName : getStringPropertyFromMap(card, "flavorName"); + if (!flavorName.isEmpty()) { + printingInfo.setProperty("flavorName", flavorName); + } + // Identifiers for (auto i = identifierProperties.cbegin(), end = identifierProperties.cend(); i != end; ++i) { QString mtgjsonProperty = i.key(); From 846f16ddaa706cf7028a2e03a67c05e87668ee27 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:54:32 +0100 Subject: [PATCH 013/325] [DeckEditor] Deck List History Manager. (#6340) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [DeckEditor] Deck List History Manager. Took 23 minutes Took 17 minutes * Add icons. Took 2 minutes Took 3 seconds * Small fixes. Took 12 minutes * Style lint. Took 48 seconds * tr() things. Took 5 minutes * Add tooltips for buttons. Took 3 minutes * Add explanation label to history. Took 3 minutes * Refactor to .cpp, delegate undo/redo to manager, don't return memento Took 8 minutes * Clear history when setting deck. Took 6 minutes * Move to value based stacks. Took 52 seconds * Default constructor. Took 31 seconds Took 3 minutes Took 4 minutes Took 2 minutes * Have it listen to deck editor additions. Took 18 minutes * Don't connect buttons *and* actions. Took 2 minutes --------- Co-authored-by: Lukas Brübach --- cockatrice/CMakeLists.txt | 1 + cockatrice/cockatrice.qrc | 3 + cockatrice/resources/icons/arrow_history.svg | 8 + cockatrice/resources/icons/arrow_redo.svg | 40 +++++ cockatrice/resources/icons/arrow_undo.svg | 40 +++++ .../deck_editor_deck_dock_widget.cpp | 115 +++++++++++-- .../deck_editor_deck_dock_widget.h | 10 ++ .../deck_list_history_manager_widget.cpp | 160 ++++++++++++++++++ .../deck_list_history_manager_widget.h | 58 +++++++ .../deck_editor/deck_list_style_proxy.cpp | 8 +- .../widgets/tabs/abstract_tab_deck_editor.cpp | 2 + .../widgets/tabs/abstract_tab_deck_editor.h | 1 + .../deck_preview_deck_tags_display_widget.cpp | 29 ++-- .../deck_preview_deck_tags_display_widget.h | 6 +- .../deck_preview/deck_preview_widget.cpp | 2 +- libcockatrice_deck_list/CMakeLists.txt | 18 +- .../libcockatrice/deck_list/deck_list.cpp | 14 +- .../libcockatrice/deck_list/deck_list.h | 3 + .../deck_list/deck_list_history_manager.cpp | 53 ++++++ .../deck_list/deck_list_history_manager.h | 54 ++++++ .../deck_list/deck_list_memento.h | 28 +++ 21 files changed, 612 insertions(+), 41 deletions(-) create mode 100644 cockatrice/resources/icons/arrow_history.svg create mode 100644 cockatrice/resources/icons/arrow_redo.svg create mode 100644 cockatrice/resources/icons/arrow_undo.svg create mode 100644 cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp create mode 100644 cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h create mode 100644 libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.cpp create mode 100644 libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.h create mode 100644 libcockatrice_deck_list/libcockatrice/deck_list/deck_list_memento.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 03a64ba3a..522300346 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -145,6 +145,7 @@ set(cockatrice_SOURCES src/interface/widgets/deck_analytics/mana_base_widget.cpp src/interface/widgets/deck_analytics/mana_curve_widget.cpp src/interface/widgets/deck_analytics/mana_devotion_widget.cpp + src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc index 469cf3d77..99f5bff23 100644 --- a/cockatrice/cockatrice.qrc +++ b/cockatrice/cockatrice.qrc @@ -7,11 +7,14 @@ resources/icons/arrow_bottom_green.svg resources/icons/arrow_down_green.svg + resources/icons/arrow_history.svg resources/icons/arrow_left_green.svg + resources/icons/arrow_redo.svg resources/icons/arrow_right_blue.svg resources/icons/arrow_right_green.svg resources/icons/arrow_top_green.svg resources/icons/arrow_up_green.svg + resources/icons/arrow_undo.svg resources/icons/clearsearch.svg resources/icons/cogwheel.svg resources/icons/conceded.svg diff --git a/cockatrice/resources/icons/arrow_history.svg b/cockatrice/resources/icons/arrow_history.svg new file mode 100644 index 000000000..d130758e6 --- /dev/null +++ b/cockatrice/resources/icons/arrow_history.svg @@ -0,0 +1,8 @@ + + + + history + + + + \ No newline at end of file diff --git a/cockatrice/resources/icons/arrow_redo.svg b/cockatrice/resources/icons/arrow_redo.svg new file mode 100644 index 000000000..dd89e3314 --- /dev/null +++ b/cockatrice/resources/icons/arrow_redo.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cockatrice/resources/icons/arrow_undo.svg b/cockatrice/resources/icons/arrow_undo.svg new file mode 100644 index 000000000..2e182ffb2 --- /dev/null +++ b/cockatrice/resources/icons/arrow_undo.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file 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 11818a912..18b05f96b 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 @@ -28,6 +28,11 @@ DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent void DeckEditorDeckDockWidget::createDeckDock() { + historyManager = new DeckListHistoryManager(); + + connect(deckEditor, &AbstractTabDeckEditor::cardAboutToBeAdded, this, + &DeckEditorDeckDockWidget::onCardAboutToBeAdded); + deckModel = new DeckListModel(this); deckModel->setObjectName("deckModel"); connect(deckModel, &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash); @@ -37,6 +42,10 @@ void DeckEditorDeckDockWidget::createDeckDock() proxy = new DeckListStyleProxy(this); proxy->setSourceModel(deckModel); + historyManagerWidget = new DeckListHistoryManagerWidget(deckModel, proxy, historyManager, this); + connect(historyManagerWidget, &DeckListHistoryManagerWidget::requestDisplayWidgetSync, this, + &DeckEditorDeckDockWidget::syncDisplayWidgetsToModel); + deckView = new QTreeView(); deckView->setObjectName("deckView"); deckView->setModel(proxy); @@ -65,7 +74,15 @@ void DeckEditorDeckDockWidget::createDeckDock() nameEdit->setMaxLength(MAX_NAME_LENGTH); nameEdit->setObjectName("nameEdit"); nameLabel->setBuddy(nameEdit); - connect(nameEdit, &LineEditUnfocusable::textChanged, this, &DeckEditorDeckDockWidget::updateName); + + nameDebounceTimer = new QTimer(this); + nameDebounceTimer->setSingleShot(true); + nameDebounceTimer->setInterval(300); // debounce duration in ms + connect(nameDebounceTimer, &QTimer::timeout, this, [this]() { updateName(nameEdit->text()); }); + + connect(nameEdit, &LineEditUnfocusable::textChanged, this, [this]() { + nameDebounceTimer->start(); // restart debounce timer + }); quickSettingsWidget = new SettingsButtonWidget(this); @@ -95,7 +112,16 @@ void DeckEditorDeckDockWidget::createDeckDock() commentsEdit->setMinimumHeight(nameEdit->minimumSizeHint().height()); commentsEdit->setObjectName("commentsEdit"); commentsLabel->setBuddy(commentsEdit); - connect(commentsEdit, &QTextEdit::textChanged, this, &DeckEditorDeckDockWidget::updateComments); + + commentsDebounceTimer = new QTimer(this); + commentsDebounceTimer->setSingleShot(true); + commentsDebounceTimer->setInterval(400); // longer debounce for multi-line + connect(commentsDebounceTimer, &QTimer::timeout, this, [this]() { updateComments(); }); + + connect(commentsEdit, &QTextEdit::textChanged, this, [this]() { + commentsDebounceTimer->start(); // restart debounce timer + }); + bannerCardLabel = new QLabel(); bannerCardLabel->setObjectName("bannerCardLabel"); bannerCardLabel->setText(tr("Banner Card")); @@ -109,7 +135,7 @@ void DeckEditorDeckDockWidget::createDeckDock() &DeckEditorDeckDockWidget::setBannerCard); bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); - deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader); + deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList()); deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible()); activeGroupCriteriaLabel = new QLabel(this); @@ -187,7 +213,8 @@ void DeckEditorDeckDockWidget::createDeckDock() lowerLayout->addWidget(tbDecrement, 0, 3); lowerLayout->addWidget(tbRemoveCard, 0, 4); lowerLayout->addWidget(tbSwapCard, 0, 5); - lowerLayout->addWidget(deckView, 1, 0, 1, 6); + lowerLayout->addWidget(historyManagerWidget, 0, 6); + lowerLayout->addWidget(deckView, 1, 0, 1, 7); // Create widgets for both layouts to make splitter work correctly auto *topWidget = new QWidget; @@ -249,6 +276,8 @@ void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const void DeckEditorDeckDockWidget::updateName(const QString &name) { + historyManager->save(deckLoader->getDeckList()->createMemento( + QString(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeckList()->getName()))); deckModel->getDeckList()->setName(name); deckEditor->setModified(name.isEmpty()); emit nameChanged(); @@ -257,6 +286,11 @@ void DeckEditorDeckDockWidget::updateName(const QString &name) void DeckEditorDeckDockWidget::updateComments() { + historyManager->save( + deckLoader->getDeckList()->createMemento(QString(tr("Updated comments (was %1 chars, now %2 chars)")) + .arg(deckLoader->getDeckList()->getComments().size()) + .arg(commentsEdit->toPlainText().size()))); + deckModel->getDeckList()->setComments(commentsEdit->toPlainText()); deckEditor->setModified(commentsEdit->toPlainText().isEmpty()); emit commentsChanged(); @@ -329,6 +363,7 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */) { + historyManager->save(deckLoader->getDeckList()->createMemento(tr("Banner card changed"))); auto [name, id] = bannerCardComboBox->currentData().value>(); deckModel->getDeckList()->setBannerCard({name, id}); deckEditor->setModified(true); @@ -372,21 +407,40 @@ void DeckEditorDeckDockWidget::setDeck(DeckLoader *_deck) connect(deckLoader, &DeckLoader::deckLoaded, deckModel, &DeckListModel::rebuildTree); connect(deckLoader->getDeckList(), &DeckList::deckHashChanged, deckModel, &DeckListModel::deckHashChanged); - nameEdit->setText(deckModel->getDeckList()->getName()); - commentsEdit->setText(deckModel->getDeckList()->getComments()); + historyManager->clear(); + historyManagerWidget->setDeckListModel(deckModel); - syncBannerCardComboBoxSelectionWithDeck(); - updateBannerCardComboBox(); - updateHash(); - deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); - deckView->expandAll(); - deckView->expandAll(); - - deckTagsDisplayWidget->connectDeckList(); + syncDisplayWidgetsToModel(); emit deckChanged(); } +void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel() +{ + nameEdit->blockSignals(true); + nameEdit->setText(deckModel->getDeckList()->getName()); + nameEdit->blockSignals(false); + + commentsEdit->blockSignals(true); + commentsEdit->setText(deckModel->getDeckList()->getComments()); + commentsEdit->blockSignals(false); + + bannerCardComboBox->blockSignals(true); + syncBannerCardComboBoxSelectionWithDeck(); + updateBannerCardComboBox(); + bannerCardComboBox->blockSignals(false); + updateHash(); + sortDeckModelToDeckView(); + expandAll(); + + deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList()); +} + +void DeckEditorDeckDockWidget::sortDeckModelToDeckView() +{ + deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); +} + DeckLoader *DeckEditorDeckDockWidget::getDeckLoader() { return deckLoader; @@ -412,7 +466,7 @@ void DeckEditorDeckDockWidget::cleanDeck() emit deckModified(); emit deckChanged(); updateBannerCardComboBox(); - deckTagsDisplayWidget->connectDeckList(); + deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList()); } void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index) @@ -422,6 +476,12 @@ void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index) deckView->expand(index); } +void DeckEditorDeckDockWidget::expandAll() +{ + deckView->expandAll(); + deckView->expandAll(); +} + /** * Gets the index of all the currently selected card nodes in the decklist table. * The list is in reverse order of the visual selection, so that rows can be deleted while iterating over them. @@ -441,6 +501,14 @@ QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodes() const return selectedRows; } +void DeckEditorDeckDockWidget::onCardAboutToBeAdded(const ExactCard &addedCard, const QString &zoneName) +{ + historyManager->save(deckLoader->getDeckList()->createMemento( + QString(tr("Added (%1): %2 (%3) %4")) + .arg(zoneName, addedCard.getName(), addedCard.getPrinting().getSet()->getCorrectedShortName(), + addedCard.getPrinting().getProperty("num")))); +} + void DeckEditorDeckDockWidget::actIncrement() { auto selectedRows = getSelectedCardNodes(); @@ -559,6 +627,11 @@ void DeckEditorDeckDockWidget::actRemoveCard() continue; } QModelIndex sourceIndex = proxy->mapToSource(index); + QString cardName = sourceIndex.sibling(sourceIndex.row(), 1).data().toString(); + + historyManager->save( + deckLoader->getDeckList()->createMemento(QString(tr("Removed \"%1\" (all copies)")).arg(cardName))); + deckModel->removeRow(sourceIndex.row(), sourceIndex.parent()); isModified = true; } @@ -579,9 +652,21 @@ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, int of QModelIndex sourceIndex = proxy->mapToSource(idx); const QModelIndex numberIndex = sourceIndex.sibling(sourceIndex.row(), 0); + const QModelIndex nameIndex = sourceIndex.sibling(sourceIndex.row(), 1); + + const QString cardName = deckModel->data(nameIndex, Qt::EditRole).toString(); const int count = deckModel->data(numberIndex, Qt::EditRole).toInt(); const int new_count = count + offset; + const auto reason = + QString(tr("%1 %2 × \"%3\" (%4)")) + .arg(offset > 0 ? tr("Added") : tr("Removed")) + .arg(qAbs(offset)) + .arg(cardName) + .arg(deckModel->data(sourceIndex.sibling(sourceIndex.row(), 4), Qt::DisplayRole).toString()); + + historyManager->save(deckLoader->getDeckList()->createMemento(reason)); + if (new_count <= 0) { deckModel->removeRow(sourceIndex.row(), sourceIndex.parent()); } else { diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index d4703a5dc..b633bdaed 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -12,7 +12,9 @@ #include "../../key_signals.h" #include "../utility/custom_line_edit.h" #include "../visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h" +#include "deck_list_history_manager_widget.h" #include "deck_list_style_proxy.h" +#include "libcockatrice/deck_list/deck_list_history_manager.h" #include #include @@ -53,6 +55,8 @@ public slots: void cleanDeck(); void updateBannerCardComboBox(); void setDeck(DeckLoader *_deck); + void syncDisplayWidgetsToModel(); + void sortDeckModelToDeckView(); DeckLoader *getDeckLoader(); DeckList *getDeckList(); void actIncrement(); @@ -61,6 +65,7 @@ public slots: void actDecrementSelection(); void actSwapCard(); void actRemoveCard(); + void onCardAboutToBeAdded(const ExactCard &card, const QString &zoneName); void offsetCountAtIndex(const QModelIndex &idx, int offset); signals: @@ -73,14 +78,18 @@ signals: private: AbstractTabDeckEditor *deckEditor; + DeckListHistoryManager *historyManager; + DeckListHistoryManagerWidget *historyManagerWidget; KeySignals deckViewKeySignals; QLabel *nameLabel; LineEditUnfocusable *nameEdit; + QTimer *nameDebounceTimer; SettingsButtonWidget *quickSettingsWidget; QCheckBox *showBannerCardCheckBox; QCheckBox *showTagsWidgetCheckBox; QLabel *commentsLabel; QTextEdit *commentsEdit; + QTimer *commentsDebounceTimer; QLabel *bannerCardLabel; DeckPreviewDeckTagsDisplayWidget *deckTagsDisplayWidget; QLabel *hashLabel1; @@ -104,6 +113,7 @@ private slots: void updateShowBannerCardComboBox(bool visible); void updateShowTagsWidget(bool visible); void syncBannerCardComboBoxSelectionWithDeck(); + void expandAll(); }; #endif // DECK_EDITOR_DECK_DOCK_WIDGET_H 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 new file mode 100644 index 000000000..c68934ea6 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp @@ -0,0 +1,160 @@ +#include "deck_list_history_manager_widget.h" + +DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckListModel *_deckListModel, + DeckListStyleProxy *_styleProxy, + DeckListHistoryManager *manager, + QWidget *parent) + : QWidget(parent), deckListModel(_deckListModel), styleProxy(_styleProxy), historyManager(manager) +{ + layout = new QHBoxLayout(this); + + aUndo = new QAction(QString(), this); + aUndo->setIcon(QPixmap("theme:icons/arrow_undo")); + aUndo->setShortcut(QKeySequence::Undo); + aUndo->setShortcutContext(Qt::ApplicationShortcut); + connect(aUndo, &QAction::triggered, this, &DeckListHistoryManagerWidget::doUndo); + + undoButton = new QToolButton(this); + undoButton->setDefaultAction(aUndo); + + aRedo = new QAction(QString(), this); + aRedo->setIcon(QPixmap("theme:icons/arrow_redo")); + aRedo->setShortcut(QKeySequence::Redo); + aRedo->setShortcutContext(Qt::ApplicationShortcut); + connect(aRedo, &QAction::triggered, this, &DeckListHistoryManagerWidget::doRedo); + + redoButton = new QToolButton(this); + redoButton->setDefaultAction(aRedo); + + layout->addWidget(undoButton); + layout->addWidget(redoButton); + + historyButton = new SettingsButtonWidget(this); + historyButton->setButtonIcon(QPixmap("theme:icons/arrow_history")); + + historyLabel = new QLabel(this); + + historyList = new QListWidget(this); + + historyButton->addSettingsWidget(historyLabel); + historyButton->addSettingsWidget(historyList); + + layout->addWidget(historyButton); + + connect(historyList, &QListWidget::itemClicked, this, &DeckListHistoryManagerWidget::onListClicked); + + connect(historyManager, &DeckListHistoryManager::undoRedoStateChanged, this, + &DeckListHistoryManagerWidget::refreshList); + + refreshList(); + retranslateUi(); +} + +void DeckListHistoryManagerWidget::retranslateUi() +{ + undoButton->setToolTip(tr("Undo")); + redoButton->setToolTip(tr("Redo")); + historyButton->setToolTip(tr("Undo/Redo history")); + historyLabel->setText(tr("Click on an entry to revert to that point in the history.")); +} + +void DeckListHistoryManagerWidget::setDeckListModel(DeckListModel *_deckListModel) +{ + deckListModel = _deckListModel; +} + +void DeckListHistoryManagerWidget::refreshList() +{ + historyList->clear(); + + // Fill redo section first (oldest redo at top, newest redo closest to divider) + const auto redoStack = historyManager->getRedoStack(); + for (int i = 0; i < redoStack.size(); ++i) { // iterate forward + auto item = new QListWidgetItem(tr("[redo] ") + redoStack[i].getReason(), historyList); + item->setData(Qt::UserRole, QVariant("redo")); + item->setData(Qt::UserRole + 1, i); // index in redo stack + item->setForeground(Qt::gray); + historyList->addItem(item); + } + + // Divider + if (!historyManager->getUndoStack().isEmpty() && !historyManager->getRedoStack().isEmpty()) { + auto divider = new QListWidgetItem("──────────", historyList); + divider->setFlags(Qt::NoItemFlags); // not selectable + historyList->addItem(divider); + } + + // Fill undo section + const auto undoStack = historyManager->getUndoStack(); + for (int i = undoStack.size() - 1; i >= 0; --i) { + auto item = new QListWidgetItem(tr("[undo] ") + undoStack[i].getReason(), historyList); + item->setData(Qt::UserRole, QVariant("undo")); + item->setData(Qt::UserRole + 1, i); // index in undo stack + historyList->addItem(item); + } + + // Button enabled states + undoButton->setEnabled(historyManager->canUndo()); + redoButton->setEnabled(historyManager->canRedo()); +} + +void DeckListHistoryManagerWidget::doUndo() +{ + if (!historyManager->canUndo()) { + return; + } + + historyManager->undo(deckListModel->getDeckList()); + deckListModel->rebuildTree(); + emit deckListModel->layoutChanged(); + emit requestDisplayWidgetSync(); + + refreshList(); +} + +void DeckListHistoryManagerWidget::doRedo() +{ + if (!historyManager->canRedo()) { + return; + } + + historyManager->redo(deckListModel->getDeckList()); + deckListModel->rebuildTree(); + + emit deckListModel->layoutChanged(); + emit requestDisplayWidgetSync(); + + refreshList(); +} + +void DeckListHistoryManagerWidget::onListClicked(QListWidgetItem *item) +{ + // Ignore non-selectable items (like divider) + if (!(item->flags() & Qt::ItemIsSelectable)) { + return; + } + + const QString mode = item->data(Qt::UserRole).toString(); + int index = item->data(Qt::UserRole + 1).toInt(); + + if (mode == "redo") { + const auto redoStack = historyManager->getRedoStack(); + int steps = redoStack.size() - index; + for (int i = 0; i < steps; ++i) { + historyManager->redo(deckListModel->getDeckList()); + } + } else if (mode == "undo") { + const auto undoStack = historyManager->getUndoStack(); + int steps = undoStack.size() - 1 - index; + for (int i = 0; i < steps + 1; ++i) { + historyManager->undo(deckListModel->getDeckList()); + } + } + + deckListModel->rebuildTree(); + + emit deckListModel->layoutChanged(); + emit requestDisplayWidgetSync(); + + refreshList(); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h new file mode 100644 index 000000000..23619b91b --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h @@ -0,0 +1,58 @@ +#ifndef COCKATRICE_DECK_EDITOR_DECK_LIST_HISTORY_MANAGER_WIDGET_H +#define COCKATRICE_DECK_EDITOR_DECK_LIST_HISTORY_MANAGER_WIDGET_H + +#ifndef COCKATRICE_DECK_UNDO_WIDGET_H +#define COCKATRICE_DECK_UNDO_WIDGET_H + +#include "../quick_settings/settings_button_widget.h" +#include "deck_list_style_proxy.h" + +#include +#include +#include +#include +#include +#include +#include + +class DeckListHistoryManagerWidget : public QWidget +{ + Q_OBJECT + +signals: + void requestDisplayWidgetSync(); + +public slots: + void retranslateUi(); + +public: + explicit DeckListHistoryManagerWidget(DeckListModel *deckListModel, + DeckListStyleProxy *styleProxy, + DeckListHistoryManager *manager, + QWidget *parent = nullptr); + void setDeckListModel(DeckListModel *_deckListModel); + +private slots: + void refreshList(); + void onListClicked(QListWidgetItem *item); + void doUndo(); + void doRedo(); + +private: + DeckListModel *deckListModel; + DeckListStyleProxy *styleProxy; + DeckListHistoryManager *historyManager; + + QHBoxLayout *layout; + QAction *aUndo; + QToolButton *undoButton; + QAction *aRedo; + QToolButton *redoButton; + SettingsButtonWidget *historyButton; + QLabel *historyLabel; + QListWidget *historyList; +}; + +#endif // COCKATRICE_DECK_UNDO_WIDGET_H + +#endif // COCKATRICE_DECK_EDITOR_DECK_LIST_HISTORY_MANAGER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp index b697f130f..9c2265dfd 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp @@ -7,9 +7,13 @@ QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const { + QModelIndex src = mapToSource(index); + if (!src.isValid()) + return {}; + QVariant value = QIdentityProxyModel::data(index, role); - const bool isCard = QIdentityProxyModel::data(index, DeckRoles::IsCardRole).toBool(); + bool isCard = src.data(DeckRoles::IsCardRole).toBool(); if (role == Qt::FontRole && !isCard) { QFont f; @@ -24,7 +28,7 @@ QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const int base = 255 - (index.row() % 2) * 30; return legal ? QBrush(QColor(base, base, base)) : QBrush(QColor(255, base / 3, base / 3)); } else { - int depth = QIdentityProxyModel::data(index, DeckRoles::DepthRole).toInt(); + int depth = src.data(DeckRoles::DepthRole).toInt(); int color = 90 + 60 * depth; return QBrush(QColor(color, 255, color)); } 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 8a26b3a6a..ad68ecdbc 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -131,6 +131,8 @@ void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, QString zoneNam if (card.getInfo().getIsToken()) zoneName = DECK_ZONE_TOKENS; + emit cardAboutToBeAdded(card, zoneName); + QModelIndex newCardIndex = deckDockWidget->deckModel->addCard(card, zoneName); deckDockWidget->deckView->clearSelection(); deckDockWidget->deckView->setCurrentIndex(newCardIndex); diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h index c0a5518c6..86517de63 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h @@ -180,6 +180,7 @@ public slots: virtual void dockTopLevelChanged(bool topLevel) = 0; signals: + void cardAboutToBeAdded(const ExactCard &addedCard, const QString &zoneName); /** @brief Emitted when a deck should be opened in a new editor tab. */ void openDeckEditor(DeckLoader *deckLoader); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp index e5cbc0adb..eb105697f 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp @@ -14,8 +14,8 @@ #include #include -DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckLoader *_deckLoader) - : QWidget(_parent), deckLoader(_deckLoader) +DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList) + : QWidget(_parent), deckList(nullptr) { setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); @@ -28,20 +28,21 @@ DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_par flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); - connectDeckList(); + if (_deckList) { + connectDeckList(_deckList); + } layout->addWidget(flowWidget); } -void DeckPreviewDeckTagsDisplayWidget::connectDeckList() +void DeckPreviewDeckTagsDisplayWidget::connectDeckList(DeckList *_deckList) { - if (deckLoader) { - disconnect(deckLoader->getDeckList(), &DeckList::deckTagsChanged, this, - &DeckPreviewDeckTagsDisplayWidget::refreshTags); + if (deckList) { + disconnect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags); } - connect(deckLoader->getDeckList(), &DeckList::deckTagsChanged, this, - &DeckPreviewDeckTagsDisplayWidget::refreshTags); + deckList = _deckList; + connect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags); refreshTags(); } @@ -50,7 +51,7 @@ void DeckPreviewDeckTagsDisplayWidget::refreshTags() { flowWidget->clearLayout(); - for (const QString &tag : deckLoader->getDeckList()->getTags()) { + for (const QString &tag : deckList->getTags()) { flowWidget->addWidget(new DeckPreviewTagDisplayWidget(this, tag)); } @@ -97,7 +98,7 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() if (qobject_cast(parentWidget())) { auto *deckPreviewWidget = qobject_cast(parentWidget()); QStringList knownTags = deckPreviewWidget->visualDeckStorageWidget->tagFilterWidget->getAllKnownTags(); - QStringList activeTags = deckLoader->getDeckList()->getTags(); + QStringList activeTags = deckList->getTags(); bool canAddTags = true; @@ -148,7 +149,7 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() DeckPreviewTagDialog dialog(knownTags, activeTags); if (dialog.exec() == QDialog::Accepted) { QStringList updatedTags = dialog.getActiveTags(); - deckLoader->getDeckList()->setTags(updatedTags); + deckList->setTags(updatedTags); deckPreviewWidget->deckLoader->saveToFile(deckPreviewWidget->filePath, DeckLoader::CockatriceFormat); } } @@ -174,12 +175,12 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() knownTags.removeDuplicates(); } - QStringList activeTags = deckLoader->getDeckList()->getTags(); + QStringList activeTags = deckList->getTags(); DeckPreviewTagDialog dialog(knownTags, activeTags); if (dialog.exec() == QDialog::Accepted) { QStringList updatedTags = dialog.getActiveTags(); - deckLoader->getDeckList()->setTags(updatedTags); + deckList->setTags(updatedTags); deckEditor->setModified(true); } } diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h index 645c769bd..4270e38e5 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h @@ -20,10 +20,10 @@ class DeckPreviewDeckTagsDisplayWidget : public QWidget Q_OBJECT public: - explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckLoader *_deckLoader); - void connectDeckList(); + explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList); + void connectDeckList(DeckList *_deckList); void refreshTags(); - DeckLoader *deckLoader; + DeckList *deckList; FlowWidget *flowWidget; public slots: 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 ba111100d..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 @@ -82,7 +82,7 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess) setFilePath(deckLoader->getLastFileName()); colorIdentityWidget = new ColorIdentityWidget(this, getColorIdentity()); - deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader); + deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeckList()); bannerCardLabel = new QLabel(this); bannerCardLabel->setObjectName("bannerCardLabel"); diff --git a/libcockatrice_deck_list/CMakeLists.txt b/libcockatrice_deck_list/CMakeLists.txt index 1e0511c01..d9b27354b 100644 --- a/libcockatrice_deck_list/CMakeLists.txt +++ b/libcockatrice_deck_list/CMakeLists.txt @@ -3,8 +3,12 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) set(HEADERS - libcockatrice/deck_list/abstract_deck_list_card_node.h libcockatrice/deck_list/abstract_deck_list_node.h - libcockatrice/deck_list/deck_list.h libcockatrice/deck_list/deck_list_card_node.h + libcockatrice/deck_list/abstract_deck_list_card_node.h + libcockatrice/deck_list/abstract_deck_list_node.h + libcockatrice/deck_list/deck_list.h + libcockatrice/deck_list/deck_list_card_node.h + libcockatrice/deck_list/deck_list_history_manager.h + libcockatrice/deck_list/deck_list_memento.h libcockatrice/deck_list/inner_deck_list_node.h ) @@ -16,9 +20,13 @@ endif() add_library( libcockatrice_deck_list STATIC - ${MOC_SOURCES} libcockatrice/deck_list/abstract_deck_list_card_node.cpp - libcockatrice/deck_list/abstract_deck_list_node.cpp libcockatrice/deck_list/deck_list.cpp - libcockatrice/deck_list/deck_list_card_node.cpp libcockatrice/deck_list/inner_deck_list_node.cpp + ${MOC_SOURCES} + libcockatrice/deck_list/abstract_deck_list_card_node.cpp + libcockatrice/deck_list/abstract_deck_list_node.cpp + libcockatrice/deck_list/deck_list.cpp + libcockatrice/deck_list/deck_list_card_node.cpp + libcockatrice/deck_list/deck_list_history_manager.cpp + libcockatrice/deck_list/inner_deck_list_node.cpp ) add_dependencies(libcockatrice_deck_list libcockatrice_protocol) diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index be26c51a3..e26e55e08 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -2,6 +2,7 @@ #include "abstract_deck_list_node.h" #include "deck_list_card_node.h" +#include "deck_list_memento.h" #include "inner_deck_list_node.h" #include @@ -719,4 +720,15 @@ void DeckList::forEachCard(const std::function @@ -317,6 +318,8 @@ public: * @param func Function taking (zone node, card node). */ void forEachCard(const std::function &func) const; + DeckListMemento createMemento(const QString &reason) const; + void restoreMemento(const DeckListMemento &m); }; #endif 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 new file mode 100644 index 000000000..83c9cc0bb --- /dev/null +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.cpp @@ -0,0 +1,53 @@ +#include "deck_list_history_manager.h" + +void DeckListHistoryManager::save(const DeckListMemento &memento) +{ + undoStack.push(memento); + redoStack.clear(); + emit undoRedoStateChanged(); +} + +void DeckListHistoryManager::clear() +{ + undoStack.clear(); + redoStack.clear(); + emit undoRedoStateChanged(); +} + +void DeckListHistoryManager::undo(DeckList *deck) +{ + if (undoStack.isEmpty()) + return; + + // Peek at the memento we are going to restore + const DeckListMemento &mementoToRestore = undoStack.top(); + + // Save current state for redo + DeckListMemento currentState = deck->createMemento(mementoToRestore.getReason()); + redoStack.push(currentState); + + // Pop the last state from undo stack and restore it + DeckListMemento memento = undoStack.pop(); + deck->restoreMemento(memento); + + emit undoRedoStateChanged(); +} + +void DeckListHistoryManager::redo(DeckList *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 new file mode 100644 index 000000000..f30c0affe --- /dev/null +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_history_manager.h @@ -0,0 +1,54 @@ +#ifndef COCKATRICE_DECK_LIST_HISTORY_MANAGER_H +#define COCKATRICE_DECK_LIST_HISTORY_MANAGER_H + +#include "deck_list.h" +#include "deck_list_memento.h" + +#include +#include + +class DeckListHistoryManager : public QObject +{ + Q_OBJECT + +signals: + void undoRedoStateChanged(); + +public: + explicit DeckListHistoryManager(QObject *parent = nullptr) : QObject(parent) + { + } + + void save(const DeckListMemento &memento); + + void clear(); + + bool canUndo() const + { + return !undoStack.isEmpty(); + } + + bool canRedo() const + { + return !redoStack.isEmpty(); + } + + void undo(DeckList *deck); + + void redo(DeckList *deck); + + QStack getRedoStack() const + { + return redoStack; + } + QStack getUndoStack() const + { + return undoStack; + } + +private: + QStack undoStack; + QStack redoStack; +}; + +#endif // COCKATRICE_DECK_LIST_HISTORY_MANAGER_H diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_memento.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_memento.h new file mode 100644 index 000000000..d2e36b804 --- /dev/null +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_memento.h @@ -0,0 +1,28 @@ +#ifndef COCKATRICE_DECK_LIST_MEMENTO_H +#define COCKATRICE_DECK_LIST_MEMENTO_H +#include + +class DeckListMemento +{ +public: + DeckListMemento() = default; + explicit DeckListMemento(const QString &memento, const QString &reason = QString()) + : memento(memento), reason(reason) + { + } + + QString getMemento() const + { + return memento; + } + QString getReason() const + { + return reason; + } + +private: + QString memento; + QString reason; +}; + +#endif // COCKATRICE_DECK_LIST_MEMENTO_H From 73591d5d0fb368b8982b2a6100e743730d3093f0 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:44:35 +0100 Subject: [PATCH 014/325] [BannerCard] Try to restore by providerId (#6341) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [BannerCard] Try to restore by providerId Took 27 minutes Took 41 seconds * Style lint. Took 2 minutes * Don't look up by providerId if it's empty. Took 8 minutes * Add extra name guard to providerId clause. Took 4 minutes * Update cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com> * Update cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com> * Adjust to comments. Took 11 minutes * Extract to helper function. Took 3 minutes * Make helper static. Took 5 minutes * Remove const qualifier. Took 3 minutes * Finally. Took 5 minutes --------- Co-authored-by: Lukas Brübach Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com> --- .../deck_editor_deck_dock_widget.cpp | 72 ++++++++++++------- 1 file changed, 46 insertions(+), 26 deletions(-) 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 18b05f96b..56bd16f0a 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 @@ -13,6 +13,30 @@ #include #include +static int findRestoreIndex(const CardRef &wanted, const QComboBox *combo) +{ + // Try providerId + name (strongest match) + if (!wanted.providerId.isEmpty()) { + for (int i = 0; i < combo->count(); ++i) { + auto pair = combo->itemData(i).value>(); + if (pair.second == wanted.providerId && pair.first == wanted.name) { + return i; + } + } + } + + // Try name only + for (int i = 0; i < combo->count(); ++i) { + auto pair = combo->itemData(i).value>(); + if (pair.first == wanted.name) { + return i; + } + } + + // Not found + return -1; +} + DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent) : QDockWidget(parent), deckEditor(parent) { @@ -306,8 +330,8 @@ void DeckEditorDeckDockWidget::updateHash() void DeckEditorDeckDockWidget::updateBannerCardComboBox() { - // Store the current text of the combo box - QString currentText = bannerCardComboBox->currentText(); + // Store current banner card identity + CardRef wanted = deckModel->getDeckList()->getBannerCard(); // Block signals temporarily bool wasBlocked = bannerCardComboBox->blockSignals(true); @@ -315,49 +339,45 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() // Clear the existing items in the combo box bannerCardComboBox->clear(); - // Prepare the new items with deduplication + // Collect unique (name, providerId) pairs QSet> bannerCardSet; QList cardsInDeck = deckModel->getDeckList()->getCardNodes(); for (auto currentCard : cardsInDeck) { - for (int k = 0; k < currentCard->getNumber(); ++k) { - if (CardDatabaseManager::query()->getCard(currentCard->toCardRef())) { - bannerCardSet.insert({currentCard->getName(), currentCard->getCardProviderId()}); - } + if (!CardDatabaseManager::query()->getCard(currentCard->toCardRef())) { + continue; } + + // Insert one entry per distinct card, ignore copies + bannerCardSet.insert({currentCard->getName(), currentCard->getCardProviderId()}); } + // Convert to sorted list QList> pairList = bannerCardSet.values(); // Sort QList by the first() element of the QPair - std::sort(pairList.begin(), pairList.end(), [](const QPair &a, const QPair &b) { - return a.first.toLower() < b.first.toLower(); - }); + std::sort(pairList.begin(), pairList.end(), + [](const auto &a, const auto &b) { return a.first.toLower() < b.first.toLower(); }); + // Add to combo box for (const auto &pair : pairList) { bannerCardComboBox->addItem(pair.first, QVariant::fromValue(pair)); } - // Try to restore the previous selection by finding the currentText - int restoredIndex = bannerCardComboBox->findText(currentText); - if (restoredIndex != -1) { - bannerCardComboBox->setCurrentIndex(restoredIndex); - if (deckModel->getDeckList()->getBannerCard().providerId != - bannerCardComboBox->currentData().value>().second) { - setBannerCard(restoredIndex); - } + // Try to find an index with a matching card + int restoreIndex = findRestoreIndex(wanted, bannerCardComboBox); + + // Handle results + if (restoreIndex != -1) { + bannerCardComboBox->setCurrentIndex(restoreIndex); + setBannerCard(restoreIndex); } else { // Add a placeholder "-" and set it as the current selection - int bannerIndex = bannerCardComboBox->findText(deckModel->getDeckList()->getBannerCard().name); - if (bannerIndex != -1) { - bannerCardComboBox->setCurrentIndex(bannerIndex); - } else { - bannerCardComboBox->insertItem(0, "-"); - bannerCardComboBox->setCurrentIndex(0); - } + bannerCardComboBox->insertItem(0, "-"); + bannerCardComboBox->setCurrentIndex(0); } - // Restore the previous signal blocking state + // Restore signal state bannerCardComboBox->blockSignals(wasBlocked); } From 621c6a8d73e1a16dcd020dec404bb833d898797d Mon Sep 17 00:00:00 2001 From: tooomm Date: Sat, 22 Nov 2025 19:38:39 +0100 Subject: [PATCH 015/325] Doxygen: Improve file structure and includes (#6344) --- .github/workflows/documentation-build.yml | 2 +- Doxyfile | 154 +----------------- doc/doxygen/DoxygenLayout.xml | 4 - .../card_database_schema_and_parsing.md | 0 .../displaying_cards.md | 0 .../developer_documentation/index.md | 0 .../loading_card_pictures.md | 0 .../developer_documentation/primer_cards.md | 0 .../querying_the_card_database.md | 0 .../extra-pages}/index.md | 0 .../deck_management/creating_decks.md | 0 .../deck_management/editing_decks.md | 0 .../deck_management/editing_decks_classic.md | 0 .../editing_decks_printings.md | 0 .../deck_management/editing_decks_visual.md | 0 .../deck_management/exporting_decks.md | 0 .../deck_management/importing_decks.md | 0 .../extra-pages}/user_documentation/index.md | 0 .../groups}/doc_groups.dox | 0 .../images}/classic_database_display.png | Bin .../classic_database_display_add_buttons.png | Bin .../images}/classic_deck_editor.png | Bin .../images}/deck_dock_deck_list.png | Bin .../images}/deck_dock_deck_list_buttons.png | Bin .../images}/deck_dock_deck_list_group_by.png | Bin .../images}/deckeditordeckdockwidget.png | Bin .../images}/printing_selector.png | Bin .../images}/printing_selector_disable.png | Bin .../images}/printing_selector_enable.png | Bin .../images}/printing_selector_navigation.png | Bin .../images}/printing_selector_options.png | Bin .../printing_selector_pre_providerid.png | Bin .../images}/vde_deck_analytics.png | Bin .../images}/vde_flat_layout_color_grouped.png | Bin .../images}/vde_flat_layout_type_grouped.png | Bin .../vde_overlap_layout_type_grouped.png | Bin .../images}/vde_sample_hand.png | Bin 37 files changed, 7 insertions(+), 153 deletions(-) rename doc/{doxygen-extra-pages => doxygen/extra-pages}/developer_documentation/card_database_schema_and_parsing.md (100%) rename doc/{doxygen-extra-pages => doxygen/extra-pages}/developer_documentation/displaying_cards.md (100%) rename doc/{doxygen-extra-pages => doxygen/extra-pages}/developer_documentation/index.md (100%) rename doc/{doxygen-extra-pages => doxygen/extra-pages}/developer_documentation/loading_card_pictures.md (100%) rename doc/{doxygen-extra-pages => doxygen/extra-pages}/developer_documentation/primer_cards.md (100%) rename doc/{doxygen-extra-pages => doxygen/extra-pages}/developer_documentation/querying_the_card_database.md (100%) rename doc/{doxygen-extra-pages => doxygen/extra-pages}/index.md (100%) rename doc/{doxygen-extra-pages => doxygen/extra-pages}/user_documentation/deck_management/creating_decks.md (100%) rename doc/{doxygen-extra-pages => doxygen/extra-pages}/user_documentation/deck_management/editing_decks.md (100%) rename doc/{doxygen-extra-pages => doxygen/extra-pages}/user_documentation/deck_management/editing_decks_classic.md (100%) rename doc/{doxygen-extra-pages => doxygen/extra-pages}/user_documentation/deck_management/editing_decks_printings.md (100%) rename doc/{doxygen-extra-pages => doxygen/extra-pages}/user_documentation/deck_management/editing_decks_visual.md (100%) rename doc/{doxygen-extra-pages => doxygen/extra-pages}/user_documentation/deck_management/exporting_decks.md (100%) rename doc/{doxygen-extra-pages => doxygen/extra-pages}/user_documentation/deck_management/importing_decks.md (100%) rename doc/{doxygen-extra-pages => doxygen/extra-pages}/user_documentation/index.md (100%) rename doc/{doxygen-groups => doxygen/groups}/doc_groups.dox (100%) rename doc/{doxygen-images => doxygen/images}/classic_database_display.png (100%) rename doc/{doxygen-images => doxygen/images}/classic_database_display_add_buttons.png (100%) rename doc/{doxygen-images => doxygen/images}/classic_deck_editor.png (100%) rename doc/{doxygen-images => doxygen/images}/deck_dock_deck_list.png (100%) rename doc/{doxygen-images => doxygen/images}/deck_dock_deck_list_buttons.png (100%) rename doc/{doxygen-images => doxygen/images}/deck_dock_deck_list_group_by.png (100%) rename doc/{doxygen-images => doxygen/images}/deckeditordeckdockwidget.png (100%) rename doc/{doxygen-images => doxygen/images}/printing_selector.png (100%) rename doc/{doxygen-images => doxygen/images}/printing_selector_disable.png (100%) rename doc/{doxygen-images => doxygen/images}/printing_selector_enable.png (100%) rename doc/{doxygen-images => doxygen/images}/printing_selector_navigation.png (100%) rename doc/{doxygen-images => doxygen/images}/printing_selector_options.png (100%) rename doc/{doxygen-images => doxygen/images}/printing_selector_pre_providerid.png (100%) rename doc/{doxygen-images => doxygen/images}/vde_deck_analytics.png (100%) rename doc/{doxygen-images => doxygen/images}/vde_flat_layout_color_grouped.png (100%) rename doc/{doxygen-images => doxygen/images}/vde_flat_layout_type_grouped.png (100%) rename doc/{doxygen-images => doxygen/images}/vde_overlap_layout_type_grouped.png (100%) rename doc/{doxygen-images => doxygen/images}/vde_sample_hand.png (100%) diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml index ef0f0af91..12df6bc26 100644 --- a/.github/workflows/documentation-build.yml +++ b/.github/workflows/documentation-build.yml @@ -6,9 +6,9 @@ on: - '*' # Only re-generate docs when a new tagged version is pushed pull_request: paths: + - 'doc/doxygen/**' - '.github/workflows/documentation-build.yml' - 'Doxyfile' - - 'doxygen_style.css' workflow_dispatch: env: diff --git a/Doxyfile b/Doxyfile index 6a09e2619..85d66bbd3 100644 --- a/Doxyfile +++ b/Doxyfile @@ -64,12 +64,6 @@ PROJECT_BRIEF = A cross-platform virtual tabletop for multiplayer card PROJECT_LOGO = cockatrice/resources/cockatrice.png -# With the PROJECT_ICON tag one can specify an icon that is included in the tabs -# when the HTML document is shown. Doxygen will copy the logo to the output -# directory. - -PROJECT_ICON = cockatrice/resources/cockatrice.png - # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where Doxygen was started. If @@ -391,14 +385,6 @@ MARKDOWN_ID_STYLE = DOXYGEN AUTOLINK_SUPPORT = YES -# This tag specifies a list of words that, when matching the start of a word in -# the documentation, will suppress auto links generation, if it is enabled via -# AUTOLINK_SUPPORT. This list does not affect links explicitly created using \# -# or the \link or commands. -# This tag requires that the tag AUTOLINK_SUPPORT is set to YES. - -AUTOLINK_IGNORE_WORDS = - # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let Doxygen match functions declarations and @@ -500,7 +486,7 @@ TYPEDEF_HIDES_STRUCT = NO # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. -LOOKUP_CACHE_SIZE = 0 +LOOKUP_CACHE_SIZE = 1 # The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use # during processing. When set to 0 Doxygen will based this on the number of @@ -610,14 +596,6 @@ HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO -# If the HIDE_UNDOC_NAMESPACES tag is set to YES, Doxygen will hide all -# undocumented namespaces that are normally visible in the namespace hierarchy. -# If set to NO, these namespaces will be included in the various overviews. This -# option has no effect if EXTRACT_ALL is enabled. -# The default value is: YES. - -HIDE_UNDOC_NAMESPACES = YES - # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all friend # declarations. If set to NO, these declarations will be included in the # documentation. @@ -852,22 +830,6 @@ LAYOUT_FILE = doc/doxygen/DoxygenLayout.xml CITE_BIB_FILES = -# The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH -# environment variable) so that external tools such as latex and gs can be -# found. -# Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the -# path already specified by the PATH variable, and are added in the order -# specified. -# Note: This option is particularly useful for macOS version 14 (Sonoma) and -# higher, when running Doxygen from Doxywizard, because in this case any user- -# defined changes to the PATH are ignored. A typical example on macOS is to set -# EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin -# together with the standard path, the full search path used by doxygen when -# launching external tools will then become -# PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin - -EXTERNAL_TOOL_PATH = - #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- @@ -928,14 +890,6 @@ WARN_NO_PARAMDOC = NO WARN_IF_UNDOC_ENUM_VAL = NO -# If WARN_LAYOUT_FILE option is set to YES, Doxygen will warn about issues found -# while parsing the user defined layout file, such as missing or wrong elements. -# See also LAYOUT_FILE for details. If set to NO, problems with the layout file -# will be suppressed. -# The default value is: YES. - -WARN_LAYOUT_FILE = YES - # If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but @@ -992,7 +946,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = cockatrice doc/doxygen-extra-pages doc/doxygen-groups libcockatrice_card libcockatrice_deck_list libcockatrice_filters libcockatrice_interfaces libcockatrice_models libcockatrice_network libcockatrice_protocol libcockatrice_rng libcockatrice_settings libcockatrice_utility +INPUT = . # This tag can be used to specify the character encoding of the source files # that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses @@ -1041,47 +995,19 @@ FILE_PATTERNS = *.c \ *.ccm \ *.c++ \ *.c++m \ - *.java \ *.ii \ *.ixx \ *.ipp \ *.i++ \ *.inl \ - *.idl \ - *.ddl \ - *.odl \ *.h \ *.hh \ *.hxx \ *.hpp \ *.h++ \ - *.l \ - *.cs \ - *.d \ - *.php \ - *.php4 \ - *.php5 \ - *.phtml \ - *.inc \ - *.m \ *.markdown \ *.md \ - *.mm \ - *.dox \ - *.py \ - *.pyw \ - *.f90 \ - *.f95 \ - *.f03 \ - *.f08 \ - *.f18 \ - *.f \ - *.for \ - *.vhd \ - *.vhdl \ - *.ucf \ - *.qsf \ - *.ice + *.dox # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -1096,7 +1022,7 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which Doxygen is # run. -EXCLUDE = common/lib +EXCLUDE = build/ cmake/ vcpkg/ dbconverter/ webclient/ # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -1112,7 +1038,7 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = .* .*/ # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -1146,7 +1072,7 @@ EXAMPLE_RECURSIVE = NO # that contain images that are to be included in the documentation (see the # \image command). -IMAGE_PATH = doc/doxygen-images +IMAGE_PATH = doc/doxygen/images # The INPUT_FILTER tag can be used to specify a program that Doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program @@ -1209,15 +1135,6 @@ FILTER_SOURCE_PATTERNS = USE_MDFILE_AS_MAINPAGE = -# If the IMPLICIT_DIR_DOCS tag is set to YES, any README.md file found in sub- -# directories of the project's root, is used as the documentation for that sub- -# directory, except when the README.md starts with a \dir, \page or \mainpage -# command. If set to NO, the README.md file needs to start with an explicit \dir -# command in order to be used as directory documentation. -# The default value is: YES. - -IMPLICIT_DIR_DOCS = YES - # The Fortran standard specifies that for fixed formatted Fortran code all # characters from position 72 are to be considered as comment. A common # extension is to allow longer lines before the automatic comment starts. The @@ -1497,26 +1414,6 @@ HTML_DYNAMIC_SECTIONS = NO HTML_CODE_FOLDING = YES -# If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in -# the top right corner of code and text fragments that allows the user to copy -# its content to the clipboard. Note this only works if supported by the browser -# and the web page is served via a secure context (see: -# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: -# protocol. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COPY_CLIPBOARD = YES - -# Doxygen stores a couple of settings persistently in the browser (via e.g. -# cookies). By default these settings apply to all HTML pages generated by -# Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store -# the settings under a project specific key, such that the user preferences will -# be stored separately. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_PROJECT_COOKIE = - # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to @@ -1769,16 +1666,6 @@ DISABLE_INDEX = YES GENERATE_TREEVIEW = YES -# When GENERATE_TREEVIEW is set to YES, the PAGE_OUTLINE_PANEL option determines -# if an additional navigation panel is shown at the right hand side of the -# screen, displaying an outline of the contents of the main page, similar to -# e.g. https://developer.android.com/reference If GENERATE_TREEVIEW is set to -# NO, this option has no effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -PAGE_OUTLINE_PANEL = YES - # When GENERATE_TREEVIEW is set to YES, the FULL_SIDEBAR option determines if # the side bar is limited to only the treeview area (value NO) or if it should # extend to the full height of the window (value YES). Setting this to YES gives @@ -1800,12 +1687,6 @@ FULL_SIDEBAR = NO ENUM_VALUES_PER_LINE = 4 -# When the SHOW_ENUM_VALUES tag is set doxygen will show the specified -# enumeration values besides the enumeration mnemonics. -# The default value is: NO. - -SHOW_ENUM_VALUES = NO - # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. @@ -2254,14 +2135,6 @@ RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = -# The RTF_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the RTF_OUTPUT output directory. -# Note that the files will be copied as-is; there are no commands or markers -# available. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_EXTRA_FILES = - #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- @@ -2685,15 +2558,6 @@ UML_LOOK = NO UML_LIMIT_NUM_FIELDS = 10 -# If the UML_LOOK tag is enabled, field labels are shown along the edge between -# two class nodes. If there are many fields and many nodes the graph may become -# too cluttered. The UML_MAX_EDGE_LABELS threshold limits the number of items to -# make the size more manageable. Set this to 0 for no limit. -# Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag UML_LOOK is set to YES. - -UML_MAX_EDGE_LABELS = 10 - # If the DOT_UML_DETAILS tag is set to NO, Doxygen will show attributes and # methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS # tag is set to YES, Doxygen will add type and arguments for attributes and @@ -2873,12 +2737,6 @@ PLANTUML_CFG_FILE = PLANTUML_INCLUDE_PATH = -# The PLANTUMLFILE_DIRS tag can be used to specify one or more directories that -# contain PlantUml files that are included in the documentation (see the -# \plantumlfile command). - -PLANTUMLFILE_DIRS = - # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes # that will be shown in the graph. If the number of nodes in a graph becomes # larger than this value, Doxygen will truncate the graph, which is visualized diff --git a/doc/doxygen/DoxygenLayout.xml b/doc/doxygen/DoxygenLayout.xml index d6847755b..a1070d696 100644 --- a/doc/doxygen/DoxygenLayout.xml +++ b/doc/doxygen/DoxygenLayout.xml @@ -121,7 +121,6 @@ - @@ -133,7 +132,6 @@ - @@ -169,7 +167,6 @@ - @@ -182,7 +179,6 @@ - diff --git a/doc/doxygen-extra-pages/developer_documentation/card_database_schema_and_parsing.md b/doc/doxygen/extra-pages/developer_documentation/card_database_schema_and_parsing.md similarity index 100% rename from doc/doxygen-extra-pages/developer_documentation/card_database_schema_and_parsing.md rename to doc/doxygen/extra-pages/developer_documentation/card_database_schema_and_parsing.md diff --git a/doc/doxygen-extra-pages/developer_documentation/displaying_cards.md b/doc/doxygen/extra-pages/developer_documentation/displaying_cards.md similarity index 100% rename from doc/doxygen-extra-pages/developer_documentation/displaying_cards.md rename to doc/doxygen/extra-pages/developer_documentation/displaying_cards.md diff --git a/doc/doxygen-extra-pages/developer_documentation/index.md b/doc/doxygen/extra-pages/developer_documentation/index.md similarity index 100% rename from doc/doxygen-extra-pages/developer_documentation/index.md rename to doc/doxygen/extra-pages/developer_documentation/index.md diff --git a/doc/doxygen-extra-pages/developer_documentation/loading_card_pictures.md b/doc/doxygen/extra-pages/developer_documentation/loading_card_pictures.md similarity index 100% rename from doc/doxygen-extra-pages/developer_documentation/loading_card_pictures.md rename to doc/doxygen/extra-pages/developer_documentation/loading_card_pictures.md diff --git a/doc/doxygen-extra-pages/developer_documentation/primer_cards.md b/doc/doxygen/extra-pages/developer_documentation/primer_cards.md similarity index 100% rename from doc/doxygen-extra-pages/developer_documentation/primer_cards.md rename to doc/doxygen/extra-pages/developer_documentation/primer_cards.md diff --git a/doc/doxygen-extra-pages/developer_documentation/querying_the_card_database.md b/doc/doxygen/extra-pages/developer_documentation/querying_the_card_database.md similarity index 100% rename from doc/doxygen-extra-pages/developer_documentation/querying_the_card_database.md rename to doc/doxygen/extra-pages/developer_documentation/querying_the_card_database.md diff --git a/doc/doxygen-extra-pages/index.md b/doc/doxygen/extra-pages/index.md similarity index 100% rename from doc/doxygen-extra-pages/index.md rename to doc/doxygen/extra-pages/index.md diff --git a/doc/doxygen-extra-pages/user_documentation/deck_management/creating_decks.md b/doc/doxygen/extra-pages/user_documentation/deck_management/creating_decks.md similarity index 100% rename from doc/doxygen-extra-pages/user_documentation/deck_management/creating_decks.md rename to doc/doxygen/extra-pages/user_documentation/deck_management/creating_decks.md 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 similarity index 100% rename from doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks.md rename to doc/doxygen/extra-pages/user_documentation/deck_management/editing_decks.md diff --git a/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_classic.md b/doc/doxygen/extra-pages/user_documentation/deck_management/editing_decks_classic.md similarity index 100% rename from doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_classic.md rename to doc/doxygen/extra-pages/user_documentation/deck_management/editing_decks_classic.md 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 similarity index 100% rename from doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_printings.md rename to doc/doxygen/extra-pages/user_documentation/deck_management/editing_decks_printings.md diff --git a/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_visual.md b/doc/doxygen/extra-pages/user_documentation/deck_management/editing_decks_visual.md similarity index 100% rename from doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_visual.md rename to doc/doxygen/extra-pages/user_documentation/deck_management/editing_decks_visual.md diff --git a/doc/doxygen-extra-pages/user_documentation/deck_management/exporting_decks.md b/doc/doxygen/extra-pages/user_documentation/deck_management/exporting_decks.md similarity index 100% rename from doc/doxygen-extra-pages/user_documentation/deck_management/exporting_decks.md rename to doc/doxygen/extra-pages/user_documentation/deck_management/exporting_decks.md diff --git a/doc/doxygen-extra-pages/user_documentation/deck_management/importing_decks.md b/doc/doxygen/extra-pages/user_documentation/deck_management/importing_decks.md similarity index 100% rename from doc/doxygen-extra-pages/user_documentation/deck_management/importing_decks.md rename to doc/doxygen/extra-pages/user_documentation/deck_management/importing_decks.md diff --git a/doc/doxygen-extra-pages/user_documentation/index.md b/doc/doxygen/extra-pages/user_documentation/index.md similarity index 100% rename from doc/doxygen-extra-pages/user_documentation/index.md rename to doc/doxygen/extra-pages/user_documentation/index.md diff --git a/doc/doxygen-groups/doc_groups.dox b/doc/doxygen/groups/doc_groups.dox similarity index 100% rename from doc/doxygen-groups/doc_groups.dox rename to doc/doxygen/groups/doc_groups.dox diff --git a/doc/doxygen-images/classic_database_display.png b/doc/doxygen/images/classic_database_display.png similarity index 100% rename from doc/doxygen-images/classic_database_display.png rename to doc/doxygen/images/classic_database_display.png diff --git a/doc/doxygen-images/classic_database_display_add_buttons.png b/doc/doxygen/images/classic_database_display_add_buttons.png similarity index 100% rename from doc/doxygen-images/classic_database_display_add_buttons.png rename to doc/doxygen/images/classic_database_display_add_buttons.png diff --git a/doc/doxygen-images/classic_deck_editor.png b/doc/doxygen/images/classic_deck_editor.png similarity index 100% rename from doc/doxygen-images/classic_deck_editor.png rename to doc/doxygen/images/classic_deck_editor.png diff --git a/doc/doxygen-images/deck_dock_deck_list.png b/doc/doxygen/images/deck_dock_deck_list.png similarity index 100% rename from doc/doxygen-images/deck_dock_deck_list.png rename to doc/doxygen/images/deck_dock_deck_list.png diff --git a/doc/doxygen-images/deck_dock_deck_list_buttons.png b/doc/doxygen/images/deck_dock_deck_list_buttons.png similarity index 100% rename from doc/doxygen-images/deck_dock_deck_list_buttons.png rename to doc/doxygen/images/deck_dock_deck_list_buttons.png diff --git a/doc/doxygen-images/deck_dock_deck_list_group_by.png b/doc/doxygen/images/deck_dock_deck_list_group_by.png similarity index 100% rename from doc/doxygen-images/deck_dock_deck_list_group_by.png rename to doc/doxygen/images/deck_dock_deck_list_group_by.png diff --git a/doc/doxygen-images/deckeditordeckdockwidget.png b/doc/doxygen/images/deckeditordeckdockwidget.png similarity index 100% rename from doc/doxygen-images/deckeditordeckdockwidget.png rename to doc/doxygen/images/deckeditordeckdockwidget.png diff --git a/doc/doxygen-images/printing_selector.png b/doc/doxygen/images/printing_selector.png similarity index 100% rename from doc/doxygen-images/printing_selector.png rename to doc/doxygen/images/printing_selector.png diff --git a/doc/doxygen-images/printing_selector_disable.png b/doc/doxygen/images/printing_selector_disable.png similarity index 100% rename from doc/doxygen-images/printing_selector_disable.png rename to doc/doxygen/images/printing_selector_disable.png diff --git a/doc/doxygen-images/printing_selector_enable.png b/doc/doxygen/images/printing_selector_enable.png similarity index 100% rename from doc/doxygen-images/printing_selector_enable.png rename to doc/doxygen/images/printing_selector_enable.png diff --git a/doc/doxygen-images/printing_selector_navigation.png b/doc/doxygen/images/printing_selector_navigation.png similarity index 100% rename from doc/doxygen-images/printing_selector_navigation.png rename to doc/doxygen/images/printing_selector_navigation.png diff --git a/doc/doxygen-images/printing_selector_options.png b/doc/doxygen/images/printing_selector_options.png similarity index 100% rename from doc/doxygen-images/printing_selector_options.png rename to doc/doxygen/images/printing_selector_options.png diff --git a/doc/doxygen-images/printing_selector_pre_providerid.png b/doc/doxygen/images/printing_selector_pre_providerid.png similarity index 100% rename from doc/doxygen-images/printing_selector_pre_providerid.png rename to doc/doxygen/images/printing_selector_pre_providerid.png diff --git a/doc/doxygen-images/vde_deck_analytics.png b/doc/doxygen/images/vde_deck_analytics.png similarity index 100% rename from doc/doxygen-images/vde_deck_analytics.png rename to doc/doxygen/images/vde_deck_analytics.png diff --git a/doc/doxygen-images/vde_flat_layout_color_grouped.png b/doc/doxygen/images/vde_flat_layout_color_grouped.png similarity index 100% rename from doc/doxygen-images/vde_flat_layout_color_grouped.png rename to doc/doxygen/images/vde_flat_layout_color_grouped.png diff --git a/doc/doxygen-images/vde_flat_layout_type_grouped.png b/doc/doxygen/images/vde_flat_layout_type_grouped.png similarity index 100% rename from doc/doxygen-images/vde_flat_layout_type_grouped.png rename to doc/doxygen/images/vde_flat_layout_type_grouped.png diff --git a/doc/doxygen-images/vde_overlap_layout_type_grouped.png b/doc/doxygen/images/vde_overlap_layout_type_grouped.png similarity index 100% rename from doc/doxygen-images/vde_overlap_layout_type_grouped.png rename to doc/doxygen/images/vde_overlap_layout_type_grouped.png diff --git a/doc/doxygen-images/vde_sample_hand.png b/doc/doxygen/images/vde_sample_hand.png similarity index 100% rename from doc/doxygen-images/vde_sample_hand.png rename to doc/doxygen/images/vde_sample_hand.png From aea468bc7f10e63bdae13af346c6b37d5a9e037a Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 23 Nov 2025 19:06:00 +0100 Subject: [PATCH 016/325] Doxygen: Use newer version (#6345) * readd properties * use newer doxygen version + print config update diff * readd config options * fix config * revert cache change * GITHUB md * graphviz version * Add doxygen output to .gitignore --- .github/workflows/documentation-build.yml | 25 ++- .gitignore | 1 + Doxyfile | 179 ++++++++++++++++++++-- doc/doxygen/DoxygenLayout.xml | 4 + 4 files changed, 196 insertions(+), 13 deletions(-) diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml index 12df6bc26..bebb3a9f4 100644 --- a/.github/workflows/documentation-build.yml +++ b/.github/workflows/documentation-build.yml @@ -23,10 +23,31 @@ jobs: - name: Checkout code uses: actions/checkout@v5 - - name: Install Doxygen and Graphviz - run: sudo apt-get install -y doxygen graphviz + - name: Install Graphviz + run: | + sudo apt-get install -y graphviz + dot -V + + - name: Install Doxygen + uses: ssciwr/doxygen-install@v1 + with: + version: "1.14.0" + + - name: Update Doxygen Configuration + run: | + git diff Doxyfile + doxygen -u Doxyfile + if git diff --quiet Doxyfile; then + echo "::notice::No config changes in Doxyfile detected." + else + echo "::error::Config changes in Doxyfile detected! Please update the file by running 'doxygen -u Doxyfile'." + echo "" + git diff --color=always Doxyfile + exit 1 + fi - name: Generate Documentation + if: always() run: doxygen Doxyfile - name: Deploy to cockatrice.github.io diff --git a/.gitignore b/.gitignore index f147c1458..33c1ae31d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ compile_commands.json .cache .gdb_history cockatrice/resources/config/qtlogging.ini +docs/ diff --git a/Doxyfile b/Doxyfile index 85d66bbd3..d5a3a4b7a 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,5 +1,4 @@ # 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. @@ -43,7 +42,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "Cockatrice" +PROJECT_NAME = Cockatrice # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version @@ -55,7 +54,7 @@ PROJECT_NUMBER = $(COCKATRICE_REF) # 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 = A cross-platform virtual tabletop for multiplayer card games +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 @@ -64,6 +63,12 @@ PROJECT_BRIEF = A cross-platform virtual tabletop for multiplayer card PROJECT_LOGO = cockatrice/resources/cockatrice.png +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. + +PROJECT_ICON = cockatrice/resources/cockatrice.png + # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where Doxygen was started. If @@ -374,7 +379,7 @@ TOC_INCLUDE_HEADINGS = 6 # The default value is: DOXYGEN. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. -MARKDOWN_ID_STYLE = DOXYGEN +MARKDOWN_ID_STYLE = GITHUB # When enabled Doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can @@ -385,6 +390,14 @@ MARKDOWN_ID_STYLE = DOXYGEN AUTOLINK_SUPPORT = YES +# This tag specifies a list of words that, when matching the start of a word in +# the documentation, will suppress auto links generation, if it is enabled via +# AUTOLINK_SUPPORT. This list does not affect links explicitly created using \# +# or the \link or commands. +# This tag requires that the tag AUTOLINK_SUPPORT is set to YES. + +AUTOLINK_IGNORE_WORDS = + # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let Doxygen match functions declarations and @@ -486,7 +499,7 @@ TYPEDEF_HIDES_STRUCT = NO # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. -LOOKUP_CACHE_SIZE = 1 +LOOKUP_CACHE_SIZE = 0 # The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use # during processing. When set to 0 Doxygen will based this on the number of @@ -596,6 +609,14 @@ HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO +# If the HIDE_UNDOC_NAMESPACES tag is set to YES, Doxygen will hide all +# undocumented namespaces that are normally visible in the namespace hierarchy. +# If set to NO, these namespaces will be included in the various overviews. This +# option has no effect if EXTRACT_ALL is enabled. +# The default value is: YES. + +HIDE_UNDOC_NAMESPACES = YES + # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all friend # declarations. If set to NO, these declarations will be included in the # documentation. @@ -830,6 +851,22 @@ LAYOUT_FILE = doc/doxygen/DoxygenLayout.xml CITE_BIB_FILES = +# The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH +# environment variable) so that external tools such as latex and gs can be +# found. +# Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the +# path already specified by the PATH variable, and are added in the order +# specified. +# Note: This option is particularly useful for macOS version 14 (Sonoma) and +# higher, when running Doxygen from Doxywizard, because in this case any user- +# defined changes to the PATH are ignored. A typical example on macOS is to set +# EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin +# together with the standard path, the full search path used by doxygen when +# launching external tools will then become +# PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + +EXTERNAL_TOOL_PATH = + #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- @@ -890,6 +927,14 @@ WARN_NO_PARAMDOC = NO WARN_IF_UNDOC_ENUM_VAL = NO +# If WARN_LAYOUT_FILE option is set to YES, Doxygen will warn about issues found +# while parsing the user defined layout file, such as missing or wrong elements. +# See also LAYOUT_FILE for details. If set to NO, problems with the layout file +# will be suppressed. +# The default value is: YES. + +WARN_LAYOUT_FILE = YES + # If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but @@ -986,8 +1031,7 @@ INPUT_FILE_ENCODING = # provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. -FILE_PATTERNS = *.c \ - *.cc \ +FILE_PATTERNS = *.cc \ *.cxx \ *.cxxm \ *.cpp \ @@ -1022,7 +1066,11 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which Doxygen is # run. -EXCLUDE = build/ cmake/ vcpkg/ dbconverter/ webclient/ +EXCLUDE = build/ \ + cmake/ \ + dbconverter/ \ + vcpkg/ \ + webclient/ # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -1038,7 +1086,8 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = .* .*/ +EXCLUDE_PATTERNS = .* \ + .*/ # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -1135,6 +1184,15 @@ FILTER_SOURCE_PATTERNS = USE_MDFILE_AS_MAINPAGE = +# If the IMPLICIT_DIR_DOCS tag is set to YES, any README.md file found in sub- +# directories of the project's root, is used as the documentation for that sub- +# directory, except when the README.md starts with a \dir, \page or \mainpage +# command. If set to NO, the README.md file needs to start with an explicit \dir +# command in order to be used as directory documentation. +# The default value is: YES. + +IMPLICIT_DIR_DOCS = YES + # The Fortran standard specifies that for fixed formatted Fortran code all # characters from position 72 are to be considered as comment. A common # extension is to allow longer lines before the automatic comment starts. The @@ -1231,6 +1289,46 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES +# If the CLANG_ASSISTED_PARSING tag is set to YES then Doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which Doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not Doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS +# tag is set to YES then Doxygen will add the directory of each input to the +# include path. +# The default value is: YES. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by Doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not Doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- @@ -1414,6 +1512,26 @@ HTML_DYNAMIC_SECTIONS = NO HTML_CODE_FOLDING = YES +# If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = YES + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to @@ -1666,6 +1784,16 @@ DISABLE_INDEX = YES GENERATE_TREEVIEW = YES +# When GENERATE_TREEVIEW is set to YES, the PAGE_OUTLINE_PANEL option determines +# if an additional navigation panel is shown at the right hand side of the +# screen, displaying an outline of the contents of the main page, similar to +# e.g. https://developer.android.com/reference If GENERATE_TREEVIEW is set to +# NO, this option has no effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +PAGE_OUTLINE_PANEL = YES + # When GENERATE_TREEVIEW is set to YES, the FULL_SIDEBAR option determines if # the side bar is limited to only the treeview area (value NO) or if it should # extend to the full height of the window (value YES). Setting this to YES gives @@ -1687,6 +1815,12 @@ FULL_SIDEBAR = NO ENUM_VALUES_PER_LINE = 4 +# When the SHOW_ENUM_VALUES tag is set doxygen will show the specified +# enumeration values besides the enumeration mnemonics. +# The default value is: NO. + +SHOW_ENUM_VALUES = NO + # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. @@ -2135,6 +2269,14 @@ RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = +# The RTF_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the RTF_OUTPUT output directory. +# Note that the files will be copied as-is; there are no commands or markers +# available. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_EXTRA_FILES = + #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- @@ -2481,7 +2623,7 @@ DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" # The default value is: labelfontname=Helvetica,labelfontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10,arrowhead=open, arrowtail=open, arrowsize=0.5" +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10,arrowhead=open, arrowtail=open, arrowsize=0.5" # DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes # around nodes set 'shape=plain' or 'shape=plaintext' + @@ -132,6 +133,7 @@ + @@ -167,6 +169,7 @@ + @@ -179,6 +182,7 @@ + From adee67115c27b75ba1b380bf90770bfc145c3aa6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:42:21 +0100 Subject: [PATCH 017/325] Bump actions/checkout from 5 to 6 (#6347) --- .github/workflows/desktop-build.yml | 6 +++--- .github/workflows/desktop-lint.yml | 2 +- .github/workflows/docker-release.yml | 2 +- .github/workflows/documentation-build.yml | 2 +- .github/workflows/translations-pull.yml | 2 +- .github/workflows/translations-push.yml | 2 +- .github/workflows/web-build.yml | 2 +- .github/workflows/web-lint.yml | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index fe75f4916..58b246071 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -70,7 +70,7 @@ jobs: - name: Checkout if: steps.configure.outputs.tag != null - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -162,7 +162,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Restore compiler cache (ccache) id: ccache_restore @@ -345,7 +345,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive diff --git a/.github/workflows/desktop-lint.yml b/.github/workflows/desktop-lint.yml index 087b27b79..433f302a5 100644 --- a/.github/workflows/desktop-lint.yml +++ b/.github/workflows/desktop-lint.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 20 # should be enough to find merge base diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 028d5ddf9..1846766bf 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Docker metadata id: metadata diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml index bebb3a9f4..396ccd62b 100644 --- a/.github/workflows/documentation-build.yml +++ b/.github/workflows/documentation-build.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Graphviz run: | diff --git a/.github/workflows/translations-pull.yml b/.github/workflows/translations-pull.yml index 7834e06b0..81d6f4e75 100644 --- a/.github/workflows/translations-pull.yml +++ b/.github/workflows/translations-pull.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Pull translated strings from Transifex uses: transifex/cli-action@v2 diff --git a/.github/workflows/translations-push.yml b/.github/workflows/translations-push.yml index 602845a49..d201e7ad8 100644 --- a/.github/workflows/translations-push.yml +++ b/.github/workflows/translations-push.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install lupdate shell: bash diff --git a/.github/workflows/web-build.yml b/.github/workflows/web-build.yml index 91080fa29..8d756da02 100644 --- a/.github/workflows/web-build.yml +++ b/.github/workflows/web-build.yml @@ -35,7 +35,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v6 diff --git a/.github/workflows/web-lint.yml b/.github/workflows/web-lint.yml index a07463780..8a90325e7 100644 --- a/.github/workflows/web-lint.yml +++ b/.github/workflows/web-lint.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v6 From a21e45ed360e8dad1583cd578c05c1f7dbc79665 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Wed, 26 Nov 2025 15:16:10 +0100 Subject: [PATCH 018/325] add phase to delete arrows in to protocol (#6159) * protocol changes * servatrice changes * add new setting * implement client side with static 4 phases * reading the code explains the code * add subphases to phase.cpp * use new subphase definition --- .../src/client/settings/cache_settings.cpp | 7 ++++ .../src/client/settings/cache_settings.h | 6 +++ cockatrice/src/game/board/arrow_item.cpp | 24 ++++++++---- cockatrice/src/game/board/arrow_item.h | 3 +- cockatrice/src/game/board/card_item.cpp | 14 +++++-- cockatrice/src/game/phase.cpp | 23 ++++++++++++ cockatrice/src/game/phase.h | 2 + .../widgets/dialogs/dlg_settings.cpp | 14 +++++-- .../interface/widgets/dialogs/dlg_settings.h | 1 + dbconverter/src/mocks.cpp | 5 ++- .../remote/game/server_abstract_player.cpp | 4 +- .../server/remote/game/server_arrow.cpp | 10 ++++- .../network/server/remote/game/server_arrow.h | 12 +++++- .../server/remote/game/server_game.cpp | 37 +++++++++++-------- .../network/server/remote/game/server_game.h | 5 ++- .../protocol/pb/command_create_arrow.proto | 5 +++ 16 files changed, 133 insertions(+), 39 deletions(-) diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 0c6091a35..7b8458726 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -261,6 +261,7 @@ SettingsCache::SettingsCache() doubleClickToPlay = settings->value("interface/doubleclicktoplay", true).toBool(); clickPlaysAllSelected = settings->value("interface/clickPlaysAllSelected", true).toBool(); playToStack = settings->value("interface/playtostack", true).toBool(); + doNotDeleteArrowsInSubPhases = settings->value("interface/doNotDeleteArrowsInSubPhases", true).toBool(); startingHandSize = settings->value("interface/startinghandsize", 7).toInt(); annotateTokens = settings->value("interface/annotatetokens", false).toBool(); tabGameSplitterSizes = settings->value("interface/tabgame_splittersizes").toByteArray(); @@ -664,6 +665,12 @@ void SettingsCache::setPlayToStack(QT_STATE_CHANGED_T _playToStack) settings->setValue("interface/playtostack", playToStack); } +void SettingsCache::setDoNotDeleteArrowsInSubPhases(QT_STATE_CHANGED_T _doNotDeleteArrowsInSubPhases) +{ + doNotDeleteArrowsInSubPhases = static_cast(_doNotDeleteArrowsInSubPhases); + settings->setValue("interface/doNotDeleteArrowsInSubPhases", doNotDeleteArrowsInSubPhases); +} + void SettingsCache::setStartingHandSize(int _startingHandSize) { startingHandSize = _startingHandSize; diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index cf0daff69..eb504e387 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -228,6 +228,7 @@ private: bool doubleClickToPlay; bool clickPlaysAllSelected; bool playToStack; + bool doNotDeleteArrowsInSubPhases; int startingHandSize; bool annotateTokens; QByteArray tabGameSplitterSizes; @@ -525,6 +526,10 @@ public: { return playToStack; } + bool getDoNotDeleteArrowsInSubPhases() const + { + return doNotDeleteArrowsInSubPhases; + } int getStartingHandSize() const { return startingHandSize; @@ -984,6 +989,7 @@ public slots: void setDoubleClickToPlay(QT_STATE_CHANGED_T _doubleClickToPlay); void setClickPlaysAllSelected(QT_STATE_CHANGED_T _clickPlaysAllSelected); void setPlayToStack(QT_STATE_CHANGED_T _playToStack); + void setDoNotDeleteArrowsInSubPhases(QT_STATE_CHANGED_T _doNotDeleteArrowsInSubPhases); void setStartingHandSize(int _startingHandSize); void setAnnotateTokens(QT_STATE_CHANGED_T _annotateTokens); void setTabGameSplitterSizes(const QByteArray &_tabGameSplitterSizes); diff --git a/cockatrice/src/game/board/arrow_item.cpp b/cockatrice/src/game/board/arrow_item.cpp index 22e3ceb50..d5d0ebfe0 100644 --- a/cockatrice/src/game/board/arrow_item.cpp +++ b/cockatrice/src/game/board/arrow_item.cpp @@ -151,8 +151,8 @@ void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event) } } -ArrowDragItem::ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color) - : ArrowItem(_owner, -1, _startItem, 0, _color) +ArrowDragItem::ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase) + : ArrowItem(_owner, -1, _startItem, 0, _color), deleteInPhase(_deleteInPhase) { } @@ -231,20 +231,28 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId()); cmd.set_target_zone(targetZone->getName().toStdString()); cmd.set_target_card_id(targetCard->getId()); - } else { + } else { // failed to cast target to card, this means it's a player PlayerTarget *targetPlayer = qgraphicsitem_cast(targetItem); cmd.set_target_player_id(targetPlayer->getOwner()->getPlayerInfo()->getId()); } - if (startZone->getName().compare("hand") == 0) { + + // if the card is in hand then we will move the card to stack or table as part of drawing the arrow + if (startZone->getName() == "hand") { startCard->playCard(false); CardInfoPtr ci = startCard->getCard().getCardPtr(); - if (ci && ((!SettingsCache::instance().getPlayToStack() && ci->getUiAttributes().tableRow == 3) || - (SettingsCache::instance().getPlayToStack() && ci->getUiAttributes().tableRow != 0 && - startCard->getZone()->getName().toStdString() != "stack"))) + bool playToStack = SettingsCache::instance().getPlayToStack(); + if (ci && + ((!playToStack && ci->getUiAttributes().tableRow == 3) || + (playToStack && ci->getUiAttributes().tableRow != 0 && startCard->getZone()->getName() != "stack"))) cmd.set_start_zone("stack"); else - cmd.set_start_zone(SettingsCache::instance().getPlayToStack() ? "stack" : "table"); + cmd.set_start_zone(playToStack ? "stack" : "table"); } + + if (deleteInPhase != 0) { + cmd.set_delete_in_phase(deleteInPhase); + } + player->getPlayerActions()->sendGameCommand(cmd); } delArrow(); diff --git a/cockatrice/src/game/board/arrow_item.h b/cockatrice/src/game/board/arrow_item.h index 8405083fe..cb78ee066 100644 --- a/cockatrice/src/game/board/arrow_item.h +++ b/cockatrice/src/game/board/arrow_item.h @@ -82,10 +82,11 @@ class ArrowDragItem : public ArrowItem { Q_OBJECT private: + int deleteInPhase; QList childArrows; public: - ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color); + ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase); void addChildArrow(ArrowDragItem *childArrow); protected: diff --git a/cockatrice/src/game/board/card_item.cpp b/cockatrice/src/game/board/card_item.cpp index 52e237897..e786b5329 100644 --- a/cockatrice/src/game/board/card_item.cpp +++ b/cockatrice/src/game/board/card_item.cpp @@ -3,6 +3,7 @@ #include "../../client/settings/cache_settings.h" #include "../../interface/widgets/tabs/tab_game.h" #include "../game_scene.h" +#include "../phase.h" #include "../player/player.h" #include "../zones/card_zone.h" #include "../zones/logic/view_zone_logic.h" @@ -275,9 +276,14 @@ void CardItem::drawArrow(const QColor &arrowColor) if (owner->getGame()->getPlayerManager()->isSpectator()) return; - Player *arrowOwner = - owner->getGame()->getPlayerManager()->getActiveLocalPlayer(owner->getGame()->getGameState()->getActivePlayer()); - ArrowDragItem *arrow = new ArrowDragItem(arrowOwner, this, arrowColor); + auto *game = owner->getGame(); + Player *arrowOwner = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer()); + int phase = 0; // 0 means to not set the phase + if (SettingsCache::instance().getDoNotDeleteArrowsInSubPhases()) { + int currentPhase = game->getGameState()->getCurrentPhase(); + phase = Phases::getLastSubphase(currentPhase) + 1; + } + ArrowDragItem *arrow = new ArrowDragItem(arrowOwner, this, arrowColor, phase); scene()->addItem(arrow); arrow->grabMouse(); @@ -288,7 +294,7 @@ void CardItem::drawArrow(const QColor &arrowColor) if (card->getZone() != zone) continue; - ArrowDragItem *childArrow = new ArrowDragItem(arrowOwner, card, arrowColor); + ArrowDragItem *childArrow = new ArrowDragItem(arrowOwner, card, arrowColor, phase); scene()->addItem(childArrow); arrow->addChildArrow(childArrow); } diff --git a/cockatrice/src/game/phase.cpp b/cockatrice/src/game/phase.cpp index 32c9060ee..7fb1700a5 100644 --- a/cockatrice/src/game/phase.cpp +++ b/cockatrice/src/game/phase.cpp @@ -22,6 +22,28 @@ Phase Phases::getPhase(int phase) } } +int Phases::getLastSubphase(int phase) +{ + if (0 <= phase && phase < Phases::phaseTypesCount) { + return subPhasesEnd[phase]; + } else { + return phase; + } +} + +QVector getSubPhasesEnd() +{ + QVector array(Phases::phaseTypesCount); + for (int phaseEnd = Phases::phaseTypesCount - 1; phaseEnd >= 0;) { + int subPhase = phaseEnd; + for (; subPhase >= 0 && Phases::phases[phaseEnd].color == Phases::phases[subPhase].color; --subPhase) { + array[subPhase] = phaseEnd; + } + phaseEnd = subPhase; + } + return array; +} + const Phase Phases::unknownPhase(QT_TRANSLATE_NOOP("Phase", "Unknown Phase"), "black", "unknown_phase"); const Phase Phases::phases[Phases::phaseTypesCount] = { {QT_TRANSLATE_NOOP("Phase", "Untap"), "green", "untap_step"}, @@ -35,3 +57,4 @@ const Phase Phases::phases[Phases::phaseTypesCount] = { {QT_TRANSLATE_NOOP("Phase", "End of Combat"), "red", "end_combat"}, {QT_TRANSLATE_NOOP("Phase", "Second Main"), "blue", "main_2"}, {QT_TRANSLATE_NOOP("Phase", "End/Cleanup"), "green", "end_step"}}; +const QVector Phases::subPhasesEnd = getSubPhasesEnd(); diff --git a/cockatrice/src/game/phase.h b/cockatrice/src/game/phase.h index 6e451161e..2e712932f 100644 --- a/cockatrice/src/game/phase.h +++ b/cockatrice/src/game/phase.h @@ -28,8 +28,10 @@ struct Phases const static int phaseTypesCount = 11; const static Phase unknownPhase; const static Phase phases[phaseTypesCount]; + const static QVector subPhasesEnd; static Phase getPhase(int); + static int getLastSubphase(int phase); }; #endif // PHASE_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp index 73508d843..e92593d55 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp @@ -801,6 +801,10 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() connect(&playToStackCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setPlayToStack); + doNotDeleteArrowsInSubPhasesCheckBox.setChecked(SettingsCache::instance().getDoNotDeleteArrowsInSubPhases()); + connect(&doNotDeleteArrowsInSubPhasesCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setDoNotDeleteArrowsInSubPhases); + closeEmptyCardViewCheckBox.setChecked(SettingsCache::instance().getCloseEmptyCardView()); connect(&closeEmptyCardViewCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setCloseEmptyCardView); @@ -821,10 +825,11 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() generalGrid->addWidget(&doubleClickToPlayCheckBox, 0, 0); generalGrid->addWidget(&clickPlaysAllSelectedCheckBox, 1, 0); generalGrid->addWidget(&playToStackCheckBox, 2, 0); - generalGrid->addWidget(&closeEmptyCardViewCheckBox, 3, 0); - generalGrid->addWidget(&focusCardViewSearchBarCheckBox, 4, 0); - generalGrid->addWidget(&annotateTokensCheckBox, 5, 0); - generalGrid->addWidget(&useTearOffMenusCheckBox, 6, 0); + generalGrid->addWidget(&doNotDeleteArrowsInSubPhasesCheckBox, 3, 0); + generalGrid->addWidget(&closeEmptyCardViewCheckBox, 4, 0); + generalGrid->addWidget(&focusCardViewSearchBarCheckBox, 5, 0); + generalGrid->addWidget(&annotateTokensCheckBox, 6, 0); + generalGrid->addWidget(&useTearOffMenusCheckBox, 7, 0); generalGroupBox = new QGroupBox; generalGroupBox->setLayout(generalGrid); @@ -942,6 +947,7 @@ void UserInterfaceSettingsPage::retranslateUi() doubleClickToPlayCheckBox.setText(tr("&Double-click cards to play them (instead of single-click)")); clickPlaysAllSelectedCheckBox.setText(tr("&Clicking plays all selected cards (instead of just the clicked card)")); playToStackCheckBox.setText(tr("&Play all nonlands onto the stack (not the battlefield) by default")); + doNotDeleteArrowsInSubPhasesCheckBox.setText(tr("Do not delete &arrows inside of subphases")); closeEmptyCardViewCheckBox.setText(tr("Close card view window when last card is removed")); focusCardViewSearchBarCheckBox.setText(tr("Auto focus search bar when card view window is opened")); annotateTokensCheckBox.setText(tr("Annotate card text on tokens")); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h index 22c9fa1c0..10134f3a8 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h @@ -163,6 +163,7 @@ private: QCheckBox doubleClickToPlayCheckBox; QCheckBox clickPlaysAllSelectedCheckBox; QCheckBox playToStackCheckBox; + QCheckBox doNotDeleteArrowsInSubPhasesCheckBox; QCheckBox closeEmptyCardViewCheckBox; QCheckBox focusCardViewSearchBarCheckBox; QCheckBox annotateTokensCheckBox; diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp index 75195fc96..b97bd09c7 100644 --- a/dbconverter/src/mocks.cpp +++ b/dbconverter/src/mocks.cpp @@ -182,6 +182,9 @@ void SettingsCache::setClickPlaysAllSelected(QT_STATE_CHANGED_T /* _clickPlaysAl void SettingsCache::setPlayToStack(QT_STATE_CHANGED_T /* _playToStack */) { } +void SettingsCache::setDoNotDeleteArrowsInSubPhases(QT_STATE_CHANGED_T /* _doNotDeleteArrowsInSubPhases */) +{ +} void SettingsCache::setStartingHandSize(int /* _startingHandSize */) { } @@ -445,4 +448,4 @@ void SettingsCache::setRoundCardCorners(bool /* _roundCardCorners */) void CardPictureLoader::clearPixmapCache(CardInfoPtr /* card */) { -} \ No newline at end of file +} 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 1c81b6823..ea62c96fa 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 @@ -1210,7 +1210,9 @@ Server_AbstractPlayer::cmdCreateArrow(const Command_CreateArrow &cmd, ResponseCo } } - auto arrow = new Server_Arrow(newArrowId(), startCard, targetItem, cmd.arrow_color()); + int currentPhase = game->getActivePhase(); + int deletionPhase = cmd.has_delete_in_phase() ? cmd.delete_in_phase() : currentPhase; + auto arrow = new Server_Arrow(newArrowId(), startCard, targetItem, cmd.arrow_color(), currentPhase, deletionPhase); addArrow(arrow); Event_CreateArrow event; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.cpp index 9433d8d3f..f6787baa2 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.cpp @@ -6,8 +6,14 @@ #include -Server_Arrow::Server_Arrow(int _id, Server_Card *_startCard, Server_ArrowTarget *_targetItem, const color &_arrowColor) - : id(_id), startCard(_startCard), targetItem(_targetItem), arrowColor(_arrowColor) +Server_Arrow::Server_Arrow(int _id, + Server_Card *_startCard, + Server_ArrowTarget *_targetItem, + const color &_arrowColor, + int _phaseCreated, + int _phaseDeleted) + : id(_id), startCard(_startCard), targetItem(_targetItem), arrowColor(_arrowColor), phaseCreated(_phaseCreated), + phaseDeleted(_phaseDeleted) { } diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.h index 0b3e63550..1f302358b 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.h @@ -14,9 +14,15 @@ private: Server_Card *startCard; Server_ArrowTarget *targetItem; color arrowColor; + int phaseCreated, phaseDeleted; public: - Server_Arrow(int _id, Server_Card *_startCard, Server_ArrowTarget *_targetItem, const color &_arrowColor); + Server_Arrow(int _id, + Server_Card *_startCard, + Server_ArrowTarget *_targetItem, + const color &_arrowColor, + int _phaseCreated, + int _phaseDeleted); int getId() const { return id; @@ -45,6 +51,10 @@ public: { return arrowColor; } + bool checkPhaseDeletion(int phase) const // returns true if the arrow should be deleted in this phase + { + return phase < phaseCreated || phase >= phaseDeleted; + } void getInfo(ServerInfo_Arrow *info); }; 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 82b24ccde..b0dc7fc5b 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -654,6 +654,8 @@ void Server_Game::setActivePlayer(int _activePlayer) { QMutexLocker locker(&gameMutex); + removeArrows(0, true); + activePlayer = _activePlayer; Event_SetActivePlayer event; @@ -663,30 +665,35 @@ void Server_Game::setActivePlayer(int _activePlayer) setActivePhase(0); } -void Server_Game::setActivePhase(int _activePhase) +void Server_Game::setActivePhase(int newPhase) { QMutexLocker locker(&gameMutex); - for (auto *player : getPlayers().values()) { - QList toDelete = player->getArrows().values(); - for (int i = 0; i < toDelete.size(); ++i) { - Server_Arrow *a = toDelete[i]; - - Event_DeleteArrow event; - event.set_arrow_id(a->getId()); - sendGameEventContainer(prepareGameEvent(event, player->getPlayerId())); - - player->deleteArrow(a->getId()); - } - } - - activePhase = _activePhase; + removeArrows(newPhase); + activePhase = newPhase; Event_SetActivePhase event; event.set_phase(activePhase); sendGameEventContainer(prepareGameEvent(event, -1)); } +void Server_Game::removeArrows(int newPhase, bool force) +{ + QMutexLocker locker(&gameMutex); + + for (auto *anyPlayer : getPlayers().values()) { + for (auto *arrowToDelete : anyPlayer->getArrows().values()) { // values creates a copy + if (force || arrowToDelete->checkPhaseDeletion(newPhase)) { + Event_DeleteArrow event; + event.set_arrow_id(arrowToDelete->getId()); + sendGameEventContainer(prepareGameEvent(event, anyPlayer->getPlayerId())); + + anyPlayer->deleteArrow(arrowToDelete->getId()); + } + } + } +} + void Server_Game::nextTurn() { QMutexLocker locker(&gameMutex); 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 50ffbef36..033542fad 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h @@ -198,8 +198,9 @@ public: { return activePhase; } - void setActivePlayer(int _activePlayer); - void setActivePhase(int _activePhase); + void setActivePlayer(int newPlayer); + void setActivePhase(int newPhase); + void removeArrows(int newPhase, bool force = false); void nextTurn(); int getSecondsElapsed() const { diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/command_create_arrow.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/command_create_arrow.proto index fcfa028a6..cb3871b60 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/command_create_arrow.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/command_create_arrow.proto @@ -27,4 +27,9 @@ message Command_CreateArrow { // the color of the arrow optional color arrow_color = 7; + + // the phase that this arrow is deleted in, arrows are deleted when this or a later phase is activated and also + // when the phase moves back before the current phase or the turn is passed, when not set the arrow is deleted + // immediately when the phase is changed + optional sint32 delete_in_phase = 8; } From 65aef396fb234185368176492522ba0a3cec7272 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Wed, 26 Nov 2025 15:16:50 +0100 Subject: [PATCH 019/325] do not allow other players to know which cards are in a player's hand (#6125) --- .../remote/game/server_abstract_player.cpp | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) 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 ea62c96fa..aaf7f1332 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 @@ -375,20 +375,17 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, eventPrivate.set_x(newX); - // Other players do not get to see the start and/or target position of the card if the respective - // part of the zone is being looked at. The information is not needed anyway because in hidden zones, - // all cards are equal. - if (((startzone->getType() == ServerInfo_Zone::HiddenZone) && - ((startzone->getCardsBeingLookedAt() > position) || (startzone->getCardsBeingLookedAt() == -1))) || - (startzone->getType() == ServerInfo_Zone::PublicZone)) { - eventOthers.set_position(-1); - } else { + if ( + // cards from public zones have their id known, their previous position is already known, the event does + // not accomodate for previous locations in zones with coordinates (which are always public) + (startzone->getType() != ServerInfo_Zone::PublicZone) && + // other players are not allowed to be able to track which card is which in private zones like the hand + (startzone->getType() != ServerInfo_Zone::PrivateZone)) { eventOthers.set_position(position); } - if ((targetzone->getType() == ServerInfo_Zone::HiddenZone) && - ((targetzone->getCardsBeingLookedAt() > newX) || (targetzone->getCardsBeingLookedAt() == -1))) { - eventOthers.set_x(-1); - } else { + if ( + // other players are not allowed to be able to track which card is which in private zones like the hand + (targetzone->getType() != ServerInfo_Zone::PrivateZone)) { eventOthers.set_x(newX); } From 1931eb11a996d96c5187b727488c3e0003f3ce27 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 27 Nov 2025 21:56:49 +0100 Subject: [PATCH 020/325] [VDD] Change 'filter to most recent sets' default to false (#6358) --- cockatrice/src/client/settings/cache_settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 7b8458726..b9d2e6bb2 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -310,7 +310,7 @@ SettingsCache::SettingsCache() settings->value("interface/visualdeckstorageselectionanimation", true).toBool(); defaultDeckEditorType = settings->value("interface/defaultDeckEditorType", 1).toInt(); visualDatabaseDisplayFilterToMostRecentSetsEnabled = - settings->value("interface/visualdatabasedisplayfiltertomostrecentsetsenabled", true).toBool(); + settings->value("interface/visualdatabasedisplayfiltertomostrecentsetsenabled", false).toBool(); visualDatabaseDisplayFilterToMostRecentSetsAmount = settings->value("interface/visualdatabasedisplayfiltertomostrecentsetsamount", 10).toInt(); visualDeckEditorSampleHandSize = settings->value("interface/visualdeckeditorsamplehandsize", 7).toInt(); From 553952132f0654349b60b6a326a4add7b5d5367e Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 27 Nov 2025 22:00:35 +0100 Subject: [PATCH 021/325] [Game] Fix CardZoneLogic::clearContents() (#6356) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 6 minutes Took 28 seconds Co-authored-by: Lukas Brübach --- .../src/game/zones/logic/card_zone_logic.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/cockatrice/src/game/zones/logic/card_zone_logic.cpp b/cockatrice/src/game/zones/logic/card_zone_logic.cpp index b5869489a..a5dc64b50 100644 --- a/cockatrice/src/game/zones/logic/card_zone_logic.cpp +++ b/cockatrice/src/game/zones/logic/card_zone_logic.cpp @@ -148,17 +148,24 @@ void CardZoneLogic::moveAllToZone() void CardZoneLogic::clearContents() { - for (int i = 0; i < cards.size(); i++) { + // First gather the cards into a safe temporary list. + const CardList toClear = cards; + + // Detach and notify attached cards and zones *before* deleting anything. + for (CardItem *card : toClear) { // If an incorrectly implemented server doesn't return attached cards to whom they belong before dropping a // player, we have to return them to avoid a crash. - - const QList &attachedCards = cards[i]->getAttachedCards(); - for (auto attachedCard : attachedCards) { + const QList &attachedCards = card->getAttachedCards(); + for (CardItem *attachedCard : attachedCards) { emit attachedCard->getZone()->cardAdded(attachedCard); } - - player->deleteCard(cards.at(i)); } + + // Now request deletions after all manipulations are done. + for (CardItem *card : toClear) { + player->deleteCard(card); + } + cards.clear(); emit cardCountChanged(); } From 1c5bfdbabe592c9ce2dca0b7bee6975a5bb08c43 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 27 Nov 2025 22:02:27 +0100 Subject: [PATCH 022/325] Rebuild tree any time setDeckList is called. (#6353) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 2 hours 5 minutes Co-authored-by: Lukas Brübach --- .../libcockatrice/models/deck_list/deck_list_model.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ed77b55ca..e3e2de58d 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -539,8 +539,8 @@ void DeckListModel::setDeckList(DeckList *_deck) { if (deckList != _deck) { deckList = _deck; - rebuildTree(); } + rebuildTree(); } QList DeckListModel::getCards() const From c75a483ee66e75a10627aef9166ed245f8ef8130 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 27 Nov 2025 22:16:12 +0100 Subject: [PATCH 023/325] [VDE] Add selection model (#6354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 22 minutes Took 1 minute Took 17 seconds Co-authored-by: Lukas Brübach --- .../card_group_display_widget.cpp | 49 ++++++- .../card_group_display_widget.h | 5 + .../flat_card_group_display_widget.cpp | 2 + .../flat_card_group_display_widget.h | 1 + .../overlapped_card_group_display_widget.cpp | 5 +- .../overlapped_card_group_display_widget.h | 1 + ..._info_picture_with_text_overlay_widget.cpp | 47 ++++++- ...rd_info_picture_with_text_overlay_widget.h | 2 + .../cards/deck_card_zone_display_widget.cpp | 43 +++++-- .../cards/deck_card_zone_display_widget.h | 3 + .../deck_editor_deck_dock_widget.h | 5 + .../tab_deck_editor_visual.cpp | 70 ++++++++-- .../tab_deck_editor_visual.h | 5 +- .../tab_deck_editor_visual_tab_widget.cpp | 2 +- .../visual_deck_editor_widget.cpp | 121 ++++++++++++------ .../visual_deck_editor_widget.h | 11 +- 16 files changed, 303 insertions(+), 69 deletions(-) diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp index 81bd52702..7734a61d0 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp @@ -10,6 +10,7 @@ CardGroupDisplayWidget::CardGroupDisplayWidget(QWidget *parent, DeckListModel *_deckListModel, + QItemSelectionModel *_selectionModel, QPersistentModelIndex _trackedIndex, QString _zoneName, QString _cardGroupCategory, @@ -17,8 +18,8 @@ CardGroupDisplayWidget::CardGroupDisplayWidget(QWidget *parent, QStringList _activeSortCriteria, int bannerOpacity, CardSizeWidget *_cardSizeWidget) - : QWidget(parent), deckListModel(_deckListModel), trackedIndex(_trackedIndex), zoneName(_zoneName), - cardGroupCategory(_cardGroupCategory), activeGroupCriteria(_activeGroupCriteria), + : QWidget(parent), deckListModel(_deckListModel), selectionModel(_selectionModel), trackedIndex(_trackedIndex), + zoneName(_zoneName), cardGroupCategory(_cardGroupCategory), activeGroupCriteria(_activeGroupCriteria), activeSortCriteria(_activeSortCriteria), cardSizeWidget(_cardSizeWidget) { layout = new QVBoxLayout(this); @@ -32,9 +33,47 @@ CardGroupDisplayWidget::CardGroupDisplayWidget(QWidget *parent, CardGroupDisplayWidget::updateCardDisplays(); connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &CardGroupDisplayWidget::onCardAddition); + connect(selectionModel, &QItemSelectionModel::selectionChanged, this, &CardGroupDisplayWidget::onSelectionChanged); connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &CardGroupDisplayWidget::onCardRemoval); } +void CardGroupDisplayWidget::onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) +{ + auto proxyModel = qobject_cast(selectionModel->model()); + + for (auto &range : selected) { + for (int row = range.top(); row <= range.bottom(); ++row) { + QModelIndex idx = range.model()->index(row, 0, range.parent()); + + if (proxyModel) { + idx = proxyModel->mapToSource(idx); + } + + auto it = indexToWidgetMap.find(QPersistentModelIndex(idx)); + if (it != indexToWidgetMap.end()) { + if (auto displayWidget = qobject_cast(it.value())) { + displayWidget->setHighlighted(true); + } + } + } + } + + for (auto &range : deselected) { + for (int row = range.top(); row <= range.bottom(); ++row) { + QModelIndex idx = range.model()->index(row, 0, range.parent()); + if (proxyModel) + idx = proxyModel->mapToSource(idx); + + auto it = indexToWidgetMap.find(QPersistentModelIndex(idx)); + if (it != indexToWidgetMap.end()) { + if (auto displayWidget = qobject_cast(it.value())) { + displayWidget->setHighlighted(false); + } + } + } + } +} + void CardGroupDisplayWidget::clearAllDisplayWidgets() { for (auto idx : indexToWidgetMap.keys()) { @@ -138,6 +177,12 @@ void CardGroupDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeSort updateCardDisplays(); } +void CardGroupDisplayWidget::mousePressEvent(QMouseEvent *event) +{ + QWidget::mousePressEvent(event); + selectionModel->clearSelection(); +} + void CardGroupDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card) { emit cardClicked(event, card); diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h index a62a66803..4d911edde 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h @@ -11,6 +11,7 @@ #include "../card_info_picture_with_text_overlay_widget.h" #include "../card_size_widget.h" +#include #include #include #include @@ -24,6 +25,7 @@ class CardGroupDisplayWidget : public QWidget public: CardGroupDisplayWidget(QWidget *parent, DeckListModel *deckListModel, + QItemSelectionModel *selectionModel, QPersistentModelIndex trackedIndex, QString zoneName, QString cardGroupCategory, @@ -31,9 +33,11 @@ public: QStringList activeSortCriteria, int bannerOpacity, CardSizeWidget *cardSizeWidget); + void onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); void clearAllDisplayWidgets(); DeckListModel *deckListModel; + QItemSelectionModel *selectionModel; QPersistentModelIndex trackedIndex; QHash indexToWidgetMap; QString zoneName; @@ -43,6 +47,7 @@ public: CardSizeWidget *cardSizeWidget; public slots: + void mousePressEvent(QMouseEvent *event) override; void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card); void onHover(const ExactCard &card); virtual QWidget *constructWidgetForIndex(QPersistentModelIndex index); diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp index dad581874..5f3a2c2c0 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp @@ -10,6 +10,7 @@ FlatCardGroupDisplayWidget::FlatCardGroupDisplayWidget(QWidget *parent, DeckListModel *_deckListModel, + QItemSelectionModel *_selectionModel, QPersistentModelIndex _trackedIndex, QString _zoneName, QString _cardGroupCategory, @@ -19,6 +20,7 @@ FlatCardGroupDisplayWidget::FlatCardGroupDisplayWidget(QWidget *parent, CardSizeWidget *_cardSizeWidget) : CardGroupDisplayWidget(parent, _deckListModel, + _selectionModel, std::move(_trackedIndex), _zoneName, _cardGroupCategory, diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.h b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.h index 8b536c5e5..e07152b34 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.h @@ -17,6 +17,7 @@ class FlatCardGroupDisplayWidget : public CardGroupDisplayWidget public: FlatCardGroupDisplayWidget(QWidget *parent, DeckListModel *deckListModel, + QItemSelectionModel *selectionModel, QPersistentModelIndex trackedIndex, QString zoneName, QString cardGroupCategory, diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp index 4d8759cd8..27741a79c 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp @@ -1,14 +1,12 @@ #include "overlapped_card_group_display_widget.h" -#include "../card_info_picture_with_text_overlay_widget.h" - #include -#include #include #include OverlappedCardGroupDisplayWidget::OverlappedCardGroupDisplayWidget(QWidget *parent, DeckListModel *_deckListModel, + QItemSelectionModel *_selectionModel, QPersistentModelIndex _trackedIndex, QString _zoneName, QString _cardGroupCategory, @@ -18,6 +16,7 @@ OverlappedCardGroupDisplayWidget::OverlappedCardGroupDisplayWidget(QWidget *pare CardSizeWidget *_cardSizeWidget) : CardGroupDisplayWidget(parent, _deckListModel, + _selectionModel, _trackedIndex, _zoneName, _cardGroupCategory, diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.h b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.h index 1440c37e7..288e46129 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.h @@ -17,6 +17,7 @@ class OverlappedCardGroupDisplayWidget : public CardGroupDisplayWidget public: OverlappedCardGroupDisplayWidget(QWidget *parent, DeckListModel *deckListModel, + QItemSelectionModel *selectionModel, QPersistentModelIndex trackedIndex, QString zoneName, QString cardGroupCategory, diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp index 1545dc4f6..4cb61deab 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp @@ -25,7 +25,7 @@ CardInfoPictureWithTextOverlayWidget::CardInfoPictureWithTextOverlayWidget(QWidg const int fontSize, const Qt::Alignment alignment) : CardInfoPictureWidget(parent, hoverToZoomEnabled, raiseOnEnter), textColor(textColor), outlineColor(outlineColor), - fontSize(fontSize), textAlignment(alignment) + fontSize(fontSize), textAlignment(alignment), highlighted(false) { this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); } @@ -82,6 +82,16 @@ void CardInfoPictureWithTextOverlayWidget::setTextAlignment(const Qt::Alignment update(); } +void CardInfoPictureWithTextOverlayWidget::setHighlighted(bool _highlighted) +{ + if (highlighted == _highlighted) { + return; + } + + highlighted = _highlighted; + update(); +} + void CardInfoPictureWithTextOverlayWidget::mousePressEvent(QMouseEvent *event) { emit imageClicked(event, this); @@ -98,11 +108,6 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event) // Call the base class's paintEvent to draw the card image CardInfoPictureWidget::paintEvent(event); - // If no overlay text, skip drawing the text - if (overlayText.isEmpty()) { - return; - } - QStylePainter painter(this); // Get the pixmap from the base class using the getter @@ -116,6 +121,36 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event) const QPoint topLeft{(width() - scaledSize.width()) / 2, (height() - scaledSize.height()) / 2}; const QRect pixmapRect(topLeft, scaledSize); + if (highlighted) { + painter.save(); + painter.setRenderHint(QPainter::Antialiasing, true); + + // Soft glow and border around the pixmap + const int padding = 4; // glow extends a little beyond image + QRect glowRect = pixmapRect.adjusted(-padding, -padding, padding, padding); + + QPainterPath path; + int radius = 8; // rounded corners + path.addRoundedRect(glowRect, radius, radius); + + // Soft outer glow + QColor glowColor(0, 150, 255, 80); // subtle blu + painter.setPen(QPen(glowColor, 6)); + painter.drawPath(path); + + // Thin inner border for crispness + QColor borderColor(0, 150, 255, 200); + painter.setPen(QPen(borderColor, 2)); + painter.drawRoundedRect(pixmapRect, radius, radius); + + painter.restore(); + } + + // If no overlay text, skip drawing the text + if (overlayText.isEmpty()) { + return; + } + // Calculate the optimal font size QFont font = painter.font(); int optimalFontSize = fontSize; // Start with the user-defined font size diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h index 91c44cbb1..eed827292 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h @@ -32,6 +32,7 @@ public: void setOutlineColor(const QColor &color); void setFontSize(int size); void setTextAlignment(Qt::Alignment alignment); + void setHighlighted(bool _highlighted); [[nodiscard]] QSize sizeHint() const override; signals: @@ -53,6 +54,7 @@ private: QColor outlineColor; int fontSize; Qt::Alignment textAlignment; + bool highlighted; }; #endif // CARD_PICTURE_WITH_TEXT_OVERLAY_H diff --git a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp index be32d4f0e..5529c35b6 100644 --- a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp @@ -9,6 +9,7 @@ DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent, DeckListModel *_deckListModel, + QItemSelectionModel *_selectionModel, QPersistentModelIndex _trackedIndex, QString _zoneName, QString _activeGroupCriteria, @@ -17,9 +18,10 @@ DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent, int bannerOpacity, int subBannerOpacity, CardSizeWidget *_cardSizeWidget) - : QWidget(parent), deckListModel(_deckListModel), trackedIndex(_trackedIndex), zoneName(_zoneName), - activeGroupCriteria(_activeGroupCriteria), activeSortCriteria(_activeSortCriteria), displayType(_displayType), - bannerOpacity(bannerOpacity), subBannerOpacity(subBannerOpacity), cardSizeWidget(_cardSizeWidget) + : QWidget(parent), deckListModel(_deckListModel), selectionModel(_selectionModel), trackedIndex(_trackedIndex), + zoneName(_zoneName), activeGroupCriteria(_activeGroupCriteria), activeSortCriteria(_activeSortCriteria), + displayType(_displayType), bannerOpacity(bannerOpacity), subBannerOpacity(subBannerOpacity), + cardSizeWidget(_cardSizeWidget) { layout = new QVBoxLayout(this); setLayout(layout); @@ -37,9 +39,34 @@ DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent, displayCards(); connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &DeckCardZoneDisplayWidget::onCategoryAddition); + connect(selectionModel, &QItemSelectionModel::selectionChanged, this, + &DeckCardZoneDisplayWidget::onSelectionChanged); connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &DeckCardZoneDisplayWidget::onCategoryRemoval); } +void DeckCardZoneDisplayWidget::onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) +{ + for (auto &range : selected) { + for (int row = range.top(); row <= range.bottom(); ++row) { + QModelIndex idx = range.model()->index(row, 0, range.parent()); + auto it = indexToWidgetMap.find(QPersistentModelIndex(idx)); + if (it != indexToWidgetMap.end()) { + // it.value()->setHighlighted(true); + } + } + } + + for (auto &range : deselected) { + for (int row = range.top(); row <= range.bottom(); ++row) { + QModelIndex idx = range.model()->index(row, 0, range.parent()); + auto it = indexToWidgetMap.find(QPersistentModelIndex(idx)); + if (it != indexToWidgetMap.end()) { + // it.value()->setHighlighted(false); + } + } + } +} + void DeckCardZoneDisplayWidget::cleanupInvalidCardGroup(CardGroupDisplayWidget *displayWidget) { cardGroupLayout->removeWidget(displayWidget); @@ -60,8 +87,8 @@ void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex } if (displayType == DisplayType::Overlap) { auto *displayWidget = new OverlappedCardGroupDisplayWidget( - cardGroupContainer, deckListModel, index, zoneName, categoryName, activeGroupCriteria, activeSortCriteria, - subBannerOpacity, cardSizeWidget); + cardGroupContainer, deckListModel, selectionModel, index, zoneName, categoryName, activeGroupCriteria, + activeSortCriteria, subBannerOpacity, cardSizeWidget); connect(displayWidget, &OverlappedCardGroupDisplayWidget::cardClicked, this, &DeckCardZoneDisplayWidget::onClick); connect(displayWidget, &OverlappedCardGroupDisplayWidget::cardHovered, this, @@ -73,9 +100,9 @@ void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex cardGroupLayout->addWidget(displayWidget); indexToWidgetMap.insert(index, displayWidget); } else if (displayType == DisplayType::Flat) { - auto *displayWidget = - new FlatCardGroupDisplayWidget(cardGroupContainer, deckListModel, index, zoneName, categoryName, - activeGroupCriteria, activeSortCriteria, subBannerOpacity, cardSizeWidget); + auto *displayWidget = new FlatCardGroupDisplayWidget(cardGroupContainer, deckListModel, selectionModel, index, + zoneName, categoryName, activeGroupCriteria, + activeSortCriteria, subBannerOpacity, cardSizeWidget); connect(displayWidget, &FlatCardGroupDisplayWidget::cardClicked, this, &DeckCardZoneDisplayWidget::onClick); connect(displayWidget, &FlatCardGroupDisplayWidget::cardHovered, this, &DeckCardZoneDisplayWidget::onHover); connect(displayWidget, &CardGroupDisplayWidget::cleanupRequested, this, diff --git a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h index dbaeaa9ae..ac53cb704 100644 --- a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h +++ b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h @@ -26,6 +26,7 @@ class DeckCardZoneDisplayWidget : public QWidget public: DeckCardZoneDisplayWidget(QWidget *parent, DeckListModel *deckListModel, + QItemSelectionModel *selectionModel, QPersistentModelIndex trackedIndex, QString zoneName, QString activeGroupCriteria, @@ -34,7 +35,9 @@ public: int bannerOpacity, int subBannerOpacity, CardSizeWidget *_cardSizeWidget); + void onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); DeckListModel *deckListModel; + QItemSelectionModel *selectionModel; QPersistentModelIndex trackedIndex; QString zoneName; void addCardsToOverlapWidget(); diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index b633bdaed..78d5da9f7 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -51,6 +51,11 @@ public: return activeGroupCriteriaComboBox; } + [[nodiscard]] QItemSelectionModel *getSelectionModel() const + { + return deckView->selectionModel(); + } + public slots: void cleanDeck(); void updateBannerCardComboBox(); diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 109815816..7a811fd28 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -176,20 +176,74 @@ void TabDeckEditorVisual::changeModelIndexToCard(const ExactCard &activeCard) if (!index.isValid()) { index = deckDockWidget->deckModel->findCard(cardName, DECK_ZONE_SIDE); } - deckDockWidget->deckView->setCurrentIndex(index); + if (!deckDockWidget->getSelectionModel()->hasSelection()) { + deckDockWidget->getSelectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + } } -/** @brief Handles clicks on cards in the mainboard deck. */ void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, - QString zoneName) + const QString &zoneName) { + auto card = instance->getCard(); + + // Get the model index for the card + QModelIndex idx = deckDockWidget->deckModel->findCard(card.getName(), zoneName); + if (!idx.isValid()) { + return; + } + + QItemSelectionModel *sel = deckDockWidget->getSelectionModel(); + + // Double click = swap + if (event->type() == QEvent::MouseButtonDblClick && event->button() == Qt::LeftButton) { + actSwapCard(card, zoneName); + idx = deckDockWidget->deckModel->findCard(card.getName(), zoneName); + sel->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect); + return; + } + + // Right-click = decrement + if (event->button() == Qt::RightButton) { + actDecrementCard(card); + // Keep selection intact. + return; + } + + // Alt + Left click = increment + if (event->button() == Qt::LeftButton && event->modifiers().testFlag(Qt::AltModifier)) { + // actIncrementCard(card); + // Keep selection intact. + return; + } + + // Normal selection behavior if (event->button() == Qt::LeftButton) { - actSwapCard(instance->getCard(), zoneName); - } else if (event->button() == Qt::RightButton) { - actDecrementCard(instance->getCard()); - } else if (event->button() == Qt::MiddleButton) { - deckDockWidget->actRemoveCard(); + Qt::KeyboardModifiers mods = event->modifiers(); + QItemSelectionModel::SelectionFlags flags; + + if (mods.testFlag(Qt::ControlModifier)) { + // CTRL + click = toggle selection + flags = QItemSelectionModel::Toggle; + } else if (mods.testFlag(Qt::ShiftModifier)) { + // SHIFT + click = select range + QModelIndex anchor = sel->currentIndex(); + if (!anchor.isValid()) { + anchor = idx; + } + + QItemSelection range(anchor, idx); + sel->select(range, QItemSelectionModel::SelectCurrent); + sel->setCurrentIndex(idx, QItemSelectionModel::NoUpdate); + return; + } else { + // Normal click = clear selection, select this, set current + deckDockWidget->deckView->setCurrentIndex(idx); + deckDockWidget->deckView->scrollTo(idx); + return; + } + + sel->setCurrentIndex(idx, flags); } } diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h index 87f6c5df8..2f1d11d82 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h @@ -176,8 +176,9 @@ public slots: * @param instance Widget representing the clicked card. * @param zoneName Deck zone of the card. */ - void - processMainboardCardClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName); + void processMainboardCardClick(QMouseEvent *event, + CardInfoPictureWithTextOverlayWidget *instance, + const QString &zoneName); /** * @brief Handle card clicks in the database visual display. diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp index 08728dac0..98b6aff00 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp @@ -28,7 +28,7 @@ TabDeckEditorVisualTabWidget::TabDeckEditorVisualTabWidget(QWidget *parent, layout = new QVBoxLayout(this); setLayout(layout); - visualDeckView = new VisualDeckEditorWidget(this, deckModel); + visualDeckView = new VisualDeckEditorWidget(this, deckModel, _deckEditor->deckDockWidget->getSelectionModel()); visualDeckView->setObjectName("visualDeckView"); connect(visualDeckView, &VisualDeckEditorWidget::activeCardChanged, this, &TabDeckEditorVisualTabWidget::onCardChanged); 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 005efd6db..3536359be 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 @@ -25,8 +25,10 @@ #include #include -VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, DeckListModel *_deckListModel) - : QWidget(parent), deckListModel(_deckListModel) +VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, + DeckListModel *_deckListModel, + QItemSelectionModel *_selectionModel) + : QWidget(parent), deckListModel(_deckListModel), selectionModel(_selectionModel) { // The Main Widget and Main Layout, which contain a single Widget: The Scroll Area setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); @@ -204,6 +206,11 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, DeckListModel *_ connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &VisualDeckEditorWidget::onCardRemoval); constructZoneWidgetsFromDeckListModel(); + if (selectionModel) { + connect(selectionModel, &QItemSelectionModel::selectionChanged, this, + &VisualDeckEditorWidget::onSelectionChanged); + } + retranslateUi(); } @@ -219,6 +226,47 @@ void VisualDeckEditorWidget::retranslateUi() tr("Change how cards are displayed within zones (i.e. overlapped or fully visible.)")); } +void VisualDeckEditorWidget::setSelectionModel(QItemSelectionModel *model) +{ + if (selectionModel == model) { + return; + } + + if (selectionModel) { + // TODO: Possibly disconnect old ones? + } + + selectionModel = model; + + if (selectionModel) { + connect(selectionModel, &QItemSelectionModel::selectionChanged, this, + &VisualDeckEditorWidget::onSelectionChanged); + } +} + +void VisualDeckEditorWidget::onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) +{ + for (auto &range : selected) { + for (int row = range.top(); row <= range.bottom(); ++row) { + QModelIndex idx = range.model()->index(row, 0, range.parent()); + auto it = indexToWidgetMap.find(QPersistentModelIndex(idx)); + if (it != indexToWidgetMap.end()) { + // it.value()->setHighlighted(true); + } + } + } + + for (auto &range : deselected) { + for (int row = range.top(); row <= range.bottom(); ++row) { + QModelIndex idx = range.model()->index(row, 0, range.parent()); + auto it = indexToWidgetMap.find(QPersistentModelIndex(idx)); + if (it != indexToWidgetMap.end()) { + // it.value()->setHighlighted(false); + } + } + } +} + void VisualDeckEditorWidget::clearAllDisplayWidgets() { for (auto idx : indexToWidgetMap.keys()) { @@ -250,25 +298,7 @@ void VisualDeckEditorWidget::onCardAddition(const QModelIndex &parent, int first continue; } - DeckCardZoneDisplayWidget *zoneDisplayWidget = new DeckCardZoneDisplayWidget( - zoneContainer, deckListModel, index, - deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString(), activeGroupCriteria, - activeSortCriteria, currentDisplayType, 20, 10, cardSizeWidget); - connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover); - connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardClicked, this, - &VisualDeckEditorWidget::onCardClick); - connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::requestCleanup, this, - &VisualDeckEditorWidget::cleanupInvalidZones); - connect(this, &VisualDeckEditorWidget::activeSortCriteriaChanged, zoneDisplayWidget, - &DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged); - connect(this, &VisualDeckEditorWidget::activeGroupCriteriaChanged, zoneDisplayWidget, - &DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged); - connect(this, &VisualDeckEditorWidget::displayTypeChanged, zoneDisplayWidget, - &DeckCardZoneDisplayWidget::refreshDisplayType); - zoneDisplayWidget->refreshDisplayType(currentDisplayType); - zoneContainerLayout->addWidget(zoneDisplayWidget); - - indexToWidgetMap.insert(index, zoneDisplayWidget); + constructZoneWidgetForIndex(index); } } } @@ -287,6 +317,28 @@ void VisualDeckEditorWidget::onCardRemoval(const QModelIndex &parent, int first, } } +void VisualDeckEditorWidget::constructZoneWidgetForIndex(QPersistentModelIndex persistent) +{ + DeckCardZoneDisplayWidget *zoneDisplayWidget = new DeckCardZoneDisplayWidget( + zoneContainer, deckListModel, selectionModel, persistent, + deckListModel->data(persistent.sibling(persistent.row(), 1), Qt::EditRole).toString(), activeGroupCriteria, + activeSortCriteria, currentDisplayType, 20, 10, cardSizeWidget); + connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover); + connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardClicked, this, &VisualDeckEditorWidget::onCardClick); + connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::requestCleanup, this, + &VisualDeckEditorWidget::cleanupInvalidZones); + connect(this, &VisualDeckEditorWidget::activeSortCriteriaChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged); + connect(this, &VisualDeckEditorWidget::activeGroupCriteriaChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged); + connect(this, &VisualDeckEditorWidget::displayTypeChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::refreshDisplayType); + zoneDisplayWidget->refreshDisplayType(currentDisplayType); + zoneContainerLayout->addWidget(zoneDisplayWidget); + + indexToWidgetMap.insert(persistent, zoneDisplayWidget); +} + void VisualDeckEditorWidget::constructZoneWidgetsFromDeckListModel() { QSortFilterProxyModel proxy; @@ -305,23 +357,7 @@ void VisualDeckEditorWidget::constructZoneWidgetsFromDeckListModel() continue; } - DeckCardZoneDisplayWidget *zoneDisplayWidget = new DeckCardZoneDisplayWidget( - zoneContainer, deckListModel, persistent, - deckListModel->data(persistent.sibling(persistent.row(), 1), Qt::EditRole).toString(), activeGroupCriteria, - activeSortCriteria, currentDisplayType, 20, 10, cardSizeWidget); - connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover); - connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardClicked, this, &VisualDeckEditorWidget::onCardClick); - connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::requestCleanup, this, - &VisualDeckEditorWidget::cleanupInvalidZones); - connect(this, &VisualDeckEditorWidget::activeSortCriteriaChanged, zoneDisplayWidget, - &DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged); - connect(this, &VisualDeckEditorWidget::activeGroupCriteriaChanged, zoneDisplayWidget, - &DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged); - connect(this, &VisualDeckEditorWidget::displayTypeChanged, zoneDisplayWidget, - &DeckCardZoneDisplayWidget::refreshDisplayType); - zoneContainerLayout->addWidget(zoneDisplayWidget); - - indexToWidgetMap.insert(persistent, zoneDisplayWidget); + constructZoneWidgetForIndex(persistent); } } @@ -389,7 +425,16 @@ void VisualDeckEditorWidget::decklistDataChanged(QModelIndex topLeft, QModelInde void VisualDeckEditorWidget::onHover(const ExactCard &hoveredCard) { + // If user has any card selected, ignore hover + if (selectionModel->hasSelection()) { + return; + } + + // If nothing is selected -> this is our "active/preview" card emit activeCardChanged(hoveredCard); + + // TODO: highlight hovered card visually: + // highlightHoveredCard(hoveredCard); } void VisualDeckEditorWidget::onCardClick(QMouseEvent *event, diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h index 176086019..4cff68407 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h @@ -36,13 +36,20 @@ class VisualDeckEditorWidget : public QWidget Q_OBJECT public: - explicit VisualDeckEditorWidget(QWidget *parent, DeckListModel *deckListModel); + explicit VisualDeckEditorWidget(QWidget *parent, DeckListModel *deckListModel, QItemSelectionModel *selectionModel); void retranslateUi(); void clearAllDisplayWidgets(); void resizeEvent(QResizeEvent *event) override; void setDeckList(const DeckList &_deckListModel); + void setSelectionModel(QItemSelectionModel *model); + void onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + QItemSelectionModel *getSelectionModel() const + { + return selectionModel; + } + QLineEdit *searchBar; CardSizeWidget *cardSizeWidget; @@ -53,6 +60,7 @@ public slots: void cleanupInvalidZones(DeckCardZoneDisplayWidget *displayWidget); void onCardAddition(const QModelIndex &parent, int first, int last); void onCardRemoval(const QModelIndex &parent, int first, int last); + void constructZoneWidgetForIndex(QPersistentModelIndex persistent); void constructZoneWidgetsFromDeckListModel(); signals: @@ -72,6 +80,7 @@ protected slots: private: DeckListModel *deckListModel; + QItemSelectionModel *selectionModel; QVBoxLayout *mainLayout; QWidget *searchContainer; QHBoxLayout *searchLayout; From bac6beeb5026fe70952294530f28919e928d83c9 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 27 Nov 2025 23:03:30 +0100 Subject: [PATCH 024/325] [VDE] Allow visual database display to toggle to table based display. (#6357) --- .../deck_editor_database_display_widget.h | 21 +++-- .../tabs/tab_visual_database_display.h | 3 +- .../visual_database_display_widget.cpp | 86 +++++++++++++------ .../visual_database_display_widget.h | 2 + 4 files changed, 78 insertions(+), 34 deletions(-) diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h index efac97eae..0d3ce9f4c 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h @@ -29,10 +29,23 @@ public: CardDatabaseModel *databaseModel; CardDatabaseDisplayModel *databaseDisplayModel; + QTreeView *getDatabaseView() + { + return databaseView; + }; + public slots: ExactCard currentCard() const; void setFilterTree(FilterTree *filterTree); void clearAllDatabaseFilters(); + void updateSearch(const QString &search); + void updateCard(const QModelIndex ¤t, const QModelIndex &); + void actAddCardToMainDeck(); + void actAddCardToSideboard(); + void actDecrementCardFromMainDeck(); + void actDecrementCardFromSideboard(); + void databaseCustomMenu(QPoint point); + void copyDatabaseCellContents(); signals: void addCardToMainDeck(const ExactCard &card); @@ -51,14 +64,6 @@ private: private slots: void retranslateUi(); - void updateSearch(const QString &search); - void updateCard(const QModelIndex ¤t, const QModelIndex &); - void actAddCardToMainDeck(); - void actAddCardToSideboard(); - void actDecrementCardFromMainDeck(); - void actDecrementCardFromSideboard(); - void databaseCustomMenu(QPoint point); - void copyDatabaseCellContents(); void saveDbHeaderState(); }; diff --git a/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.h b/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.h index dd6128885..b01f3ab06 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.h +++ b/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.h @@ -25,7 +25,8 @@ public: void retranslateUi() override; [[nodiscard]] QString getTabText() const override { - return tr("Visual Database Display"); + return visualDatabaseDisplayWidget->displayModeButton->isChecked() ? tr("Database Display") + : tr("Visual Database Display"); } }; 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 d371d0ed3..66f6238b9 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 @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -60,28 +61,39 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, setFocusProxy(searchEdit); setFocusPolicy(Qt::ClickFocus); + displayModeButton = new QPushButton(tr("Visual"), this); + displayModeButton->setCheckable(true); // Toggle button + + connect(displayModeButton, &QPushButton::toggled, this, &VisualDatabaseDisplayWidget::onDisplayModeChanged); + + displayModeButton->setChecked(false); // Start in Visual mode + filterModel = new FilterTreeModel(); filterModel->setObjectName("filterModel"); searchKeySignals.setObjectName("searchKeySignals"); - connect(searchEdit, &QLineEdit::textChanged, this, &VisualDatabaseDisplayWidget::updateSearch); - /*connect(&searchKeySignals, SIGNAL(onEnter()), this, SLOT(actAddCard())); - connect(&searchKeySignals, SIGNAL(onCtrlAltEqual()), this, SLOT(actAddCard())); - connect(&searchKeySignals, SIGNAL(onCtrlAltRBracket()), this, SLOT(actAddCardToSideboard())); - connect(&searchKeySignals, SIGNAL(onCtrlAltMinus()), this, SLOT(actDecrementCard())); - connect(&searchKeySignals, SIGNAL(onCtrlAltLBracket()), this, SLOT(actDecrementCardFromSideboard())); - connect(&searchKeySignals, SIGNAL(onCtrlAltEnter()), this, SLOT(actAddCardToSideboard())); - connect(&searchKeySignals, SIGNAL(onCtrlEnter()), this, SLOT(actAddCardToSideboard())); - connect(&searchKeySignals, SIGNAL(onCtrlC()), this, SLOT(copyDatabaseCellContents()));*/ + connect(searchEdit, &SearchLineEdit::textChanged, this, &VisualDatabaseDisplayWidget::updateSearch); + connect(&searchKeySignals, &KeySignals::onEnter, deckEditor->databaseDisplayDockWidget, + &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck); + connect(&searchKeySignals, &KeySignals::onCtrlAltEqual, deckEditor->databaseDisplayDockWidget, + &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck); + connect(&searchKeySignals, &KeySignals::onCtrlAltRBracket, deckEditor->databaseDisplayDockWidget, + &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard); + connect(&searchKeySignals, &KeySignals::onCtrlAltMinus, deckEditor->databaseDisplayDockWidget, + &DeckEditorDatabaseDisplayWidget::actDecrementCardFromMainDeck); + connect(&searchKeySignals, &KeySignals::onCtrlAltLBracket, deckEditor->databaseDisplayDockWidget, + &DeckEditorDatabaseDisplayWidget::actDecrementCardFromSideboard); + connect(&searchKeySignals, &KeySignals::onCtrlAltEnter, deckEditor->databaseDisplayDockWidget, + &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard); + connect(&searchKeySignals, &KeySignals::onCtrlEnter, deckEditor->databaseDisplayDockWidget, + &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard); + connect(&searchKeySignals, &KeySignals::onCtrlC, deckEditor->databaseDisplayDockWidget, + &DeckEditorDatabaseDisplayWidget::copyDatabaseCellContents); + connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(searchEdit); }); - databaseView = new QTreeView(this); - databaseView->setObjectName("databaseView"); + databaseView = deckEditor->databaseDisplayDockWidget->getDatabaseView(); databaseView->setFocusProxy(searchEdit); - databaseView->setRootIsDecorated(false); databaseView->setItemDelegate(nullptr); - databaseView->setSortingEnabled(true); - databaseView->sortByColumn(0, Qt::AscendingOrder); - databaseView->setModel(databaseDisplayModel); databaseView->setVisible(false); searchEdit->setTreeView(databaseView); @@ -168,11 +180,14 @@ void VisualDatabaseDisplayWidget::initialize() searchLayout->addWidget(colorFilterWidget); searchLayout->addWidget(clearFilterWidget); searchLayout->addWidget(searchEdit); + searchLayout->addWidget(displayModeButton); mainLayout->addWidget(searchContainer); mainLayout->addWidget(filterContainer); + mainLayout->addWidget(databaseView); + mainLayout->addWidget(flowWidget); mainLayout->addWidget(cardSizeWidget); @@ -213,6 +228,25 @@ void VisualDatabaseDisplayWidget::resizeEvent(QResizeEvent *event) loadCurrentPage(); } +void VisualDatabaseDisplayWidget::onDisplayModeChanged(bool checked) +{ + if (checked) { + // Table mode + displayModeButton->setText(tr("Table")); + flowWidget->setVisible(false); + cardSizeWidget->setVisible(false); + databaseView->setItemDelegate(new QStyledItemDelegate(databaseView)); + databaseView->setVisible(true); + } else { + // Visual mode + displayModeButton->setText(tr("Visual")); + flowWidget->setVisible(true); + cardSizeWidget->setVisible(true); + databaseView->setVisible(false); + populateCards(); + } +} + void VisualDatabaseDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance) { emit cardClickedDatabaseDisplay(event, instance); @@ -246,17 +280,19 @@ void VisualDatabaseDisplayWidget::updateSearch(const QString &search) const void VisualDatabaseDisplayWidget::searchModelChanged() { - // Clear the current page and prepare for new data - flowWidget->clearLayout(); // Clear existing cards - cards->clear(); // Clear the card list - // Reset scrollbar position to the top after loading new cards - if (QScrollBar *scrollBar = flowWidget->scrollArea->verticalScrollBar()) { - scrollBar->setValue(0); // Reset scrollbar to top - } + if (flowWidget->isVisible()) { + // Clear the current page and prepare for new data + flowWidget->clearLayout(); // Clear existing cards + cards->clear(); // Clear the card list + // Reset scrollbar position to the top after loading new cards + if (QScrollBar *scrollBar = flowWidget->scrollArea->verticalScrollBar()) { + scrollBar->setValue(0); // Reset scrollbar to top + } - currentPage = 0; - loadCurrentPage(); - qCDebug(VisualDatabaseDisplayLog) << "Search model changed"; + currentPage = 0; + loadCurrentPage(); + qCDebug(VisualDatabaseDisplayLog) << "Search model changed"; + } } void VisualDatabaseDisplayWidget::loadCurrentPage() diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h index c05c755f1..65eab99a4 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h @@ -57,6 +57,7 @@ public: QWidget *searchContainer; QHBoxLayout *searchLayout; SearchLineEdit *searchEdit; + QPushButton *displayModeButton; FilterTreeModel *filterModel; VisualDatabaseDisplayColorFilterWidget *colorFilterWidget; @@ -76,6 +77,7 @@ protected slots: void wheelEvent(QWheelEvent *event) override; void modelDirty() const; void updateSearch(const QString &search) const; + void onDisplayModeChanged(bool checked); private: QLabel *databaseLoadIndicator; From 122926c6cd8838e6c042bcf6ac97d7be378e87f4 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 27 Nov 2025 23:11:43 +0100 Subject: [PATCH 025/325] Deck Editor owns DeckHistoryManager (#6359) Took 4 minutes --- .../deck_editor_deck_dock_widget.cpp | 46 ++++++++----------- .../deck_editor_deck_dock_widget.h | 6 +-- .../widgets/tabs/abstract_tab_deck_editor.cpp | 27 ++++++++++- .../widgets/tabs/abstract_tab_deck_editor.h | 18 +++++++- 4 files changed, 64 insertions(+), 33 deletions(-) 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 56bd16f0a..4db7b8774 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 @@ -52,11 +52,6 @@ DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent void DeckEditorDeckDockWidget::createDeckDock() { - historyManager = new DeckListHistoryManager(); - - connect(deckEditor, &AbstractTabDeckEditor::cardAboutToBeAdded, this, - &DeckEditorDeckDockWidget::onCardAboutToBeAdded); - deckModel = new DeckListModel(this); deckModel->setObjectName("deckModel"); connect(deckModel, &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash); @@ -66,7 +61,7 @@ void DeckEditorDeckDockWidget::createDeckDock() proxy = new DeckListStyleProxy(this); proxy->setSourceModel(deckModel); - historyManagerWidget = new DeckListHistoryManagerWidget(deckModel, proxy, historyManager, this); + historyManagerWidget = new DeckListHistoryManagerWidget(deckModel, proxy, deckEditor->getHistoryManager(), this); connect(historyManagerWidget, &DeckListHistoryManagerWidget::requestDisplayWidgetSync, this, &DeckEditorDeckDockWidget::syncDisplayWidgetsToModel); @@ -300,8 +295,8 @@ void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const void DeckEditorDeckDockWidget::updateName(const QString &name) { - historyManager->save(deckLoader->getDeckList()->createMemento( - QString(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeckList()->getName()))); + emit requestDeckHistorySave( + QString(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeckList()->getName())); deckModel->getDeckList()->setName(name); deckEditor->setModified(name.isEmpty()); emit nameChanged(); @@ -310,10 +305,9 @@ void DeckEditorDeckDockWidget::updateName(const QString &name) void DeckEditorDeckDockWidget::updateComments() { - historyManager->save( - deckLoader->getDeckList()->createMemento(QString(tr("Updated comments (was %1 chars, now %2 chars)")) - .arg(deckLoader->getDeckList()->getComments().size()) - .arg(commentsEdit->toPlainText().size()))); + emit requestDeckHistorySave(tr("Updated comments (was %1 chars, now %2 chars)") + .arg(deckLoader->getDeckList()->getComments().size()) + .arg(commentsEdit->toPlainText().size())); deckModel->getDeckList()->setComments(commentsEdit->toPlainText()); deckEditor->setModified(commentsEdit->toPlainText().isEmpty()); @@ -370,7 +364,7 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() // Handle results if (restoreIndex != -1) { bannerCardComboBox->setCurrentIndex(restoreIndex); - setBannerCard(restoreIndex); + syncDeckListBannerCardWithComboBox(); } else { // Add a placeholder "-" and set it as the current selection bannerCardComboBox->insertItem(0, "-"); @@ -383,13 +377,18 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */) { - historyManager->save(deckLoader->getDeckList()->createMemento(tr("Banner card changed"))); - auto [name, id] = bannerCardComboBox->currentData().value>(); - deckModel->getDeckList()->setBannerCard({name, id}); + emit requestDeckHistorySave(tr("Banner card changed")); + syncDeckListBannerCardWithComboBox(); deckEditor->setModified(true); emit deckModified(); } +void DeckEditorDeckDockWidget::syncDeckListBannerCardWithComboBox() +{ + auto [name, id] = bannerCardComboBox->currentData().value>(); + deckModel->getDeckList()->setBannerCard({name, id}); +} + void DeckEditorDeckDockWidget::updateShowBannerCardComboBox(const bool visible) { bannerCardLabel->setHidden(!visible); @@ -427,7 +426,7 @@ void DeckEditorDeckDockWidget::setDeck(DeckLoader *_deck) connect(deckLoader, &DeckLoader::deckLoaded, deckModel, &DeckListModel::rebuildTree); connect(deckLoader->getDeckList(), &DeckList::deckHashChanged, deckModel, &DeckListModel::deckHashChanged); - historyManager->clear(); + emit requestDeckHistoryClear(); historyManagerWidget->setDeckListModel(deckModel); syncDisplayWidgetsToModel(); @@ -521,14 +520,6 @@ QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodes() const return selectedRows; } -void DeckEditorDeckDockWidget::onCardAboutToBeAdded(const ExactCard &addedCard, const QString &zoneName) -{ - historyManager->save(deckLoader->getDeckList()->createMemento( - QString(tr("Added (%1): %2 (%3) %4")) - .arg(zoneName, addedCard.getName(), addedCard.getPrinting().getSet()->getCorrectedShortName(), - addedCard.getPrinting().getProperty("num")))); -} - void DeckEditorDeckDockWidget::actIncrement() { auto selectedRows = getSelectedCardNodes(); @@ -649,8 +640,7 @@ void DeckEditorDeckDockWidget::actRemoveCard() QModelIndex sourceIndex = proxy->mapToSource(index); QString cardName = sourceIndex.sibling(sourceIndex.row(), 1).data().toString(); - historyManager->save( - deckLoader->getDeckList()->createMemento(QString(tr("Removed \"%1\" (all copies)")).arg(cardName))); + emit requestDeckHistorySave(QString(tr("Removed \"%1\" (all copies)")).arg(cardName)); deckModel->removeRow(sourceIndex.row(), sourceIndex.parent()); isModified = true; @@ -685,7 +675,7 @@ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, int of .arg(cardName) .arg(deckModel->data(sourceIndex.sibling(sourceIndex.row(), 4), Qt::DisplayRole).toString()); - historyManager->save(deckLoader->getDeckList()->createMemento(reason)); + emit requestDeckHistorySave(reason); if (new_count <= 0) { deckModel->removeRow(sourceIndex.row(), sourceIndex.parent()); diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index 78d5da9f7..86f3db9f8 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -14,7 +14,6 @@ #include "../visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h" #include "deck_list_history_manager_widget.h" #include "deck_list_style_proxy.h" -#include "libcockatrice/deck_list/deck_list_history_manager.h" #include #include @@ -70,7 +69,6 @@ public slots: void actDecrementSelection(); void actSwapCard(); void actRemoveCard(); - void onCardAboutToBeAdded(const ExactCard &card, const QString &zoneName); void offsetCountAtIndex(const QModelIndex &idx, int offset); signals: @@ -79,11 +77,12 @@ signals: void hashChanged(); void deckChanged(); void deckModified(); + void requestDeckHistorySave(const QString &modificationReason); + void requestDeckHistoryClear(); void cardChanged(const ExactCard &_card); private: AbstractTabDeckEditor *deckEditor; - DeckListHistoryManager *historyManager; DeckListHistoryManagerWidget *historyManagerWidget; KeySignals deckViewKeySignals; QLabel *nameLabel; @@ -113,6 +112,7 @@ private slots: void updateName(const QString &name); void updateComments(); void setBannerCard(int); + void syncDeckListBannerCardWithComboBox(); void updateHash(); void refreshShortcuts(); void updateShowBannerCardComboBox(bool visible); 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 ad68ecdbc..a3d3fe2cf 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -58,6 +58,8 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta { setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks); + historyManager = new DeckListHistoryManager(this); + databaseDisplayDockWidget = new DeckEditorDatabaseDisplayWidget(this); deckDockWidget = new DeckEditorDeckDockWidget(this); cardInfoDockWidget = new DeckEditorCardInfoDockWidget(this); @@ -70,6 +72,10 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta // Connect deck signals to this tab connect(deckDockWidget, &DeckEditorDeckDockWidget::deckChanged, this, &AbstractTabDeckEditor::onDeckChanged); connect(deckDockWidget, &DeckEditorDeckDockWidget::deckModified, this, &AbstractTabDeckEditor::onDeckModified); + connect(deckDockWidget, &DeckEditorDeckDockWidget::requestDeckHistorySave, this, + &AbstractTabDeckEditor::onDeckHistorySaveRequested); + connect(deckDockWidget, &DeckEditorDeckDockWidget::requestDeckHistoryClear, this, + &AbstractTabDeckEditor::onDeckHistoryClearRequested); connect(deckDockWidget, &DeckEditorDeckDockWidget::cardChanged, this, &AbstractTabDeckEditor::updateCard); connect(this, &AbstractTabDeckEditor::decrementCard, deckDockWidget, &DeckEditorDeckDockWidget::actDecrementCard); @@ -107,6 +113,7 @@ void AbstractTabDeckEditor::updateCard(const ExactCard &card) /** @brief Placeholder: called when the deck changes. */ void AbstractTabDeckEditor::onDeckChanged() { + historyManager->clear(); } /** @@ -118,6 +125,22 @@ void AbstractTabDeckEditor::onDeckModified() deckMenu->setSaveStatus(!isBlankNewDeck()); } +/** + * @brief Marks the tab as modified and updates the save menu status. + */ +void AbstractTabDeckEditor::onDeckHistorySaveRequested(const QString &modificationReason) +{ + historyManager->save(deckDockWidget->getDeckList()->createMemento(modificationReason)); +} + +/** + * @brief Marks the tab as modified and updates the save menu status. + */ +void AbstractTabDeckEditor::onDeckHistoryClearRequested() +{ + historyManager->clear(); +} + /** * @brief Helper for adding a card to a deck zone. * @param card Card to add. @@ -131,7 +154,9 @@ void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, QString zoneNam if (card.getInfo().getIsToken()) zoneName = DECK_ZONE_TOKENS; - emit cardAboutToBeAdded(card, zoneName); + onDeckHistorySaveRequested(QString(tr("Added (%1): %2 (%3) %4")) + .arg(zoneName, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(), + card.getPrinting().getProperty("num"))); QModelIndex newCardIndex = deckDockWidget->deckModel->addCard(card, zoneName); deckDockWidget->deckView->clearSelection(); diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h index 86517de63..8544e6c00 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h @@ -17,6 +17,8 @@ #include "../interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h" #include "tab.h" +#include + class CardDatabaseModel; class CardDatabaseDisplayModel; @@ -132,6 +134,13 @@ public: return deckDockWidget; } + DeckListHistoryManager *getHistoryManager() const + { + return historyManager; + } + + DeckListHistoryManager *historyManager; + // UI Elements DeckEditorMenu *deckMenu; ///< Menu for deck operations DeckEditorDatabaseDisplayWidget *databaseDisplayDockWidget; ///< Database dock @@ -147,6 +156,14 @@ public slots: /** @brief Called when the deck is modified. */ virtual void onDeckModified(); + /** @brief Called when a widget is about to modify the state of the DeckList. + * @param modificationReason The reason for the state modification + */ + virtual void onDeckHistorySaveRequested(const QString &modificationReason); + + /** @brief Called when a widget would like to clear the history. */ + virtual void onDeckHistoryClearRequested(); + /** @brief Updates the card info panel. * @param card The card to display. */ @@ -180,7 +197,6 @@ public slots: virtual void dockTopLevelChanged(bool topLevel) = 0; signals: - void cardAboutToBeAdded(const ExactCard &addedCard, const QString &zoneName); /** @brief Emitted when a deck should be opened in a new editor tab. */ void openDeckEditor(DeckLoader *deckLoader); From 587a8bc524505d5c12a3535e8b9f3788d92cf4e2 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 27 Nov 2025 23:31:40 +0100 Subject: [PATCH 026/325] [VDD] Add sorting (#6355) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [VDD] Add sorting Took 17 seconds Took 3 minutes * Adjust to contents. Took 13 minutes * Adjust sort order as well. Took 5 minutes --------- Co-authored-by: Lukas Brübach --- .../visual_database_display_widget.cpp | 49 +++++++++++++++++++ .../visual_database_display_widget.h | 5 ++ 2 files changed, 54 insertions(+) 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 66f6238b9..0c6f56894 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 @@ -98,12 +98,50 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, searchEdit->setTreeView(databaseView); + sortByLabel = new QLabel(this); + sortColumnCombo = new QComboBox(this); + sortColumnCombo->setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy::AdjustToContents); + sortOrderCombo = new QComboBox(this); + sortOrderCombo->setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy::AdjustToContents); + + sortOrderCombo->addItem("Ascending", Qt::AscendingOrder); + sortOrderCombo->addItem("Descending", Qt::DescendingOrder); + sortOrderCombo->view()->setMinimumWidth(sortOrderCombo->view()->sizeHintForColumn(0)); + sortOrderCombo->adjustSize(); + + // Populate columns dynamically from the model + for (int i = 0; i < databaseDisplayModel->columnCount(); ++i) { + QString header = databaseDisplayModel->headerData(i, Qt::Horizontal).toString(); + sortColumnCombo->addItem(header, i); + } + + sortColumnCombo->view()->setMinimumWidth(sortColumnCombo->view()->sizeHintForColumn(0)); + sortColumnCombo->adjustSize(); + + connect(sortColumnCombo, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { + int column = sortColumnCombo->currentData().toInt(); + Qt::SortOrder order = static_cast(sortOrderCombo->currentData().toInt()); + databaseView->sortByColumn(column, order); + + searchModelChanged(); + }); + + connect(sortOrderCombo, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { + int column = sortColumnCombo->currentData().toInt(); + Qt::SortOrder order = static_cast(sortOrderCombo->currentData().toInt()); + databaseView->sortByColumn(column, order); + + searchModelChanged(); + }); + colorFilterWidget = new VisualDatabaseDisplayColorFilterWidget(this, filterModel); filterContainer = new QWidget(this); filterContainerLayout = new QHBoxLayout(filterContainer); filterContainer->setLayout(filterContainerLayout); + filterByLabel = new QLabel(this); + clearFilterWidget = new QToolButton(); clearFilterWidget->setFixedSize(32, 32); clearFilterWidget->setIcon(QPixmap("theme:icons/delete")); @@ -139,6 +177,8 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, if (CardDatabaseManager::getInstance()->getLoadStatus() != LoadStatus::Ok) { connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this, &VisualDatabaseDisplayWidget::initialize); + sortByLabel->setVisible(false); + filterByLabel->setVisible(false); quickFilterSaveLoadWidget->setVisible(false); quickFilterNameWidget->setVisible(false); quickFilterSubTypeWidget->setVisible(false); @@ -155,6 +195,8 @@ void VisualDatabaseDisplayWidget::initialize() { databaseLoadIndicator->setVisible(false); + sortByLabel->setVisible(true); + filterByLabel->setVisible(true); quickFilterSaveLoadWidget->setVisible(true); quickFilterNameWidget->setVisible(true); quickFilterSubTypeWidget->setVisible(true); @@ -171,6 +213,10 @@ void VisualDatabaseDisplayWidget::initialize() quickFilterSubTypeWidget->addSettingsWidget(subTypeFilterWidget); quickFilterSetWidget->addSettingsWidget(setFilterWidget); + filterContainerLayout->addWidget(sortByLabel); + filterContainerLayout->addWidget(sortColumnCombo); + filterContainerLayout->addWidget(sortOrderCombo); + filterContainerLayout->addWidget(filterByLabel); filterContainerLayout->addWidget(quickFilterSaveLoadWidget); filterContainerLayout->addWidget(quickFilterNameWidget); filterContainerLayout->addWidget(quickFilterSubTypeWidget); @@ -216,6 +262,9 @@ void VisualDatabaseDisplayWidget::retranslateUi() clearFilterWidget->setToolTip(tr("Clear all filters")); + sortByLabel->setText(tr("Sort by:")); + filterByLabel->setText(tr("Filter by:")); + quickFilterSaveLoadWidget->setToolTip(tr("Save and load filters")); quickFilterNameWidget->setToolTip(tr("Filter by exact card name")); quickFilterSubTypeWidget->setToolTip(tr("Filter by card sub-type")); diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h index 65eab99a4..cc51cb003 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h @@ -81,6 +81,11 @@ protected slots: private: QLabel *databaseLoadIndicator; + + QLabel *sortByLabel; + QComboBox *sortColumnCombo, *sortOrderCombo; + + QLabel *filterByLabel; QToolButton *clearFilterWidget; QWidget *filterContainer; QHBoxLayout *filterContainerLayout; From bc2ae6c486a82d6aa6896008dc0d04b519f9b3fe Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 27 Nov 2025 23:41:44 +0100 Subject: [PATCH 027/325] Remember more card sizes. (#6360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 22 minutes Co-authored-by: Lukas Brübach --- .../src/client/settings/cache_settings.cpp | 33 +++++++++++++++++++ .../src/client/settings/cache_settings.h | 28 ++++++++++++++++ .../tabs/api/edhrec/tab_edhrec_main.cpp | 5 ++- .../visual_database_display_widget.cpp | 4 ++- .../visual_deck_editor_widget.cpp | 5 ++- 5 files changed, 72 insertions(+), 3 deletions(-) diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index b9d2e6bb2..7a518df67 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -314,6 +314,11 @@ SettingsCache::SettingsCache() visualDatabaseDisplayFilterToMostRecentSetsAmount = settings->value("interface/visualdatabasedisplayfiltertomostrecentsetsamount", 10).toInt(); visualDeckEditorSampleHandSize = settings->value("interface/visualdeckeditorsamplehandsize", 7).toInt(); + visualDeckEditorCardSize = settings->value("interface/visualdeckeditorcardsize", 100).toInt(); + visualDatabaseDisplayCardSize = settings->value("interface/visualdatabasedisplaycardsize", 100).toInt(); + edhrecCardSize = settings->value("interface/edhreccardsize", 100).toInt(); + archidektPreviewSize = settings->value("interface/archidektpreviewsize", 100).toInt(); + horizontalHand = settings->value("hand/horizontal", true).toBool(); invertVerticalCoordinate = settings->value("table/invert_vertical", false).toBool(); minPlayersForMultiColumnLayout = settings->value("interface/min_players_multicolumn", 4).toInt(); @@ -863,6 +868,34 @@ void SettingsCache::setVisualDeckStorageSelectionAnimation(QT_STATE_CHANGED_T va emit visualDeckStorageSelectionAnimationChanged(visualDeckStorageSelectionAnimation); } +void SettingsCache::setVisualDeckEditorCardSize(int _visualDeckEditorCardSize) +{ + visualDeckEditorCardSize = _visualDeckEditorCardSize; + settings->setValue("interface/visualdeckeditorcardsize", visualDeckEditorCardSize); + emit visualDeckEditorCardSizeChanged(); +} + +void SettingsCache::setVisualDatabaseDisplayCardSize(int _visualDatabaseDisplayCardSize) +{ + visualDatabaseDisplayCardSize = _visualDatabaseDisplayCardSize; + settings->setValue("interface/visualdatabasedisplaycardsize", visualDatabaseDisplayCardSize); + emit visualDatabaseDisplayCardSizeChanged(); +} + +void SettingsCache::setEDHRecCardSize(int _edhrecCardSize) +{ + edhrecCardSize = _edhrecCardSize; + settings->setValue("interface/edhreccardsize", edhrecCardSize); + emit edhRecCardSizeChanged(); +} + +void SettingsCache::setArchidektPreviewCardSize(int _archidektPreviewCardSize) +{ + archidektPreviewSize = _archidektPreviewCardSize; + settings->setValue("interface/archidektpreviewsize", archidektPreviewSize); + emit archidektPreviewSizeChanged(); +} + void SettingsCache::setDefaultDeckEditorType(int value) { defaultDeckEditorType = value; diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index eb504e387..1cf04070c 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -167,6 +167,10 @@ signals: void visualDatabaseDisplayFilterToMostRecentSetsEnabledChanged(bool enabled); void visualDatabaseDisplayFilterToMostRecentSetsAmountChanged(int amount); void visualDeckEditorSampleHandSizeAmountChanged(int amount); + void visualDeckEditorCardSizeChanged(); + void visualDatabaseDisplayCardSizeChanged(); + void edhRecCardSizeChanged(); + void archidektPreviewSizeChanged(); void horizontalHandChanged(); void handJustificationChanged(); void invertVerticalCoordinateChanged(); @@ -250,6 +254,10 @@ private: QStringList visualDeckStorageDefaultTagsList; bool visualDeckStorageSearchFolderNames; int visualDeckStorageCardSize; + int visualDeckEditorCardSize; + int visualDatabaseDisplayCardSize; + int edhrecCardSize; + int archidektPreviewSize; bool visualDeckStorageDrawUnusedColorIdentities; int visualDeckStorageUnusedColorIdentitiesOpacity; int visualDeckStorageTooltipType; @@ -642,6 +650,22 @@ public: { return visualDeckStorageSelectionAnimation; } + int getVisualDeckEditorCardSize() const + { + return visualDeckEditorCardSize; + } + int getVisualDatabaseDisplayCardSize() const + { + return visualDatabaseDisplayCardSize; + } + int getEDHRecCardSize() const + { + return edhrecCardSize; + } + int getArchidektPreviewSize() const + { + return archidektPreviewSize; + } int getDefaultDeckEditorType() const { return defaultDeckEditorType; @@ -1018,6 +1042,10 @@ public slots: void setVisualDeckStorageAlwaysConvert(bool _visualDeckStorageAlwaysConvert); void setVisualDeckStorageInGame(QT_STATE_CHANGED_T value); void setVisualDeckStorageSelectionAnimation(QT_STATE_CHANGED_T value); + void setVisualDeckEditorCardSize(int _visualDeckEditorCardSize); + void setVisualDatabaseDisplayCardSize(int _visualDatabaseDisplayCardSize); + void setEDHRecCardSize(int _EDHRecCardSize); + void setArchidektPreviewCardSize(int _archidektPreviewCardSize); void setDefaultDeckEditorType(int value); void setVisualDatabaseDisplayFilterToMostRecentSetsEnabled(QT_STATE_CHANGED_T _enabled); void setVisualDatabaseDisplayFilterToMostRecentSetsAmount(int _amount); diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp index 208a5f717..0dcd5d0ae 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp @@ -1,5 +1,6 @@ #include "tab_edhrec_main.h" +#include "../../../../../client/settings/cache_settings.h" #include "../../tab_supervisor.h" #include "api_response/average_deck/edhrec_average_deck_api_response.h" #include "api_response/commander/edhrec_commander_api_response.h" @@ -96,7 +97,9 @@ TabEdhRecMain::TabEdhRecMain(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor settingsButton = new SettingsButtonWidget(this); - cardSizeSlider = new CardSizeWidget(this); + cardSizeSlider = new CardSizeWidget(this, nullptr, SettingsCache::instance().getEDHRecCardSize()); + connect(cardSizeSlider, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(), + &SettingsCache::setEDHRecCardSize); settingsButton->addSettingsWidget(cardSizeSlider); 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 0c6f56894..7171d93b2 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 @@ -43,7 +43,9 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, mainLayout->setContentsMargins(0, 0, 0, 0); flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarPolicy::ScrollBarAsNeeded); - cardSizeWidget = new CardSizeWidget(this, flowWidget); + cardSizeWidget = new CardSizeWidget(this, flowWidget, SettingsCache::instance().getVisualDatabaseDisplayCardSize()); + connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(), + &SettingsCache::setVisualDatabaseDisplayCardSize); searchContainer = new QWidget(this); searchLayout = new QHBoxLayout(searchContainer); 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 3536359be..27f5b8e2e 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 @@ -1,5 +1,6 @@ #include "visual_deck_editor_widget.h" +#include "../../../client/settings/cache_settings.h" #include "../../../main.h" #include "../../deck_loader/deck_loader.h" #include "../../layouts/overlap_layout.h" @@ -194,7 +195,9 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, scrollArea->addScrollBarWidget(zoneContainer, Qt::AlignHCenter); scrollArea->setWidget(zoneContainer); - cardSizeWidget = new CardSizeWidget(this); + cardSizeWidget = new CardSizeWidget(this, nullptr, SettingsCache::instance().getVisualDeckEditorCardSize()); + connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(), + &SettingsCache::setVisualDeckEditorCardSize); mainLayout->addWidget(groupAndSortContainer); mainLayout->addWidget(scrollArea); From a1a3b02d3a6eadc3b597df4e1ae1464b092baf9e Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Fri, 28 Nov 2025 00:42:46 +0100 Subject: [PATCH 028/325] Add clearer labeling, more tooltips, condense layout. (#6361) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 17 minutes Co-authored-by: Lukas Brübach --- .../visual_deck_editor_widget.cpp | 27 ++++++++++--------- .../visual_deck_editor_widget.h | 4 +-- 2 files changed, 17 insertions(+), 14 deletions(-) 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 27f5b8e2e..c1e5ead3a 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 @@ -38,10 +38,6 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, mainLayout->setContentsMargins(9, 0, 9, 5); mainLayout->setSpacing(0); - searchContainer = new QWidget(this); - searchLayout = new QHBoxLayout(searchContainer); - searchContainer->setLayout(searchLayout); - searchBar = new QLineEdit(this); connect(searchBar, &QLineEdit::returnPressed, this, [=, this]() { if (!searchBar->hasFocus()) @@ -114,16 +110,13 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, } }); - searchLayout->addWidget(searchBar); - searchLayout->addWidget(searchPushButton); - - mainLayout->addWidget(searchContainer); - groupAndSortContainer = new QWidget(this); groupAndSortLayout = new QHBoxLayout(groupAndSortContainer); groupAndSortLayout->setAlignment(Qt::AlignLeft); groupAndSortContainer->setLayout(groupAndSortLayout); + groupByLabel = new QLabel(groupAndSortContainer); + groupByComboBox = new QComboBox(this); if (auto tabWidget = qobject_cast(parent)) { // Inside a central widget QWidget container inside TabDeckEditorVisual @@ -149,6 +142,8 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, actChangeActiveGroupCriteria(); } + sortByLabel = new QLabel(groupAndSortContainer); + sortCriteriaButton = new SettingsButtonWidget(this); sortLabel = new QLabel(sortCriteriaButton); @@ -177,9 +172,13 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, displayTypeButton = new QPushButton(this); connect(displayTypeButton, &QPushButton::clicked, this, &VisualDeckEditorWidget::updateDisplayType); + groupAndSortLayout->addWidget(groupByLabel); groupAndSortLayout->addWidget(groupByComboBox); + groupAndSortLayout->addWidget(sortByLabel); groupAndSortLayout->addWidget(sortCriteriaButton); groupAndSortLayout->addWidget(displayTypeButton); + groupAndSortLayout->addWidget(searchBar); + groupAndSortLayout->addWidget(searchPushButton); scrollArea = new QScrollArea(this); scrollArea->setWidgetResizable(true); @@ -219,12 +218,16 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, void VisualDeckEditorWidget::retranslateUi() { + searchBar->setPlaceholderText(tr("Type a card name here for suggestions from the database...")); + groupByLabel->setText(tr("Group by:")); + groupByComboBox->setToolTip(tr("Change how cards are divided into categories/groups.")); + sortByLabel->setText(tr("Sort by:")); sortLabel->setText(tr("Click and drag to change the sort order within the groups")); searchPushButton->setText(tr("Quick search and add card")); searchPushButton->setToolTip(tr("Search for closest match in the database (with auto-suggestions) and add " "preferred printing to the deck on pressing enter")); sortCriteriaButton->setToolTip(tr("Configure how cards are sorted within their groups")); - displayTypeButton->setText(tr("Overlap Layout")); + displayTypeButton->setText(tr("Toggle Layout: Overlap")); displayTypeButton->setToolTip( tr("Change how cards are displayed within zones (i.e. overlapped or fully visible.)")); } @@ -376,10 +379,10 @@ void VisualDeckEditorWidget::updateDisplayType() // Update UI and emit signal switch (currentDisplayType) { case DisplayType::Flat: - displayTypeButton->setText(tr("Flat Layout")); + displayTypeButton->setText(tr("Toggle Layout: Flat")); break; case DisplayType::Overlap: - displayTypeButton->setText(tr("Overlap Layout")); + displayTypeButton->setText(tr("Toggle Layout: Overlap")); break; } emit displayTypeChanged(currentDisplayType); diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h index 4cff68407..e8ededc77 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h @@ -82,8 +82,6 @@ private: DeckListModel *deckListModel; QItemSelectionModel *selectionModel; QVBoxLayout *mainLayout; - QWidget *searchContainer; - QHBoxLayout *searchLayout; CardDatabaseModel *cardDatabaseModel; CardDatabaseDisplayModel *cardDatabaseDisplayModel; CardCompleterProxyModel *proxyModel; @@ -93,9 +91,11 @@ private: QPushButton *displayTypeButton; QWidget *groupAndSortContainer; QHBoxLayout *groupAndSortLayout; + QLabel *groupByLabel; QComboBox *groupByComboBox; QString activeGroupCriteria = "maintype"; SettingsButtonWidget *sortCriteriaButton; + QLabel *sortByLabel; QLabel *sortLabel; QListWidget *sortByListWidget; QStringList activeSortCriteria = {"name", "cmc", "colors", "maintype"}; From 9ece4bfd9ba9908b5db5a8b0e1da1ad5c9621450 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Fri, 28 Nov 2025 21:38:54 +0100 Subject: [PATCH 029/325] [Fix-Warnings] Mark const getters as [[nodiscard]] (#6365) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 45 minutes Co-authored-by: Lukas Brübach --- .../src/client/settings/cache_settings.h | 304 +++++++++--------- .../client/settings/card_counter_settings.h | 4 +- .../src/client/settings/shortcut_treeview.h | 2 +- .../src/client/settings/shortcuts_settings.h | 28 +- .../settings_card_preference_provider.h | 4 +- cockatrice/src/filters/deck_filter_string.h | 2 +- cockatrice/src/filters/filter_tree_model.h | 20 +- .../card_picture_loader_local.h | 10 +- .../src/interface/deck_loader/deck_loader.h | 8 +- .../src/interface/layouts/flow_layout.h | 8 +- .../deck_editor_deck_dock_widget.h | 2 +- .../dialogs/dlg_convert_deck_to_cod_format.h | 2 +- .../widgets/dialogs/dlg_edit_password.h | 4 +- .../widgets/dialogs/dlg_filter_games.h | 34 +- .../dialogs/dlg_forgot_password_request.h | 6 +- .../dialogs/dlg_forgot_password_reset.h | 10 +- .../interface/widgets/dialogs/dlg_register.h | 14 +- .../widgets/dialogs/tip_of_the_day.h | 12 +- .../widgets/replay/replay_timeline_widget.h | 6 +- .../interface/widgets/server/games_model.h | 51 +-- .../remote/remote_decklist_tree_widget.h | 41 +-- .../remote/remote_replay_list_tree_widget.h | 31 +- .../server/user/user_info_connection.h | 14 +- .../widgets/server/user/user_list_manager.h | 10 +- .../widgets/server/user/user_list_widget.h | 28 +- .../edhrec_api_response_card_container.h | 12 +- ...commander_api_response_commander_details.h | 58 ++-- .../widgets/tabs/api/edhrec/tab_edhrec_main.h | 4 +- cockatrice/src/interface/widgets/tabs/tab.h | 8 +- .../src/interface/widgets/tabs/tab_home.h | 2 +- .../src/interface/widgets/tabs/tab_message.h | 4 +- .../deck_preview_tag_display_widget.h | 6 +- .../libcockatrice/card/card_info.h | 48 +-- .../libcockatrice/card/card_info_comparator.h | 4 +- .../card/database/card_database_loader.h | 2 +- .../card/printing/printing_info.h | 10 +- .../libcockatrice/card/set/card_set.h | 20 +- .../deck_list/deck_list_card_node.h | 12 +- .../deck_list/deck_list_history_manager.h | 8 +- .../deck_list/deck_list_memento.h | 4 +- .../libcockatrice/filters/filter_card.h | 8 +- .../libcockatrice/filters/filter_string.h | 2 +- .../libcockatrice/filters/filter_tree.h | 86 ++--- .../interface_card_database_path_provider.h | 8 +- .../interface_card_preference_provider.h | 4 +- .../noop_card_database_path_provider.h | 8 +- .../noop_card_preference_provider.h | 4 +- .../card/card_completer_proxy_model.h | 2 +- .../models/database/card/card_search_model.h | 4 +- .../database/card_database_display_model.h | 10 +- .../models/database/card_database_model.h | 13 +- .../database/card_set/card_sets_model.h | 25 +- .../database/token/token_display_model.h | 4 +- .../models/database/token/token_edit_model.h | 4 +- .../models/deck_list/deck_list_model.h | 46 +-- .../deck_list_sort_filter_proxy_model.h | 2 +- .../libcockatrice/rng/rng_abstract.h | 2 +- .../settings/game_filters_settings.h | 2 +- 58 files changed, 548 insertions(+), 543 deletions(-) diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index 1cf04070c..5e329bf09 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -325,8 +325,8 @@ private: int keepalive; int timeout; void translateLegacySettings(); - QString getSafeConfigPath(QString configEntry, QString defaultPath) const; - QString getSafeConfigFilePath(QString configEntry, QString defaultPath) const; + [[nodiscard]] QString getSafeConfigPath(QString configEntry, QString defaultPath) const; + [[nodiscard]] QString getSafeConfigFilePath(QString configEntry, QString defaultPath) const; void loadPaths(); bool rememberGameSettings; QList releaseChannels; @@ -338,137 +338,137 @@ public: SettingsCache(); QString getDataPath(); QString getSettingsPath(); - QString getCachePath() const; - QString getNetworkCachePath() const; - const QByteArray &getMainWindowGeometry() const + [[nodiscard]] QString getCachePath() const; + [[nodiscard]] QString getNetworkCachePath() const; + [[nodiscard]] const QByteArray &getMainWindowGeometry() const { return mainWindowGeometry; } - const QByteArray &getTokenDialogGeometry() const + [[nodiscard]] const QByteArray &getTokenDialogGeometry() const { return tokenDialogGeometry; } - const QByteArray &getSetsDialogGeometry() const + [[nodiscard]] const QByteArray &getSetsDialogGeometry() const { return setsDialogGeometry; } - QString getLang() const + [[nodiscard]] QString getLang() const { return lang; } - QString getDeckPath() const + [[nodiscard]] QString getDeckPath() const { return deckPath; } - QString getFiltersPath() const + [[nodiscard]] QString getFiltersPath() const { return filtersPath; } - QString getReplaysPath() const + [[nodiscard]] QString getReplaysPath() const { return replaysPath; } - QString getThemesPath() const + [[nodiscard]] QString getThemesPath() const { return themesPath; } - QString getPicsPath() const + [[nodiscard]] QString getPicsPath() const { return picsPath; } - QString getRedirectCachePath() const + [[nodiscard]] QString getRedirectCachePath() const { return redirectCachePath; } - QString getCustomPicsPath() const + [[nodiscard]] QString getCustomPicsPath() const { return customPicsPath; } - QString getCustomCardDatabasePath() const override + [[nodiscard]] QString getCustomCardDatabasePath() const override { return customCardDatabasePath; } - QString getCardDatabasePath() const override + [[nodiscard]] QString getCardDatabasePath() const override { return cardDatabasePath; } - QString getSpoilerCardDatabasePath() const override + [[nodiscard]] QString getSpoilerCardDatabasePath() const override { return spoilerDatabasePath; } - QString getTokenDatabasePath() const override + [[nodiscard]] QString getTokenDatabasePath() const override { return tokenDatabasePath; } - QString getThemeName() const + [[nodiscard]] QString getThemeName() const { return themeName; } - QString getHomeTabBackgroundSource() const + [[nodiscard]] QString getHomeTabBackgroundSource() const { return homeTabBackgroundSource; } - int getHomeTabBackgroundShuffleFrequency() const + [[nodiscard]] int getHomeTabBackgroundShuffleFrequency() const { return homeTabBackgroundShuffleFrequency; } - bool getTabVisualDeckStorageOpen() const + [[nodiscard]] bool getTabVisualDeckStorageOpen() const { return tabVisualDeckStorageOpen; } - bool getTabServerOpen() const + [[nodiscard]] bool getTabServerOpen() const { return tabServerOpen; } - bool getTabAccountOpen() const + [[nodiscard]] bool getTabAccountOpen() const { return tabAccountOpen; } - bool getTabDeckStorageOpen() const + [[nodiscard]] bool getTabDeckStorageOpen() const { return tabDeckStorageOpen; } - bool getTabReplaysOpen() const + [[nodiscard]] bool getTabReplaysOpen() const { return tabReplaysOpen; } - bool getTabAdminOpen() const + [[nodiscard]] bool getTabAdminOpen() const { return tabAdminOpen; } - bool getTabLogOpen() const + [[nodiscard]] bool getTabLogOpen() const { return tabLogOpen; } - QString getChatMentionColor() const + [[nodiscard]] QString getChatMentionColor() const { return chatMentionColor; } - QString getChatHighlightColor() const + [[nodiscard]] QString getChatHighlightColor() const { return chatHighlightColor; } - bool getPicDownload() const + [[nodiscard]] bool getPicDownload() const { return picDownload; } - bool getShowStatusBar() const + [[nodiscard]] bool getShowStatusBar() const { return showStatusBar; } - bool getNotificationsEnabled() const + [[nodiscard]] bool getNotificationsEnabled() const { return notificationsEnabled; } - bool getSpectatorNotificationsEnabled() const + [[nodiscard]] bool getSpectatorNotificationsEnabled() const { return spectatorNotificationsEnabled; } - bool getBuddyConnectNotificationsEnabled() const + [[nodiscard]] bool getBuddyConnectNotificationsEnabled() const { return buddyConnectNotificationsEnabled; } - bool getCheckUpdatesOnStartup() const + [[nodiscard]] bool getCheckUpdatesOnStartup() const { return checkUpdatesOnStartup; } @@ -480,263 +480,263 @@ public: { return startupCardUpdateCheckAlwaysUpdate; } - int getCardUpdateCheckInterval() const + [[nodiscard]] int getCardUpdateCheckInterval() const { return cardUpdateCheckInterval; } - QDate getLastCardUpdateCheck() const + [[nodiscard]] QDate getLastCardUpdateCheck() const { return lastCardUpdateCheck; } - bool getCardUpdateCheckRequired() const + [[nodiscard]] bool getCardUpdateCheckRequired() const { return getLastCardUpdateCheck().daysTo(QDateTime::currentDateTime().date()) >= getCardUpdateCheckInterval() && getLastCardUpdateCheck() != QDateTime::currentDateTime().date(); } - bool getNotifyAboutUpdates() const override + [[nodiscard]] bool getNotifyAboutUpdates() const override { return notifyAboutUpdates; } - bool getNotifyAboutNewVersion() const + [[nodiscard]] bool getNotifyAboutNewVersion() const { return notifyAboutNewVersion; } - bool getShowTipsOnStartup() const + [[nodiscard]] bool getShowTipsOnStartup() const { return showTipsOnStartup; } - QList getSeenTips() const + [[nodiscard]] QList getSeenTips() const { return seenTips; } - int getUpdateReleaseChannelIndex() const + [[nodiscard]] int getUpdateReleaseChannelIndex() const { return updateReleaseChannel; } - ReleaseChannel *getUpdateReleaseChannel() const + [[nodiscard]] ReleaseChannel *getUpdateReleaseChannel() const { return releaseChannels.at(qMax(0, updateReleaseChannel)); } - QList getUpdateReleaseChannels() const + [[nodiscard]] QList getUpdateReleaseChannels() const { return releaseChannels; } - bool getDoubleClickToPlay() const + [[nodiscard]] bool getDoubleClickToPlay() const { return doubleClickToPlay; } - bool getClickPlaysAllSelected() const + [[nodiscard]] bool getClickPlaysAllSelected() const { return clickPlaysAllSelected; } - bool getPlayToStack() const + [[nodiscard]] bool getPlayToStack() const { return playToStack; } - bool getDoNotDeleteArrowsInSubPhases() const + [[nodiscard]] bool getDoNotDeleteArrowsInSubPhases() const { return doNotDeleteArrowsInSubPhases; } - int getStartingHandSize() const + [[nodiscard]] int getStartingHandSize() const { return startingHandSize; } - bool getAnnotateTokens() const + [[nodiscard]] bool getAnnotateTokens() const { return annotateTokens; } - QByteArray getTabGameSplitterSizes() const + [[nodiscard]] QByteArray getTabGameSplitterSizes() const { return tabGameSplitterSizes; } - bool getShowShortcuts() const + [[nodiscard]] bool getShowShortcuts() const { return showShortcuts; } - bool getDisplayCardNames() const + [[nodiscard]] bool getDisplayCardNames() const { return displayCardNames; } - bool getOverrideAllCardArtWithPersonalPreference() const + [[nodiscard]] bool getOverrideAllCardArtWithPersonalPreference() const { return overrideAllCardArtWithPersonalPreference; } - bool getBumpSetsWithCardsInDeckToTop() const + [[nodiscard]] bool getBumpSetsWithCardsInDeckToTop() const { return bumpSetsWithCardsInDeckToTop; } - int getPrintingSelectorSortOrder() const + [[nodiscard]] int getPrintingSelectorSortOrder() const { return printingSelectorSortOrder; } - int getPrintingSelectorCardSize() const + [[nodiscard]] int getPrintingSelectorCardSize() const { return printingSelectorCardSize; } - bool getIncludeRebalancedCards() const + [[nodiscard]] bool getIncludeRebalancedCards() const { return includeRebalancedCards; } - bool getPrintingSelectorNavigationButtonsVisible() const + [[nodiscard]] bool getPrintingSelectorNavigationButtonsVisible() const { return printingSelectorNavigationButtonsVisible; } - bool getDeckEditorBannerCardComboBoxVisible() const + [[nodiscard]] bool getDeckEditorBannerCardComboBoxVisible() const { return deckEditorBannerCardComboBoxVisible; } - bool getDeckEditorTagsWidgetVisible() const + [[nodiscard]] bool getDeckEditorTagsWidgetVisible() const { return deckEditorTagsWidgetVisible; } - int getVisualDeckStorageSortingOrder() const + [[nodiscard]] int getVisualDeckStorageSortingOrder() const { return visualDeckStorageSortingOrder; } - bool getVisualDeckStorageShowFolders() const + [[nodiscard]] bool getVisualDeckStorageShowFolders() const { return visualDeckStorageShowFolders; } - bool getVisualDeckStorageShowTagFilter() const + [[nodiscard]] bool getVisualDeckStorageShowTagFilter() const { return visualDeckStorageShowTagFilter; } - QStringList getVisualDeckStorageDefaultTagsList() const + [[nodiscard]] QStringList getVisualDeckStorageDefaultTagsList() const { return visualDeckStorageDefaultTagsList; } - bool getVisualDeckStorageSearchFolderNames() const + [[nodiscard]] bool getVisualDeckStorageSearchFolderNames() const { return visualDeckStorageSearchFolderNames; } - bool getVisualDeckStorageShowBannerCardComboBox() const + [[nodiscard]] bool getVisualDeckStorageShowBannerCardComboBox() const { return visualDeckStorageShowBannerCardComboBox; } - bool getVisualDeckStorageShowTagsOnDeckPreviews() const + [[nodiscard]] bool getVisualDeckStorageShowTagsOnDeckPreviews() const { return visualDeckStorageShowTagsOnDeckPreviews; } - int getVisualDeckStorageCardSize() const + [[nodiscard]] int getVisualDeckStorageCardSize() const { return visualDeckStorageCardSize; } - bool getVisualDeckStorageDrawUnusedColorIdentities() const + [[nodiscard]] bool getVisualDeckStorageDrawUnusedColorIdentities() const { return visualDeckStorageDrawUnusedColorIdentities; } - int getVisualDeckStorageUnusedColorIdentitiesOpacity() const + [[nodiscard]] int getVisualDeckStorageUnusedColorIdentitiesOpacity() const { return visualDeckStorageUnusedColorIdentitiesOpacity; } - int getVisualDeckStorageTooltipType() const + [[nodiscard]] int getVisualDeckStorageTooltipType() const { return visualDeckStorageTooltipType; } - bool getVisualDeckStoragePromptForConversion() const + [[nodiscard]] bool getVisualDeckStoragePromptForConversion() const { return visualDeckStoragePromptForConversion; } - bool getVisualDeckStorageAlwaysConvert() const + [[nodiscard]] bool getVisualDeckStorageAlwaysConvert() const { return visualDeckStorageAlwaysConvert; } - bool getVisualDeckStorageInGame() const + [[nodiscard]] bool getVisualDeckStorageInGame() const { return visualDeckStorageInGame; } - bool getVisualDeckStorageSelectionAnimation() const + [[nodiscard]] bool getVisualDeckStorageSelectionAnimation() const { return visualDeckStorageSelectionAnimation; } - int getVisualDeckEditorCardSize() const + [[nodiscard]] int getVisualDeckEditorCardSize() const { return visualDeckEditorCardSize; } - int getVisualDatabaseDisplayCardSize() const + [[nodiscard]] int getVisualDatabaseDisplayCardSize() const { return visualDatabaseDisplayCardSize; } - int getEDHRecCardSize() const + [[nodiscard]] int getEDHRecCardSize() const { return edhrecCardSize; } - int getArchidektPreviewSize() const + [[nodiscard]] int getArchidektPreviewSize() const { return archidektPreviewSize; } - int getDefaultDeckEditorType() const + [[nodiscard]] int getDefaultDeckEditorType() const { return defaultDeckEditorType; } - bool getVisualDatabaseDisplayFilterToMostRecentSetsEnabled() const + [[nodiscard]] bool getVisualDatabaseDisplayFilterToMostRecentSetsEnabled() const { return visualDatabaseDisplayFilterToMostRecentSetsEnabled; } - int getVisualDatabaseDisplayFilterToMostRecentSetsAmount() const + [[nodiscard]] int getVisualDatabaseDisplayFilterToMostRecentSetsAmount() const { return visualDatabaseDisplayFilterToMostRecentSetsAmount; } - int getVisualDeckEditorSampleHandSize() const + [[nodiscard]] int getVisualDeckEditorSampleHandSize() const { return visualDeckEditorSampleHandSize; } - bool getHorizontalHand() const + [[nodiscard]] bool getHorizontalHand() const { return horizontalHand; } - bool getInvertVerticalCoordinate() const + [[nodiscard]] bool getInvertVerticalCoordinate() const { return invertVerticalCoordinate; } - int getMinPlayersForMultiColumnLayout() const + [[nodiscard]] int getMinPlayersForMultiColumnLayout() const { return minPlayersForMultiColumnLayout; } - bool getTapAnimation() const + [[nodiscard]] bool getTapAnimation() const { return tapAnimation; } - bool getAutoRotateSidewaysLayoutCards() const + [[nodiscard]] bool getAutoRotateSidewaysLayoutCards() const { return autoRotateSidewaysLayoutCards; } - bool getOpenDeckInNewTab() const + [[nodiscard]] bool getOpenDeckInNewTab() const { return openDeckInNewTab; } - int getRewindBufferingMs() const + [[nodiscard]] int getRewindBufferingMs() const { return rewindBufferingMs; } - bool getChatMention() const + [[nodiscard]] bool getChatMention() const { return chatMention; } - bool getChatMentionCompleter() const + [[nodiscard]] bool getChatMentionCompleter() const { return chatMentionCompleter; } - bool getChatMentionForeground() const + [[nodiscard]] bool getChatMentionForeground() const { return chatMentionForeground; } - bool getChatHighlightForeground() const + [[nodiscard]] bool getChatHighlightForeground() const { return chatHighlightForeground; } /** * Currently selected index for the `Group by X` QComboBox */ - int getZoneViewGroupByIndex() const + [[nodiscard]] int getZoneViewGroupByIndex() const { return zoneViewGroupByIndex; } /** * Currently selected index for the `Sort by X` QComboBox */ - int getZoneViewSortByIndex() const + [[nodiscard]] int getZoneViewSortByIndex() const { return zoneViewSortByIndex; } @@ -744,136 +744,136 @@ public: Returns if the view should be sorted into pile view. @return zoneViewPileView if the view should be sorted into pile view. */ - bool getZoneViewPileView() const + [[nodiscard]] bool getZoneViewPileView() const { return zoneViewPileView; } - bool getSoundEnabled() const + [[nodiscard]] bool getSoundEnabled() const { return soundEnabled; } - QString getSoundThemeName() const + [[nodiscard]] QString getSoundThemeName() const { return soundThemeName; } - bool getIgnoreUnregisteredUsers() const + [[nodiscard]] bool getIgnoreUnregisteredUsers() const { return ignoreUnregisteredUsers; } - bool getIgnoreUnregisteredUserMessages() const + [[nodiscard]] bool getIgnoreUnregisteredUserMessages() const { return ignoreUnregisteredUserMessages; } - int getPixmapCacheSize() const + [[nodiscard]] int getPixmapCacheSize() const { return pixmapCacheSize; } - int getNetworkCacheSizeInMB() const + [[nodiscard]] int getNetworkCacheSizeInMB() const { return networkCacheSize; } - int getRedirectCacheTtl() const + [[nodiscard]] int getRedirectCacheTtl() const { return redirectCacheTtl; } - bool getScaleCards() const + [[nodiscard]] bool getScaleCards() const { return scaleCards; } - int getStackCardOverlapPercent() const + [[nodiscard]] int getStackCardOverlapPercent() const { return verticalCardOverlapPercent; } - bool getShowMessagePopup() const + [[nodiscard]] bool getShowMessagePopup() const { return showMessagePopups; } - bool getShowMentionPopup() const + [[nodiscard]] bool getShowMentionPopup() const { return showMentionPopups; } - bool getRoomHistory() const + [[nodiscard]] bool getRoomHistory() const { return roomHistory; } - bool getLeftJustified() const + [[nodiscard]] bool getLeftJustified() const { return leftJustified; } - int getMasterVolume() const + [[nodiscard]] int getMasterVolume() const { return masterVolume; } - int getCardInfoViewMode() const + [[nodiscard]] int getCardInfoViewMode() const { return cardInfoViewMode; } - QStringList getCountries() const; - QString getHighlightWords() const + [[nodiscard]] QStringList getCountries() const; + [[nodiscard]] QString getHighlightWords() const { return highlightWords; } - QString getGameDescription() const + [[nodiscard]] QString getGameDescription() const { return gameDescription; } - int getMaxPlayers() const + [[nodiscard]] int getMaxPlayers() const { return maxPlayers; } - QString getGameTypes() const + [[nodiscard]] QString getGameTypes() const { return gameTypes; } - bool getOnlyBuddies() const + [[nodiscard]] bool getOnlyBuddies() const { return onlyBuddies; } - bool getOnlyRegistered() const + [[nodiscard]] bool getOnlyRegistered() const { return onlyRegistered; } - bool getSpectatorsAllowed() const + [[nodiscard]] bool getSpectatorsAllowed() const { return spectatorsAllowed; } - bool getSpectatorsNeedPassword() const + [[nodiscard]] bool getSpectatorsNeedPassword() const { return spectatorsNeedPassword; } - bool getSpectatorsCanTalk() const + [[nodiscard]] bool getSpectatorsCanTalk() const { return spectatorsCanTalk; } - bool getSpectatorsCanSeeEverything() const + [[nodiscard]] bool getSpectatorsCanSeeEverything() const { return spectatorsCanSeeEverything; } - int getDefaultStartingLifeTotal() const + [[nodiscard]] int getDefaultStartingLifeTotal() const { return defaultStartingLifeTotal; } - bool getShareDecklistsOnLoad() const + [[nodiscard]] bool getShareDecklistsOnLoad() const { return shareDecklistsOnLoad; } - bool getCreateGameAsSpectator() const + [[nodiscard]] bool getCreateGameAsSpectator() const { return createGameAsSpectator; } - bool getRememberGameSettings() const + [[nodiscard]] bool getRememberGameSettings() const { return rememberGameSettings; } - int getKeepAlive() const override + [[nodiscard]] int getKeepAlive() const override { return keepalive; } - int getTimeOut() const override + [[nodiscard]] int getTimeOut() const override { return timeout; } - int getMaxFontSize() const + [[nodiscard]] int getMaxFontSize() const { return maxFontSize; } @@ -901,73 +901,73 @@ public: { return useTearOffMenus; } - int getCardViewInitialRowsMax() const + [[nodiscard]] int getCardViewInitialRowsMax() const { return cardViewInitialRowsMax; } - int getCardViewExpandedRowsMax() const + [[nodiscard]] int getCardViewExpandedRowsMax() const { return cardViewExpandedRowsMax; } - bool getCloseEmptyCardView() const + [[nodiscard]] bool getCloseEmptyCardView() const { return closeEmptyCardView; } - bool getFocusCardViewSearchBar() const + [[nodiscard]] bool getFocusCardViewSearchBar() const { return focusCardViewSearchBar; } - ShortcutsSettings &shortcuts() const + [[nodiscard]] ShortcutsSettings &shortcuts() const { return *shortcutsSettings; } - CardDatabaseSettings *cardDatabase() const + [[nodiscard]] CardDatabaseSettings *cardDatabase() const { return cardDatabaseSettings; } - ServersSettings &servers() const + [[nodiscard]] ServersSettings &servers() const { return *serversSettings; } - MessageSettings &messages() const + [[nodiscard]] MessageSettings &messages() const { return *messageSettings; } - GameFiltersSettings &gameFilters() const + [[nodiscard]] GameFiltersSettings &gameFilters() const { return *gameFiltersSettings; } - LayoutsSettings &layouts() const + [[nodiscard]] LayoutsSettings &layouts() const { return *layoutsSettings; } - DownloadSettings &downloads() const + [[nodiscard]] DownloadSettings &downloads() const { return *downloadSettings; } - RecentsSettings &recents() const + [[nodiscard]] RecentsSettings &recents() const { return *recentsSettings; } - CardOverrideSettings &cardOverrides() const + [[nodiscard]] CardOverrideSettings &cardOverrides() const { return *cardOverrideSettings; } - DebugSettings &debug() const + [[nodiscard]] DebugSettings &debug() const { return *debugSettings; } - CardCounterSettings &cardCounters() const; + [[nodiscard]] CardCounterSettings &cardCounters() const; - bool getIsPortableBuild() const + [[nodiscard]] bool getIsPortableBuild() const { return isPortableBuild; } - bool getDownloadSpoilersStatus() const + [[nodiscard]] bool getDownloadSpoilersStatus() const { return mbDownloadSpoilers; } - bool getRoundCardCorners() const + [[nodiscard]] bool getRoundCardCorners() const { return roundCardCorners; } diff --git a/cockatrice/src/client/settings/card_counter_settings.h b/cockatrice/src/client/settings/card_counter_settings.h index f2c1c1d43..63615d0e2 100644 --- a/cockatrice/src/client/settings/card_counter_settings.h +++ b/cockatrice/src/client/settings/card_counter_settings.h @@ -20,9 +20,9 @@ class CardCounterSettings : public SettingsManager public: CardCounterSettings(const QString &settingsPath, QObject *parent = nullptr); - QColor color(int counterId) const; + [[nodiscard]] QColor color(int counterId) const; - QString displayName(int counterId) const; + [[nodiscard]] QString displayName(int counterId) const; public slots: void setColor(int counterId, const QColor &color); diff --git a/cockatrice/src/client/settings/shortcut_treeview.h b/cockatrice/src/client/settings/shortcut_treeview.h index 229bf2ed2..d503140f0 100644 --- a/cockatrice/src/client/settings/shortcut_treeview.h +++ b/cockatrice/src/client/settings/shortcut_treeview.h @@ -22,7 +22,7 @@ public: explicit ShortcutFilterProxyModel(QObject *parent = nullptr); protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; }; class ShortcutTreeView : public QTreeView diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h index 31e90bdc7..d1b0301d1 100644 --- a/cockatrice/src/client/settings/shortcuts_settings.h +++ b/cockatrice/src/client/settings/shortcuts_settings.h @@ -100,11 +100,11 @@ public: { QList::operator=(_sequence); }; - QString getName() const + [[nodiscard]] QString getName() const { return QApplication::translate("shortcutsTab", name.toUtf8().data()); }; - QString getGroupName() const + [[nodiscard]] QString getGroupName() const { return ShortcutGroup::getGroupName(group); }; @@ -120,13 +120,13 @@ class ShortcutsSettings : public QObject public: explicit ShortcutsSettings(const QString &settingsFilePath, QObject *parent = nullptr); - ShortcutKey getDefaultShortcut(const QString &name) const; - ShortcutKey getShortcut(const QString &name) const; - QKeySequence getSingleShortcut(const QString &name) const; - QString getDefaultShortcutString(const QString &name) const; - QString getShortcutString(const QString &name) const; - QString getShortcutFriendlyName(const QString &shortcutName) const; - QList getAllShortcutKeys() const + [[nodiscard]] ShortcutKey getDefaultShortcut(const QString &name) const; + [[nodiscard]] ShortcutKey getShortcut(const QString &name) const; + [[nodiscard]] QKeySequence getSingleShortcut(const QString &name) const; + [[nodiscard]] QString getDefaultShortcutString(const QString &name) const; + [[nodiscard]] QString getShortcutString(const QString &name) const; + [[nodiscard]] QString getShortcutFriendlyName(const QString &shortcutName) const; + [[nodiscard]] QList getAllShortcutKeys() const { return shortCuts.keys(); }; @@ -135,9 +135,9 @@ public: void setShortcuts(const QString &name, const QKeySequence &Sequence); void setShortcuts(const QString &name, const QString &sequences); - bool isKeyAllowed(const QString &name, const QString &sequences) const; - bool isValid(const QString &name, const QString &sequences) const; - QStringList findOverlaps(const QString &name, const QString &sequences) const; + [[nodiscard]] bool isKeyAllowed(const QString &name, const QString &sequences) const; + [[nodiscard]] bool isValid(const QString &name, const QString &sequences) const; + [[nodiscard]] QStringList findOverlaps(const QString &name, const QString &sequences) const; void resetAllShortcuts(); void clearAllShortcuts(); @@ -152,8 +152,8 @@ private: QString settingsFilePath; QHash shortCuts; - QString stringifySequence(const QList &Sequence) const; - QList parseSequenceString(const QString &stringSequence) const; + [[nodiscard]] QString stringifySequence(const QList &Sequence) const; + [[nodiscard]] QList parseSequenceString(const QString &stringSequence) const; const QHash defaultShortCuts = { {"MainWindow/aCheckCardUpdates", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Check for Card Updates..."), diff --git a/cockatrice/src/database/interface/settings_card_preference_provider.h b/cockatrice/src/database/interface/settings_card_preference_provider.h index 4197bb53c..5ec23ea60 100644 --- a/cockatrice/src/database/interface/settings_card_preference_provider.h +++ b/cockatrice/src/database/interface/settings_card_preference_provider.h @@ -7,12 +7,12 @@ class SettingsCardPreferenceProvider : public ICardPreferenceProvider { public: - QString getCardPreferenceOverride(const QString &cardName) const override + [[nodiscard]] QString getCardPreferenceOverride(const QString &cardName) const override { return SettingsCache::instance().cardOverrides().getCardPreferenceOverride(cardName); } - bool getIncludeRebalancedCards() const override + [[nodiscard]] bool getIncludeRebalancedCards() const override { return SettingsCache::instance().getIncludeRebalancedCards(); }; diff --git a/cockatrice/src/filters/deck_filter_string.h b/cockatrice/src/filters/deck_filter_string.h index 15782b3ad..635745592 100644 --- a/cockatrice/src/filters/deck_filter_string.h +++ b/cockatrice/src/filters/deck_filter_string.h @@ -40,7 +40,7 @@ public: return filter(deck, info); } - bool valid() const + [[nodiscard]] bool valid() const { return _error.isEmpty(); } diff --git a/cockatrice/src/filters/filter_tree_model.h b/cockatrice/src/filters/filter_tree_model.h index ce6eb85fc..c6666a838 100644 --- a/cockatrice/src/filters/filter_tree_model.h +++ b/cockatrice/src/filters/filter_tree_model.h @@ -24,8 +24,8 @@ public slots: void addFilter(const CardFilter *f); void removeFilter(const CardFilter *f); void clearFiltersOfType(CardFilter::Attr filterType); - QList getFiltersOfType(CardFilter::Attr filterType) const; - QList allFilters() const; + [[nodiscard]] QList getFiltersOfType(CardFilter::Attr filterType) const; + [[nodiscard]] QList allFilters() const; private slots: void proxyBeginInsertRow(const FilterTreeNode *, int); @@ -34,23 +34,23 @@ private slots: void proxyEndRemoveRow(const FilterTreeNode *, int); private: - FilterTreeNode *indexToNode(const QModelIndex &idx) const; + [[nodiscard]] FilterTreeNode *indexToNode(const QModelIndex &idx) const; QModelIndex nodeIndex(const FilterTreeNode *node, int row, int column) const; public: FilterTreeModel(QObject *parent = nullptr); ~FilterTreeModel() override; - FilterTree *filterTree() const + [[nodiscard]] FilterTree *filterTree() const { return fTree; } - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - QModelIndex parent(const QModelIndex &ind) const override; - QModelIndex index(int row, int column, const QModelIndex &parent) const override; + [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override; + [[nodiscard]] QModelIndex parent(const QModelIndex &ind) const override; + [[nodiscard]] QModelIndex index(int row, int column, const QModelIndex &parent) const override; bool removeRows(int row, int count, const QModelIndex &parent) override; void clear(); }; diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.h index 84f16cc38..25692f318 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.h @@ -42,7 +42,7 @@ public: * * Uses a set of name variants and folder paths to attempt to locate the correct image. */ - QImage tryLoad(const ExactCard &toLoad) const; + [[nodiscard]] QImage tryLoad(const ExactCard &toLoad) const; private: QString picsPath; ///< Path to standard card image folder @@ -72,10 +72,10 @@ private: * Uses several filename patterns to match card images, in order from * most-specific to least-specific. */ - QImage tryLoadCardImageFromDisk(const QString &setName, - const QString &correctedCardName, - const QString &collectorNumber, - const QString &providerId) const; + [[nodiscard]] QImage tryLoadCardImageFromDisk(const QString &setName, + const QString &correctedCardName, + const QString &collectorNumber, + const QString &providerId) const; private slots: /** diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h index 57305b7de..ba9ba807d 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.h +++ b/cockatrice/src/interface/deck_loader/deck_loader.h @@ -56,7 +56,7 @@ public: DeckLoader(const DeckLoader &) = delete; DeckLoader &operator=(const DeckLoader &) = delete; - const QString &getLastFileName() const + [[nodiscard]] const QString &getLastFileName() const { return lastFileName; } @@ -64,16 +64,16 @@ public: { lastFileName = _lastFileName; } - FileFormat getLastFileFormat() const + [[nodiscard]] FileFormat getLastFileFormat() const { return lastFileFormat; } - int getLastRemoteDeckId() const + [[nodiscard]] int getLastRemoteDeckId() const { return lastRemoteDeckId; } - bool hasNotBeenLoaded() const + [[nodiscard]] bool hasNotBeenLoaded() const { return getLastFileName().isEmpty() && getLastRemoteDeckId() == -1; } diff --git a/cockatrice/src/interface/layouts/flow_layout.h b/cockatrice/src/interface/layouts/flow_layout.h index f4c9e3001..cf109d260 100644 --- a/cockatrice/src/interface/layouts/flow_layout.h +++ b/cockatrice/src/interface/layouts/flow_layout.h @@ -23,9 +23,9 @@ public: ~FlowLayout() override; void insertWidgetAtIndex(QWidget *toInsert, int index); - QSize calculateMinimumSizeHorizontal() const; - QSize calculateSizeHintVertical() const; - QSize calculateMinimumSizeVertical() const; + [[nodiscard]] QSize calculateMinimumSizeHorizontal() const; + [[nodiscard]] QSize calculateSizeHintVertical() const; + [[nodiscard]] QSize calculateMinimumSizeVertical() const; void addItem(QLayoutItem *item) override; [[nodiscard]] int count() const override; [[nodiscard]] QLayoutItem *itemAt(int index) const override; @@ -48,7 +48,7 @@ public: void layoutSingleColumn(const QVector &colItems, int x, int y); [[nodiscard]] QSize sizeHint() const override; [[nodiscard]] QSize minimumSize() const override; - QSize calculateSizeHintHorizontal() const; + [[nodiscard]] QSize calculateSizeHintHorizontal() const; protected: QList items; // List to store layout items diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index 86f3db9f8..0c89d51f5 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -104,7 +104,7 @@ private: QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard; void recursiveExpand(const QModelIndex &index); - QModelIndexList getSelectedCardNodes() const; + [[nodiscard]] QModelIndexList getSelectedCardNodes() const; private slots: void decklistCustomMenu(QPoint point); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.h b/cockatrice/src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.h index 7c5ea303a..61eea1d6e 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.h @@ -22,7 +22,7 @@ public: explicit DialogConvertDeckToCodFormat(QWidget *parent); void retranslateUi(); - bool dontAskAgain() const; + [[nodiscard]] bool dontAskAgain() const; private: QVBoxLayout *layout; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.h b/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.h index 38f782228..311774d11 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.h @@ -20,11 +20,11 @@ class DlgEditPassword : public QDialog Q_OBJECT public: explicit DlgEditPassword(QWidget *parent = nullptr); - QString getOldPassword() const + [[nodiscard]] QString getOldPassword() const { return oldPasswordEdit->text(); } - QString getNewPassword() const + [[nodiscard]] QString getNewPassword() const { return newPasswordEdit->text(); } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h index 37b208749..abfdab28a 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h @@ -58,32 +58,32 @@ public: const GamesProxyModel *_gamesProxyModel, QWidget *parent = nullptr); - bool getHideFullGames() const; - bool getHideGamesThatStarted() const; - bool getHidePasswordProtectedGames() const; + [[nodiscard]] bool getHideFullGames() const; + [[nodiscard]] bool getHideGamesThatStarted() const; + [[nodiscard]] bool getHidePasswordProtectedGames() const; void setShowPasswordProtectedGames(bool _passwordProtectedGamesHidden); - bool getHideBuddiesOnlyGames() const; + [[nodiscard]] bool getHideBuddiesOnlyGames() const; void setHideBuddiesOnlyGames(bool _hideBuddiesOnlyGames); - bool getHideOpenDecklistGames() const; + [[nodiscard]] bool getHideOpenDecklistGames() const; void setHideOpenDecklistGames(bool _hideOpenDecklistGames); - bool getHideIgnoredUserGames() const; + [[nodiscard]] bool getHideIgnoredUserGames() const; void setHideIgnoredUserGames(bool _hideIgnoredUserGames); - bool getHideNotBuddyCreatedGames() const; - QString getGameNameFilter() const; + [[nodiscard]] bool getHideNotBuddyCreatedGames() const; + [[nodiscard]] QString getGameNameFilter() const; void setGameNameFilter(const QString &_gameNameFilter); - QStringList getCreatorNameFilters() const; + [[nodiscard]] QStringList getCreatorNameFilters() const; void setCreatorNameFilter(const QString &_creatorNameFilter); - QSet getGameTypeFilter() const; + [[nodiscard]] QSet getGameTypeFilter() const; void setGameTypeFilter(const QSet &_gameTypeFilter); - int getMaxPlayersFilterMin() const; - int getMaxPlayersFilterMax() const; + [[nodiscard]] int getMaxPlayersFilterMin() const; + [[nodiscard]] int getMaxPlayersFilterMax() const; void setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax); - QTime getMaxGameAge() const; + [[nodiscard]] QTime getMaxGameAge() const; const QMap gameAgeMap; - bool getShowOnlyIfSpectatorsCanWatch() const; - bool getShowSpectatorPasswordProtected() const; - bool getShowOnlyIfSpectatorsCanChat() const; - bool getShowOnlyIfSpectatorsCanSeeHands() const; + [[nodiscard]] bool getShowOnlyIfSpectatorsCanWatch() const; + [[nodiscard]] bool getShowSpectatorPasswordProtected() const; + [[nodiscard]] bool getShowOnlyIfSpectatorsCanChat() const; + [[nodiscard]] bool getShowOnlyIfSpectatorsCanSeeHands() const; }; #endif diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.h b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.h index da4d9e726..e51dad810 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.h @@ -20,15 +20,15 @@ class DlgForgotPasswordRequest : public QDialog Q_OBJECT public: explicit DlgForgotPasswordRequest(QWidget *parent = nullptr); - QString getHost() const + [[nodiscard]] QString getHost() const { return hostEdit->text(); } - int getPort() const + [[nodiscard]] int getPort() const { return portEdit->text().toInt(); } - QString getPlayerName() const + [[nodiscard]] QString getPlayerName() const { return playernameEdit->text(); } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.h b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.h index ede22e2aa..3d110c71d 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.h @@ -20,23 +20,23 @@ class DlgForgotPasswordReset : public QDialog Q_OBJECT public: explicit DlgForgotPasswordReset(QWidget *parent = nullptr); - QString getHost() const + [[nodiscard]] QString getHost() const { return hostEdit->text(); } - int getPort() const + [[nodiscard]] int getPort() const { return portEdit->text().toInt(); } - QString getPlayerName() const + [[nodiscard]] QString getPlayerName() const { return playernameEdit->text(); } - QString getToken() const + [[nodiscard]] QString getToken() const { return tokenEdit->text(); } - QString getPassword() const + [[nodiscard]] QString getPassword() const { return newpasswordEdit->text(); } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_register.h b/cockatrice/src/interface/widgets/dialogs/dlg_register.h index a614386fc..bebbfc500 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_register.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_register.h @@ -20,31 +20,31 @@ class DlgRegister : public QDialog Q_OBJECT public: explicit DlgRegister(QWidget *parent = nullptr); - QString getHost() const + [[nodiscard]] QString getHost() const { return hostEdit->text(); } - int getPort() const + [[nodiscard]] int getPort() const { return portEdit->text().toInt(); } - QString getPlayerName() const + [[nodiscard]] QString getPlayerName() const { return playernameEdit->text(); } - QString getPassword() const + [[nodiscard]] QString getPassword() const { return passwordEdit->text(); } - QString getEmail() const + [[nodiscard]] QString getEmail() const { return emailEdit->text(); } - QString getCountry() const + [[nodiscard]] QString getCountry() const { return countryEdit->currentIndex() == 0 ? "" : countryEdit->currentText(); } - QString getRealName() const + [[nodiscard]] QString getRealName() const { return realnameEdit->text(); } diff --git a/cockatrice/src/interface/widgets/dialogs/tip_of_the_day.h b/cockatrice/src/interface/widgets/dialogs/tip_of_the_day.h index 1cb0cf6fb..d61fddab5 100644 --- a/cockatrice/src/interface/widgets/dialogs/tip_of_the_day.h +++ b/cockatrice/src/interface/widgets/dialogs/tip_of_the_day.h @@ -14,19 +14,19 @@ class TipOfTheDay { public: explicit TipOfTheDay(QString _title, QString _content, QString _imagePath, QDate _date); - QString getTitle() const + [[nodiscard]] QString getTitle() const { return title; } - QString getContent() const + [[nodiscard]] QString getContent() const { return content; } - QString getImagePath() const + [[nodiscard]] QString getImagePath() const { return imagePath; } - QDate getDate() const + [[nodiscard]] QDate getDate() const { return date; } @@ -51,8 +51,8 @@ public: explicit TipsOfTheDay(QString xmlPath, QObject *parent = nullptr); ~TipsOfTheDay() override; TipOfTheDay getTip(int tipId); - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; private: QList *tipList; diff --git a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h index e55d408ce..17a235365 100644 --- a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h +++ b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h @@ -60,10 +60,10 @@ public: explicit ReplayTimelineWidget(QWidget *parent = nullptr); void setTimeline(const QList &_replayTimeline); - QSize sizeHint() const override; - QSize minimumSizeHint() const override; + [[nodiscard]] QSize sizeHint() const override; + [[nodiscard]] QSize minimumSizeHint() const override; void setTimeScaleFactor(qreal _timeScaleFactor); - int getCurrentEvent() const + [[nodiscard]] int getCurrentEvent() const { return currentEvent; } diff --git a/cockatrice/src/interface/widgets/server/games_model.h b/cockatrice/src/interface/widgets/server/games_model.h index c704befcc..03602a74e 100644 --- a/cockatrice/src/interface/widgets/server/games_model.h +++ b/cockatrice/src/interface/widgets/server/games_model.h @@ -42,19 +42,20 @@ public: */ GamesModel(const QMap &_rooms, const QMap &_gameTypes, QObject *parent = nullptr); - int rowCount(const QModelIndex &parent = QModelIndex()) const override + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override { return parent.isValid() ? 0 : gameList.size(); } - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override + [[nodiscard]] int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override { return NUM_COLS; } - QVariant data(const QModelIndex &index, int role) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + [[nodiscard]] QVariant + headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; /** * @brief Formats the game creation time into a human-readable string. @@ -148,71 +149,71 @@ public: explicit GamesProxyModel(QObject *parent = nullptr, const UserListProxy *_userListProxy = nullptr); // Getters for filter parameters - bool getHideBuddiesOnlyGames() const + [[nodiscard]] bool getHideBuddiesOnlyGames() const { return hideBuddiesOnlyGames; } - bool getHideIgnoredUserGames() const + [[nodiscard]] bool getHideIgnoredUserGames() const { return hideIgnoredUserGames; } - bool getHideFullGames() const + [[nodiscard]] bool getHideFullGames() const { return hideFullGames; } - bool getHideGamesThatStarted() const + [[nodiscard]] bool getHideGamesThatStarted() const { return hideGamesThatStarted; } - bool getHidePasswordProtectedGames() const + [[nodiscard]] bool getHidePasswordProtectedGames() const { return hidePasswordProtectedGames; } - bool getHideNotBuddyCreatedGames() const + [[nodiscard]] bool getHideNotBuddyCreatedGames() const { return hideNotBuddyCreatedGames; } - bool getHideOpenDecklistGames() const + [[nodiscard]] bool getHideOpenDecklistGames() const { return hideOpenDecklistGames; } - QString getGameNameFilter() const + [[nodiscard]] QString getGameNameFilter() const { return gameNameFilter; } - QStringList getCreatorNameFilters() const + [[nodiscard]] QStringList getCreatorNameFilters() const { return creatorNameFilters; } - QSet getGameTypeFilter() const + [[nodiscard]] QSet getGameTypeFilter() const { return gameTypeFilter; } - int getMaxPlayersFilterMin() const + [[nodiscard]] int getMaxPlayersFilterMin() const { return maxPlayersFilterMin; } - int getMaxPlayersFilterMax() const + [[nodiscard]] int getMaxPlayersFilterMax() const { return maxPlayersFilterMax; } - const QTime &getMaxGameAge() const + [[nodiscard]] const QTime &getMaxGameAge() const { return maxGameAge; } - bool getShowOnlyIfSpectatorsCanWatch() const + [[nodiscard]] bool getShowOnlyIfSpectatorsCanWatch() const { return showOnlyIfSpectatorsCanWatch; } - bool getShowSpectatorPasswordProtected() const + [[nodiscard]] bool getShowSpectatorPasswordProtected() const { return showSpectatorPasswordProtected; } - bool getShowOnlyIfSpectatorsCanChat() const + [[nodiscard]] bool getShowOnlyIfSpectatorsCanChat() const { return showOnlyIfSpectatorsCanChat; } - bool getShowOnlyIfSpectatorsCanSeeHands() const + [[nodiscard]] bool getShowOnlyIfSpectatorsCanSeeHands() const { return showOnlyIfSpectatorsCanSeeHands; } @@ -328,7 +329,7 @@ public: /** * @brief Returns the number of games filtered out by the current filter. */ - int getNumFilteredGames() const; + [[nodiscard]] int getNumFilteredGames() const; /** * @brief Resets all filter parameters to default values. @@ -338,7 +339,7 @@ public: /** * @brief Returns true if all filter parameters are set to their defaults. */ - bool areFilterParametersSetToDefaults() const; + [[nodiscard]] bool areFilterParametersSetToDefaults() const; /** * @brief Loads filter parameters from persistent settings. @@ -358,8 +359,8 @@ public: void refresh(); protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; - bool filterAcceptsRow(int sourceRow) const; + [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + [[nodiscard]] bool filterAcceptsRow(int sourceRow) const; }; #endif diff --git a/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.h b/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.h index 0153f0414..6836ff435 100644 --- a/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.h +++ b/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.h @@ -34,11 +34,11 @@ public: { } virtual ~Node() = default; - DirectoryNode *getParent() const + [[nodiscard]] DirectoryNode *getParent() const { return parent; } - QString getName() const + [[nodiscard]] QString getName() const { return name; } @@ -49,9 +49,9 @@ public: explicit DirectoryNode(const QString &_name = QString(), DirectoryNode *_parent = nullptr); ~DirectoryNode() override; void clearTree(); - QString getPath() const; + [[nodiscard]] QString getPath() const; DirectoryNode *getNodeByPath(QStringList path); - FileNode *getNodeById(int id) const; + [[nodiscard]] FileNode *getNodeById(int id) const; }; class FileNode : public Node { @@ -64,17 +64,17 @@ public: : Node(_name, _parent), id(_id), uploadTime(_uploadTime) { } - int getId() const + [[nodiscard]] int getId() const { return id; } - QDateTime getUploadTime() const + [[nodiscard]] QDateTime getUploadTime() const { return uploadTime; } }; - template T getNode(const QModelIndex &index) const + template [[nodiscard]] T getNode(const QModelIndex &index) const { if (!index.isValid()) return dynamic_cast(root); @@ -96,15 +96,16 @@ private slots: public: explicit RemoteDeckList_TreeModel(AbstractClient *_client, QObject *parent = nullptr); ~RemoteDeckList_TreeModel() override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex &index) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; + [[nodiscard]] QVariant + headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + [[nodiscard]] QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] QModelIndex parent(const QModelIndex &index) const override; + [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override; - DirectoryNode *getRoot() const + [[nodiscard]] DirectoryNode *getRoot() const { return root; } @@ -124,11 +125,11 @@ private: public: explicit RemoteDeckList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr); - RemoteDeckList_TreeModel::Node *getNode(const QModelIndex &ind) const; - RemoteDeckList_TreeModel::Node *getCurrentItem() const; - QList getCurrentSelection() const; - RemoteDeckList_TreeModel::DirectoryNode *getNodeByPath(const QString &path) const; - RemoteDeckList_TreeModel::FileNode *getNodeById(int id) const; + [[nodiscard]] RemoteDeckList_TreeModel::Node *getNode(const QModelIndex &ind) const; + [[nodiscard]] RemoteDeckList_TreeModel::Node *getCurrentItem() const; + [[nodiscard]] QList getCurrentSelection() const; + [[nodiscard]] RemoteDeckList_TreeModel::DirectoryNode *getNodeByPath(const QString &path) const; + [[nodiscard]] RemoteDeckList_TreeModel::FileNode *getNodeById(int id) const; void addFileToTree(const ServerInfo_DeckStorage_TreeItem &file, RemoteDeckList_TreeModel::DirectoryNode *parent); void addFolderToTree(const ServerInfo_DeckStorage_TreeItem &folder, RemoteDeckList_TreeModel::DirectoryNode *parent); diff --git a/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.h b/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.h index d5d70af53..f83cce46a 100644 --- a/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.h +++ b/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.h @@ -35,7 +35,7 @@ private: { } virtual ~Node() = default; - QString getName() const + [[nodiscard]] QString getName() const { return name; } @@ -91,21 +91,22 @@ private slots: public: explicit RemoteReplayList_TreeModel(AbstractClient *_client, QObject *parent = nullptr); ~RemoteReplayList_TreeModel() override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override { return numberOfColumns; } - QVariant data(const QModelIndex &index, int role) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex &index) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; + [[nodiscard]] QVariant + headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + [[nodiscard]] QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] QModelIndex parent(const QModelIndex &index) const override; + [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override; void clearTree(); void refreshTree(); - ServerInfo_Replay const *getReplay(const QModelIndex &index) const; - ServerInfo_ReplayMatch const *getReplayMatch(const QModelIndex &index) const; - ServerInfo_ReplayMatch const *getEnclosingReplayMatch(const QModelIndex &index) const; + [[nodiscard]] ServerInfo_Replay const *getReplay(const QModelIndex &index) const; + [[nodiscard]] ServerInfo_ReplayMatch const *getReplayMatch(const QModelIndex &index) const; + [[nodiscard]] ServerInfo_ReplayMatch const *getEnclosingReplayMatch(const QModelIndex &index) const; void addMatchInfo(const ServerInfo_ReplayMatch &matchInfo); void updateMatchInfo(int gameId, const ServerInfo_ReplayMatch &matchInfo); void removeMatchInfo(int gameId); @@ -119,10 +120,10 @@ private: public: explicit RemoteReplayList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr); - ServerInfo_Replay const *getReplay(const QModelIndex &ind) const; - ServerInfo_ReplayMatch const *getReplayMatch(const QModelIndex &ind) const; - QList getSelectedReplays() const; - QSet getSelectedReplayMatches() const; + [[nodiscard]] ServerInfo_Replay const *getReplay(const QModelIndex &ind) const; + [[nodiscard]] ServerInfo_ReplayMatch const *getReplayMatch(const QModelIndex &ind) const; + [[nodiscard]] QList getSelectedReplays() const; + [[nodiscard]] QSet getSelectedReplayMatches() const; void clearTree() { treeModel->clearTree(); diff --git a/cockatrice/src/interface/widgets/server/user/user_info_connection.h b/cockatrice/src/interface/widgets/server/user/user_info_connection.h index 90be05570..cfba22346 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_connection.h +++ b/cockatrice/src/interface/widgets/server/user/user_info_connection.h @@ -30,31 +30,31 @@ private: public: UserConnection_Information(); UserConnection_Information(QString, QString, QString, QString, QString, bool, QString); - QString getSaveName() const + [[nodiscard]] QString getSaveName() const { return saveName; } - QString getServer() const + [[nodiscard]] QString getServer() const { return server; } - QString getPort() const + [[nodiscard]] QString getPort() const { return port; } - QString getUsername() const + [[nodiscard]] QString getUsername() const { return username; } - QString getPassword() const + [[nodiscard]] QString getPassword() const { return password; } - bool getSavePassword() const + [[nodiscard]] bool getSavePassword() const { return savePassword; } - QString getSite() const + [[nodiscard]] QString getSite() const { return site; } diff --git a/cockatrice/src/interface/widgets/server/user/user_list_manager.h b/cockatrice/src/interface/widgets/server/user/user_list_manager.h index 56f50c0be..f378bd08d 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_manager.h +++ b/cockatrice/src/interface/widgets/server/user/user_list_manager.h @@ -60,11 +60,11 @@ public: return ignoredUsers; } - bool isOwnUserRegistered() const override; - QString getOwnUsername() const override; - bool isUserBuddy(const QString &userName) const override; - bool isUserIgnored(const QString &userName) const override; - const ServerInfo_User *getOnlineUser(const QString &userName) const override; + [[nodiscard]] bool isOwnUserRegistered() const override; + [[nodiscard]] QString getOwnUsername() const override; + [[nodiscard]] bool isUserBuddy(const QString &userName) const override; + [[nodiscard]] bool isUserIgnored(const QString &userName) const override; + [[nodiscard]] const ServerInfo_User *getOnlineUser(const QString &userName) const override; public slots: void handleConnect(); diff --git a/cockatrice/src/interface/widgets/server/user/user_list_widget.h b/cockatrice/src/interface/widgets/server/user/user_list_widget.h index 897c01b83..036837673 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.h +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.h @@ -45,13 +45,13 @@ private slots: public: explicit BanDialog(const ServerInfo_User &info, QWidget *parent = nullptr); - QString getBanName() const; - QString getBanIP() const; - QString getBanId() const; - int getMinutes() const; - QString getReason() const; - QString getVisibleReason() const; - int getDeleteMessages() const; + [[nodiscard]] QString getBanName() const; + [[nodiscard]] QString getBanIP() const; + [[nodiscard]] QString getBanId() const; + [[nodiscard]] int getMinutes() const; + [[nodiscard]] QString getReason() const; + [[nodiscard]] QString getVisibleReason() const; + [[nodiscard]] int getDeleteMessages() const; }; class WarningDialog : public QDialog @@ -68,10 +68,10 @@ private slots: public: WarningDialog(const QString userName, const QString clientID, QWidget *parent = nullptr); - QString getName() const; - QString getWarnID() const; - QString getReason() const; - int getDeleteMessages() const; + [[nodiscard]] QString getName() const; + [[nodiscard]] QString getWarnID() const; + [[nodiscard]] QString getReason() const; + [[nodiscard]] int getDeleteMessages() const; void addWarningOption(const QString warning); }; @@ -85,11 +85,11 @@ private: public: explicit AdminNotesDialog(const QString &_userName, const QString &_notes, QWidget *_parent = nullptr); - QString getName() const + [[nodiscard]] QString getName() const { return userName; } - QString getNotes() const; + [[nodiscard]] QString getNotes() const; }; class UserListItemDelegate : public QStyledItemDelegate @@ -159,7 +159,7 @@ public: void processUserInfo(const ServerInfo_User &user, bool online); bool deleteUser(const QString &userName); void setUserOnline(const QString &userName, bool online); - const QMap &getUsers() const + [[nodiscard]] const QMap &getUsers() const { return users; } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h index d23c4f27d..df6c2eaeb 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h @@ -29,27 +29,27 @@ public: void debugPrint() const; // Getter methods for deck container - const QString &getDescription() const + [[nodiscard]] const QString &getDescription() const { return description; } - const QVector &getBreadcrumb() const + [[nodiscard]] const QVector &getBreadcrumb() const { return breadcrumb; } - const EdhrecCommanderApiResponseCommanderDetails &getCommanderDetails() const + [[nodiscard]] const EdhrecCommanderApiResponseCommanderDetails &getCommanderDetails() const { return card; } - const QVector &getCardlists() const + [[nodiscard]] const QVector &getCardlists() const { return cardlists; } - const QString &getKeywords() const + [[nodiscard]] const QString &getKeywords() const { return keywords; } - const QString &getTitle() const + [[nodiscard]] const QString &getTitle() const { return title; } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h index 64b5970da..09d0667c7 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h @@ -27,119 +27,119 @@ public: void debugPrint() const; // Getters for the card data - const QString &getAetherhubUri() const + [[nodiscard]] const QString &getAetherhubUri() const { return aetherhubUri; } - const QString &getArchidektUri() const + [[nodiscard]] const QString &getArchidektUri() const { return archidektUri; } - int getCmc() const + [[nodiscard]] int getCmc() const { return cmc; } - const QJsonArray &getColorIdentity() const + [[nodiscard]] const QJsonArray &getColorIdentity() const { return colorIdentity; } - bool isCombos() const + [[nodiscard]] bool isCombos() const { return combos; } - const QString &getDeckstatsUri() const + [[nodiscard]] const QString &getDeckstatsUri() const { return deckstatsUri; } - const QVector &getImageUris() const + [[nodiscard]] const QVector &getImageUris() const { return imageUris; } - int getInclusion() const + [[nodiscard]] int getInclusion() const { return inclusion; } - bool getIsCommander() const + [[nodiscard]] bool getIsCommander() const { return isCommander; } - const QString &getLabel() const + [[nodiscard]] const QString &getLabel() const { return label; } - const QString &getLayout() const + [[nodiscard]] const QString &getLayout() const { return layout; } - bool getLegalCommander() const + [[nodiscard]] bool getLegalCommander() const { return legalCommander; } - const QString &getMoxfieldUri() const + [[nodiscard]] const QString &getMoxfieldUri() const { return moxfieldUri; } - const QString &getMtggoldfishUri() const + [[nodiscard]] const QString &getMtggoldfishUri() const { return mtggoldfishUri; } - const QString &getName() const + [[nodiscard]] const QString &getName() const { return name; } - const QJsonArray &getNames() const + [[nodiscard]] const QJsonArray &getNames() const { return names; } - int getNumDecks() const + [[nodiscard]] int getNumDecks() const { return numDecks; } - int getPotentialDecks() const + [[nodiscard]] int getPotentialDecks() const { return potentialDecks; } - const QString &getPrecon() const + [[nodiscard]] const QString &getPrecon() const { return precon; } - const CardPrices &getPrices() const + [[nodiscard]] const CardPrices &getPrices() const { return prices; } - const QString &getPrimaryType() const + [[nodiscard]] const QString &getPrimaryType() const { return primaryType; } - const QString &getRarity() const + [[nodiscard]] const QString &getRarity() const { return rarity; } - double getSalt() const + [[nodiscard]] double getSalt() const { return salt; } - const QString &getSanitized() const + [[nodiscard]] const QString &getSanitized() const { return sanitized; } - const QString &getSanitizedWo() const + [[nodiscard]] const QString &getSanitizedWo() const { return sanitizedWo; } - const QString &getScryfallUri() const + [[nodiscard]] const QString &getScryfallUri() const { return scryfallUri; } - const QString &getSpellbookUri() const + [[nodiscard]] const QString &getSpellbookUri() const { return spellbookUri; } - const QString &getType() const + [[nodiscard]] const QString &getType() const { return type; } - const QString &getUrl() const + [[nodiscard]] const QString &getUrl() const { return url; } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.h index 2cca18504..3d4c2fa00 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.h @@ -27,13 +27,13 @@ public: void retranslateUi() override; void doSearch(); - QString getTabText() const override + [[nodiscard]] QString getTabText() const override { auto cardName = cardToQuery.isNull() ? QString() : cardToQuery->getName(); return tr("EDHRec: ") + cardName; } - CardSizeWidget *getCardSizeSlider() + CardSizeWidget *getCardSizeSlider() const { return cardSizeSlider; } diff --git a/cockatrice/src/interface/widgets/tabs/tab.h b/cockatrice/src/interface/widgets/tabs/tab.h index 56a9f0b7e..7cff01130 100644 --- a/cockatrice/src/interface/widgets/tabs/tab.h +++ b/cockatrice/src/interface/widgets/tabs/tab.h @@ -39,15 +39,15 @@ private: public: explicit Tab(TabSupervisor *_tabSupervisor); - const QList &getTabMenus() const + [[nodiscard]] const QList &getTabMenus() const { return tabMenus; } - TabSupervisor *getTabSupervisor() const + [[nodiscard]] TabSupervisor *getTabSupervisor() const { return tabSupervisor; } - bool getContentsChanged() const + [[nodiscard]] bool getContentsChanged() const { return contentsChanged; } @@ -55,7 +55,7 @@ public: { contentsChanged = _contentsChanged; } - virtual QString getTabText() const = 0; + [[nodiscard]] virtual QString getTabText() const = 0; virtual void retranslateUi() = 0; /** diff --git a/cockatrice/src/interface/widgets/tabs/tab_home.h b/cockatrice/src/interface/widgets/tabs/tab_home.h index 3738b73fd..fbadc99aa 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_home.h +++ b/cockatrice/src/interface/widgets/tabs/tab_home.h @@ -26,7 +26,7 @@ private: public: TabHome(TabSupervisor *_tabSupervisor, AbstractClient *_client); void retranslateUi() override; - QString getTabText() const override + [[nodiscard]] QString getTabText() const override { return tr("Home"); } diff --git a/cockatrice/src/interface/widgets/tabs/tab_message.h b/cockatrice/src/interface/widgets/tabs/tab_message.h index 098089e46..36a0b5f78 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_message.h +++ b/cockatrice/src/interface/widgets/tabs/tab_message.h @@ -54,8 +54,8 @@ public: ~TabMessage() override; void retranslateUi() override; void tabActivated() override; - QString getUserName() const; - QString getTabText() const override; + [[nodiscard]] QString getUserName() const; + [[nodiscard]] QString getTabText() const override; void processUserMessageEvent(const Event_UserMessage &event); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h index 9b828fa3d..a4ea3633b 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h @@ -30,12 +30,12 @@ public: * @param tagName The name of the tag to display. */ explicit DeckPreviewTagDisplayWidget(QWidget *parent = nullptr, const QString &tagName = ""); - QSize sizeHint() const override; - QString getTagName() const + [[nodiscard]] QSize sizeHint() const override; + [[nodiscard]] QString getTagName() const { return tagName; } - TagState getState() const + [[nodiscard]] TagState getState() const { return state; } diff --git a/libcockatrice_card/libcockatrice/card/card_info.h b/libcockatrice_card/libcockatrice/card/card_info.h index ab1d51a49..3ff78f38c 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.h +++ b/libcockatrice_card/libcockatrice/card/card_info.h @@ -159,7 +159,7 @@ public: * * @return Shared pointer to the cloned CardInfo. */ - CardInfoPtr clone() const + [[nodiscard]] CardInfoPtr clone() const { auto newCardInfo = CardInfoPtr(new CardInfo(*this)); newCardInfo->setSmartPointer(newCardInfo); // Set the smart pointer for the new instance @@ -179,11 +179,11 @@ public: } /** @name Basic Properties Accessors */ //@{ - inline const QString &getName() const + [[nodiscard]] inline const QString &getName() const { return name; } - const QString &getSimpleName() const + [[nodiscard]] const QString &getSimpleName() const { return simpleName; } @@ -191,7 +191,7 @@ public: { return altNames; }; - const QString &getText() const + [[nodiscard]] const QString &getText() const { return text; } @@ -200,15 +200,15 @@ public: text = _text; emit cardInfoChanged(smartThis); } - bool getIsToken() const + [[nodiscard]] bool getIsToken() const { return isToken; } - QStringList getProperties() const + [[nodiscard]] QStringList getProperties() const { return properties.keys(); } - QString getProperty(const QString &propertyName) const + [[nodiscard]] QString getProperty(const QString &propertyName) const { return properties.value(propertyName).toString(); } @@ -217,34 +217,34 @@ public: properties.insert(_name, _value); emit cardInfoChanged(smartThis); } - bool hasProperty(const QString &propertyName) const + [[nodiscard]] bool hasProperty(const QString &propertyName) const { return properties.contains(propertyName); } - const SetToPrintingsMap &getSets() const + [[nodiscard]] const SetToPrintingsMap &getSets() const { return setsToPrintings; } - const QString &getSetsNames() const + [[nodiscard]] const QString &getSetsNames() const { return setsNames; } //@} /** @name Related Cards Accessors */ //@{ - const QList &getRelatedCards() const + [[nodiscard]] const QList &getRelatedCards() const { return relatedCards; } - const QList &getReverseRelatedCards() const + [[nodiscard]] const QList &getReverseRelatedCards() const { return reverseRelatedCards; } - const QList &getReverseRelatedCards2Me() const + [[nodiscard]] const QList &getReverseRelatedCards2Me() const { return reverseRelatedCardsToMe; } - QList getAllRelatedCards() const + [[nodiscard]] QList getAllRelatedCards() const { QList result; result.append(getRelatedCards()); @@ -259,24 +259,24 @@ public: //@} /** @name UI Positioning */ //@{ - const UiAttributes &getUiAttributes() const + [[nodiscard]] const UiAttributes &getUiAttributes() const { return uiAttributes; } //@} - const QChar getColorChar() const; + [[nodiscard]] const QChar getColorChar() const; /** @name Legacy/Convenience Property Accessors */ //@{ - const QString getCardType() const; + [[nodiscard]] const QString getCardType() const; void setCardType(const QString &value); - const QString getCmc() const; - const QString getColors() const; + [[nodiscard]] const QString getCmc() const; + [[nodiscard]] const QString getColors() const; void setColors(const QString &value); - const QString getLoyalty() const; - const QString getMainCardType() const; - const QString getManaCost() const; - const QString getPowTough() const; + [[nodiscard]] const QString getLoyalty() const; + [[nodiscard]] const QString getMainCardType() const; + [[nodiscard]] const QString getManaCost() const; + [[nodiscard]] const QString getPowTough() const; void setPowTough(const QString &value); //@} @@ -287,7 +287,7 @@ public: * * @return Corrected card name as a QString. */ - QString getCorrectedName() const; + [[nodiscard]] QString getCorrectedName() const; /** * @brief Adds a printing to a specific set. diff --git a/libcockatrice_card/libcockatrice/card/card_info_comparator.h b/libcockatrice_card/libcockatrice/card/card_info_comparator.h index bafc303a5..2b101701c 100644 --- a/libcockatrice_card/libcockatrice/card/card_info_comparator.h +++ b/libcockatrice_card/libcockatrice/card/card_info_comparator.h @@ -23,8 +23,8 @@ private: QStringList m_properties; // List of properties to sort by Qt::SortOrder m_order; - QVariant getProperty(const CardInfoPtr &card, const QString &property) const; - bool compareVariants(const QVariant &a, const QVariant &b) const; + [[nodiscard]] QVariant getProperty(const CardInfoPtr &card, const QString &property) const; + [[nodiscard]] bool compareVariants(const QVariant &a, const QVariant &b) const; }; #endif // CARD_INFO_COMPARATOR_H diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_loader.h b/libcockatrice_card/libcockatrice/card/database/card_database_loader.h index db1376ac7..631ba685f 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_loader.h +++ b/libcockatrice_card/libcockatrice/card/database/card_database_loader.h @@ -110,7 +110,7 @@ private: * @brief Collects custom card database paths recursively. * @return Sorted list of file paths to custom databases. */ - QStringList collectCustomDatabasePaths() const; + [[nodiscard]] QStringList collectCustomDatabasePaths() const; private: CardDatabase *database; /**< Non-owning pointer to the target CardDatabase. */ diff --git a/libcockatrice_card/libcockatrice/card/printing/printing_info.h b/libcockatrice_card/libcockatrice/card/printing/printing_info.h index 4329ca2f4..e0978bd55 100644 --- a/libcockatrice_card/libcockatrice/card/printing/printing_info.h +++ b/libcockatrice_card/libcockatrice/card/printing/printing_info.h @@ -65,7 +65,7 @@ public: * * @return Pointer to the associated CardSet. */ - CardSetPtr getSet() const + [[nodiscard]] CardSetPtr getSet() const { return set; } @@ -75,7 +75,7 @@ public: * * @return List of keys stored in the properties map. */ - QStringList getProperties() const + [[nodiscard]] QStringList getProperties() const { return properties.keys(); } @@ -86,7 +86,7 @@ public: * @param propertyName The key name of the property to query. * @return The property value as a string, or an empty string if not set. */ - QString getProperty(const QString &propertyName) const + [[nodiscard]] QString getProperty(const QString &propertyName) const { return properties.value(propertyName).toString(); } @@ -109,14 +109,14 @@ public: * * @return A string representing the providerID. */ - QString getUuid() const; + [[nodiscard]] QString getUuid() const; /** * @brief Returns the flavorName for this printing. * * @return The flavorName, or empty if it isn't present. */ - QString getFlavorName() const; + [[nodiscard]] QString getFlavorName() const; }; #endif // COCKATRICE_PRINTING_INFO_H diff --git a/libcockatrice_card/libcockatrice/card/set/card_set.h b/libcockatrice_card/libcockatrice/card/set/card_set.h index 5fc769bb6..fe4b66522 100644 --- a/libcockatrice_card/libcockatrice/card/set/card_set.h +++ b/libcockatrice_card/libcockatrice/card/set/card_set.h @@ -105,34 +105,34 @@ public: * * @return Sanitized short name. */ - QString getCorrectedShortName() const; + [[nodiscard]] QString getCorrectedShortName() const; /// @return Short identifier of the set. - QString getShortName() const + [[nodiscard]] QString getShortName() const { return shortName; } /// @return Descriptive name of the set. - QString getLongName() const + [[nodiscard]] QString getLongName() const { return longName; } /// @return Type/category string of the set. - QString getSetType() const + [[nodiscard]] QString getSetType() const { return setType; } /// @return Release date of the set. - QDate getReleaseDate() const + [[nodiscard]] QDate getReleaseDate() const { return releaseDate; } /// @return Priority level of the set. - Priority getPriority() const + [[nodiscard]] Priority getPriority() const { return priority; } @@ -182,7 +182,7 @@ public: void loadSetOptions(); /// @return The sort key assigned to this set. - int getSortKey() const + [[nodiscard]] int getSortKey() const { return sortKey; } @@ -194,7 +194,7 @@ public: void setSortKey(unsigned int _sortKey); /// @return True if the set is enabled. - bool getEnabled() const + [[nodiscard]] bool getEnabled() const { return enabled; } @@ -206,7 +206,7 @@ public: void setEnabled(bool _enabled); /// @return True if the set is considered known. - bool getIsKnown() const + [[nodiscard]] bool getIsKnown() const { return isknown; } @@ -222,7 +222,7 @@ public: * * @return True if the long name, type, and release date are all empty. */ - bool getIsKnownIgnored() const + [[nodiscard]] bool getIsKnownIgnored() const { return longName.length() + setType.length() + releaseDate.toString().length() == 0; } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_card_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_card_node.h index 6d61ed970..c2fe038e6 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_card_node.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_card_node.h @@ -91,7 +91,7 @@ public: explicit DecklistCardNode(DecklistCardNode *other, InnerDecklistNode *_parent); /// @return The quantity of this card. - int getNumber() const override + [[nodiscard]] int getNumber() const override { return number; } @@ -103,7 +103,7 @@ public: } /// @return The display name of this card. - QString getName() const override + [[nodiscard]] QString getName() const override { return name; } @@ -115,7 +115,7 @@ public: } /// @return The provider identifier for this card. - QString getCardProviderId() const override + [[nodiscard]] QString getCardProviderId() const override { return cardProviderId; } @@ -127,7 +127,7 @@ public: } /// @return The short set code (e.g., "NEO"). - QString getCardSetShortName() const override + [[nodiscard]] QString getCardSetShortName() const override { return cardSetShortName; } @@ -139,7 +139,7 @@ public: } /// @return The collector number of this card within its set. - QString getCardCollectorNumber() const override + [[nodiscard]] QString getCardCollectorNumber() const override { return cardSetNumber; } @@ -162,7 +162,7 @@ public: * @return A CardRef with the card’s name and provider ID, suitable * for database lookups or comparison with other card sources. */ - CardRef toCardRef() const + [[nodiscard]] CardRef toCardRef() const { return {name, cardProviderId}; } 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 f30c0affe..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 @@ -23,12 +23,12 @@ public: void clear(); - bool canUndo() const + [[nodiscard]] bool canUndo() const { return !undoStack.isEmpty(); } - bool canRedo() const + [[nodiscard]] bool canRedo() const { return !redoStack.isEmpty(); } @@ -37,11 +37,11 @@ public: void redo(DeckList *deck); - QStack getRedoStack() const + [[nodiscard]] QStack getRedoStack() const { return redoStack; } - QStack getUndoStack() const + [[nodiscard]] QStack getUndoStack() const { return undoStack; } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_memento.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_memento.h index d2e36b804..6ddf430c5 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_memento.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_memento.h @@ -11,11 +11,11 @@ public: { } - QString getMemento() const + [[nodiscard]] QString getMemento() const { return memento; } - QString getReason() const + [[nodiscard]] QString getReason() const { return reason; } diff --git a/libcockatrice_filters/libcockatrice/filters/filter_card.h b/libcockatrice_filters/libcockatrice/filters/filter_card.h index 58dbf954b..344ae5224 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_card.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_card.h @@ -56,20 +56,20 @@ public: { } - Type type() const + [[nodiscard]] Type type() const { return t; } - const QString &term() const + [[nodiscard]] const QString &term() const { return trm; } - Attr attr() const + [[nodiscard]] Attr attr() const { return a; } - QJsonObject toJson() const; + [[nodiscard]] QJsonObject toJson() const; static CardFilter *fromJson(const QJsonObject &json); static const QString typeName(Type t); static const QString attrName(Attr a); diff --git a/libcockatrice_filters/libcockatrice/filters/filter_string.h b/libcockatrice_filters/libcockatrice/filters/filter_string.h index 4e6cb38bf..8fc41ce68 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_string.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_string.h @@ -35,7 +35,7 @@ class FilterString public: FilterString(); explicit FilterString(const QString &exp); - bool check(const CardData &card) const + [[nodiscard]] bool check(const CardData &card) const { if (card.isNull()) { static CardInfoPtr blankCard = CardInfo::newInstance(""); diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.h b/libcockatrice_filters/libcockatrice/filters/filter_tree.h index 350fd2f14..7e0f211ef 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.h @@ -24,7 +24,7 @@ public: FilterTreeNode() : enabled(true) { } - virtual bool isEnabled() const + [[nodiscard]] virtual bool isEnabled() const { return enabled; } @@ -38,18 +38,18 @@ public: enabled = false; nodeChanged(); } - virtual FilterTreeNode *parent() const + [[nodiscard]] virtual FilterTreeNode *parent() const { return nullptr; } - virtual FilterTreeNode *nodeAt(int /* i */) const + [[nodiscard]] virtual FilterTreeNode *nodeAt(int /* i */) const { return nullptr; } virtual void deleteAt(int /* i */) { } - virtual int childCount() const + [[nodiscard]] virtual int childCount() const { return 0; } @@ -57,15 +57,15 @@ public: { return -1; } - virtual int index() const + [[nodiscard]] virtual int index() const { return (parent() != nullptr) ? parent()->childIndex(this) : -1; } - virtual const QString text() const + [[nodiscard]] virtual const QString text() const { return QString(""); } - virtual bool isLeaf() const + [[nodiscard]] virtual bool isLeaf() const { return false; } @@ -104,9 +104,9 @@ protected: public: virtual ~FilterTreeBranch(); void removeFiltersByAttr(CardFilter::Attr filterType); - FilterTreeNode *nodeAt(int i) const override; + [[nodiscard]] FilterTreeNode *nodeAt(int i) const override; void deleteAt(int i) override; - int childCount() const override + [[nodiscard]] int childCount() const override { return childNodes.size(); } @@ -127,10 +127,10 @@ public: LogicMap(CardFilter::Attr a, FilterTree *parent) : p(parent), attr(a) { } - const FilterItemList *findTypeList(CardFilter::Type type) const; + [[nodiscard]] const FilterItemList *findTypeList(CardFilter::Type type) const; FilterItemList *typeList(CardFilter::Type type); - FilterTreeNode *parent() const override; - const QString text() const override + [[nodiscard]] FilterTreeNode *parent() const override; + [[nodiscard]] const QString text() const override { return CardFilter::attrName(attr); } @@ -148,25 +148,25 @@ public: FilterItemList(CardFilter::Type t, LogicMap *parent) : p(parent), type(t) { } - CardFilter::Attr attr() const + [[nodiscard]] CardFilter::Attr attr() const { return p->attr; } - FilterTreeNode *parent() const override + [[nodiscard]] FilterTreeNode *parent() const override { return p; } - int termIndex(const QString &term) const; + [[nodiscard]] int termIndex(const QString &term) const; FilterTreeNode *termNode(const QString &term); - const QString text() const override + [[nodiscard]] const QString text() const override { return CardFilter::typeName(type); } - bool testTypeAnd(CardInfoPtr info, CardFilter::Attr attr) const; - bool testTypeAndNot(CardInfoPtr info, CardFilter::Attr attr) const; - bool testTypeOr(CardInfoPtr info, CardFilter::Attr attr) const; - bool testTypeOrNot(CardInfoPtr info, CardFilter::Attr attr) const; + [[nodiscard]] bool testTypeAnd(CardInfoPtr info, CardFilter::Attr attr) const; + [[nodiscard]] bool testTypeAndNot(CardInfoPtr info, CardFilter::Attr attr) const; + [[nodiscard]] bool testTypeOr(CardInfoPtr info, CardFilter::Attr attr) const; + [[nodiscard]] bool testTypeOrNot(CardInfoPtr info, CardFilter::Attr attr) const; }; class FilterItem : public FilterTreeNode @@ -182,42 +182,42 @@ public: } virtual ~FilterItem() = default; - CardFilter::Attr attr() const + [[nodiscard]] CardFilter::Attr attr() const { return p->attr(); } - CardFilter::Type type() const + [[nodiscard]] CardFilter::Type type() const { return p->type; } - FilterTreeNode *parent() const override + [[nodiscard]] FilterTreeNode *parent() const override { return p; } - const QString text() const override + [[nodiscard]] const QString text() const override { return term; } - bool isLeaf() const override + [[nodiscard]] bool isLeaf() const override { return true; } - bool acceptName(CardInfoPtr info) const; - bool acceptType(CardInfoPtr info) const; - bool acceptMainType(CardInfoPtr info) const; - bool acceptSubType(CardInfoPtr info) const; - bool acceptColor(CardInfoPtr info) const; - bool acceptText(CardInfoPtr info) const; - bool acceptSet(CardInfoPtr info) const; - bool acceptManaCost(CardInfoPtr info) const; - bool acceptCmc(CardInfoPtr info) const; - bool acceptPowerToughness(CardInfoPtr info, CardFilter::Attr attr) const; - bool acceptLoyalty(CardInfoPtr info) const; - bool acceptRarity(CardInfoPtr info) const; - bool acceptCardAttr(CardInfoPtr info, CardFilter::Attr attr) const; - bool acceptFormat(CardInfoPtr info) const; - bool relationCheck(int cardInfo) const; + [[nodiscard]] bool acceptName(CardInfoPtr info) const; + [[nodiscard]] bool acceptType(CardInfoPtr info) const; + [[nodiscard]] bool acceptMainType(CardInfoPtr info) const; + [[nodiscard]] bool acceptSubType(CardInfoPtr info) const; + [[nodiscard]] bool acceptColor(CardInfoPtr info) const; + [[nodiscard]] bool acceptText(CardInfoPtr info) const; + [[nodiscard]] bool acceptSet(CardInfoPtr info) const; + [[nodiscard]] bool acceptManaCost(CardInfoPtr info) const; + [[nodiscard]] bool acceptCmc(CardInfoPtr info) const; + [[nodiscard]] bool acceptPowerToughness(CardInfoPtr info, CardFilter::Attr attr) const; + [[nodiscard]] bool acceptLoyalty(CardInfoPtr info) const; + [[nodiscard]] bool acceptRarity(CardInfoPtr info) const; + [[nodiscard]] bool acceptCardAttr(CardInfoPtr info, CardFilter::Attr attr) const; + [[nodiscard]] bool acceptFormat(CardInfoPtr info) const; + [[nodiscard]] bool relationCheck(int cardInfo) const; }; class FilterTree : public QObject, public FilterTreeBranch @@ -265,16 +265,16 @@ public: FilterTreeNode *termNode(CardFilter::Attr attr, CardFilter::Type type, const QString &term); FilterTreeNode *termNode(const CardFilter *f); - const QString text() const override + [[nodiscard]] const QString text() const override { return QString("root"); } - int index() const override + [[nodiscard]] int index() const override { return 0; } - bool acceptsCard(CardInfoPtr info) const; + [[nodiscard]] bool acceptsCard(CardInfoPtr info) const; void removeFiltersByAttr(CardFilter::Attr filterType); void removeFilter(const CardFilter *toRemove); void clear(); diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_database_path_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_database_path_provider.h index f5e54a6f8..dd55ab789 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_database_path_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_database_path_provider.h @@ -9,10 +9,10 @@ class ICardDatabasePathProvider : public QObject public: virtual ~ICardDatabasePathProvider() = default; - virtual QString getCardDatabasePath() const = 0; - virtual QString getCustomCardDatabasePath() const = 0; - virtual QString getTokenDatabasePath() const = 0; - virtual QString getSpoilerCardDatabasePath() const = 0; + [[nodiscard]] virtual QString getCardDatabasePath() const = 0; + [[nodiscard]] virtual QString getCustomCardDatabasePath() const = 0; + [[nodiscard]] virtual QString getTokenDatabasePath() const = 0; + [[nodiscard]] virtual QString getSpoilerCardDatabasePath() const = 0; signals: void cardDatabasePathChanged(); diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_preference_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_preference_provider.h index 7c0944f29..a6cf941fb 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_preference_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_preference_provider.h @@ -7,8 +7,8 @@ class ICardPreferenceProvider { public: virtual ~ICardPreferenceProvider() = default; - virtual QString getCardPreferenceOverride(const QString &cardName) const = 0; - virtual bool getIncludeRebalancedCards() const = 0; + [[nodiscard]] virtual QString getCardPreferenceOverride(const QString &cardName) const = 0; + [[nodiscard]] virtual bool getIncludeRebalancedCards() const = 0; }; #endif // COCKATRICE_INTERFACE_CARD_PREFERENCE_PROVIDER_H diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_database_path_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_database_path_provider.h index fb3ab0cc3..bacd7a2bb 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_database_path_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_database_path_provider.h @@ -5,19 +5,19 @@ class NoopCardDatabasePathProvider : public ICardDatabasePathProvider { public: - QString getCardDatabasePath() const override + [[nodiscard]] QString getCardDatabasePath() const override { return ""; } - QString getCustomCardDatabasePath() const override + [[nodiscard]] QString getCustomCardDatabasePath() const override { return ""; } - QString getTokenDatabasePath() const override + [[nodiscard]] QString getTokenDatabasePath() const override { return ""; } - QString getSpoilerCardDatabasePath() const override + [[nodiscard]] QString getSpoilerCardDatabasePath() const override { return ""; } diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_preference_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_preference_provider.h index 9fc7e5bb4..3781f4b63 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_preference_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_preference_provider.h @@ -5,12 +5,12 @@ class NoopCardPreferenceProvider : public ICardPreferenceProvider { public: - QString getCardPreferenceOverride(const QString &) const override + [[nodiscard]] QString getCardPreferenceOverride(const QString &) const override { return {}; } - bool getIncludeRebalancedCards() const override + [[nodiscard]] bool getIncludeRebalancedCards() const override { return true; } diff --git a/libcockatrice_models/libcockatrice/models/database/card/card_completer_proxy_model.h b/libcockatrice_models/libcockatrice/models/database/card/card_completer_proxy_model.h index 7a4626059..afb6f1fcf 100644 --- a/libcockatrice_models/libcockatrice/models/database/card/card_completer_proxy_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card/card_completer_proxy_model.h @@ -16,7 +16,7 @@ public: explicit CardCompleterProxyModel(QObject *parent = nullptr); protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; }; #endif // CARD_COMPLETER_PROXY_MODEL_H diff --git a/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h b/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h index 98d66cb5e..18be2c55a 100644 --- a/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h @@ -17,8 +17,8 @@ class CardSearchModel : public QAbstractListModel public: explicit CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent = nullptr); - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; void updateSearchResults(const QString &query); // Update results based on input diff --git a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h index 467b7f0a5..e7abb2642 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h @@ -77,17 +77,17 @@ public: dirtyTimer.start(20); } void clearFilterAll(); - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - bool canFetchMore(const QModelIndex &parent) const override; + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] bool canFetchMore(const QModelIndex &parent) const override; void fetchMore(const QModelIndex &parent) override; signals: void modelDirty(); protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + [[nodiscard]] bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; static int lessThanNumerically(const QString &left, const QString &right); - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; - bool rowMatchesCardName(CardInfoPtr info) const; + [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + [[nodiscard]] bool rowMatchesCardName(CardInfoPtr info) const; private slots: void filterTreeChanged(); diff --git a/libcockatrice_models/libcockatrice/models/database/card_database_model.h b/libcockatrice_models/libcockatrice/models/database/card_database_model.h index 7c48c4e6d..218cfff92 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_database_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card_database_model.h @@ -31,15 +31,16 @@ public: }; CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent = nullptr); ~CardDatabaseModel() override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - CardDatabase *getDatabase() const + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] int columnCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; + [[nodiscard]] QVariant + headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + [[nodiscard]] CardDatabase *getDatabase() const { return db; } - CardInfoPtr getCard(int index) const + [[nodiscard]] CardInfoPtr getCard(int index) const { return cardList[index]; } diff --git a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.h b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.h index 6f5911b6e..0ffc5a847 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.h @@ -25,11 +25,11 @@ public: SetsMimeData(int _oldRow) : oldRow(_oldRow) { } - int getOldRow() const + [[nodiscard]] int getOldRow() const { return oldRow; } - QStringList formats() const + [[nodiscard]] QStringList formats() const { return QStringList() << "application/x-cockatricecardset"; } @@ -64,22 +64,23 @@ public: explicit SetsModel(CardDatabase *_db, QObject *parent = nullptr); ~SetsModel() override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] int columnCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent); return NUM_COLS; } - QVariant data(const QModelIndex &index, int role) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - Qt::DropActions supportedDropActions() const override; + [[nodiscard]] QVariant + headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override; + [[nodiscard]] Qt::DropActions supportedDropActions() const override; - QMimeData *mimeData(const QModelIndexList &indexes) const override; + [[nodiscard]] QMimeData *mimeData(const QModelIndexList &indexes) const override; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; - QStringList mimeTypes() const override; + [[nodiscard]] QStringList mimeTypes() const override; void swapRows(int oldRow, int newRow); void toggleRow(int row, bool enable); void toggleRow(int row); @@ -97,8 +98,8 @@ public: explicit SetsDisplayModel(QObject *parent = nullptr); protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + [[nodiscard]] bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; void fetchMore(const QModelIndex &index) override; }; diff --git a/libcockatrice_models/libcockatrice/models/database/token/token_display_model.h b/libcockatrice_models/libcockatrice/models/database/token/token_display_model.h index 5d3b63a36..f6b2fdfbb 100644 --- a/libcockatrice_models/libcockatrice/models/database/token/token_display_model.h +++ b/libcockatrice_models/libcockatrice/models/database/token/token_display_model.h @@ -14,10 +14,10 @@ class TokenDisplayModel : public CardDatabaseDisplayModel Q_OBJECT public: explicit TokenDisplayModel(QObject *parent = nullptr); - int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; }; #endif // COCKATRICE_TOKEN_DISPLAY_MODEL_H diff --git a/libcockatrice_models/libcockatrice/models/database/token/token_edit_model.h b/libcockatrice_models/libcockatrice/models/database/token/token_edit_model.h index 50c7eb642..5e5843761 100644 --- a/libcockatrice_models/libcockatrice/models/database/token/token_edit_model.h +++ b/libcockatrice_models/libcockatrice/models/database/token/token_edit_model.h @@ -14,10 +14,10 @@ class TokenEditModel : public CardDatabaseDisplayModel Q_OBJECT public: explicit TokenEditModel(QObject *parent = nullptr); - int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; }; #endif // COCKATRICE_TOKEN_EDIT_MODEL_H 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 8beabd4ba..08a4592ae 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -101,7 +101,7 @@ public: : AbstractDecklistCardNode(_parent, position), dataNode(_dataNode) { } - int getNumber() const override + [[nodiscard]] int getNumber() const override { return dataNode->getNumber(); } @@ -109,7 +109,7 @@ public: { dataNode->setNumber(_number); } - QString getName() const override + [[nodiscard]] QString getName() const override { return dataNode->getName(); } @@ -117,7 +117,7 @@ public: { dataNode->setName(_name); } - QString getCardProviderId() const override + [[nodiscard]] QString getCardProviderId() const override { return dataNode->getCardProviderId(); } @@ -125,7 +125,7 @@ public: { dataNode->setCardProviderId(_cardProviderId); } - QString getCardSetShortName() const override + [[nodiscard]] QString getCardSetShortName() const override { return dataNode->getCardSetShortName(); } @@ -133,7 +133,7 @@ public: { dataNode->setCardSetShortName(_cardSetShortName); } - QString getCardCollectorNumber() const override + [[nodiscard]] QString getCardCollectorNumber() const override { return dataNode->getCardCollectorNumber(); } @@ -146,7 +146,7 @@ public: * @brief Returns the underlying data node. * @return Pointer to the DecklistCardNode wrapped by this node. */ - DecklistCardNode *getDataNode() const + [[nodiscard]] DecklistCardNode *getDataNode() const { return dataNode; } @@ -200,7 +200,7 @@ public: * @brief Returns the root index of the model. * @return QModelIndex representing the root node. */ - QModelIndex getRoot() const + [[nodiscard]] QModelIndex getRoot() const { return nodeToIndex(root); }; @@ -210,17 +210,17 @@ public: * @param info Pointer to card information. * @return String representing the value of the current grouping criteria for the card. */ - QString getGroupCriteriaForCard(CardInfoPtr info) const; + [[nodiscard]] QString getGroupCriteriaForCard(CardInfoPtr info) const; // Qt model overrides - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; + [[nodiscard]] int rowCount(const QModelIndex &parent) const override; + [[nodiscard]] int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; void emitBackgroundUpdates(const QModelIndex &parent); - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - QModelIndex index(int row, int column, const QModelIndex &parent) const override; - QModelIndex parent(const QModelIndex &index) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; + [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + [[nodiscard]] QModelIndex index(int row, int column, const QModelIndex &parent) const override; + [[nodiscard]] QModelIndex parent(const QModelIndex &index) const override; + [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; bool removeRows(int row, int count, const QModelIndex &parent) override; @@ -232,8 +232,8 @@ public: * @param cardNumber Optional collector number. * @return QModelIndex of the card, or invalid index if not found. */ - QModelIndex findCard(const QString &cardName, - const QString &zoneName, + [[nodiscard]] QModelIndex findCard(const QString &cardName, + const QString &zoneName, const QString &providerId = "", const QString &cardNumber = "") const; @@ -269,15 +269,15 @@ public: * @brief Removes all cards and resets the model. */ void cleanList(); - DeckList *getDeckList() const + [[nodiscard]] DeckList *getDeckList() const { return deckList; } void setDeckList(DeckList *_deck); - QList getCards() const; - QList getCardsForZone(const QString &zoneName) const; - QList *getZones() const; + [[nodiscard]] QList getCards() const; + [[nodiscard]] QList getCardsForZone(const QString &zoneName) const; + [[nodiscard]] QList *getZones() const; /** * @brief Sets the criteria used to group cards in the model. @@ -294,8 +294,8 @@ private: InnerDecklistNode *createNodeIfNeeded(const QString &name, InnerDecklistNode *parent); QModelIndex nodeToIndex(AbstractDecklistNode *node) const; - DecklistModelCardNode *findCardNode(const QString &cardName, - const QString &zoneName, + [[nodiscard]] DecklistModelCardNode *findCardNode(const QString &cardName, + const QString &zoneName, const QString &providerId = "", const QString &cardNumber = "") const; void emitRecursiveUpdates(const QModelIndex &index); diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.h b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.h index 0603beace..94742795d 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.h @@ -25,7 +25,7 @@ public: } protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + [[nodiscard]] bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; private: QStringList sortCriteria; diff --git a/libcockatrice_rng/libcockatrice/rng/rng_abstract.h b/libcockatrice_rng/libcockatrice/rng/rng_abstract.h index ffd45b9d7..903e6ef1a 100644 --- a/libcockatrice_rng/libcockatrice/rng/rng_abstract.h +++ b/libcockatrice_rng/libcockatrice/rng/rng_abstract.h @@ -13,7 +13,7 @@ public: } virtual unsigned int rand(int min, int max) = 0; QVector makeNumbersVector(int n, int min, int max); - double testRandom(const QVector &numbers) const; + [[nodiscard]] double testRandom(const QVector &numbers) const; }; extern RNG_Abstract *rng; diff --git a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h index d62fea03d..45e9b7441 100644 --- a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h @@ -57,7 +57,7 @@ private: explicit GameFiltersSettings(const QString &settingPath, QObject *parent = nullptr); GameFiltersSettings(const GameFiltersSettings & /*other*/); - QString hashGameType(const QString &gameType) const; + [[nodiscard]] QString hashGameType(const QString &gameType) const; }; #endif // GAMEFILTERSSETTINGS_H From 858361e6d3acc968a31318ef0c87bd94014b9703 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 28 Nov 2025 14:41:11 -0800 Subject: [PATCH 030/325] [DeckLoader] Refactor last load info into struct (#6366) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [DeckLoader] Refactor last load info into struct * Use constant * [[nodiscard]] * do discard, I guess. --------- Co-authored-by: Brübach, Lukas --- .../src/interface/deck_loader/deck_loader.cpp | 42 +++++++++++-------- .../src/interface/deck_loader/deck_loader.h | 38 ++++++++++------- .../widgets/tabs/abstract_tab_deck_editor.cpp | 12 +++--- .../deck_preview_deck_tags_display_widget.cpp | 4 +- .../deck_preview/deck_preview_widget.cpp | 8 ++-- 5 files changed, 60 insertions(+), 44 deletions(-) diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index 320302142..372aa0aa8 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -25,13 +25,11 @@ const QStringList DeckLoader::ACCEPTED_FILE_EXTENSIONS = {"*.cod", "*.dec", "*.d const QStringList DeckLoader::FILE_NAME_FILTERS = { tr("Common deck formats (%1)").arg(ACCEPTED_FILE_EXTENSIONS.join(" ")), tr("All files (*.*)")}; -DeckLoader::DeckLoader(QObject *parent) - : QObject(parent), deckList(new DeckList()), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1) +DeckLoader::DeckLoader(QObject *parent) : QObject(parent), deckList(new DeckList()) { } -DeckLoader::DeckLoader(QObject *parent, DeckList *_deckList) - : QObject(parent), deckList(_deckList), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1) +DeckLoader::DeckLoader(QObject *parent, DeckList *_deckList) : QObject(parent), deckList(_deckList) { } @@ -64,8 +62,10 @@ bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool user } if (result) { - lastFileName = fileName; - lastFileFormat = fmt; + lastLoadInfo = { + .fileName = fileName, + .fileFormat = fmt, + }; if (userRequest) { updateLastLoadedTimestamp(fileName, fmt); } @@ -86,8 +86,10 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, FileFormat fmt, bool watcher->deleteLater(); if (result) { - lastFileName = fileName; - lastFileFormat = fmt; + lastLoadInfo = { + .fileName = fileName, + .fileFormat = fmt, + }; if (userRequest) { updateLastLoadedTimestamp(fileName, fmt); } @@ -129,9 +131,9 @@ bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId) { bool result = deckList->loadFromString_Native(nativeString); if (result) { - lastFileName = QString(); - lastFileFormat = CockatriceFormat; - lastRemoteDeckId = remoteDeckId; + lastLoadInfo = { + .remoteDeckId = remoteDeckId, + }; emit deckLoaded(); } @@ -157,8 +159,10 @@ bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt) } if (result) { - lastFileName = fileName; - lastFileFormat = fmt; + lastLoadInfo = { + .fileName = fileName, + .fileFormat = fmt, + }; qCInfo(DeckLoaderLog) << "Deck was saved -" << result; } @@ -201,8 +205,10 @@ bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, FileFormat f file.close(); // Close the file to ensure changes are flushed if (result) { - lastFileName = fileName; - lastFileFormat = fmt; + lastLoadInfo = { + .fileName = fileName, + .fileFormat = fmt, + }; // Re-open the file and set the original timestamp if (!file.open(QIODevice::ReadWrite)) { @@ -582,8 +588,10 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName) } else { qCInfo(DeckLoaderLog) << "Original file deleted successfully:" << fileName; } - lastFileName = newFileName; - lastFileFormat = CockatriceFormat; + lastLoadInfo = { + .fileName = newFileName, + .fileFormat = CockatriceFormat, + }; } return result; diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h index ba9ba807d..00136e6bb 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.h +++ b/cockatrice/src/interface/deck_loader/deck_loader.h @@ -28,6 +28,21 @@ public: CockatriceFormat }; + /** + * @brief Information about where the deck was loaded from. + * + * For local decks, the remoteDeckId field will always be -1. + * For remote decks, fileName will be empty and fileFormat will always be CockatriceFormat + */ + struct LoadInfo + { + static constexpr int NON_REMOTE_ID = -1; + + QString fileName = ""; + FileFormat fileFormat = CockatriceFormat; + int remoteDeckId = NON_REMOTE_ID; + }; + /** * Supported file extensions for decklist files */ @@ -46,9 +61,7 @@ public: private: DeckList *deckList; - QString lastFileName; - FileFormat lastFileFormat; - int lastRemoteDeckId; + LoadInfo lastLoadInfo; public: DeckLoader(QObject *parent); @@ -56,26 +69,19 @@ public: DeckLoader(const DeckLoader &) = delete; DeckLoader &operator=(const DeckLoader &) = delete; - [[nodiscard]] const QString &getLastFileName() const + const LoadInfo &getLastLoadInfo() const { - return lastFileName; + return lastLoadInfo; } - void setLastFileName(const QString &_lastFileName) + + void setLastLoadInfo(const LoadInfo &info) { - lastFileName = _lastFileName; - } - [[nodiscard]] FileFormat getLastFileFormat() const - { - return lastFileFormat; - } - [[nodiscard]] int getLastRemoteDeckId() const - { - return lastRemoteDeckId; + lastLoadInfo = info; } [[nodiscard]] bool hasNotBeenLoaded() const { - return getLastFileName().isEmpty() && getLastRemoteDeckId() == -1; + return lastLoadInfo.fileName.isEmpty() && lastLoadInfo.remoteDeckId == LoadInfo::NON_REMOTE_ID; } static void clearSetNamesAndNumbers(const DeckList *deckList); 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 a3d3fe2cf..89611aa48 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -224,8 +224,8 @@ void AbstractTabDeckEditor::openDeck(DeckLoader *deck) { setDeck(deck); - if (!deck->getLastFileName().isEmpty()) { - SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(deck->getLastFileName()); + if (!deck->getLastLoadInfo().fileName.isEmpty()) { + SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(deck->getLastLoadInfo().fileName); } } @@ -411,7 +411,7 @@ void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLo bool AbstractTabDeckEditor::actSaveDeck() { DeckLoader *const deck = getDeckLoader(); - if (deck->getLastRemoteDeckId() != -1) { + if (deck->getLastLoadInfo().remoteDeckId != DeckLoader::LoadInfo::NON_REMOTE_ID) { QString deckString = deck->getDeckList()->writeToString_Native(); if (deckString.length() > MAX_FILE_LENGTH) { QMessageBox::critical(this, tr("Error"), tr("Could not save remote deck")); @@ -419,7 +419,7 @@ bool AbstractTabDeckEditor::actSaveDeck() } Command_DeckUpload cmd; - cmd.set_deck_id(static_cast(deck->getLastRemoteDeckId())); + cmd.set_deck_id(static_cast(deck->getLastLoadInfo().remoteDeckId)); cmd.set_deck_list(deckString.toStdString()); PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd); @@ -427,9 +427,9 @@ bool AbstractTabDeckEditor::actSaveDeck() tabSupervisor->getClient()->sendCommand(pend); return true; - } else if (deck->getLastFileName().isEmpty()) + } else if (deck->getLastLoadInfo().fileName.isEmpty()) return actSaveDeckAs(); - else if (deck->saveToFile(deck->getLastFileName(), deck->getLastFileFormat())) { + else if (deck->saveToFile(deck->getLastLoadInfo().fileName, deck->getLastLoadInfo().fileFormat)) { setModified(false); return true; } diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp index eb105697f..b8425e842 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp @@ -112,7 +112,7 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() return; deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath); - deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastFileName(); + deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastLoadInfo().fileName; deckPreviewWidget->refreshBannerCardText(); canAddTags = true; } @@ -125,7 +125,7 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() return; deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath); - deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastFileName(); + deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastLoadInfo().fileName; deckPreviewWidget->refreshBannerCardText(); canAddTags = true; 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 8b2bec10b..b330b0c4c 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 @@ -79,7 +79,7 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess) bannerCardDisplayWidget->setCard(bannerCard); bannerCardDisplayWidget->setFontSize(24); - setFilePath(deckLoader->getLastFileName()); + setFilePath(deckLoader->getLastLoadInfo().fileName); colorIdentityWidget = new ColorIdentityWidget(this, getColorIdentity()); deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeckList()); @@ -185,7 +185,7 @@ QString DeckPreviewWidget::getColorIdentity() */ QString DeckPreviewWidget::getDisplayName() const { - return deckLoader->getDeckList()->getName().isEmpty() ? QFileInfo(deckLoader->getLastFileName()).fileName() + return deckLoader->getDeckList()->getName().isEmpty() ? QFileInfo(deckLoader->getLastLoadInfo().fileName).fileName() : deckLoader->getDeckList()->getName(); } @@ -408,7 +408,9 @@ void DeckPreviewWidget::actRenameFile() return; } - deckLoader->setLastFileName(newFilePath); + DeckLoader::LoadInfo lastLoadInfo = deckLoader->getLastLoadInfo(); + lastLoadInfo.fileName = newFilePath; + deckLoader->setLastLoadInfo(lastLoadInfo); // update VDS setFilePath(newFilePath); From 8abd04dab1820cf5e14802bd8e2cb0a4b7811dcc Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sat, 29 Nov 2025 14:19:11 +0100 Subject: [PATCH 031/325] [Fix-Warnings] Remove more redundant empty declarations. (extra semicolons) (#6374) --- .../update/card_spoiler/spoiler_background_updater.h | 2 +- cockatrice/src/client/settings/shortcuts_settings.h | 8 ++++---- .../interface/settings_card_preference_provider.h | 2 +- cockatrice/src/game/player/player.h | 4 ++-- cockatrice/src/game/zones/logic/card_zone_logic.h | 2 +- .../cards/additional_info/mana_symbol_widget.h | 6 +++--- .../deck_editor_database_display_widget.h | 2 +- .../widgets/printing_selector/printing_selector.h | 2 +- .../widgets/server/user/user_list_manager.cpp | 2 +- .../edhrec_api_response_card_list_display_widget.h | 2 +- .../deck_preview/deck_preview_tag_display_widget.h | 2 +- .../visual_deck_storage_folder_display_widget.h | 2 +- cockatrice/src/interface/window_main.h | 2 +- libcockatrice_card/libcockatrice/card/card_info.h | 2 +- .../libcockatrice/card/game_specific_terms.h | 2 +- .../libcockatrice/deck_list/deck_list.h | 4 ++-- .../libcockatrice/models/deck_list/deck_list_model.h | 2 +- .../network/server/local/local_server_interface.h | 2 +- .../server/remote/game/server_abstract_participant.h | 2 +- .../server/remote/server_database_interface.h | 10 +++++----- servatrice/src/serversocketinterface.h | 12 ++++++------ 21 files changed, 37 insertions(+), 37 deletions(-) diff --git a/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.h b/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.h index 387f93820..a2feb5ccf 100644 --- a/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.h +++ b/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.h @@ -22,7 +22,7 @@ public: inline QString getCardUpdaterBinaryName() { return "oracle"; - }; + } QByteArray getHash(const QString fileName); QByteArray getHash(QByteArray data); static bool deleteSpoilerFile(); diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h index d1b0301d1..736f0c896 100644 --- a/cockatrice/src/client/settings/shortcuts_settings.h +++ b/cockatrice/src/client/settings/shortcuts_settings.h @@ -99,15 +99,15 @@ public: void setSequence(const QList &_sequence) { QList::operator=(_sequence); - }; + } [[nodiscard]] QString getName() const { return QApplication::translate("shortcutsTab", name.toUtf8().data()); - }; + } [[nodiscard]] QString getGroupName() const { return ShortcutGroup::getGroupName(group); - }; + } private: QString name; @@ -129,7 +129,7 @@ public: [[nodiscard]] QList getAllShortcutKeys() const { return shortCuts.keys(); - }; + } void setShortcuts(const QString &name, const QList &Sequence); void setShortcuts(const QString &name, const QKeySequence &Sequence); diff --git a/cockatrice/src/database/interface/settings_card_preference_provider.h b/cockatrice/src/database/interface/settings_card_preference_provider.h index 5ec23ea60..6c31b9d6d 100644 --- a/cockatrice/src/database/interface/settings_card_preference_provider.h +++ b/cockatrice/src/database/interface/settings_card_preference_provider.h @@ -15,7 +15,7 @@ public: [[nodiscard]] bool getIncludeRebalancedCards() const override { return SettingsCache::instance().getIncludeRebalancedCards(); - }; + } }; #endif // COCKATRICE_SETTINGS_CARD_PREFERENCE_PROVIDER_H diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h index 0751abb14..4501b0c91 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player.h @@ -113,7 +113,7 @@ public: [[nodiscard]] PlayerActions *getPlayerActions() const { return playerActions; - }; + } [[nodiscard]] PlayerEventHandler *getPlayerEventHandler() const { @@ -123,7 +123,7 @@ public: [[nodiscard]] PlayerInfo *getPlayerInfo() const { return playerInfo; - }; + } [[nodiscard]] PlayerMenu *getPlayerMenu() const { diff --git a/cockatrice/src/game/zones/logic/card_zone_logic.h b/cockatrice/src/game/zones/logic/card_zone_logic.h index 4d02b7dd1..1aa46effd 100644 --- a/cockatrice/src/game/zones/logic/card_zone_logic.h +++ b/cockatrice/src/game/zones/logic/card_zone_logic.h @@ -52,7 +52,7 @@ public: void rawInsertCard(CardItem *card, int index) { cards.insert(index, card); - }; + } [[nodiscard]] const CardList &getCards() const { diff --git a/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.h b/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.h index 2ae76e9c0..8d2c6813f 100644 --- a/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.h +++ b/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.h @@ -23,15 +23,15 @@ public: [[nodiscard]] bool isColorActive() const { return isActive; - }; + } [[nodiscard]] QString getSymbol() const { return symbol; - }; + } [[nodiscard]] QChar getSymbolChar() const { return symbol[0]; - }; + } void loadManaIcon(); diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h index 0d3ce9f4c..593be7825 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h @@ -32,7 +32,7 @@ public: QTreeView *getDatabaseView() { return databaseView; - }; + } public slots: ExactCard currentCard() const; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h index b83768327..88a49f1e0 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h @@ -39,7 +39,7 @@ public: [[nodiscard]] DeckListModel *getDeckModel() const { return deckModel; - }; + } public slots: void retranslateUi(); diff --git a/cockatrice/src/interface/widgets/server/user/user_list_manager.cpp b/cockatrice/src/interface/widgets/server/user/user_list_manager.cpp index 9b930b7f6..4bc2c84d6 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_manager.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_manager.cpp @@ -167,4 +167,4 @@ const ServerInfo_User *UserListManager::getOnlineUser(const QString &userName) c } return nullptr; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.h index 0ebb9b927..d34c3da34 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.h @@ -24,7 +24,7 @@ public: [[nodiscard]] QString getBannerText() const { return header->getText(); - }; + } private: QVBoxLayout *layout; diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h index a4ea3633b..a868aa4f1 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h @@ -44,7 +44,7 @@ public: { state = newState; update(); - }; + } signals: /** diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h index fe1693c7e..f735d0ad3 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h @@ -31,7 +31,7 @@ public: [[nodiscard]] FlowWidget *getFlowWidget() const { return flowWidget; - }; + } public slots: void updateVisibility(bool recursive = true); diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h index 620a09467..477d7b07c 100644 --- a/cockatrice/src/interface/window_main.h +++ b/cockatrice/src/interface/window_main.h @@ -135,7 +135,7 @@ private: inline QString getCardUpdaterBinaryName() { return "oracle"; - }; + } void createCardUpdateProcess(bool background = false); void exitCardDatabaseUpdate(); diff --git a/libcockatrice_card/libcockatrice/card/card_info.h b/libcockatrice_card/libcockatrice/card/card_info.h index 3ff78f38c..92b7f3121 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.h +++ b/libcockatrice_card/libcockatrice/card/card_info.h @@ -190,7 +190,7 @@ public: const QSet &getAltNames() { return altNames; - }; + } [[nodiscard]] const QString &getText() const { return text; diff --git a/libcockatrice_card/libcockatrice/card/game_specific_terms.h b/libcockatrice_card/libcockatrice/card/game_specific_terms.h index 8f13039da..2931365ad 100644 --- a/libcockatrice_card/libcockatrice/card/game_specific_terms.h +++ b/libcockatrice_card/libcockatrice/card/game_specific_terms.h @@ -53,6 +53,6 @@ inline static const QString getNicePropertyName(QString key) return QCoreApplication::translate("Mtg", "Color Identity"); return key; } -}; // namespace Mtg +} // namespace Mtg #endif diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index 1be673fcf..fd3b4677e 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -158,7 +158,7 @@ protected: virtual QString getCardZoneFromName(const QString /*cardName*/, QString currentZoneName) { return currentZoneName; - }; + } /** * @brief Produce the complete display name of a card. @@ -169,7 +169,7 @@ protected: virtual QString getCompleteCardName(const QString &cardName) const { return cardName; - }; + } signals: /// Emitted when the deck hash changes. 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 08a4592ae..9b96c0ce6 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -203,7 +203,7 @@ public: [[nodiscard]] QModelIndex getRoot() const { return nodeToIndex(root); - }; + } /** * @brief Returns the value of the grouping category for a card based on the current criteria. diff --git a/libcockatrice_network/libcockatrice/network/server/local/local_server_interface.h b/libcockatrice_network/libcockatrice/network/server/local/local_server_interface.h index 1ddf5a85a..4410fd65c 100644 --- a/libcockatrice_network/libcockatrice/network/server/local/local_server_interface.h +++ b/libcockatrice_network/libcockatrice/network/server/local/local_server_interface.h @@ -25,7 +25,7 @@ public: QString getConnectionType() const override { return "local"; - }; + } void transmitProtocolItem(const ServerMessage &item) override; signals: void itemToClient(const ServerMessage &item); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.h index 10fbb3349..f60d56dcf 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.h @@ -76,7 +76,7 @@ public: virtual void prepareDestroy() { removeFromGame(); - }; + } void removeFromGame(); Server_AbstractUserInterface *getUserInterface() const { diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h b/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h index d2577a99b..b43dbde42 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h @@ -83,7 +83,7 @@ public: virtual bool usernameIsValid(const QString & /*userName */, QString & /* error */) { return true; - }; + } public slots: virtual void endSession(qint64 /* sessionId */) { @@ -154,16 +154,16 @@ public: int & /* banSecondsRemaining */) { return false; - }; + } virtual int checkNumberOfUserAccounts(const QString & /* email */) { return 0; - }; + } virtual bool changeUserPassword(const QString & /* user */, const QString & /* password */, bool /* passwordNeedsHash */) { return false; - }; + } virtual bool changeUserPassword(const QString & /* user */, const QString & /* oldPassword */, bool /* oldPasswordNeedsHash */, @@ -171,7 +171,7 @@ public: bool /* newPasswordNeedsHash */) { return false; - }; + } }; #endif diff --git a/servatrice/src/serversocketinterface.h b/servatrice/src/serversocketinterface.h index e522ea6c1..e10aa0dde 100644 --- a/servatrice/src/serversocketinterface.h +++ b/servatrice/src/serversocketinterface.h @@ -177,7 +177,7 @@ public: QString getConnectionType() const { return "tcp"; - }; + } private: QTcpSocket *socket; @@ -190,11 +190,11 @@ protected: void writeToSocket(QByteArray &data) { socket->write(data); - }; + } void flushSocket() { socket->flush(); - }; + } void initSessionDeprecated(); bool initTcpSession(); protected slots: @@ -224,7 +224,7 @@ public: QString getConnectionType() const { return "websocket"; - }; + } private: QWebSocket *socket; @@ -234,11 +234,11 @@ protected: void writeToSocket(QByteArray &data) { socket->sendBinaryMessage(data); - }; + } void flushSocket() { socket->flush(); - }; + } bool initWebsocketSession(); protected slots: void binaryMessageReceived(const QByteArray &message); From c5fde071e79c162fa20a74d133b97c9488a4cb95 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sat, 29 Nov 2025 18:53:11 +0100 Subject: [PATCH 032/325] [Cleanup] Unused #includes (#6367) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Cleanup] Unused #includes Took 44 minutes * [Cleanup] More unused #includes Took 55 minutes * [Cleanup] Include QSet Took 4 minutes * [Cleanup] Include QDebug in deck_list.cpp Took 3 minutes * [Cleanup] Include protocol stuff in servatrice_database_interface.h Took 3 minutes * [Cleanup] Include QDialogButtonBox Took 8 minutes * [Cleanup] Include QUrl Took 8 minutes * [Cleanup] Include QTextOption in header. Took 3 minutes * [Cleanup] Include QMap in user_list_manager.h Took 8 minutes * [Cleanup] Adjust qjson Took 8 minutes * [Cleanup] include button box. Took 3 minutes * [Cleanup] Redo fwd declarations. * [Cleanup] Redo last removed fwd declarations. --------- Co-authored-by: Lukas Brübach --- .../interfaces/deck_stats_interface.cpp | 1 - .../network/interfaces/deck_stats_interface.h | 1 - .../interfaces/tapped_out_interface.cpp | 1 - .../network/interfaces/tapped_out_interface.h | 2 - .../spoiler_background_updater.cpp | 2 - .../update/client/update_downloader.cpp | 1 - .../network/update/client/update_downloader.h | 2 - .../src/client/settings/cache_settings.h | 1 - .../client/settings/card_counter_settings.h | 1 - .../src/client/settings/shortcut_treeview.h | 1 - cockatrice/src/filters/deck_filter_string.cpp | 1 + cockatrice/src/filters/deck_filter_string.h | 2 - .../src/game/board/abstract_counter.cpp | 3 +- cockatrice/src/game/board/arrow_item.cpp | 1 + cockatrice/src/game/board/card_item.cpp | 2 +- cockatrice/src/game/deckview/deck_view.h | 3 -- .../src/game/deckview/deck_view_container.cpp | 4 -- .../src/game/dialogs/dlg_create_token.cpp | 1 - .../src/game/dialogs/dlg_create_token.h | 1 - cockatrice/src/game/game_event_handler.cpp | 1 - cockatrice/src/game/game_scene.cpp | 1 - cockatrice/src/game/game_state.h | 2 - .../src/game/log/message_log_widget.cpp | 2 - cockatrice/src/game/log/message_log_widget.h | 3 -- .../src/game/player/menu/grave_menu.cpp | 1 - .../src/game/player/menu/player_menu.cpp | 5 +-- .../src/game/player/menu/sideboard_menu.cpp | 1 + .../src/game/player/menu/utility_menu.cpp | 2 + cockatrice/src/game/player/player.cpp | 2 +- cockatrice/src/game/player/player.h | 8 ++-- cockatrice/src/game/player/player_actions.cpp | 3 +- cockatrice/src/game/player/player_actions.h | 1 + .../src/game/player/player_event_handler.cpp | 1 + .../src/game/player/player_graphics_item.cpp | 5 +++ cockatrice/src/game/player/player_info.h | 5 --- .../src/game/player/player_list_widget.cpp | 4 -- cockatrice/src/game/player/player_target.h | 1 - cockatrice/src/game/zones/hand_zone.cpp | 1 + .../src/game/zones/logic/card_zone_logic.cpp | 3 +- cockatrice/src/game/zones/pile_zone.cpp | 1 + cockatrice/src/game/zones/stack_zone.cpp | 2 +- cockatrice/src/game/zones/table_zone.cpp | 2 +- cockatrice/src/game/zones/view_zone.cpp | 2 +- .../src/game/zones/view_zone_widget.cpp | 2 +- .../card_picture_loader.cpp | 1 - .../card_picture_loader/card_picture_loader.h | 1 - .../card_picture_loader_local.h | 2 - ...ure_loader_request_status_display_widget.h | 4 +- .../card_picture_loader_status_bar.h | 1 - .../card_picture_loader_worker.cpp | 1 - .../card_picture_loader_worker_work.cpp | 2 - .../card_picture_to_load.h | 1 - .../additional_info/color_identity_widget.cpp | 1 - .../additional_info/mana_cost_widget.cpp | 1 - .../card_group_display_widget.cpp | 1 - .../card_group_display_widget.h | 1 - .../flat_card_group_display_widget.cpp | 3 -- .../cards/card_info_display_widget.cpp | 2 - .../widgets/cards/card_info_frame_widget.cpp | 1 - .../card_info_picture_enlarged_widget.cpp | 1 - .../cards/card_info_picture_enlarged_widget.h | 1 - .../widgets/cards/card_info_picture_widget.h | 1 - ..._info_picture_with_text_overlay_widget.cpp | 1 - .../widgets/cards/card_info_text_widget.cpp | 1 - .../widgets/cards/card_size_widget.cpp | 1 - .../cards/deck_card_zone_display_widget.cpp | 1 - .../cards/deck_card_zone_display_widget.h | 1 - .../deck_preview_card_picture_widget.cpp | 1 - .../cards/deck_preview_card_picture_widget.h | 1 + .../deck_analytics/deck_analytics_widget.h | 3 -- .../deck_analytics/mana_devotion_widget.cpp | 3 -- .../deck_editor_database_display_widget.cpp | 2 - .../deck_editor_deck_dock_widget.h | 1 - .../deck_editor_filter_dock_widget.cpp | 1 - .../deck_editor_filter_dock_widget.h | 1 - .../deck_list_history_manager_widget.h | 1 - .../interface/widgets/dialogs/dlg_connect.cpp | 2 - .../widgets/dialogs/dlg_create_game.cpp | 1 - .../dialogs/dlg_default_tags_editor.cpp | 1 + .../widgets/dialogs/dlg_default_tags_editor.h | 1 - .../widgets/dialogs/dlg_edit_avatar.cpp | 1 - .../widgets/dialogs/dlg_edit_password.h | 1 - .../widgets/dialogs/dlg_edit_tokens.cpp | 2 - .../widgets/dialogs/dlg_edit_user.cpp | 1 - .../dialogs/dlg_forgot_password_challenge.cpp | 2 - .../dialogs/dlg_forgot_password_request.cpp | 2 - .../dialogs/dlg_forgot_password_reset.cpp | 2 - .../widgets/dialogs/dlg_load_remote_deck.cpp | 2 - .../widgets/dialogs/dlg_manage_sets.cpp | 3 -- .../widgets/dialogs/dlg_manage_sets.h | 1 - .../widgets/dialogs/dlg_register.cpp | 2 - .../dialogs/dlg_select_set_for_cards.cpp | 2 +- .../widgets/dialogs/dlg_settings.cpp | 1 - .../widgets/dialogs/dlg_tip_of_the_day.cpp | 1 - .../widgets/dialogs/dlg_tip_of_the_day.h | 2 - .../interface/widgets/dialogs/dlg_update.cpp | 2 - .../widgets/general/display/bar_widget.h | 2 - .../display/dynamic_font_size_label.cpp | 1 - .../display/dynamic_font_size_push_button.cpp | 1 - .../general/display/percent_bar_widget.h | 1 - .../interface/widgets/general/home_widget.cpp | 1 + .../interface/widgets/general/home_widget.h | 1 - .../widgets/menus/deck_editor_menu.cpp | 1 + .../widgets/menus/deck_editor_menu.h | 2 - .../all_zones_card_amount_widget.h | 1 - .../printing_selector/card_amount_widget.h | 2 - .../printing_selector/printing_selector.cpp | 3 +- .../printing_selector/printing_selector.h | 1 - .../printing_selector_card_display_widget.cpp | 2 - .../printing_selector_card_display_widget.h | 2 - .../printing_selector_card_overlay_widget.h | 3 -- .../widgets/replay/replay_timeline_widget.cpp | 1 - .../widgets/replay/replay_timeline_widget.h | 1 - .../widgets/server/game_selector.cpp | 5 --- .../game_selector_quick_filter_toolbar.cpp | 1 - .../game_selector_quick_filter_toolbar.h | 3 -- .../interface/widgets/server/games_model.cpp | 3 -- .../interface/widgets/server/games_model.h | 2 - .../widgets/server/handle_public_servers.cpp | 2 - .../remote/remote_decklist_tree_widget.h | 1 - .../remote/remote_replay_list_tree_widget.h | 1 - .../widgets/server/user/user_context_menu.cpp | 2 - .../widgets/server/user/user_list_widget.cpp | 7 ---- .../widgets/tabs/abstract_tab_deck_editor.cpp | 6 --- .../edhrec_api_response_archidekt_links.cpp | 1 - .../edhrec_api_response_archidekt_links.h | 1 - .../edhrec_average_deck_api_response.h | 1 - .../average_deck/edhrec_deck_api_response.cpp | 1 - .../average_deck/edhrec_deck_api_response.h | 6 --- .../edhrec_api_response_card_container.h | 2 - .../cards/edhrec_api_response_card_list.h | 1 - ...commander_api_response_commander_details.h | 1 - .../commander/edhrec_commander_api_response.h | 1 - .../top_cards/edhrec_top_cards_api_response.h | 1 - .../edhrec_top_commanders_api_response.h | 1 - .../top_tags/edhrec_top_tags_api_response.h | 1 - ...rec_top_tags_api_response_display_widget.h | 1 - .../widgets/tabs/api/edhrec/tab_edhrec.cpp | 1 - .../tabs/api/edhrec/tab_edhrec_main.cpp | 1 - .../widgets/tabs/api/edhrec/tab_edhrec_main.h | 1 - .../widgets/tabs/tab_deck_editor.cpp | 17 -------- .../widgets/tabs/tab_deck_storage.cpp | 1 - .../src/interface/widgets/tabs/tab_game.cpp | 5 --- .../src/interface/widgets/tabs/tab_game.h | 2 - .../src/interface/widgets/tabs/tab_home.cpp | 3 -- .../src/interface/widgets/tabs/tab_home.h | 2 - .../src/interface/widgets/tabs/tab_logs.cpp | 8 ++-- .../src/interface/widgets/tabs/tab_logs.h | 1 - .../interface/widgets/tabs/tab_replays.cpp | 1 - .../src/interface/widgets/tabs/tab_room.h | 1 - .../src/interface/widgets/tabs/tab_server.cpp | 5 --- .../interface/widgets/tabs/tab_supervisor.cpp | 1 - .../interface/widgets/tabs/tab_supervisor.h | 2 - .../tabs/tab_visual_database_display.h | 2 - .../tab_deck_editor_visual.cpp | 6 --- .../tab_deck_storage_visual.cpp | 3 -- .../widgets/utility/get_text_with_max.cpp | 2 + .../widgets/utility/get_text_with_max.h | 3 +- .../widgets/utility/line_edit_completer.cpp | 1 - .../widgets/utility/line_edit_completer.h | 2 - .../widgets/utility/sequence_edit.cpp | 1 - .../interface/widgets/utility/sequence_edit.h | 1 - ...l_database_display_color_filter_widget.cpp | 1 - ...tabase_display_filter_save_load_widget.cpp | 3 +- ...database_display_filter_save_load_widget.h | 2 - ...database_display_main_type_filter_widget.h | 1 - ...sual_database_display_name_filter_widget.h | 1 - ...isual_database_display_set_filter_widget.h | 1 - ..._database_display_sub_type_filter_widget.h | 2 - .../visual_database_display_widget.cpp | 1 - .../visual_database_display_widget.h | 3 -- .../visual_deck_editor_widget.cpp | 1 - .../visual_deck_editor_widget.h | 1 - ...k_preview_color_identity_filter_widget.cpp | 1 - ...eck_preview_color_identity_filter_widget.h | 2 - .../deck_preview_deck_tags_display_widget.cpp | 1 - .../deck_preview_tag_addition_widget.cpp | 1 - .../deck_preview_tag_addition_widget.h | 4 -- .../deck_preview/deck_preview_tag_dialog.cpp | 1 - .../deck_preview/deck_preview_tag_dialog.h | 3 -- .../deck_preview/deck_preview_widget.cpp | 1 + ...ual_deck_storage_folder_display_widget.cpp | 1 + ...isual_deck_storage_folder_display_widget.h | 5 +-- .../visual_deck_storage_search_widget.cpp | 1 + .../visual_deck_storage_search_widget.h | 3 +- .../visual_deck_storage_sort_widget.cpp | 2 + .../visual_deck_storage_tag_filter_widget.cpp | 4 +- .../visual_deck_storage_tag_filter_widget.h | 2 +- .../visual_deck_storage_widget.cpp | 1 - .../visual_deck_storage_widget.h | 4 -- cockatrice/src/interface/window_main.cpp | 1 - cockatrice/src/interface/window_main.h | 2 - cockatrice/src/main.cpp | 6 --- cockatrice/src/main.h | 1 - .../libcockatrice/card/card_info.h | 1 - .../libcockatrice/card/card_info_comparator.h | 1 - .../card/database/card_database.cpp | 2 - .../card/database/card_database.h | 2 - .../card/database/card_database_loader.h | 1 - .../card/printing/printing_info.h | 1 - .../libcockatrice/card/set/card_set_list.h | 1 - .../deck_list/abstract_deck_list_node.h | 1 - .../libcockatrice/deck_list/deck_list.cpp | 1 + .../libcockatrice/deck_list/deck_list.h | 1 - .../libcockatrice/filters/filter_card.h | 1 - .../libcockatrice/filters/filter_tree.h | 1 - .../interface_card_set_priority_controller.h | 1 - .../database/card_database_display_model.h | 2 - .../network/client/abstract/abstract_client.h | 1 - .../network/client/remote/remote_client.h | 1 - .../game/server_abstract_participant.cpp | 37 ------------------ .../remote/game/server_abstract_player.h | 1 - .../server/remote/game/server_cardzone.h | 1 - .../server/remote/game/server_game.cpp | 2 - .../server/remote/game/server_player.cpp | 39 ------------------- .../network/server/remote/server.cpp | 1 - .../network/server/remote/server.h | 4 -- .../remote/server_abstractuserinterface.cpp | 2 - .../remote/server_abstractuserinterface.h | 1 - .../server/remote/server_protocolhandler.h | 1 - .../network/server/remote/server_room.h | 2 - .../libcockatrice/protocol/featureset.cpp | 1 - .../libcockatrice/protocol/featureset.h | 1 - .../settings/card_database_settings.h | 3 -- .../settings/download_settings.h | 2 - .../libcockatrice/settings/settings_manager.h | 1 - .../src/servatrice_database_interface.h | 2 + 227 files changed, 58 insertions(+), 446 deletions(-) diff --git a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp index a5cea5358..eeb48959c 100644 --- a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp +++ b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include diff --git a/cockatrice/src/client/network/interfaces/deck_stats_interface.h b/cockatrice/src/client/network/interfaces/deck_stats_interface.h index f621fff7c..fd1691a8b 100644 --- a/cockatrice/src/client/network/interfaces/deck_stats_interface.h +++ b/cockatrice/src/client/network/interfaces/deck_stats_interface.h @@ -7,7 +7,6 @@ #ifndef DECKSTATS_INTERFACE_H #define DECKSTATS_INTERFACE_H -#include #include #include diff --git a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp index d18d811b0..cebbce8b8 100644 --- a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp +++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include diff --git a/cockatrice/src/client/network/interfaces/tapped_out_interface.h b/cockatrice/src/client/network/interfaces/tapped_out_interface.h index e4e9498a3..056d7fd9a 100644 --- a/cockatrice/src/client/network/interfaces/tapped_out_interface.h +++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.h @@ -7,8 +7,6 @@ #ifndef TAPPEDOUT_INTERFACE_H #define TAPPEDOUT_INTERFACE_H -#include -#include #include #include diff --git a/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.cpp b/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.cpp index cfd4cbbf8..f6f851037 100644 --- a/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.cpp +++ b/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.cpp @@ -4,13 +4,11 @@ #include "../../../../main.h" #include "../../../settings/cache_settings.h" -#include #include #include #include #include #include -#include #include #include #include diff --git a/cockatrice/src/client/network/update/client/update_downloader.cpp b/cockatrice/src/client/network/update/client/update_downloader.cpp index 40186bec4..c0f3e945c 100644 --- a/cockatrice/src/client/network/update/client/update_downloader.cpp +++ b/cockatrice/src/client/network/update/client/update_downloader.cpp @@ -1,6 +1,5 @@ #include "update_downloader.h" -#include #include UpdateDownloader::UpdateDownloader(QObject *parent) : QObject(parent), response(nullptr) diff --git a/cockatrice/src/client/network/update/client/update_downloader.h b/cockatrice/src/client/network/update/client/update_downloader.h index 4f79f3f6d..d70759038 100644 --- a/cockatrice/src/client/network/update/client/update_downloader.h +++ b/cockatrice/src/client/network/update/client/update_downloader.h @@ -7,9 +7,7 @@ #ifndef COCKATRICE_UPDATEDOWNLOADER_H #define COCKATRICE_UPDATEDOWNLOADER_H -#include #include -#include #include class UpdateDownloader : public QObject diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index 5e329bf09..df68ce0c4 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -11,7 +11,6 @@ #include #include -#include #include #include #include diff --git a/cockatrice/src/client/settings/card_counter_settings.h b/cockatrice/src/client/settings/card_counter_settings.h index 63615d0e2..b1d467d67 100644 --- a/cockatrice/src/client/settings/card_counter_settings.h +++ b/cockatrice/src/client/settings/card_counter_settings.h @@ -7,7 +7,6 @@ #ifndef CARD_COUNTER_SETTINGS_H #define CARD_COUNTER_SETTINGS_H -#include #include class QSettings; diff --git a/cockatrice/src/client/settings/shortcut_treeview.h b/cockatrice/src/client/settings/shortcut_treeview.h index d503140f0..8d74a6f1e 100644 --- a/cockatrice/src/client/settings/shortcut_treeview.h +++ b/cockatrice/src/client/settings/shortcut_treeview.h @@ -7,7 +7,6 @@ #ifndef SHORTCUT_TREEVIEW_H #define SHORTCUT_TREEVIEW_H -#include #include #include #include diff --git a/cockatrice/src/filters/deck_filter_string.cpp b/cockatrice/src/filters/deck_filter_string.cpp index c9adc17e9..52b6a38d1 100644 --- a/cockatrice/src/filters/deck_filter_string.cpp +++ b/cockatrice/src/filters/deck_filter_string.cpp @@ -1,5 +1,6 @@ #include "deck_filter_string.h" +#include #include #include #include diff --git a/cockatrice/src/filters/deck_filter_string.h b/cockatrice/src/filters/deck_filter_string.h index 635745592..1b43d770d 100644 --- a/cockatrice/src/filters/deck_filter_string.h +++ b/cockatrice/src/filters/deck_filter_string.h @@ -10,10 +10,8 @@ #include "../interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h" #include -#include #include #include -#include inline Q_LOGGING_CATEGORY(DeckFilterStringLog, "deck_filter_string"); diff --git a/cockatrice/src/game/board/abstract_counter.cpp b/cockatrice/src/game/board/abstract_counter.cpp index 985c78ad2..08d19ec8a 100644 --- a/cockatrice/src/game/board/abstract_counter.cpp +++ b/cockatrice/src/game/board/abstract_counter.cpp @@ -3,15 +3,14 @@ #include "../../client/settings/cache_settings.h" #include "../../interface/widgets/tabs/tab_game.h" #include "../player/player.h" +#include "../player/player_actions.h" #include "translate_counter_name.h" #include #include -#include #include #include #include -#include #include #include #include diff --git a/cockatrice/src/game/board/arrow_item.cpp b/cockatrice/src/game/board/arrow_item.cpp index d5d0ebfe0..1352b3a05 100644 --- a/cockatrice/src/game/board/arrow_item.cpp +++ b/cockatrice/src/game/board/arrow_item.cpp @@ -3,6 +3,7 @@ #include "../../client/settings/cache_settings.h" #include "../player/player.h" +#include "../player/player_actions.h" #include "../player/player_target.h" #include "../zones/card_zone.h" #include "card_item.h" diff --git a/cockatrice/src/game/board/card_item.cpp b/cockatrice/src/game/board/card_item.cpp index e786b5329..34c2f9a98 100644 --- a/cockatrice/src/game/board/card_item.cpp +++ b/cockatrice/src/game/board/card_item.cpp @@ -5,7 +5,7 @@ #include "../game_scene.h" #include "../phase.h" #include "../player/player.h" -#include "../zones/card_zone.h" +#include "../player/player_actions.h" #include "../zones/logic/view_zone_logic.h" #include "../zones/table_zone.h" #include "../zones/view_zone.h" diff --git a/cockatrice/src/game/deckview/deck_view.h b/cockatrice/src/game/deckview/deck_view.h index 027d0fafd..5abc558bd 100644 --- a/cockatrice/src/game/deckview/deck_view.h +++ b/cockatrice/src/game/deckview/deck_view.h @@ -9,11 +9,8 @@ #include "../board/abstract_card_drag_item.h" -#include #include #include -#include -#include #include class DeckList; diff --git a/cockatrice/src/game/deckview/deck_view_container.cpp b/cockatrice/src/game/deckview/deck_view_container.cpp index da24aa814..cf6ab00b8 100644 --- a/cockatrice/src/game/deckview/deck_view_container.cpp +++ b/cockatrice/src/game/deckview/deck_view_container.cpp @@ -8,13 +8,9 @@ #include "../../interface/widgets/dialogs/dlg_load_deck_from_website.h" #include "../../interface/widgets/dialogs/dlg_load_remote_deck.h" #include "../../interface/widgets/tabs/tab_game.h" -#include "../game_scene.h" #include "deck_view.h" #include -#include -#include -#include #include #include #include diff --git a/cockatrice/src/game/dialogs/dlg_create_token.cpp b/cockatrice/src/game/dialogs/dlg_create_token.cpp index 6594e339d..1f6f9b08c 100644 --- a/cockatrice/src/game/dialogs/dlg_create_token.cpp +++ b/cockatrice/src/game/dialogs/dlg_create_token.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/game/dialogs/dlg_create_token.h b/cockatrice/src/game/dialogs/dlg_create_token.h index 8235aa4cc..9a18f1a57 100644 --- a/cockatrice/src/game/dialogs/dlg_create_token.h +++ b/cockatrice/src/game/dialogs/dlg_create_token.h @@ -8,7 +8,6 @@ #define DLG_CREATETOKEN_H #include -#include class QLabel; class QLineEdit; diff --git a/cockatrice/src/game/game_event_handler.cpp b/cockatrice/src/game/game_event_handler.cpp index 1cb47f368..7bfc4da75 100644 --- a/cockatrice/src/game/game_event_handler.cpp +++ b/cockatrice/src/game/game_event_handler.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/game/game_scene.cpp b/cockatrice/src/game/game_scene.cpp index 2175f4aa2..77037cd6e 100644 --- a/cockatrice/src/game/game_scene.cpp +++ b/cockatrice/src/game/game_scene.cpp @@ -8,7 +8,6 @@ #include "zones/view_zone.h" #include "zones/view_zone_widget.h" -#include #include #include #include diff --git a/cockatrice/src/game/game_state.h b/cockatrice/src/game/game_state.h index b5d6a6572..54f6d9276 100644 --- a/cockatrice/src/game/game_state.h +++ b/cockatrice/src/game/game_state.h @@ -7,10 +7,8 @@ #ifndef COCKATRICE_GAME_STATE_H #define COCKATRICE_GAME_STATE_H -#include #include #include -#include class AbstractGame; class ServerInfo_PlayerProperties; diff --git a/cockatrice/src/game/log/message_log_widget.cpp b/cockatrice/src/game/log/message_log_widget.cpp index d4af5d00d..1ff3e32ff 100644 --- a/cockatrice/src/game/log/message_log_widget.cpp +++ b/cockatrice/src/game/log/message_log_widget.cpp @@ -6,12 +6,10 @@ #include "../board/translate_counter_name.h" #include "../phase.h" #include "../player/player.h" -#include "../zones/card_zone.h" #include <../../client/settings/card_counter_settings.h> #include #include -#include #include static const QString TABLE_ZONE_NAME = "table"; diff --git a/cockatrice/src/game/log/message_log_widget.h b/cockatrice/src/game/log/message_log_widget.h index a185cb288..369debd33 100644 --- a/cockatrice/src/game/log/message_log_widget.h +++ b/cockatrice/src/game/log/message_log_widget.h @@ -7,12 +7,9 @@ #ifndef MESSAGELOGWIDGET_H #define MESSAGELOGWIDGET_H -#include "../../client/translation.h" #include "../../interface/widgets/server/chat_view/chat_view.h" #include "../zones/logic/card_zone_logic.h" -#include - class AbstractGame; class CardItem; class GameEventContext; diff --git a/cockatrice/src/game/player/menu/grave_menu.cpp b/cockatrice/src/game/player/menu/grave_menu.cpp index 47d01bc77..d4faca42b 100644 --- a/cockatrice/src/game/player/menu/grave_menu.cpp +++ b/cockatrice/src/game/player/menu/grave_menu.cpp @@ -3,7 +3,6 @@ #include "../../abstract_game.h" #include "../player.h" #include "../player_actions.h" -#include "grave_menu.h" #include #include diff --git a/cockatrice/src/game/player/menu/player_menu.cpp b/cockatrice/src/game/player/menu/player_menu.cpp index 82747d482..3016a727f 100644 --- a/cockatrice/src/game/player/menu/player_menu.cpp +++ b/cockatrice/src/game/player/menu/player_menu.cpp @@ -3,12 +3,11 @@ #include "../../../interface/widgets/tabs/tab_game.h" #include "../../board/card_item.h" #include "../../zones/hand_zone.h" -#include "../card_menu_action_type.h" -#include "../player_actions.h" +#include "../../zones/pile_zone.h" +#include "../../zones/table_zone.h" #include "card_menu.h" #include "hand_menu.h" -#include #include PlayerMenu::PlayerMenu(Player *_player) : player(_player) diff --git a/cockatrice/src/game/player/menu/sideboard_menu.cpp b/cockatrice/src/game/player/menu/sideboard_menu.cpp index bfc40cfd3..beaabfcc6 100644 --- a/cockatrice/src/game/player/menu/sideboard_menu.cpp +++ b/cockatrice/src/game/player/menu/sideboard_menu.cpp @@ -1,6 +1,7 @@ #include "sideboard_menu.h" #include "../player.h" +#include "../player_actions.h" SideboardMenu::SideboardMenu(Player *player, QMenu *playerMenu) : QMenu(playerMenu) { diff --git a/cockatrice/src/game/player/menu/utility_menu.cpp b/cockatrice/src/game/player/menu/utility_menu.cpp index 15128defe..ca2832ec7 100644 --- a/cockatrice/src/game/player/menu/utility_menu.cpp +++ b/cockatrice/src/game/player/menu/utility_menu.cpp @@ -1,7 +1,9 @@ #include "utility_menu.h" +#include "../../../interface/deck_loader/deck_loader.h" #include "../player.h" #include "../player_actions.h" +#include "libcockatrice/deck_list/inner_deck_list_node.h" #include "player_menu.h" UtilityMenu::UtilityMenu(Player *_player, QMenu *playerMenu) : QMenu(playerMenu), player(_player) diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index ffb26c26e..efce40cd7 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -11,7 +11,7 @@ #include "../zones/pile_zone.h" #include "../zones/stack_zone.h" #include "../zones/table_zone.h" -#include "../zones/view_zone.h" +#include "player_actions.h" #include "player_target.h" #include diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h index 4501b0c91..0a4fc9e69 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player.h @@ -9,9 +9,11 @@ #include "../../game_graphics/board/abstract_graphics_item.h" #include "../../interface/widgets/menus/tearoff_menu.h" -#include "../dialogs/dlg_create_token.h" +#include "../zones/logic/hand_zone_logic.h" +#include "../zones/logic/pile_zone_logic.h" +#include "../zones/logic/stack_zone_logic.h" +#include "../zones/logic/table_zone_logic.h" #include "menu/player_menu.h" -#include "player_actions.h" #include "player_area.h" #include "player_event_handler.h" #include "player_graphics_item.h" @@ -20,9 +22,7 @@ #include #include #include -#include #include -#include #include #include #include diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index e6efaf854..058fdb510 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -5,14 +5,15 @@ #include "../board/card_item.h" #include "../dialogs/dlg_move_top_cards_until.h" #include "../dialogs/dlg_roll_dice.h" +#include "../zones/hand_zone.h" #include "../zones/logic/view_zone_logic.h" +#include "../zones/table_zone.h" #include "card_menu_action_type.h" #include #include #include #include -#include #include #include #include diff --git a/cockatrice/src/game/player/player_actions.h b/cockatrice/src/game/player/player_actions.h index 9c775691c..e0f86e4fc 100644 --- a/cockatrice/src/game/player/player_actions.h +++ b/cockatrice/src/game/player/player_actions.h @@ -7,6 +7,7 @@ #ifndef COCKATRICE_PLAYER_ACTIONS_H #define COCKATRICE_PLAYER_ACTIONS_H +#include "../dialogs/dlg_create_token.h" #include "event_processing_options.h" #include "player.h" diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp index 972229489..4cf814d86 100644 --- a/cockatrice/src/game/player/player_event_handler.cpp +++ b/cockatrice/src/game/player/player_event_handler.cpp @@ -6,6 +6,7 @@ #include "../board/card_list.h" #include "../zones/view_zone.h" #include "player.h" +#include "player_actions.h" #include #include diff --git a/cockatrice/src/game/player/player_graphics_item.cpp b/cockatrice/src/game/player/player_graphics_item.cpp index 27fbc82c8..30648c926 100644 --- a/cockatrice/src/game/player/player_graphics_item.cpp +++ b/cockatrice/src/game/player/player_graphics_item.cpp @@ -1,7 +1,12 @@ #include "player_graphics_item.h" #include "../../interface/widgets/tabs/tab_game.h" +#include "../board/abstract_card_item.h" #include "../hand_counter.h" +#include "../zones/hand_zone.h" +#include "../zones/pile_zone.h" +#include "../zones/stack_zone.h" +#include "../zones/table_zone.h" PlayerGraphicsItem::PlayerGraphicsItem(Player *_player) : player(_player) { diff --git a/cockatrice/src/game/player/player_info.h b/cockatrice/src/game/player/player_info.h index d60c245e2..2e9b49d6d 100644 --- a/cockatrice/src/game/player/player_info.h +++ b/cockatrice/src/game/player/player_info.h @@ -7,11 +7,6 @@ #ifndef COCKATRICE_PLAYER_INFO_H #define COCKATRICE_PLAYER_INFO_H -#include "../../interface/deck_loader/deck_loader.h" -#include "../zones/hand_zone.h" -#include "../zones/pile_zone.h" -#include "../zones/stack_zone.h" -#include "../zones/table_zone.h" #include "player_target.h" #include diff --git a/cockatrice/src/game/player/player_list_widget.cpp b/cockatrice/src/game/player/player_list_widget.cpp index 7cf7c34a6..9506e0729 100644 --- a/cockatrice/src/game/player/player_list_widget.cpp +++ b/cockatrice/src/game/player/player_list_widget.cpp @@ -4,15 +4,11 @@ #include "../../interface/widgets/server/user/user_context_menu.h" #include "../../interface/widgets/server/user/user_list_manager.h" #include "../../interface/widgets/server/user/user_list_widget.h" -#include "../../interface/widgets/tabs/tab_account.h" #include "../../interface/widgets/tabs/tab_game.h" #include "../../interface/widgets/tabs/tab_supervisor.h" -#include #include -#include #include -#include #include #include #include diff --git a/cockatrice/src/game/player/player_target.h b/cockatrice/src/game/player/player_target.h index aaa0f0610..b60464e12 100644 --- a/cockatrice/src/game/player/player_target.h +++ b/cockatrice/src/game/player/player_target.h @@ -11,7 +11,6 @@ #include "../board/abstract_counter.h" #include "../board/arrow_target.h" -#include #include class Player; diff --git a/cockatrice/src/game/zones/hand_zone.cpp b/cockatrice/src/game/zones/hand_zone.cpp index 860881f38..cc3b44910 100644 --- a/cockatrice/src/game/zones/hand_zone.cpp +++ b/cockatrice/src/game/zones/hand_zone.cpp @@ -5,6 +5,7 @@ #include "../board/card_drag_item.h" #include "../board/card_item.h" #include "../player/player.h" +#include "../player/player_actions.h" #include #include diff --git a/cockatrice/src/game/zones/logic/card_zone_logic.cpp b/cockatrice/src/game/zones/logic/card_zone_logic.cpp index a5dc64b50..bd32eab3e 100644 --- a/cockatrice/src/game/zones/logic/card_zone_logic.cpp +++ b/cockatrice/src/game/zones/logic/card_zone_logic.cpp @@ -2,7 +2,7 @@ #include "../../board/card_item.h" #include "../../player/player.h" -#include "../pile_zone.h" +#include "../../player/player_actions.h" #include "../view_zone.h" #include "view_zone_logic.h" @@ -10,7 +10,6 @@ #include #include #include -#include /** * @param _player the player that the zone belongs to diff --git a/cockatrice/src/game/zones/pile_zone.cpp b/cockatrice/src/game/zones/pile_zone.cpp index cafb29038..2d1390826 100644 --- a/cockatrice/src/game/zones/pile_zone.cpp +++ b/cockatrice/src/game/zones/pile_zone.cpp @@ -3,6 +3,7 @@ #include "../board/card_drag_item.h" #include "../board/card_item.h" #include "../player/player.h" +#include "../player/player_actions.h" #include "logic/pile_zone_logic.h" #include "view_zone.h" diff --git a/cockatrice/src/game/zones/stack_zone.cpp b/cockatrice/src/game/zones/stack_zone.cpp index 0c0501705..820008b27 100644 --- a/cockatrice/src/game/zones/stack_zone.cpp +++ b/cockatrice/src/game/zones/stack_zone.cpp @@ -6,10 +6,10 @@ #include "../board/card_drag_item.h" #include "../board/card_item.h" #include "../player/player.h" +#include "../player/player_actions.h" #include "logic/stack_zone_logic.h" #include -#include #include StackZone::StackZone(StackZoneLogic *_logic, int _zoneHeight, QGraphicsItem *parent) diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index 55aaeaa39..c76514350 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -6,11 +6,11 @@ #include "../board/card_drag_item.h" #include "../board/card_item.h" #include "../player/player.h" +#include "../player/player_actions.h" #include "logic/table_zone_logic.h" #include #include -#include #include #include #include diff --git a/cockatrice/src/game/zones/view_zone.cpp b/cockatrice/src/game/zones/view_zone.cpp index d5eea9dfe..4ae25989e 100644 --- a/cockatrice/src/game/zones/view_zone.cpp +++ b/cockatrice/src/game/zones/view_zone.cpp @@ -3,6 +3,7 @@ #include "../board/card_drag_item.h" #include "../board/card_item.h" #include "../player/player.h" +#include "../player/player_actions.h" #include "logic/view_zone_logic.h" #include @@ -10,7 +11,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/game/zones/view_zone_widget.cpp b/cockatrice/src/game/zones/view_zone_widget.cpp index e2f242883..91abb4663 100644 --- a/cockatrice/src/game/zones/view_zone_widget.cpp +++ b/cockatrice/src/game/zones/view_zone_widget.cpp @@ -6,6 +6,7 @@ #include "../board/card_item.h" #include "../game_scene.h" #include "../player/player.h" +#include "../player/player_actions.h" #include "view_zone.h" #include @@ -16,7 +17,6 @@ #include #include #include -#include #include /** 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 296355940..06a0476c9 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include 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 29bdf26c8..4000fd99a 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader.h @@ -5,7 +5,6 @@ #include "card_picture_loader_worker.h" #include -#include inline Q_LOGGING_CATEGORY(CardPictureLoaderLog, "card_picture_loader"); inline Q_LOGGING_CATEGORY(CardPictureLoaderCardBackCacheFailLog, "card_picture_loader.card_back_cache_fail"); diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.h index 25692f318..44163d1db 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.h @@ -1,8 +1,6 @@ #ifndef PICTURE_LOADER_LOCAL_H #define PICTURE_LOADER_LOCAL_H -#include -#include #include #include diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.h index 2e3233c2d..c9a272b68 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.h @@ -1,11 +1,11 @@ #ifndef PICTURE_LOADER_REQUEST_STATUS_DISPLAY_WIDGET_H #define PICTURE_LOADER_REQUEST_STATUS_DISPLAY_WIDGET_H -#include "card_picture_loader_worker_work.h" - #include #include +#include #include +#include /** * @class CardPictureLoaderRequestStatusDisplayWidget diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_status_bar.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader_status_bar.h index cb469269c..7f1f509db 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_status_bar.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_status_bar.h @@ -6,7 +6,6 @@ #include #include -#include #include /** 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 a2da3c953..30c979cb5 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 @@ -9,7 +9,6 @@ #include #include #include -#include #include static constexpr int MAX_REQUESTS_PER_SEC = 10; diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp index e74103419..d5aac645e 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp @@ -7,11 +7,9 @@ #include #include #include -#include #include #include #include -#include // Card back returned by gatherer when card is not found static const QStringList MD5_BLACKLIST = {"db0c48db407a907c16ade38de048a441"}; diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_to_load.h b/cockatrice/src/interface/card_picture_loader/card_picture_to_load.h index b0a555c86..b57e57644 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_to_load.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_to_load.h @@ -1,7 +1,6 @@ #ifndef PICTURE_TO_LOAD_H #define PICTURE_TO_LOAD_H -#include #include inline Q_LOGGING_CATEGORY(CardPictureToLoadLog, "card_picture_loader.picture_to_load"); diff --git a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp index 20f4b29df..e8beae61e 100644 --- a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/cards/additional_info/mana_cost_widget.cpp b/cockatrice/src/interface/widgets/cards/additional_info/mana_cost_widget.cpp index e5d1127aa..fcbb69c2e 100644 --- a/cockatrice/src/interface/widgets/cards/additional_info/mana_cost_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/additional_info/mana_cost_widget.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp index 7734a61d0..75e075de6 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp @@ -3,7 +3,6 @@ #include "../card_info_picture_with_text_overlay_widget.h" #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h index 4d911edde..73c43abbc 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h @@ -15,7 +15,6 @@ #include #include #include -#include #include class CardGroupDisplayWidget : public QWidget diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp index 5f3a2c2c0..e52242c7b 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp @@ -1,9 +1,6 @@ #include "flat_card_group_display_widget.h" -#include "../card_info_picture_with_text_overlay_widget.h" - #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp index c08e0ed3c..b13a085bd 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp @@ -1,7 +1,6 @@ #include "card_info_display_widget.h" #include "../../../game/board/card_item.h" -#include "../../../main.h" #include "card_info_picture_widget.h" #include "card_info_text_widget.h" @@ -9,7 +8,6 @@ #include #include #include -#include CardInfoDisplayWidget::CardInfoDisplayWidget(const CardRef &cardRef, QWidget *parent, Qt::WindowFlags flags) : QFrame(parent, flags), aspectRatio((qreal)CARD_HEIGHT / (qreal)CARD_WIDTH) diff --git a/cockatrice/src/interface/widgets/cards/card_info_frame_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_frame_widget.cpp index 9fea41076..829292b53 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_frame_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_frame_widget.cpp @@ -10,7 +10,6 @@ #include #include #include -#include CardInfoFrameWidget::CardInfoFrameWidget(QWidget *parent) : QTabWidget(parent), viewTransformationButton(nullptr), cardTextOnly(false) diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp index 9a79bd32f..1024315a1 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp @@ -5,7 +5,6 @@ #include #include -#include /** * @brief Constructs a CardPictureEnlargedWidget. diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.h b/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.h index ec1de166b..10eb32940 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.h @@ -8,7 +8,6 @@ #ifndef CARD_PICTURE_ENLARGED_WIDGET_H #define CARD_PICTURE_ENLARGED_WIDGET_H -#include #include #include diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h index b628e6982..980d700f6 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h @@ -10,7 +10,6 @@ #include "card_info_picture_enlarged_widget.h" #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp index 4cb61deab..2f0aeccfd 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp @@ -3,7 +3,6 @@ #include #include #include -#include /** * @brief Constructs a CardPictureWithTextOverlay widget. 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 f11be533a..775f6fb9e 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/cards/card_size_widget.cpp b/cockatrice/src/interface/widgets/cards/card_size_widget.cpp index 927d9bf5f..14e45743d 100644 --- a/cockatrice/src/interface/widgets/cards/card_size_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_size_widget.cpp @@ -1,6 +1,5 @@ #include "card_size_widget.h" -#include "../../../client/settings/cache_settings.h" #include "../printing_selector/printing_selector.h" #include "../visual_deck_storage/visual_deck_storage_widget.h" diff --git a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp index 5529c35b6..618d7e565 100644 --- a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp @@ -4,7 +4,6 @@ #include "card_group_display_widgets/overlapped_card_group_display_widget.h" #include -#include #include DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent, diff --git a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h index ac53cb704..d5603017c 100644 --- a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h +++ b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h @@ -16,7 +16,6 @@ #include #include -#include #include class DeckCardZoneDisplayWidget : public QWidget diff --git a/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.cpp b/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.cpp index 8f4e1a115..486b3302d 100644 --- a/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.cpp @@ -8,7 +8,6 @@ #include #include #include -#include /** * @brief Constructs a CardPictureWithTextOverlay widget. diff --git a/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.h b/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.h index c4339da22..acb67a49a 100644 --- a/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.h +++ b/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.h @@ -13,6 +13,7 @@ #include #include #include +#include class DeckPreviewCardPictureWidget final : public CardInfoPictureWithTextOverlayWidget { diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h index 29dc9c33c..e2f00bf0d 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h @@ -7,16 +7,13 @@ #ifndef DECK_ANALYTICS_WIDGET_H #define DECK_ANALYTICS_WIDGET_H -#include "../general/layout_containers/flow_widget.h" #include "mana_base_widget.h" #include "mana_curve_widget.h" #include "mana_devotion_widget.h" #include #include -#include #include -#include #include class DeckAnalyticsWidget : public QWidget diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp index 3cafdaeab..63e4255b5 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp @@ -1,16 +1,13 @@ #include "mana_devotion_widget.h" -#include "../../../main.h" #include "../../deck_loader/deck_loader.h" #include "../general/display/banner_widget.h" #include "../general/display/bar_widget.h" -#include #include #include #include #include -#include #include ManaDevotionWidget::ManaDevotionWidget(QWidget *parent, DeckListModel *_deckListModel) 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 b5aa007f0..b4b27e48a 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 @@ -7,10 +7,8 @@ #include "../../pixel_map_generator.h" #include -#include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index 0c89d51f5..852ce7164 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -15,7 +15,6 @@ #include "deck_list_history_manager_widget.h" #include "deck_list_style_proxy.h" -#include #include #include #include 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 4957e24ec..5790131c5 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 @@ -7,7 +7,6 @@ #include #include #include -#include DeckEditorFilterDockWidget::DeckEditorFilterDockWidget(AbstractTabDeckEditor *parent) : QDockWidget(parent), deckEditor(parent) diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.h index de135460f..b61fc72b5 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.h @@ -12,7 +12,6 @@ #include "../../key_signals.h" #include -#include class FilterTreeModel; class AbstractTabDeckEditor; diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h index 23619b91b..0e208ad2b 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp index ed32b356c..0bb0eb1c9 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_connect.cpp @@ -4,9 +4,7 @@ #include #include -#include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp index 629b23787..55b999ee9 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.cpp index 5b120b1e2..349f4ca4c 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.cpp @@ -3,6 +3,7 @@ #include "../../../client/settings/cache_settings.h" #include +#include #include DlgDefaultTagsEditor::DlgDefaultTagsEditor(QWidget *parent) : QDialog(parent) diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.h b/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.h index b552ae737..27af74105 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_default_tags_editor.h @@ -11,7 +11,6 @@ #include #include #include -#include #include class DlgDefaultTagsEditor : public QDialog diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_avatar.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_edit_avatar.cpp index 7330c749b..db5f21701 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_avatar.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_avatar.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.h b/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.h index 311774d11..ee8e45985 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.h @@ -7,7 +7,6 @@ #ifndef DLG_EDITPASSWORD_H #define DLG_EDITPASSWORD_H -#include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp index a47a8221a..fe9cd181c 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp @@ -1,7 +1,6 @@ #include "dlg_edit_tokens.h" #include "../interface/widgets/utility/get_text_with_max.h" -#include "../main.h" #include #include @@ -16,7 +15,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_user.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_edit_user.cpp index c39c87d4a..dcfbc91e9 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_user.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_user.cpp @@ -2,7 +2,6 @@ #include "../../../client/settings/cache_settings.h" -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp index 7ad957e6c..24e9030e0 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp @@ -2,8 +2,6 @@ #include "../../../client/settings/cache_settings.h" -#include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.cpp index 8f79b543f..c33a41bed 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.cpp @@ -2,8 +2,6 @@ #include "../../../client/settings/cache_settings.h" -#include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp index f08347879..e0d855d7e 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp @@ -2,8 +2,6 @@ #include "../../../client/settings/cache_settings.h" -#include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_remote_deck.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_load_remote_deck.cpp index 214ccc588..ffe08bec9 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_remote_deck.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_remote_deck.cpp @@ -1,10 +1,8 @@ #include "dlg_load_remote_deck.h" #include "../interface/widgets/server/remote/remote_decklist_tree_widget.h" -#include "../main.h" #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp index 2d3bc08a4..2cda51f2a 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp @@ -3,11 +3,9 @@ #include "../../../client/settings/cache_settings.h" #include "../interface/card_picture_loader/card_picture_loader.h" #include "../interface/widgets/utility/custom_line_edit.h" -#include "../main.h" #include #include -#include #include #include #include @@ -15,7 +13,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h index c3a3c2455..d7341caff 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h @@ -11,7 +11,6 @@ #include #include #include -#include class CardDatabase; class LineEditUnfocusable; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp index 3493c1047..4be4cdf78 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp @@ -3,13 +3,11 @@ #include "../../../client/settings/cache_settings.h" #include -#include #include #include #include #include #include -#include #include DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent) 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 121ce4c86..893c84512 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 @@ -3,7 +3,6 @@ #include "../../deck_loader/deck_loader.h" #include "../interface/widgets/cards/card_info_picture_widget.h" #include "../interface/widgets/general/layout_containers/flow_widget.h" -#include "dlg_select_set_for_cards.h" #include #include @@ -14,6 +13,7 @@ #include #include #include +#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp index e92593d55..c0b80c6c1 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp @@ -36,7 +36,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp index 0197d3dbf..59330e27e 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.h b/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.h index 46598fb02..3dad7c652 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_tip_of_the_day.h @@ -10,10 +10,8 @@ #include #include #include -#include #include #include -#include #include inline Q_LOGGING_CATEGORY(DlgTipOfTheDayLog, "dlg_tip_of_the_day"); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp index 19182c55b..0a9244dec 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp @@ -5,12 +5,10 @@ #include "../client/network/update/client/release_channel.h" #include "../interface/window_main.h" -#include #include #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/general/display/bar_widget.h b/cockatrice/src/interface/widgets/general/display/bar_widget.h index 672f5b7ac..67ad24995 100644 --- a/cockatrice/src/interface/widgets/general/display/bar_widget.h +++ b/cockatrice/src/interface/widgets/general/display/bar_widget.h @@ -8,8 +8,6 @@ #ifndef BAR_WIDGET_H #define BAR_WIDGET_H -#include -#include #include class BarWidget : public QWidget diff --git a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp index ed4db1fd3..f485e0040 100644 --- a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp +++ b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp @@ -1,7 +1,6 @@ #include "dynamic_font_size_label.h" #define FONT_PRECISION (0.5) -#include #include DynamicFontSizeLabel::DynamicFontSizeLabel(QWidget *parent, Qt::WindowFlags f) : QLabel(parent, f) diff --git a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp index fce4512e8..653b2cd5c 100644 --- a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp +++ b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp @@ -2,7 +2,6 @@ #include "dynamic_font_size_label.h" -#include #include DynamicFontSizePushButton::DynamicFontSizePushButton(QWidget *parent) : QPushButton(parent) diff --git a/cockatrice/src/interface/widgets/general/display/percent_bar_widget.h b/cockatrice/src/interface/widgets/general/display/percent_bar_widget.h index 9ebdb172a..ff7d91363 100644 --- a/cockatrice/src/interface/widgets/general/display/percent_bar_widget.h +++ b/cockatrice/src/interface/widgets/general/display/percent_bar_widget.h @@ -8,7 +8,6 @@ #ifndef PERCENT_BAR_WIDGET_H #define PERCENT_BAR_WIDGET_H -#include #include #include diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index cbf53ee4f..2dfd129d5 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -6,6 +6,7 @@ #include "background_sources.h" #include "home_styled_button.h" +#include #include #include #include diff --git a/cockatrice/src/interface/widgets/general/home_widget.h b/cockatrice/src/interface/widgets/general/home_widget.h index 23598cc5a..1db33446a 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.h +++ b/cockatrice/src/interface/widgets/general/home_widget.h @@ -12,7 +12,6 @@ #include "home_styled_button.h" #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/menus/deck_editor_menu.cpp b/cockatrice/src/interface/widgets/menus/deck_editor_menu.cpp index 775efafbd..23d19abbb 100644 --- a/cockatrice/src/interface/widgets/menus/deck_editor_menu.cpp +++ b/cockatrice/src/interface/widgets/menus/deck_editor_menu.cpp @@ -2,6 +2,7 @@ #include "../../../client/settings/cache_settings.h" #include "../../../client/settings/shortcuts_settings.h" +#include "../tabs/abstract_tab_deck_editor.h" DeckEditorMenu::DeckEditorMenu(AbstractTabDeckEditor *parent) : QMenu(parent), deckEditor(parent) { diff --git a/cockatrice/src/interface/widgets/menus/deck_editor_menu.h b/cockatrice/src/interface/widgets/menus/deck_editor_menu.h index b35f35864..ac8f3b787 100644 --- a/cockatrice/src/interface/widgets/menus/deck_editor_menu.h +++ b/cockatrice/src/interface/widgets/menus/deck_editor_menu.h @@ -7,8 +7,6 @@ #ifndef DECK_EDITOR_MENU_H #define DECK_EDITOR_MENU_H -#include "../interface/widgets/tabs/abstract_tab_deck_editor.h" - #include class AbstractTabDeckEditor; diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h index bf8ade36a..1a8ec8bfd 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h @@ -10,7 +10,6 @@ #include "../../deck_loader/deck_loader.h" #include "card_amount_widget.h" -#include #include #include diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h index 543964a79..1da999c78 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h @@ -9,12 +9,10 @@ #define CARD_AMOUNT_WIDGET_H #include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h" -#include "../../deck_loader/deck_loader.h" #include "../general/display/dynamic_font_size_push_button.h" #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp index a551ac290..f27101b93 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp @@ -8,9 +8,8 @@ #include "printing_selector_card_selection_widget.h" #include "printing_selector_card_sorting_widget.h" -#include +#include #include -#include /** * @brief Constructs a PrintingSelector widget to display and manage card printings. diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h index 88a49f1e0..7130e436c 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h @@ -13,7 +13,6 @@ #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp index 0da80b522..24c45a7e3 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp @@ -1,11 +1,9 @@ #include "printing_selector_card_display_widget.h" -#include "card_amount_widget.h" #include "printing_selector_card_overlay_widget.h" #include "set_name_and_collectors_number_display_widget.h" #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h index 931322117..2f6bd6920 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h @@ -11,9 +11,7 @@ #include "printing_selector_card_overlay_widget.h" #include "set_name_and_collectors_number_display_widget.h" -#include #include -#include #include class PrintingSelectorCardDisplayWidget : public QWidget 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 f2351f509..10df9d53e 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 @@ -8,12 +8,9 @@ #define PRINTING_SELECTOR_CARD_OVERLAY_WIDGET_H #include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h" -#include "../cards/card_info_picture_widget.h" #include "all_zones_card_amount_widget.h" -#include "card_amount_widget.h" #include "set_name_and_collectors_number_display_widget.h" -#include #include class PrintingSelectorCardOverlayWidget : public QWidget diff --git a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp index fb5936cae..25ee58b67 100644 --- a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp +++ b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp @@ -4,7 +4,6 @@ #include #include -#include #include ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent) diff --git a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h index 17a235365..07a0c0b4c 100644 --- a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h +++ b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h @@ -9,7 +9,6 @@ #include "../../../game/player/event_processing_options.h" -#include #include #include diff --git a/cockatrice/src/interface/widgets/server/game_selector.cpp b/cockatrice/src/interface/widgets/server/game_selector.cpp index f4366d8ab..48a12d5b3 100644 --- a/cockatrice/src/interface/widgets/server/game_selector.cpp +++ b/cockatrice/src/interface/widgets/server/game_selector.cpp @@ -10,17 +10,12 @@ #include "games_model.h" #include "user/user_list_manager.h" -#include -#include #include #include #include -#include -#include #include #include #include -#include #include #include #include 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 index c63b52450..daab4d6eb 100644 --- a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp +++ b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent, 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 index f7e0c6fb1..642fdd1c4 100644 --- a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h +++ b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h @@ -4,10 +4,7 @@ #include "../tabs/tab_supervisor.h" #include "games_model.h" -#include -#include #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/server/games_model.cpp b/cockatrice/src/interface/widgets/server/games_model.cpp index dfb41fd29..cdac71b28 100644 --- a/cockatrice/src/interface/widgets/server/games_model.cpp +++ b/cockatrice/src/interface/widgets/server/games_model.cpp @@ -7,10 +7,7 @@ #include "user/user_list_widget.h" #include -#include #include -#include -#include #include #include diff --git a/cockatrice/src/interface/widgets/server/games_model.h b/cockatrice/src/interface/widgets/server/games_model.h index 03602a74e..56c806fb6 100644 --- a/cockatrice/src/interface/widgets/server/games_model.h +++ b/cockatrice/src/interface/widgets/server/games_model.h @@ -3,11 +3,9 @@ #include "game_type_map.h" -#include #include #include #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/server/handle_public_servers.cpp b/cockatrice/src/interface/widgets/server/handle_public_servers.cpp index 6a8754a79..f37c957a4 100644 --- a/cockatrice/src/interface/widgets/server/handle_public_servers.cpp +++ b/cockatrice/src/interface/widgets/server/handle_public_servers.cpp @@ -3,8 +3,6 @@ #include "../../../client/settings/cache_settings.h" #include -#include -#include #include #include diff --git a/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.h b/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.h index 6836ff435..8254eb57b 100644 --- a/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.h +++ b/cockatrice/src/interface/widgets/server/remote/remote_decklist_tree_widget.h @@ -8,7 +8,6 @@ #ifndef REMOTEDECKLIST_TREEWIDGET_H #define REMOTEDECKLIST_TREEWIDGET_H -#include #include #include diff --git a/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.h b/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.h index f83cce46a..6d81aefca 100644 --- a/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.h +++ b/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.h @@ -9,7 +9,6 @@ #ifndef REMOTEREPLAYLIST_TREEWIDGET_H #define REMOTEREPLAYLIST_TREEWIDGET_H -#include #include #include #include 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 cb7d2eca9..7083ac899 100644 --- a/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp @@ -12,8 +12,6 @@ #include #include -#include -#include #include #include #include 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 b6d65c5fa..852743479 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp @@ -5,7 +5,6 @@ #include "../../interface/widgets/tabs/tab_supervisor.h" #include "../game_selector.h" #include "user_context_menu.h" -#include "user_list_manager.h" #include #include @@ -14,21 +13,15 @@ #include #include #include -#include #include #include #include #include #include #include -#include #include #include -#include #include -#include -#include -#include #include BanDialog::BanDialog(const ServerInfo_User &info, QWidget *parent) : QDialog(parent) 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 89611aa48..6d05e656d 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -14,7 +14,6 @@ #include "../client/network/interfaces/tapped_out_interface.h" #include "../interface/card_picture_loader/card_picture_loader.h" #include "../interface/pixel_map_generator.h" -#include "../interface/widgets/cards/card_info_frame_widget.h" #include "../interface/widgets/dialogs/dlg_load_deck.h" #include "../interface/widgets/dialogs/dlg_load_deck_from_clipboard.h" #include "../interface/widgets/dialogs/dlg_load_deck_from_website.h" @@ -22,15 +21,11 @@ #include #include -#include #include -#include #include -#include #include #include #include -#include #include #include #include @@ -38,7 +33,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp index f071949f2..145a3fca0 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp @@ -2,7 +2,6 @@ #include #include -#include #include void EdhrecApiResponseArchidektLink::fromJson(const QJsonObject &json) diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.h index 236e36305..d265c164d 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.h @@ -1,7 +1,6 @@ #ifndef ARCHIDEKTENTRY_H #define ARCHIDEKTENTRY_H -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.h index 1a60b95d0..70d37cbae 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.h @@ -6,7 +6,6 @@ #include "../commander/edhrec_commander_api_response_average_deck_statistics.h" #include "edhrec_deck_api_response.h" -#include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp index 73c26050d..ceafb719b 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp @@ -6,7 +6,6 @@ #include #include #include -#include void EdhrecDeckApiResponse::fromJson(const QJsonArray &json) { diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h index b362819df..96205e149 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h @@ -9,12 +9,6 @@ #include "../../../../../../deck_loader/deck_loader.h" -#include -#include -#include -#include -#include - class EdhrecDeckApiResponse { public: diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h index df6c2eaeb..07b53dd97 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.h @@ -10,8 +10,6 @@ #include "edhrec_api_response_card_list.h" #include "edhrec_commander_api_response_commander_details.h" -#include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.h index 068945a30..15691fbe4 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.h @@ -10,7 +10,6 @@ #include "edhrec_api_response_card_details.h" #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h index 09d0667c7..629eb8273 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.h @@ -10,7 +10,6 @@ #include "../card_prices/edhrec_api_response_card_prices.h" #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response.h index 9e6629142..64f2fd7cd 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response.h @@ -5,7 +5,6 @@ #include "../cards/edhrec_api_response_card_container.h" #include "edhrec_commander_api_response_average_deck_statistics.h" -#include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.h index a9a6316e3..fb89c0b12 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.h @@ -9,7 +9,6 @@ #include "../cards/edhrec_api_response_card_container.h" -#include #include class EdhrecTopCardsApiResponse diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.h index 1e99e90f0..26c5afedb 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.h @@ -9,7 +9,6 @@ #include "../cards/edhrec_api_response_card_container.h" -#include #include class EdhrecTopCommandersApiResponse diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.h index 382e2899e..48219893a 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.h @@ -9,7 +9,6 @@ #include "../cards/edhrec_api_response_card_container.h" -#include #include class EdhrecTopTagsApiResponse diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.h index 84b401eb7..64228b85d 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.h @@ -9,7 +9,6 @@ #include "../../api_response/top_tags/edhrec_top_tags_api_response.h" -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp index 4c740e5d1..93dbd6bc5 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp @@ -4,7 +4,6 @@ #include "display/commander/edhrec_commander_api_response_display_widget.h" #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp index 0dcd5d0ae..06ce88f06 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.h index 3d4c2fa00..04cf03fc1 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.h @@ -8,7 +8,6 @@ #define TAB_EDHREC_MAIN_H #include "../../interface/widgets/cards/card_size_widget.h" -#include "../../interface/widgets/general/layout_containers/flow_widget.h" #include "../../interface/widgets/quick_settings/settings_button_widget.h" #include "../../tab.h" #include "display/commander/edhrec_commander_api_response_display_widget.h" diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index 88f08eae1..4fa12259c 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -1,39 +1,22 @@ #include "tab_deck_editor.h" #include "../../../client/settings/cache_settings.h" -#include "../client/network/interfaces/tapped_out_interface.h" #include "../filters/filter_builder.h" -#include "../filters/filter_tree_model.h" #include "../interface/pixel_map_generator.h" #include "../interface/widgets/cards/card_info_frame_widget.h" #include "../interface/widgets/deck_editor/deck_editor_filter_dock_widget.h" -#include "../interface/widgets/dialogs/dlg_load_deck.h" -#include "../interface/widgets/dialogs/dlg_load_deck_from_clipboard.h" #include "../interface/widgets/menus/deck_editor_menu.h" #include "tab_supervisor.h" #include -#include #include -#include #include #include #include -#include #include -#include -#include #include -#include -#include #include -#include -#include #include -#include -#include -#include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp index b483f0616..854990e19 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index 8f187575c..2f83ba2e6 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -13,11 +13,9 @@ #include "../game/player/player.h" #include "../game/player/player_list_widget.h" #include "../game/replay.h" -#include "../game/zones/card_zone.h" #include "../interface/card_picture_loader/card_picture_loader.h" #include "../interface/widgets/cards/card_info_frame_widget.h" #include "../interface/widgets/dialogs/dlg_create_game.h" -#include "../interface/widgets/replay/replay_timeline_widget.h" #include "../interface/widgets/server/user/user_list_manager.h" #include "../interface/widgets/utility/line_edit_completer.h" #include "../interface/window_main.h" @@ -28,15 +26,12 @@ #include #include #include -#include #include #include #include #include -#include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.h b/cockatrice/src/interface/widgets/tabs/tab_game.h index e500fefbd..b6fe5b51c 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.h +++ b/cockatrice/src/interface/widgets/tabs/tab_game.h @@ -14,13 +14,11 @@ #include "../game/player/player.h" #include "../interface/widgets/menus/tearoff_menu.h" #include "../interface/widgets/replay/replay_manager.h" -#include "../interface/widgets/visual_deck_storage/visual_deck_storage_widget.h" #include "tab.h" #include #include #include -#include class ServerInfo_PlayerProperties; class TabbedDeckViewContainer; diff --git a/cockatrice/src/interface/widgets/tabs/tab_home.cpp b/cockatrice/src/interface/widgets/tabs/tab_home.cpp index a44945315..7a79b1785 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_home.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_home.cpp @@ -1,8 +1,5 @@ #include "tab_home.h" -#include -#include - TabHome::TabHome(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client) { homeWidget = new HomeWidget(this, tabSupervisor); diff --git a/cockatrice/src/interface/widgets/tabs/tab_home.h b/cockatrice/src/interface/widgets/tabs/tab_home.h index fbadc99aa..c40dfc269 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_home.h +++ b/cockatrice/src/interface/widgets/tabs/tab_home.h @@ -10,9 +10,7 @@ #include "../interface/widgets/general/home_widget.h" #include "tab.h" -#include #include -#include class AbstractClient; diff --git a/cockatrice/src/interface/widgets/tabs/tab_logs.cpp b/cockatrice/src/interface/widgets/tabs/tab_logs.cpp index 86e10b755..e4f699160 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_logs.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_logs.cpp @@ -1,20 +1,18 @@ #include "tab_logs.h" -#include "../interface/widgets/dialogs/dlg_manage_sets.h" #include "../interface/widgets/utility/custom_line_edit.h" #include -#include +#include +#include #include -#include #include #include #include #include #include #include -#include -#include +#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/tab_logs.h b/cockatrice/src/interface/widgets/tabs/tab_logs.h index 7cbf6aedc..a73af651b 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_logs.h +++ b/cockatrice/src/interface/widgets/tabs/tab_logs.h @@ -13,7 +13,6 @@ class AbstractClient; class LineEditUnfocusable; - class QGroupBox; class QPushButton; class QSpinBox; diff --git a/cockatrice/src/interface/widgets/tabs/tab_replays.cpp b/cockatrice/src/interface/widgets/tabs/tab_replays.cpp index 8d7081407..2db9e83c5 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_replays.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_replays.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/tab_room.h b/cockatrice/src/interface/widgets/tabs/tab_room.h index c5d0a950a..67d9afc86 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_room.h +++ b/cockatrice/src/interface/widgets/tabs/tab_room.h @@ -13,7 +13,6 @@ #include #include -#include #include class UserListProxy; diff --git a/cockatrice/src/interface/widgets/tabs/tab_server.cpp b/cockatrice/src/interface/widgets/tabs/tab_server.cpp index bd4fa2be3..aa52b4b1a 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_server.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_server.cpp @@ -3,17 +3,12 @@ #include "../interface/widgets/server/user/user_list_widget.h" #include "tab_supervisor.h" -#include -#include #include #include #include -#include #include #include #include -#include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp index 902b1f589..18a98f5ec 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp @@ -31,7 +31,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h index 7a3b9cbb9..9fd00f584 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h @@ -15,11 +15,9 @@ #include "api/edhrec/tab_edhrec_main.h" #include "tab_visual_database_display.h" #include "visual_deck_editor/tab_deck_editor_visual.h" -#include "visual_deck_editor/tab_deck_editor_visual_tab_widget.h" #include "visual_deck_storage/tab_deck_storage_visual.h" #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.h b/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.h index b01f3ab06..66f38fb3d 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.h +++ b/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.h @@ -10,8 +10,6 @@ #include "../interface/widgets/visual_database_display/visual_database_display_widget.h" #include "tab.h" -#include - class TabVisualDatabaseDisplay : public Tab { Q_OBJECT diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 7a811fd28..1540df0bf 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -1,7 +1,6 @@ #include "tab_deck_editor_visual.h" #include "../../../../client/settings/cache_settings.h" -#include "../../client/network/interfaces/deck_stats_interface.h" #include "../../filters/filter_builder.h" #include "../../interface/pixel_map_generator.h" #include "../../interface/widgets/cards/card_info_frame_widget.h" @@ -12,27 +11,22 @@ #include "tab_deck_editor_visual_tab_widget.h" #include -#include #include #include #include #include -#include #include #include #include -#include #include #include #include #include #include #include -#include #include #include #include -#include /** * @brief Constructs the TabDeckEditorVisual instance. diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp index bfb498909..975c011dc 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp @@ -1,12 +1,9 @@ #include "tab_deck_storage_visual.h" -#include "../../interface/widgets/cards/deck_preview_card_picture_widget.h" #include "../../interface/widgets/visual_deck_storage/visual_deck_storage_widget.h" #include "../tab_supervisor.h" #include -#include -#include #include TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor) diff --git a/cockatrice/src/interface/widgets/utility/get_text_with_max.cpp b/cockatrice/src/interface/widgets/utility/get_text_with_max.cpp index 73745be02..134352eb3 100644 --- a/cockatrice/src/interface/widgets/utility/get_text_with_max.cpp +++ b/cockatrice/src/interface/widgets/utility/get_text_with_max.cpp @@ -1,5 +1,7 @@ #include "get_text_with_max.h" +#include + QString getTextWithMax(QWidget *parent, const QString &title, const QString &label, diff --git a/cockatrice/src/interface/widgets/utility/get_text_with_max.h b/cockatrice/src/interface/widgets/utility/get_text_with_max.h index 894eb80f2..923d6f427 100644 --- a/cockatrice/src/interface/widgets/utility/get_text_with_max.h +++ b/cockatrice/src/interface/widgets/utility/get_text_with_max.h @@ -7,7 +7,8 @@ #ifndef GETTEXTWITHMAX_H #define GETTEXTWITHMAX_H -#include +#include +#include #include QString getTextWithMax(QWidget *parent, diff --git a/cockatrice/src/interface/widgets/utility/line_edit_completer.cpp b/cockatrice/src/interface/widgets/utility/line_edit_completer.cpp index 7820ad91b..389e69d57 100644 --- a/cockatrice/src/interface/widgets/utility/line_edit_completer.cpp +++ b/cockatrice/src/interface/widgets/utility/line_edit_completer.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/utility/line_edit_completer.h b/cockatrice/src/interface/widgets/utility/line_edit_completer.h index 23e4d4aba..7b3756e3c 100644 --- a/cockatrice/src/interface/widgets/utility/line_edit_completer.h +++ b/cockatrice/src/interface/widgets/utility/line_edit_completer.h @@ -9,9 +9,7 @@ #include "custom_line_edit.h" -#include #include -#include class LineEditCompleter : public LineEditUnfocusable { diff --git a/cockatrice/src/interface/widgets/utility/sequence_edit.cpp b/cockatrice/src/interface/widgets/utility/sequence_edit.cpp index 7e14d4335..a34f39042 100644 --- a/cockatrice/src/interface/widgets/utility/sequence_edit.cpp +++ b/cockatrice/src/interface/widgets/utility/sequence_edit.cpp @@ -5,7 +5,6 @@ #include #include #include -#include SequenceEdit::SequenceEdit(const QString &_shortcutName, QWidget *parent) : QWidget(parent) { diff --git a/cockatrice/src/interface/widgets/utility/sequence_edit.h b/cockatrice/src/interface/widgets/utility/sequence_edit.h index a146aa752..a5fe1a1c6 100644 --- a/cockatrice/src/interface/widgets/utility/sequence_edit.h +++ b/cockatrice/src/interface/widgets/utility/sequence_edit.h @@ -8,7 +8,6 @@ #define SEQUENCEEDIT_H #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp index d4947d6c5..3ca709071 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp @@ -2,7 +2,6 @@ #include "../cards/additional_info/mana_symbol_widget.h" -#include #include #include diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp index 062774969..4d8eb5c57 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp @@ -5,8 +5,7 @@ #include #include -#include -#include +#include #include VisualDatabaseDisplayFilterSaveLoadWidget::VisualDatabaseDisplayFilterSaveLoadWidget(QWidget *parent, diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h index 7dea3fd01..48e6f9498 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h @@ -10,10 +10,8 @@ #include "../../../filters/filter_tree_model.h" #include "../general/layout_containers/flow_widget.h" -#include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h index 34c6ddac2..27c61dffc 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.h index b98d83523..76d3ec29e 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.h @@ -14,7 +14,6 @@ #include #include #include -#include #include class VisualDatabaseDisplayNameFilterWidget : public QWidget diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h index ea006c88e..b4362d1db 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h @@ -15,7 +15,6 @@ #include #include #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h index fdddbed41..c02db29be 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h @@ -13,8 +13,6 @@ #include #include #include -#include -#include #include class VisualDatabaseDisplaySubTypeFilterWidget : public QWidget 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 7171d93b2..cc0fc8033 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 @@ -20,7 +20,6 @@ #include #include #include -#include #include VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h index cc51cb003..5cab2ca9b 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h @@ -10,7 +10,6 @@ #include "../../../filters/filter_tree_model.h" #include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h" #include "../../key_signals.h" -#include "../../layouts/flow_layout.h" #include "../cards/card_info_picture_with_text_overlay_widget.h" #include "../cards/card_size_widget.h" #include "../general/layout_containers/flow_widget.h" @@ -24,11 +23,9 @@ #include "visual_database_display_sub_type_filter_widget.h" #include -#include #include #include #include -#include #include #include #include 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 c1e5ead3a..5acad9df1 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 @@ -7,7 +7,6 @@ #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" -#include "../general/layout_containers/overlap_control_widget.h" #include "../tabs/visual_deck_editor/tab_deck_editor_visual.h" #include "../tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h" diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h index e8ededc77..524762d89 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h @@ -9,7 +9,6 @@ #include "../cards/card_info_picture_with_text_overlay_widget.h" #include "../cards/card_size_widget.h" -#include "../general/layout_containers/flow_widget.h" #include "../general/layout_containers/overlap_control_widget.h" #include "../quick_settings/settings_button_widget.h" diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp index ef59e90ff..575632724 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp @@ -4,7 +4,6 @@ #include "deck_preview_widget.h" #include -#include DeckPreviewColorIdentityFilterWidget::DeckPreviewColorIdentityFilterWidget(VisualDeckStorageWidget *parent) : QWidget(parent), layout(new QHBoxLayout(this)) diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h index 0e4a5837c..551dbb35c 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h @@ -9,9 +9,7 @@ #include "../visual_deck_storage_widget.h" -#include #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp index b8425e842..706444545 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp @@ -11,7 +11,6 @@ #include #include -#include #include DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList) diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp index a221442ba..fdf4b2a28 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp @@ -2,7 +2,6 @@ #include "../../../../client/settings/cache_settings.h" #include "../../../../interface/widgets/tabs/abstract_tab_deck_editor.h" -#include "deck_preview_tag_dialog.h" #include #include diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.h index bb39943b6..c0fa86d19 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.h @@ -9,10 +9,6 @@ #include "deck_preview_deck_tags_display_widget.h" -#include -#include -#include - class DeckPreviewTagAdditionWidget : public QWidget { Q_OBJECT diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp index df7f9738d..e8a399fe5 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp @@ -11,7 +11,6 @@ #include #include #include -#include DeckPreviewTagDialog::DeckPreviewTagDialog(const QStringList &knownTags, const QStringList &_activeTags, diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h index f23040a81..59b330a65 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h @@ -8,13 +8,10 @@ #ifndef DECK_PREVIEW_TAG_DIALOG_H #define DECK_PREVIEW_TAG_DIALOG_H -#include #include #include #include #include -#include -#include #include class DeckPreviewTagDialog : public QDialog 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 b330b0c4c..2735b4856 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 @@ -6,6 +6,7 @@ #include "deck_preview_deck_tags_display_widget.h" #include +#include #include #include #include diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp index c5758c5a6..883682749 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp @@ -2,6 +2,7 @@ #include "../../../client/settings/cache_settings.h" #include "deck_preview/deck_preview_widget.h" +#include "visual_deck_storage_widget.h" #include #include diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h index f735d0ad3..d76fb0497 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h @@ -9,11 +9,8 @@ #include "../general/display/banner_widget.h" #include "../general/layout_containers/flow_widget.h" -#include "visual_deck_storage_widget.h" - -#include -#include +class VisualDeckStorageWidget; class VisualDeckStorageFolderDisplayWidget : public QWidget { Q_OBJECT 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 dbd7929c5..70b438232 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 @@ -6,6 +6,7 @@ #include "../../pixel_map_generator.h" #include +#include /** * @brief Constructs a PrintingSelectorCardSearchWidget for searching cards by set name or set code. diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.h index eff238e7e..67d260b21 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.h @@ -7,11 +7,10 @@ #ifndef VISUAL_DECK_STORAGE_SEARCH_WIDGET_H #define VISUAL_DECK_STORAGE_SEARCH_WIDGET_H -#include "visual_deck_storage_widget.h" +#include "deck_preview/deck_preview_widget.h" #include #include -#include #include class VisualDeckStorageWidget; diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp index ad49e0065..cbc78ba50 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp @@ -2,6 +2,8 @@ #include "../../../client/settings/cache_settings.h" +#include + /** * @brief Constructs a PrintingSelectorCardSortWidget for searching cards by set name or set code. * diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp index 4e78a67c4..1686de054 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp @@ -1,12 +1,10 @@ #include "visual_deck_storage_tag_filter_widget.h" #include "../general/layout_containers/flow_widget.h" -#include "deck_preview/deck_preview_tag_addition_widget.h" #include "deck_preview/deck_preview_tag_display_widget.h" -#include "deck_preview/deck_preview_widget.h" +#include "visual_deck_storage_widget.h" #include -#include VisualDeckStorageTagFilterWidget::VisualDeckStorageTagFilterWidget(VisualDeckStorageWidget *_parent) : QWidget(_parent), parent(_parent) diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h index f5f160b55..ada94a244 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h @@ -7,7 +7,7 @@ #ifndef VISUAL_DECK_STORAGE_TAG_FILTER_WIDGET_H #define VISUAL_DECK_STORAGE_TAG_FILTER_WIDGET_H -#include "visual_deck_storage_widget.h" +#include "deck_preview/deck_preview_widget.h" #include 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 d9cf74e7f..bfc425993 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 @@ -11,7 +11,6 @@ #include #include #include -#include #include #include diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h index 69e70f969..b205cb67e 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h @@ -9,18 +9,14 @@ #include "../../deck_loader/deck_loader.h" #include "../cards/card_size_widget.h" -#include "../general/layout_containers/flow_widget.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" #include "visual_deck_storage_folder_display_widget.h" #include "visual_deck_storage_quick_settings_widget.h" #include "visual_deck_storage_search_widget.h" #include "visual_deck_storage_sort_widget.h" #include "visual_deck_storage_tag_filter_widget.h" -#include -#include #include class QSpinBox; diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 622c04a08..4c9922c80 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -54,7 +54,6 @@ #include #include #include -#include #include #include #include diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h index 477d7b07c..75b0f5062 100644 --- a/cockatrice/src/interface/window_main.h +++ b/cockatrice/src/interface/window_main.h @@ -26,10 +26,8 @@ #define WINDOW_H #include -#include #include #include -#include #include #include #include diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index f20dab6fb..29dacee74 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -20,7 +20,6 @@ #include "main.h" -#include "QtNetwork/QNetworkInterface" #include "client/network/update/card_spoiler/spoiler_background_updater.h" #include "client/settings/cache_settings.h" #include "client/sound_engine.h" @@ -36,16 +35,11 @@ #include #include #include -#include -#include #include #include #include -#include #include -#include #include -#include #include QTranslator *translator, *qtTranslator; diff --git a/cockatrice/src/main.h b/cockatrice/src/main.h index 487abc0ea..b6e086744 100644 --- a/cockatrice/src/main.h +++ b/cockatrice/src/main.h @@ -8,7 +8,6 @@ #define MAIN_H #include -#include inline Q_LOGGING_CATEGORY(MainLog, "main"); inline Q_LOGGING_CATEGORY(QtTranslatorDebug, "qt_translator"); diff --git a/libcockatrice_card/libcockatrice/card/card_info.h b/libcockatrice_card/libcockatrice/card/card_info.h index 92b7f3121..7bd51356c 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.h +++ b/libcockatrice_card/libcockatrice/card/card_info.h @@ -10,7 +10,6 @@ #include #include #include -#include #include #include diff --git a/libcockatrice_card/libcockatrice/card/card_info_comparator.h b/libcockatrice_card/libcockatrice/card/card_info_comparator.h index 2b101701c..b8d9c47e6 100644 --- a/libcockatrice_card/libcockatrice/card/card_info_comparator.h +++ b/libcockatrice_card/libcockatrice/card/card_info_comparator.h @@ -9,7 +9,6 @@ #include "card_info.h" -#include #include #include diff --git a/libcockatrice_card/libcockatrice/card/database/card_database.cpp b/libcockatrice_card/libcockatrice/card/database/card_database.cpp index 57b3ce06e..2e977901f 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database.cpp @@ -1,12 +1,10 @@ #include "card_database.h" #include "../relation/card_relation.h" -#include "parser/cockatrice_xml_3.h" #include "parser/cockatrice_xml_4.h" #include #include -#include #include #include #include diff --git a/libcockatrice_card/libcockatrice/card/database/card_database.h b/libcockatrice_card/libcockatrice/card/database/card_database.h index cb46d6d00..6028cce86 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database.h +++ b/libcockatrice_card/libcockatrice/card/database/card_database.h @@ -10,10 +10,8 @@ #include #include #include -#include #include #include -#include #include inline Q_LOGGING_CATEGORY(CardDatabaseLog, "card_database"); diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_loader.h b/libcockatrice_card/libcockatrice/card/database/card_database_loader.h index 631ba685f..f4e690428 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_loader.h +++ b/libcockatrice_card/libcockatrice/card/database/card_database_loader.h @@ -4,7 +4,6 @@ #include #include #include -#include #include #include diff --git a/libcockatrice_card/libcockatrice/card/printing/printing_info.h b/libcockatrice_card/libcockatrice/card/printing/printing_info.h index e0978bd55..43d82a9cb 100644 --- a/libcockatrice_card/libcockatrice/card/printing/printing_info.h +++ b/libcockatrice_card/libcockatrice/card/printing/printing_info.h @@ -5,7 +5,6 @@ #include #include -#include #include class PrintingInfo; diff --git a/libcockatrice_card/libcockatrice/card/set/card_set_list.h b/libcockatrice_card/libcockatrice/card/set/card_set_list.h index 21b4a55d4..81d605374 100644 --- a/libcockatrice_card/libcockatrice/card/set/card_set_list.h +++ b/libcockatrice_card/libcockatrice/card/set/card_set_list.h @@ -4,7 +4,6 @@ #include "card_set.h" #include -#include /** * @class CardSetList diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_node.h index 31e7b165e..877705705 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_node.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_node.h @@ -17,7 +17,6 @@ #ifndef COCKATRICE_ABSTRACT_DECK_LIST_NODE_H #define COCKATRICE_ABSTRACT_DECK_LIST_NODE_H -#include #include /** diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index e26e55e08..f06652d7c 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index fd3b4677e..f6bd54382 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -16,7 +16,6 @@ #include #include #include -#include #include #include diff --git a/libcockatrice_filters/libcockatrice/filters/filter_card.h b/libcockatrice_filters/libcockatrice/filters/filter_card.h index 344ae5224..4392a4630 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_card.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_card.h @@ -9,7 +9,6 @@ #include #include -#include class CardFilter : public QObject { diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.h b/libcockatrice_filters/libcockatrice/filters/filter_tree.h index 7e0f211ef..af91d2dbf 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.h @@ -10,7 +10,6 @@ #include "filter_card.h" #include -#include #include #include #include diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h index 8f8b7e62e..a4f3184cc 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h @@ -1,6 +1,5 @@ #ifndef COCKATRICE_INTERFACE_CARD_SET_PRIORITY_CONTROLLER_H #define COCKATRICE_INTERFACE_CARD_SET_PRIORITY_CONTROLLER_H -#include class ICardSetPriorityController { diff --git a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h index e7abb2642..0c5994a3a 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h @@ -8,8 +8,6 @@ #ifndef COCKATRICE_CARD_DATABASE_DISPLAY_MODEL_H #define COCKATRICE_CARD_DATABASE_DISPLAY_MODEL_H -#include -#include #include #include #include diff --git a/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h b/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h index 165737e95..dc3be5a94 100644 --- a/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h +++ b/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h @@ -8,7 +8,6 @@ #define ABSTRACTCLIENT_H #include -#include #include #include #include diff --git a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h index 939e88dfc..15e3e8ef5 100644 --- a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h +++ b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h @@ -10,7 +10,6 @@ #include "../abstract/abstract_client.h" #include -#include #include #include #include diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.cpp index c6a29b275..493b8e966 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.cpp @@ -4,18 +4,10 @@ #include "../server_abstractuserinterface.h" #include "../server_database_interface.h" #include "../server_room.h" -#include "server_arrow.h" #include "server_card.h" -#include "server_cardzone.h" -#include "server_counter.h" #include "server_game.h" #include "server_player.h" -#include -#include -#include -#include -#include #include #include #include @@ -49,42 +41,13 @@ #include #include #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include #include -#include #include -#include -#include -#include -#include -#include #include -#include -#include #include #include -#include -#include #include Server_AbstractParticipant::Server_AbstractParticipant(Server_Game *_game, 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 4a32b6fef..40fe84aa1 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 @@ -4,7 +4,6 @@ #include "../serverinfo_user_container.h" #include "server_abstract_participant.h" -#include #include #include diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.h index 0dc368a80..77fea54ca 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.h @@ -20,7 +20,6 @@ #ifndef SERVER_CARDZONE_H #define SERVER_CARDZONE_H -#include #include #include #include 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 b0dc7fc5b..5ec7b3c5d 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -50,7 +49,6 @@ #include #include #include -#include Server_Game::Server_Game(const ServerInfo_User &_creatorInfo, int _gameId, diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp index b840a172c..b59d89ed8 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp @@ -4,7 +4,6 @@ #include "../server_abstractuserinterface.h" #include "../server_database_interface.h" #include "../server_room.h" -#include "server_arrow.h" #include "server_card.h" #include "server_cardzone.h" #include "server_counter.h" @@ -16,66 +15,28 @@ #include #include #include -#include #include #include #include -#include #include -#include #include #include -#include #include -#include -#include -#include -#include #include -#include -#include #include #include -#include -#include -#include -#include -#include #include -#include -#include #include #include #include #include -#include -#include -#include #include -#include #include -#include #include -#include -#include -#include -#include #include -#include #include -#include -#include #include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include #include #include #include diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server.cpp index 2f1232b8f..a5a74c54c 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server.h b/libcockatrice_network/libcockatrice/network/server/remote/server.h index f9dada801..ab57fac4e 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server.h @@ -3,17 +3,13 @@ #include "server_player_reference.h" -#include #include #include #include #include -#include #include #include -#include #include -#include class Server_DatabaseInterface; class Server_Game; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp index e228f0128..f9b61ab48 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp @@ -9,10 +9,8 @@ #include #include -#include #include #include -#include void Server_AbstractUserInterface::sendProtocolItemByType(ServerMessage::MessageType type, const ::google::protobuf::Message &item) diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.h b/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.h index 10a2aee62..b11260003 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.h @@ -5,7 +5,6 @@ #include #include -#include #include #include diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.h b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.h index 9f3b1f4e7..0d05b91c8 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.h @@ -5,7 +5,6 @@ #include "server_abstractuserinterface.h" #include -#include #include #include diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_room.h b/libcockatrice_network/libcockatrice/network/server/remote/server_room.h index 34b9063e9..3d9988f20 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_room.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_room.h @@ -3,12 +3,10 @@ #include "serverinfo_user_container.h" -#include #include #include #include #include -#include #include #include diff --git a/libcockatrice_protocol/libcockatrice/protocol/featureset.cpp b/libcockatrice_protocol/libcockatrice/protocol/featureset.cpp index d9f253ab9..1b08c4040 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/featureset.cpp +++ b/libcockatrice_protocol/libcockatrice/protocol/featureset.cpp @@ -1,6 +1,5 @@ #include "featureset.h" -#include #include FeatureSet::FeatureSet() diff --git a/libcockatrice_protocol/libcockatrice/protocol/featureset.h b/libcockatrice_protocol/libcockatrice/protocol/featureset.h index 424d0b975..32625d5f8 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/featureset.h +++ b/libcockatrice_protocol/libcockatrice/protocol/featureset.h @@ -3,7 +3,6 @@ #include #include -#include #include class FeatureSet : public QObject diff --git a/libcockatrice_settings/libcockatrice/settings/card_database_settings.h b/libcockatrice_settings/libcockatrice/settings/card_database_settings.h index 8d45efbec..9a176a99b 100644 --- a/libcockatrice_settings/libcockatrice/settings/card_database_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/card_database_settings.h @@ -10,9 +10,6 @@ #include "settings_manager.h" -#include -#include -#include #include class CardDatabaseSettings : public SettingsManager, public ICardSetPriorityController diff --git a/libcockatrice_settings/libcockatrice/settings/download_settings.h b/libcockatrice_settings/libcockatrice/settings/download_settings.h index a67a7f9c7..ed3634ea1 100644 --- a/libcockatrice_settings/libcockatrice/settings/download_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/download_settings.h @@ -9,8 +9,6 @@ #include "settings_manager.h" -#include - class DownloadSettings : public SettingsManager { Q_OBJECT diff --git a/libcockatrice_settings/libcockatrice/settings/settings_manager.h b/libcockatrice_settings/libcockatrice/settings/settings_manager.h index a72094b4e..3592d8f8c 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_manager.h +++ b/libcockatrice_settings/libcockatrice/settings/settings_manager.h @@ -7,7 +7,6 @@ #ifndef SETTINGSMANAGER_H #define SETTINGSMANAGER_H -#include #include #include #include diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index be1d1ff19..68080404c 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include From 8ee7163014053c8d15e26f955e8cccad2829fce2 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sat, 29 Nov 2025 18:56:06 +0100 Subject: [PATCH 033/325] [Printing Selector] Notify deck editor about history changes. (#6364) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 44 minutes Took 2 minutes Co-authored-by: Lukas Brübach --- .../printing_selector/card_amount_widget.cpp | 55 +++++++++++++++++-- .../printing_selector/card_amount_widget.h | 4 ++ 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index 416de18dc..80258be61 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -61,6 +61,10 @@ CardAmountWidget::CardAmountWidget(QWidget *parent, // Connect slider for dynamic font size adjustment connect(cardSizeSlider, &QSlider::valueChanged, this, &CardAmountWidget::adjustFontSize); + + if (deckEditor) { + connect(this, &CardAmountWidget::deckModified, deckEditor, &AbstractTabDeckEditor::onDeckHistorySaveRequested); + } } /** @@ -140,24 +144,52 @@ void CardAmountWidget::updateCardCount() */ void CardAmountWidget::addPrinting(const QString &zone) { + int addedCount = 1; + // Check if we will need to add extra copies due to replacing copies without providerIds + QModelIndex existing = deckModel->findCard(rootCard.getName(), zone); + int extraCopies = 0; + bool replacingProviderless = false; + + if (existing.isValid()) { + QString providerId = deckModel->data(existing.sibling(existing.row(), 4), Qt::DisplayRole).toString(); + if (providerId.isEmpty()) { + int amount = deckModel->data(existing, Qt::DisplayRole).toInt(); + extraCopies = amount - 1; // One less because we *always* add one + replacingProviderless = true; + } + } + + addedCount += extraCopies; + + QString reason = QString("Added %1 copies of '%2 (%3) %4' to %5 [ProviderID: %6]%7") + .arg(addedCount) + .arg(rootCard.getName()) + .arg(rootCard.getPrinting().getSet()->getShortName()) + .arg(rootCard.getPrinting().getProperty("num")) + .arg(zone == DECK_ZONE_MAIN ? "mainboard" : "sideboard") + .arg(rootCard.getPrinting().getUuid()) + .arg(replacingProviderless ? " (replaced providerless printings)" : ""); + + emit deckModified(reason); + // Add the card and expand the list UI auto newCardIndex = deckModel->addCard(rootCard, zone); recursiveExpand(newCardIndex); // Check if a card without a providerId already exists in the deckModel and replace it, if so. - QModelIndex find_card = deckModel->findCard(rootCard.getName(), zone); - QString foundProviderId = deckModel->data(find_card.sibling(find_card.row(), 4), Qt::DisplayRole).toString(); - if (find_card.isValid() && find_card != newCardIndex && foundProviderId == "") { - auto amount = deckModel->data(find_card, Qt::DisplayRole); + QString foundProviderId = deckModel->data(existing.sibling(existing.row(), 4), Qt::DisplayRole).toString(); + if (existing.isValid() && existing != newCardIndex && foundProviderId == "") { + auto amount = deckModel->data(existing, Qt::DisplayRole); for (int i = 0; i < amount.toInt() - 1; i++) { deckModel->addCard(rootCard, zone); } - deckModel->removeRow(find_card.row(), find_card.parent()); + deckModel->removeRow(existing.row(), existing.parent()); } // Set Index and Focus as if the user had just clicked the new card and modify the deckEditor saveState newCardIndex = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(), rootCard.getPrinting().getProperty("num")); + deckView->setCurrentIndex(newCardIndex); deckView->setFocus(Qt::FocusReason::MouseFocusReason); deckEditor->setModified(true); @@ -223,12 +255,15 @@ void CardAmountWidget::offsetCountAtIndex(const QModelIndex &idx, int offset) const QModelIndex numberIndex = idx.sibling(idx.row(), 0); const int count = deckModel->data(numberIndex, Qt::EditRole).toInt(); const int new_count = count + offset; + deckView->setCurrentIndex(numberIndex); + if (new_count <= 0) { deckModel->removeRow(idx.row(), idx.parent()); } else { deckModel->setData(numberIndex, new_count, Qt::EditRole); } + deckEditor->setModified(true); } @@ -239,8 +274,18 @@ void CardAmountWidget::offsetCountAtIndex(const QModelIndex &idx, int offset) */ void CardAmountWidget::decrementCardHelper(const QString &zone) { + QString reason = QString("Removed 1 copy of '%1 (%2) %3' from %4 [ProviderID: %5]") + .arg(rootCard.getName()) + .arg(rootCard.getPrinting().getSet()->getShortName()) + .arg(rootCard.getPrinting().getProperty("num")) + .arg(zone == DECK_ZONE_MAIN ? "mainboard" : "sideboard") + .arg(rootCard.getPrinting().getUuid()); + + emit deckModified(reason); + QModelIndex idx = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(), rootCard.getPrinting().getProperty("num")); + offsetCountAtIndex(idx, -1); deckEditor->setModified(true); } diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h index 1da999c78..6d059bc04 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h @@ -21,6 +21,10 @@ class CardAmountWidget : public QWidget { Q_OBJECT + +signals: + void deckModified(const QString &modificationReason); + public: explicit CardAmountWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor, From de13c22552d1d96be733d7d81a741e131983f5a2 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sat, 29 Nov 2025 18:58:39 +0100 Subject: [PATCH 034/325] [Fix-Warnings] Suppress C4100: unreferenced parameter for protobuf files (#6373) * [Fix-Warnings] Suppress C4100: unreferenced parameter for protobuf files. * [Fix-Warnings] Compiler specific options. * [Fix-Warnings] Lint. --- .../libcockatrice/protocol/pb/CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt b/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt index 32e7f3238..212ab69dd 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt @@ -165,6 +165,12 @@ set(PROTO_FILES session_event.proto ) +if(MSVC) + set(unused_warning /wd4100) +else() + set(unused_warning -Wno-unused-parameter) +endif() + if(${Protobuf_VERSION} VERSION_LESS "3.21.0.0") message(STATUS "Using Protobuf Legacy Mode") include_directories(${PROTOBUF_INCLUDE_DIRS}) @@ -172,6 +178,7 @@ if(${Protobuf_VERSION} VERSION_LESS "3.21.0.0") protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${PROTO_FILES}) add_library(libcockatrice_protocol_pb ${PROTO_SRCS} ${PROTO_HDRS}) + target_compile_options(libcockatrice_protocol_pb PRIVATE ${unused_warning}) set(libcockatrice_protocol_pb_LIBS ${PROTOBUF_LIBRARIES}) if(UNIX) set(libcockatrice_protocol_pb_LIBS ${libcockatrice_protocol_pb_LIBS} -lpthread) @@ -189,6 +196,7 @@ if(${Protobuf_VERSION} VERSION_LESS "3.21.0.0") endif() else() add_library(libcockatrice_protocol_pb ${PROTO_FILES}) + target_compile_options(libcockatrice_protocol_pb PRIVATE ${unused_warning}) target_link_libraries(libcockatrice_protocol_pb PUBLIC protobuf::libprotobuf) set(PROTO_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}") target_include_directories(libcockatrice_protocol_pb PUBLIC "${PROTOBUF_INCLUDE_DIRS}") From eab4d435f8f5c04b2950ead35fc7f352819ecf37 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sun, 30 Nov 2025 08:41:01 +0100 Subject: [PATCH 035/325] [Feature] TabArchidekt and Archidekt API integration (#6348) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * TabArchidekt and Archidekt API integration. Took 37 seconds Took 4 minutes Took 40 seconds Took 4 minutes * Lint. * Lont. * Search bar, fancier display, resolve providerId * Delegate click to base. * Be explicit for pedantic compilers. * Liiint. * Leave them default I guess * Leave them default I guess * Small fixes. * New utility display widgets. * New style for deck listing. * Lint. * Lont. * Scale things. * Delegate paint to base. * Use default Archidekt preview image for decks without featured. * Consistent sizes. * Increase font size, qt version guard. * More version guards. * Clean up filter layout, use mana symbols. * Set content margins. * Refresh on filter change. * Lint. * Better elision. * Query actual new endpoints, new query parameters. * Doxygen, reorder fields in constructor, readability. * Update page size doc to min size. * Update initial min deck size value. * Add label to page selection. * Okay, so, people upload a lot of 1 card decks frequently. * Whoops. * Add a selection combobox for sorting logic. * Debounce and limit searches. * Include. * Lint. * Don't imply that Archidekt supports multiple cards/commander names. * Let's not lambda it and slot it instead. * Overload. * Add button to home tab. Took 8 minutes * Adjust to selection model change. Took 5 minutes * Cleanup auto-generated comments. Took 8 minutes * Remember card sizes. Took 1 minute * Initialize with correct size. Took 3 minutes * Use correct placeholders. Took 2 minutes * Style lint. Took 16 minutes * Parse double-faced cards correctly. * Parse double-faced cards correctly. * Allow TabArchidekt to use VDE group/sort/display buttons * Lint. * Indicate that things are clickable. * Min treshold for nicer display. * Lint. * We have good labels at home. * We do a little linting. * Qt version guards. * Qt5 is the devil. * Update comments. * Lint comments. * More doxys. * One more doxy. * Lint. * Update. * Small fixes. Took 7 minutes Took 13 seconds --------- Co-authored-by: Lukas Brübach --- cockatrice/CMakeLists.txt | 16 + .../card_group_display_widget.cpp | 9 +- .../cards/deck_card_zone_display_widget.cpp | 6 +- .../display/background_plate_widget.cpp | 34 ++ .../general/display/background_plate_widget.h | 22 + .../widgets/general/display/color_bar.cpp | 163 +++++ .../widgets/general/display/color_bar.h | 128 ++++ .../display/shadow_background_label.cpp | 10 +- .../general/display/shadow_background_label.h | 1 + .../interface/widgets/general/home_widget.cpp | 3 + .../archidekt_deck_listing_api_response.cpp | 29 + .../archidekt_deck_listing_api_response.h | 22 + .../card/archidekt_api_response_card.cpp | 61 ++ .../card/archidekt_api_response_card.h | 68 +++ .../archidekt_api_response_card_entry.cpp | 43 ++ .../card/archidekt_api_response_card_entry.h | 56 ++ .../card/archidekt_api_response_edition.cpp | 19 + .../card/archidekt_api_response_edition.h | 51 ++ .../deck/archidekt_api_response_deck.cpp | 79 +++ .../deck/archidekt_api_response_deck.h | 71 +++ .../archidekt_api_response_deck_category.cpp | 19 + .../archidekt_api_response_deck_category.h | 55 ++ ...kt_api_response_deck_listing_container.cpp | 64 ++ ...dekt_api_response_deck_listing_container.h | 133 +++++ .../archidekt_api_response_deck_owner.cpp | 22 + .../archidekt_api_response_deck_owner.h | 36 ++ ...idekt_api_response_deck_display_widget.cpp | 157 +++++ ...chidekt_api_response_deck_display_widget.h | 125 ++++ ...api_response_deck_entry_display_widget.cpp | 235 ++++++++ ...t_api_response_deck_entry_display_widget.h | 121 ++++ ..._response_deck_listings_display_widget.cpp | 47 ++ ...pi_response_deck_listings_display_widget.h | 97 +++ ...dekt_deck_preview_image_display_widget.cpp | 80 +++ ...hidekt_deck_preview_image_display_widget.h | 66 ++ .../tabs/api/archidekt/tab_archidekt.cpp | 562 ++++++++++++++++++ .../tabs/api/archidekt/tab_archidekt.h | 250 ++++++++ .../interface/widgets/tabs/tab_supervisor.cpp | 15 + .../interface/widgets/tabs/tab_supervisor.h | 4 +- .../visual_deck_display_options_widget.cpp | 124 ++++ .../visual_deck_display_options_widget.h | 138 +++++ .../visual_deck_editor_widget.cpp | 135 +---- .../visual_deck_editor_widget.h | 19 +- .../models/deck_list/deck_list_model.h | 31 +- 43 files changed, 3285 insertions(+), 141 deletions(-) create mode 100644 cockatrice/src/interface/widgets/general/display/background_plate_widget.cpp create mode 100644 cockatrice/src/interface/widgets/general/display/background_plate_widget.h create mode 100644 cockatrice/src/interface/widgets/general/display/color_bar.cpp create mode 100644 cockatrice/src/interface/widgets/general/display/color_bar.h create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.h create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.h create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.h create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.cpp create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.h create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.h create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.h create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.h create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h create mode 100644 cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp create mode 100644 cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 522300346..eacbc8825 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -153,8 +153,10 @@ set(cockatrice_SOURCES src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp src/interface/widgets/deck_editor/deck_list_style_proxy.cpp src/interface/widgets/general/background_sources.cpp + src/interface/widgets/general/display/background_plate_widget.cpp src/interface/widgets/general/display/banner_widget.cpp src/interface/widgets/general/display/bar_widget.cpp + src/interface/widgets/general/display/color_bar.cpp src/interface/widgets/general/display/dynamic_font_size_label.cpp src/interface/widgets/general/display/dynamic_font_size_push_button.cpp src/interface/widgets/general/display/labeled_input.cpp @@ -202,6 +204,7 @@ set(cockatrice_SOURCES src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp src/interface/widgets/visual_database_display/visual_database_display_widget.cpp src/interface/widgets/visual_database_display/visual_database_filter_display_widget.cpp + src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp @@ -220,6 +223,19 @@ set(cockatrice_SOURCES src/interface/window_main.cpp src/main.cpp src/interface/widgets/tabs/abstract_tab_deck_editor.cpp + src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp + src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp + src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp + src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp + src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp + src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp + src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.cpp + src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp + src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp + src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp + src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp + src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp + src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.cpp src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp index 75e075de6..cb83768f4 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp @@ -32,7 +32,10 @@ CardGroupDisplayWidget::CardGroupDisplayWidget(QWidget *parent, CardGroupDisplayWidget::updateCardDisplays(); connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &CardGroupDisplayWidget::onCardAddition); - connect(selectionModel, &QItemSelectionModel::selectionChanged, this, &CardGroupDisplayWidget::onSelectionChanged); + if (selectionModel) { + connect(selectionModel, &QItemSelectionModel::selectionChanged, this, + &CardGroupDisplayWidget::onSelectionChanged); + } connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &CardGroupDisplayWidget::onCardRemoval); } @@ -179,7 +182,9 @@ void CardGroupDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeSort void CardGroupDisplayWidget::mousePressEvent(QMouseEvent *event) { QWidget::mousePressEvent(event); - selectionModel->clearSelection(); + if (selectionModel) { + selectionModel->clearSelection(); + } } void CardGroupDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card) diff --git a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp index 618d7e565..132964f13 100644 --- a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp @@ -38,8 +38,10 @@ DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent, displayCards(); connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &DeckCardZoneDisplayWidget::onCategoryAddition); - connect(selectionModel, &QItemSelectionModel::selectionChanged, this, - &DeckCardZoneDisplayWidget::onSelectionChanged); + if (selectionModel) { + connect(selectionModel, &QItemSelectionModel::selectionChanged, this, + &DeckCardZoneDisplayWidget::onSelectionChanged); + } connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &DeckCardZoneDisplayWidget::onCategoryRemoval); } diff --git a/cockatrice/src/interface/widgets/general/display/background_plate_widget.cpp b/cockatrice/src/interface/widgets/general/display/background_plate_widget.cpp new file mode 100644 index 000000000..aad259900 --- /dev/null +++ b/cockatrice/src/interface/widgets/general/display/background_plate_widget.cpp @@ -0,0 +1,34 @@ +#include "background_plate_widget.h" + +#include +#include +#include +#include + +BackgroundPlateWidget::BackgroundPlateWidget(QWidget *parent) : QWidget(parent) +{ + setAutoFillBackground(true); // For automatic background filling +} + +void BackgroundPlateWidget::paintEvent(QPaintEvent *event) +{ + QWidget::paintEvent(event); + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + + // Set the background color to semi-transparent black with rounded corners + QRect rect = this->rect(); + painter.setPen(Qt::NoPen); // No border + if (focused) { + painter.setBrush(QColor(85, 190, 75, 140)); + } else { + painter.setBrush(QColor(0, 0, 0, 140)); // semi-transparent black + } + painter.drawRoundedRect(rect, 6, 6); // rounded corners +} + +void BackgroundPlateWidget::setFocused(bool _focused) +{ + focused = _focused; + update(); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/general/display/background_plate_widget.h b/cockatrice/src/interface/widgets/general/display/background_plate_widget.h new file mode 100644 index 000000000..d529cb6fa --- /dev/null +++ b/cockatrice/src/interface/widgets/general/display/background_plate_widget.h @@ -0,0 +1,22 @@ +#ifndef COCKATRICE_BACKGROUND_PLATE_WIDGET_H +#define COCKATRICE_BACKGROUND_PLATE_WIDGET_H + +#include + +class BackgroundPlateWidget : public QWidget +{ + Q_OBJECT + +public: + explicit BackgroundPlateWidget(QWidget *parent = nullptr); + + void setFocused(bool focused); + +private: + bool focused = false; + +protected: + void paintEvent(QPaintEvent *event) override; +}; + +#endif // COCKATRICE_BACKGROUND_PLATE_WIDGET_H diff --git a/cockatrice/src/interface/widgets/general/display/color_bar.cpp b/cockatrice/src/interface/widgets/general/display/color_bar.cpp new file mode 100644 index 000000000..d1eb7ef4c --- /dev/null +++ b/cockatrice/src/interface/widgets/general/display/color_bar.cpp @@ -0,0 +1,163 @@ + +#include "color_bar.h" + +#include +#include +#include +#include + +ColorBar::ColorBar(const QMap &_colors, QWidget *parent) : QWidget(parent), colors(_colors) +{ + setMouseTracking(true); +} + +void ColorBar::setColors(const QMap &_colors) +{ + colors = _colors; + update(); +} + +QSize ColorBar::minimumSizeHint() const +{ + return QSize(200, 22); +} + +void ColorBar::paintEvent(QPaintEvent *) +{ + if (colors.isEmpty()) + return; + + int total = 0; + for (int v : colors.values()) + total += v; + + // Prevent divide-by-zero + if (total == 0) + return; + + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing, true); + + const int w = width(); + const int h = height(); + int x = 0; + + // Draw rounded border background + QRectF bounds(0.5, 0.5, w - 1, h - 1); + p.setPen(QPen(Qt::black, 1)); + p.setBrush(Qt::NoBrush); + p.drawRoundedRect(bounds, 6, 6); + + // Clip to inside the border + p.setClipRect(bounds.adjusted(2, 2, -2, -2)); + + // Ensure predictable order + QList sortedKeys = colors.keys(); + std::sort(sortedKeys.begin(), sortedKeys.end()); // Sort alphabetically + + // Draw each color segment in the sorted order + for (const QString &key : sortedKeys) { + int value = colors[key]; + double ratio = double(value) / total; + + if (ratio <= minRatioThreshold) { + continue; + } + + int segmentWidth = int(ratio * w); + + // Ensure the segment width is at least 1 to avoid degenerate rectangles + if (segmentWidth < 1) + segmentWidth = 1; + + QColor base = colorFromName(key); + + // Slight gradient for nicer look + QLinearGradient grad(x, 0, x, h); + grad.setColorAt(0, base.lighter(120)); + grad.setColorAt(1, base.darker(120)); + + p.fillRect(QRect(x, 0, segmentWidth, h), grad); + + x += segmentWidth; + } +} + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +void ColorBar::enterEvent(QEnterEvent *event) +{ + Q_UNUSED(event); + isHovered = true; +} +#else +void ColorBar::enterEvent(QEvent *event) +{ + Q_UNUSED(event); + isHovered = true; +} +#endif + +void ColorBar::leaveEvent(QEvent *) +{ + isHovered = false; +} + +void ColorBar::mouseMoveEvent(QMouseEvent *event) +{ + if (!isHovered || colors.isEmpty()) + return; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + int x = int(event->position().x()); + QPoint gp = event->globalPosition().toPoint(); +#else + int x = event->pos().x(); + QPoint gp = event->globalPos(); +#endif + + QString text = tooltipForPosition(x); + if (!text.isEmpty()) + QToolTip::showText(gp, text, this); +} + +QString ColorBar::tooltipForPosition(int x) const +{ + int total = 0; + for (int v : colors.values()) + total += v; + + if (total == 0) + return {}; + + int pos = 0; + for (auto it = colors.cbegin(); it != colors.cend(); ++it) { + const double ratio = double(it.value()) / total; + const int segmentWidth = int(ratio * width()); + + if (x >= pos && x < pos + segmentWidth) { + const double percent = (100.0 * it.value()) / total; + return QString("%1: %2 cards (%3%)").arg(it.key()).arg(it.value()).arg(QString::number(percent, 'f', 1)); + } + + pos += segmentWidth; + } + + return {}; +} + +QColor ColorBar::colorFromName(const QString &name) const +{ + static QMap map = { + {"R", QColor(220, 30, 30)}, {"G", QColor(40, 170, 40)}, {"U", QColor(40, 90, 200)}, + {"W", QColor(235, 235, 230)}, {"B", QColor(30, 30, 30)}, + }; + + if (map.contains(name)) + return map[name]; + + QColor c(name); + if (!c.isValid()) + c = Qt::gray; + + return c; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/general/display/color_bar.h b/cockatrice/src/interface/widgets/general/display/color_bar.h new file mode 100644 index 000000000..f61ab6942 --- /dev/null +++ b/cockatrice/src/interface/widgets/general/display/color_bar.h @@ -0,0 +1,128 @@ +#ifndef COCKATRICE_COLOR_BAR_H +#define COCKATRICE_COLOR_BAR_H + +#include +#include +#include +#include + +/** + * @class ColorBar + * @brief A widget for visualizing proportional color distributions as a horizontal bar. + * + * This widget renders a horizontal bar divided into colored segments whose widths reflect + * the relative values associated with each color key in a `QMap`. The class + * is designed as a small, lightweight, and self-contained visualization component suitable + * for representing distributions such as color counts, mana statistics, categorical frequencies, and similar data sets. + * + * Key features: + * - Filled segments for better visual clarity. + * - Deterministic alphabetical ordering of color keys. + * - Optional minimum percentage threshold for filtering out insignificant segments. + * - Mouse-hover tooltips showing each segment’s key, count, and percentage of total. + * + * Default color mappings exist for `"R"`, `"G"`, `"U"`, `"W"`, and `"B"`, using named + * colors, but any string recognized by `QColor` may be used. If an unknown name is provided, + * the segment will fall back to gray. + * + * This component is display-only and does not interpret or mutate domain-level data. + */ +class ColorBar : public QWidget +{ + Q_OBJECT + +public: + /** + * @brief Constructs a ColorBar widget. + * + * @param colors Map of color identifiers to integer counts. + * @param parent Optional parent widget. + */ + explicit ColorBar(const QMap &colors, QWidget *parent = nullptr); + + /** + * @brief Updates the color distribution map. + * @param colors New color → count mapping. + * + * Triggers an immediate repaint. + */ + void setColors(const QMap &colors); + + /** + * @brief Sets a minimum percentage threshold below which segments are not drawn. + * + * @param treshold Percentage from 0 to 100. + * + * Internally converted into a ratio (0.05 = 5%). + */ + void setMinPercentThreshold(double treshold) + { + minRatioThreshold = treshold / 100.0; + } + + /** + * @brief Returns the recommended minimum size. + */ + QSize minimumSizeHint() const override; + +protected: + /** + * @brief Paints the color distribution bar. + * + * Draws: + * - A rounded border + * - Filled segments for each color + * - Only segments above the minimum ratio threshold + */ + void paintEvent(QPaintEvent *event) override; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + /** + * @brief Handles mouse hover entering (Qt6 version). + */ + void enterEvent(QEnterEvent *event) override; +#else + /** + * @brief Handles mouse hover entering (Qt5 version). + */ + void enterEvent(QEvent *event) override; +#endif + + /** + * @brief Handles mouse hover leaving. + */ + void leaveEvent(QEvent *event) override; + + /** + * @brief Handles mouse movement to update contextual tooltips. + */ + void mouseMoveEvent(QMouseEvent *event) override; + +private: + /// Map of color keys to counts used for rendering. + QMap colors; + + /// True if the mouse is currently inside the widget. + bool isHovered = false; + + /// Minimum ratio a segment must exceed to be drawn. + double minRatioThreshold = 0.0; + + /** + * @brief Converts a color name into a display QColor. + * + * Recognized special keys: `"R", "G", "U", "W", "B"`. + * Other strings are treated as QColor names or fall back to gray. + */ + QColor colorFromName(const QString &name) const; + + /** + * @brief Returns tooltip text for a given x-coordinate in the bar. + * + * @param x Horizontal coordinate relative to widget. + * @return Tooltip text or empty string if no segment applies. + */ + QString tooltipForPosition(int x) const; +}; + +#endif // COCKATRICE_COLOR_BAR_H diff --git a/cockatrice/src/interface/widgets/general/display/shadow_background_label.cpp b/cockatrice/src/interface/widgets/general/display/shadow_background_label.cpp index fbca8df8d..7db18c57d 100644 --- a/cockatrice/src/interface/widgets/general/display/shadow_background_label.cpp +++ b/cockatrice/src/interface/widgets/general/display/shadow_background_label.cpp @@ -12,12 +12,18 @@ */ ShadowBackgroundLabel::ShadowBackgroundLabel(QWidget *parent, const QString &text) : QLabel(parent) { - setAttribute(Qt::WA_TranslucentBackground); // Allows transparency. - setText("" + text + ""); ///< Ensures the text is rendered in white. + setAttribute(Qt::WA_TranslucentBackground); // Allows transparency. + setLabelText(text); setAlignment(Qt::AlignCenter); ///< Centers the text within the label. setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); ///< Ensures minimum size constraints. } +void ShadowBackgroundLabel::setLabelText(const QString &text) +{ + setText("" + text + ""); ///< Ensures the text is rendered in white. + update(); +} + /** * @brief Handles resizing of the label. * diff --git a/cockatrice/src/interface/widgets/general/display/shadow_background_label.h b/cockatrice/src/interface/widgets/general/display/shadow_background_label.h index 3483452f5..b2344b7d0 100644 --- a/cockatrice/src/interface/widgets/general/display/shadow_background_label.h +++ b/cockatrice/src/interface/widgets/general/display/shadow_background_label.h @@ -15,6 +15,7 @@ class ShadowBackgroundLabel : public QLabel public: explicit ShadowBackgroundLabel(QWidget *parent, const QString &text); + void setLabelText(const QString &text); protected: void resizeEvent(QResizeEvent *event) override; diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index 2dfd129d5..88eec230c 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -196,6 +196,9 @@ QGroupBox *HomeWidget::createButtons() auto edhrecButton = new HomeStyledButton(tr("Browse EDHRec"), gradientColors); connect(edhrecButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addEdhrecMainTab); boxLayout->addWidget(edhrecButton); + auto archidektButton = new HomeStyledButton(tr("Browse Archidekt"), gradientColors); + connect(archidektButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addArchidektTab); + boxLayout->addWidget(archidektButton); auto replaybutton = new HomeStyledButton(tr("View Replays"), gradientColors); connect(replaybutton, &QPushButton::clicked, tabSupervisor, [this] { tabSupervisor->actTabReplays(true); }); boxLayout->addWidget(replaybutton); diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp new file mode 100644 index 000000000..40882db04 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp @@ -0,0 +1,29 @@ +#include "archidekt_deck_listing_api_response.h" + +#include +#include + +void ArchidektDeckListingApiResponse::fromJson(const QJsonObject &json) +{ + count = json.value("count").toInt(); + next = QUrl(json.value("next").toString()); + + QJsonArray containerJson = json.value("results").toArray(); + + for (const QJsonValue &deckListingValue : containerJson) { + ArchidektApiResponseDeckListingContainer listingResult; + listingResult.fromJson(deckListingValue.toObject()); + results.append(listingResult); + } +} + +void ArchidektDeckListingApiResponse::debugPrint() const +{ + qDebug() << "Count:" << count; + qDebug() << "Next:" << next; + + qDebug() << "Results:"; + for (const auto &deckListing : results) { + deckListing.debugPrint(); + } +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.h new file mode 100644 index 000000000..723c65ce2 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.h @@ -0,0 +1,22 @@ +#ifndef COCKATRICE_ARCHIDEKT_DECK_LISTING_API_RESPONSE_H +#define COCKATRICE_ARCHIDEKT_DECK_LISTING_API_RESPONSE_H + +#include "deck_listings/archidekt_api_response_deck_listing_container.h" + +#include +#include +#include + +class ArchidektDeckListingApiResponse +{ + +public: + int count; + QUrl next; + QVector results; + + void fromJson(const QJsonObject &json); + void debugPrint() const; +}; + +#endif // COCKATRICE_DECK_LISTING_API_RESPONSE_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp new file mode 100644 index 000000000..909c4d9eb --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp @@ -0,0 +1,61 @@ +#include "archidekt_api_response_card.h" + +void ArchidektApiResponseCard::fromJson(const QJsonObject &json) +{ + id = json.value("id").toInt(); + artist = json.value("artist").toString(); + tcgProductId = json.value("tcgProductId").toInt(); + ckFoilId = json.value("ckFoilId").toInt(); + ckNormalId = json.value("ckNormalId").toInt(); + cmEd = json.value("cmEd").toString(); + scgSku = json.value("scgSku").toString(); + scgFoilSku = json.value("scgFoilSku").toString(); + collectorNumber = json.value("collectorNumber").toString(); + multiverseId = json.value("multiverseId").toInt(); + mtgoFoilId = json.value("mtgoFoilId").toInt(); + mtgoNormalId = json.value("mtgoNormalId").toInt(); + uid = json.value("uid").toString(); + displayName = json.value("displayName").toString(); + releasedAt = json.value("releasedAt").toString(); + + edition.fromJson(json.value("edition").toObject()); + + flavor = json.value("flavor").toString(); + // TODO but not really important + // games = {""}; + // options = {""}; + scryfallImageHash = json.value("scryfallImageHash").toString(); + oracleCard = json.value("oracleCard").toObject(); + owned = json.value("owned").toInt(); + pinnedStatus = json.value("pinnedStatus").toInt(); + rarity = json.value("rarity").toString(); + // TODO but not really important + // globalCategories = {""}; +} + +void ArchidektApiResponseCard::debugPrint() const +{ + qDebug() << "Id:" << id; + qDebug() << "id:" << artist; + qDebug() << "artist:" << tcgProductId; + qDebug() << "tcgProductId:" << ckFoilId; + qDebug() << "ckFoilId:" << ckNormalId; + qDebug() << "ckNormalId:" << cmEd; + qDebug() << "cmEd:" << scgSku; + qDebug() << "scgSku:" << scgFoilSku; + qDebug() << "scgFoilSku:" << collectorNumber; + qDebug() << "collectorNumber:" << multiverseId; + qDebug() << "multiverseId:" << mtgoFoilId; + qDebug() << "mtgoFoilId:" << mtgoNormalId; + qDebug() << "mtgoNormalId:" << uid; + qDebug() << "uid:" << displayName; + qDebug() << "displayName:" << releasedAt; + qDebug() << "releasedAt:" << flavor; + qDebug() << "flavor:" << games; + qDebug() << "games:" << options; + qDebug() << "options:" << scryfallImageHash; + qDebug() << "scryfallImageHash:" << owned; + qDebug() << "owned:" << pinnedStatus; + qDebug() << "pinnedStatus:" << rarity; + qDebug() << "rarity:" << globalCategories; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.h new file mode 100644 index 000000000..265498228 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.h @@ -0,0 +1,68 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_H + +#include "archidekt_api_response_edition.h" + +#include +#include +#include +#include +#include + +class ArchidektApiResponseCard +{ +public: + // Constructor + ArchidektApiResponseCard() = default; + + // Parse deck-related data from JSON + void fromJson(const QJsonObject &json); + + // Debug method for logging + void debugPrint() const; + + QJsonObject getOracleCard() const + { + return oracleCard; + }; + + QString getCollectorNumber() const + { + return collectorNumber; + } + + ArchidektApiResponseEdition getEdition() const + { + return edition; + } + +private: + int id; + QString artist; + int tcgProductId; + int ckFoilId; + int ckNormalId; + QString cmEd; + QString scgSku; + QString scgFoilSku; + QString collectorNumber; + int multiverseId; + int mtgoFoilId; + int mtgoNormalId; + QString uid; + QString displayName; + QString releasedAt; + ArchidektApiResponseEdition edition; + QString flavor; + QStringList games; + QStringList options; + QString scryfallImageHash; + QJsonObject oracleCard; + int owned; + int pinnedStatus; + // ArchidektApiResponsePrices prices; + QString rarity; + QStringList globalCategories; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp new file mode 100644 index 000000000..7a424de8b --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp @@ -0,0 +1,43 @@ +#include "archidekt_api_response_card_entry.h" + +void ArchidektApiResponseCardEntry::fromJson(const QJsonObject &json) +{ + id = json.value("id").toInt(); + + auto categoriesJson = json.value("categories").toArray(); + + for (auto category : categoriesJson) { + categories.append(category.toString()); + } + + companion = json.value("companion").toBool(); + flippedDefault = json.value("flippedDefault").toBool(); + label = json.value("label").toString(); + modifier = json.value("modifier").toString(); + quantity = json.value("quantity").toInt(); + customCmc = json.value("customCmc").toInt(); + // removedCategories = {""}; + createdAt = json.value("createdAt").toString(); + updatedAt = json.value("updatedAt").toString(); + deletedAt = json.value("deletedAt").toString(); + notes = json.value("notes").toString(); + card.fromJson(json.value("card").toObject()); +} + +void ArchidektApiResponseCardEntry::debugPrint() const +{ + qDebug() << "Id:" << id; + qDebug() << "Categories:" << categories; + qDebug() << "Companion:" << companion; + qDebug() << "FlippedDefault:" << flippedDefault; + qDebug() << "Label:" << label; + qDebug() << "Modifier:" << modifier; + qDebug() << "Quantity:" << quantity; + qDebug() << "CustomCmc:" << customCmc; + qDebug() << "RemovedCategories:" << removedCategories; + qDebug() << "CreatedAt:" << createdAt; + qDebug() << "UpdatedAt:" << updatedAt; + qDebug() << "DeletedAt:" << deletedAt; + qDebug() << "Notes:" << notes; + card.debugPrint(); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h new file mode 100644 index 000000000..f7f86e9ed --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h @@ -0,0 +1,56 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_ENTRY_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_ENTRY_H + +#include "archidekt_api_response_card.h" + +#include +#include +#include +#include +#include + +class ArchidektApiResponseCardEntry +{ +public: + // Constructor + ArchidektApiResponseCardEntry() = default; + + // Parse deck-related data from JSON + void fromJson(const QJsonObject &json); + + // Debug method for logging + void debugPrint() const; + + ArchidektApiResponseCard getCard() const + { + return card; + }; + + QStringList getCategories() const + { + return categories; + } + + int getQuantity() const + { + return quantity; + } + +private: + int id; + QStringList categories; + bool companion; + bool flippedDefault; + QString label; + QString modifier; + int quantity; + int customCmc; + QStringList removedCategories; + QString createdAt; + QString updatedAt; + QString deletedAt; + QString notes; + ArchidektApiResponseCard card; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_CARD_ENTRY_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp new file mode 100644 index 000000000..122617a22 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp @@ -0,0 +1,19 @@ +#include "archidekt_api_response_edition.h" + +void ArchidektApiResponseEdition::fromJson(const QJsonObject &json) +{ + editionCode = json.value("editioncode").toString(); + editionName = json.value("editionname").toString(); + editionDate = json.value("editiondate").toString(); + editionType = json.value("editiontype").toString(); + mtgoCode = json.value("mtgocode").toString(); +} + +void ArchidektApiResponseEdition::debugPrint() const +{ + qDebug() << "Edition Code: " << editionCode; + qDebug() << "Edition Name: " << editionName; + qDebug() << "Edition Date: " << editionDate; + qDebug() << "Edition Type: " << editionType; + qDebug() << "Mtgo Code: " << mtgoCode; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.h new file mode 100644 index 000000000..b898cd816 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.h @@ -0,0 +1,51 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_EDITION_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_EDITION_H + +#include +#include +#include +#include +#include + +class ArchidektApiResponseEdition +{ +public: + // Constructor + ArchidektApiResponseEdition() = default; + + // Parse deck-related data from JSON + void fromJson(const QJsonObject &json); + + // Debug method for logging + void debugPrint() const; + + [[nodiscard]] QString getEditionCode() const + { + return editionCode; + } + [[nodiscard]] QString getEditionName() const + { + return editionName; + } + [[nodiscard]] QString getEditionDate() const + { + return editionDate; + } + [[nodiscard]] QString getEditionType() const + { + return editionType; + } + [[nodiscard]] QString getMtgoCode() const + { + return mtgoCode; + } + +private: + QString editionCode; + QString editionName; + QString editionDate; + QString editionType; + QString mtgoCode; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_EDITION_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp new file mode 100644 index 000000000..98d8c6c8c --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp @@ -0,0 +1,79 @@ +#include "archidekt_api_response_deck.h" + +#include "../card/archidekt_api_response_card_entry.h" + +void ArchidektApiResponseDeck::fromJson(const QJsonObject &json) +{ + id = json.value("id").toInt(); + name = json.value("name").toString(); + size = json.value("size").toInt(); + updatedAt = json.value("updatedAt").toString(); + createdAt = json.value("createdAt").toString(); + deckFormat = json.value("deckFormat").toInt(); + edhBracket = json.value("edhBracket").toInt(); + featured = json.value("featured").toString(); + customFeatured = json.value("customFeatured").toString(); + viewCount = json.value("viewCount").toInt(); + privateDeck = json.value("private").toBool(); + unlisted = json.value("unlisted").toBool(); + theoryCrafted = json.value("theoryCrafted").toBool(); + points = json.value("points").toInt(); + userInput = json.value("userInput").toInt(); + owner.fromJson(json.value("owner").toObject()); + commentRoot = json.value("commentRoot").toInt(); + editors = json.value("editors").toString(); + parentFolderId = json.value("parentFolderId").toInt(); + bookmarked = json.value("bookmarked").toBool(); + + auto categoriesJson = json.value("categories").toArray(); + for (auto category : categoriesJson) { + ArchidektApiResponseDeckCategory categoryEntry; + categoryEntry.fromJson(category.toObject()); + categories.append(categoryEntry); + } + + // deckTags = {""}; + playgroupDeckUrl = json.value("playgroupDeckUrl").toString(); + cardPackage = json.value("cardPackage").toString(); + + auto cardsObject = json.value("cards").toArray(); + + for (auto card : cardsObject) { + ArchidektApiResponseCardEntry entry; + entry.fromJson(card.toObject()); + cards.append(entry); + } + + // TODO but not really important + // customCards = {""}; +} + +void ArchidektApiResponseDeck::debugPrint() const +{ + qDebug() << "Id:" << id; + qDebug() << "Name:" << name; + qDebug() << "Size:" << size; + qDebug() << "UpdatedAt:" << updatedAt; + qDebug() << "CreatedAt:" << createdAt; + qDebug() << "DeckFormat:" << deckFormat; + qDebug() << "EdhBracket:" << edhBracket; + qDebug() << "Featured:" << featured; + qDebug() << "CustomFeatured:" << customFeatured; + qDebug() << "ViewCount:" << viewCount; + qDebug() << "Private:" << privateDeck; + qDebug() << "Unlisted:" << unlisted; + qDebug() << "TheoryCrafted:" << theoryCrafted; + qDebug() << "Points:" << points; + qDebug() << "UserInput:" << userInput; + owner.debugPrint(); + qDebug() << "CommentRoot:" << commentRoot; + qDebug() << "Editors:" << editors; + qDebug() << "ParentFolderId:" << parentFolderId; + qDebug() << "Bookmarked:" << bookmarked; + qDebug() << "DeckTags:" << deckTags; + qDebug() << "PlaygroupDeckUrl:" << playgroupDeckUrl; + qDebug() << "CardPackage:" << cardPackage; + for (auto card : cards) { + card.debugPrint(); + } +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h new file mode 100644 index 000000000..8cb81cac3 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h @@ -0,0 +1,71 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_H + +#include "../card/archidekt_api_response_card.h" +#include "../card/archidekt_api_response_card_entry.h" +#include "../deck_listings/archidekt_api_response_deck_owner.h" +#include "archidekt_api_response_deck_category.h" + +#include +#include +#include +#include +#include + +class ArchidektApiResponseDeck +{ +public: + // Constructor + ArchidektApiResponseDeck() = default; + + // Parse deck-related data from JSON + void fromJson(const QJsonObject &json); + + // Debug method for logging + void debugPrint() const; + + QVector getCards() const + { + return cards; + }; + + QVector getCategories() const + { + return categories; + } + + QString getDeckName() const + { + return name; + }; + +private: + int id; + QString name; + int size; + QString updatedAt; + QString createdAt; + int deckFormat; + int edhBracket; + QString featured; + QString customFeatured; + int viewCount; + bool privateDeck; + bool unlisted; + bool theoryCrafted; + int points; + int userInput; + ArchidektApiResponseDeckOwner owner; + int commentRoot; + QString editors; + int parentFolderId; + bool bookmarked; + QVector categories; + QStringList deckTags; + QString playgroupDeckUrl; + QString cardPackage; + QVector cards; + QStringList customCards; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.cpp new file mode 100644 index 000000000..990293bc6 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.cpp @@ -0,0 +1,19 @@ +#include "archidekt_api_response_deck_category.h" + +void ArchidektApiResponseDeckCategory::fromJson(const QJsonObject &json) +{ + id = json.value("id").toInt(); + name = json.value("name").toString(); + isPremier = json.value("isPremier").toBool(); + includedInDeck = json.value("includedInDeck").toBool(); + includedInPrice = json.value("includedInPrice").toBool(); +} + +void ArchidektApiResponseDeckCategory::debugPrint() const +{ + qDebug() << "Id:" << id; + qDebug() << "Name:" << name; + qDebug() << "isPremier:" << isPremier; + qDebug() << "IncludedInDeck:" << includedInDeck; + qDebug() << "IncludedInPrice:" << includedInPrice; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.h new file mode 100644 index 000000000..5e218b6a4 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.h @@ -0,0 +1,55 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_CATEGORY_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_CATEGORY_H + +#include "../card/archidekt_api_response_card.h" +#include "../card/archidekt_api_response_card_entry.h" +#include "../deck_listings/archidekt_api_response_deck_owner.h" + +#include +#include +#include +#include +#include + +class ArchidektApiResponseDeckCategory +{ +public: + // Constructor + ArchidektApiResponseDeckCategory() = default; + + // Parse deck-related data from JSON + void fromJson(const QJsonObject &json); + + // Debug method for logging + void debugPrint() const; + + [[nodiscard]] int getId() const + { + return id; + } + [[nodiscard]] QString getName() const + { + return name; + } + [[nodiscard]] bool getIsPremier() const + { + return isPremier; + } + [[nodiscard]] bool getIncludedInDeck() const + { + return includedInDeck; + } + [[nodiscard]] bool getIncludedInPrice() const + { + return includedInPrice; + } + +private: + int id; + QString name; + bool isPremier; + bool includedInDeck; + bool includedInPrice; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_CATEGORY_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp new file mode 100644 index 000000000..16ea60487 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp @@ -0,0 +1,64 @@ +#include "archidekt_api_response_deck_listing_container.h" + +#include "archidekt_api_response_deck_owner.h" + +#include +#include +#include + +void ArchidektApiResponseDeckListingContainer::fromJson(const QJsonObject &json) +{ + id = json.value("id").toInt(); + name = json.value("name").toString(); + size = json.value("size").toInt(); + updatedAt = json.value("updatedAt").toString(); + createdAt = json.value("createdAt").toString(); + deckFormat = json.value("deckFormat").toInt(); + edhBracket = json.value("edhBracket").toInt(); + featured = json.value("featured").toString(); + customFeatured = json.value("customFeatured").toString(); + viewCount = json.value("viewCount").toInt(); + privateDeck = json.value("private").toBool(); + unlisted = json.value("unlisted").toBool(); + theoryCrafted = json.value("theoryCrafted").toBool(); + game = json.value("game").toString(); + hasDescription = json.value("hasDescription").toBool(); + // TODO + // tags = {""}; + parentFolderId = json.value("parentFolderId").toInt(); + owner.fromJson(json.value("owner").toObject()); + + auto colorsJson = json.value("colors").toObject(); + + for (auto color : colorsJson.keys()) { + colors[color] = colorsJson[color].toInt(); + } + + cardPackage = json.value("cardPackage").toString(); + contest = json.value("contest").toString(); +} + +void ArchidektApiResponseDeckListingContainer::debugPrint() const +{ + qDebug() << "Id:" << id; + qDebug() << "Name:" << name; + qDebug() << "Size:" << size; + qDebug() << "UpdatedAt:" << updatedAt; + qDebug() << "CreatedAt:" << createdAt; + qDebug() << "DeckFormat:" << deckFormat; + qDebug() << "EdhBracket:" << edhBracket; + qDebug() << "Featured:" << featured; + qDebug() << "CustomFeatured:" << customFeatured; + qDebug() << "ViewCount:" << viewCount; + qDebug() << "Private:" << privateDeck; + qDebug() << "Unlisted:" << unlisted; + qDebug() << "TheoryCrafted:" << theoryCrafted; + qDebug() << "Game:" << game; + qDebug() << "HasDescription:" << hasDescription; + qDebug() << "Tags:" << tags; + qDebug() << "ParentFolderId:" << parentFolderId; + owner.debugPrint(); + qDebug() << "Colors:" << colors; + qDebug() << "CardPackage" << cardPackage; + qDebug() << "Contest:" << contest; +} diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.h new file mode 100644 index 000000000..8924bb3f5 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.h @@ -0,0 +1,133 @@ +#ifndef COCKATRICE_ARCHIDEKT_DECK_LISTING_API_RESPONSE_CONTAINER_H +#define COCKATRICE_ARCHIDEKT_DECK_LISTING_API_RESPONSE_CONTAINER_H + +#include "archidekt_api_response_deck_owner.h" + +#include +#include +#include +#include +#include + +class ArchidektApiResponseDeckListingContainer +{ +public: + // Constructor + ArchidektApiResponseDeckListingContainer() = default; + + // Parse deck-related data from JSON + void fromJson(const QJsonObject &json); + + // Debug method for logging + void debugPrint() const; + + [[nodiscard]] int getId() const + { + return id; + } + [[nodiscard]] QString getName() const + { + return name; + } + [[nodiscard]] int getSize() const + { + return size; + } + [[nodiscard]] QString getUpdatedAt() const + { + return updatedAt; + } + [[nodiscard]] QString getCreatedAt() const + { + return createdAt; + } + [[nodiscard]] int getDeckFormat() const + { + return deckFormat; + } + [[nodiscard]] int getEDHBracket() const + { + return edhBracket; + } + [[nodiscard]] QString getFeatured() const + { + return featured; + } + [[nodiscard]] QString getCustomFeatured() const + { + return customFeatured; + } + [[nodiscard]] int getViewCount() const + { + return viewCount; + } + [[nodiscard]] bool getPrivateDeck() const + { + return privateDeck; + } + [[nodiscard]] bool getUnlisted() const + { + return unlisted; + } + [[nodiscard]] bool getTheoryCrafted() const + { + return theoryCrafted; + } + [[nodiscard]] QString getGame() const + { + return game; + } + [[nodiscard]] bool getHasDescription() const + { + return hasDescription; + } + [[nodiscard]] QStringList getTags() const + { + return tags; + } + [[nodiscard]] int getParentFolderId() const + { + return parentFolderId; + } + [[nodiscard]] ArchidektApiResponseDeckOwner getOwner() const + { + return owner; + } + [[nodiscard]] QMap getColors() const + { + return colors; + } + [[nodiscard]] QString getCardPackage() const + { + return cardPackage; + } + [[nodiscard]] QString getContest() const + { + return contest; + } + +private: + int id; + QString name; + int size; + QString updatedAt; + QString createdAt; + int deckFormat; + int edhBracket; + QString featured; + QString customFeatured; + int viewCount; + bool privateDeck; + bool unlisted; + bool theoryCrafted; + QString game; + bool hasDescription; + QStringList tags; + int parentFolderId; + ArchidektApiResponseDeckOwner owner; + QMap colors; + QString cardPackage; + QString contest; +}; + +#endif // COCKATRICE_ARCHIDEKT_DECK_LISTING_API_RESPONSE_CONTAINER_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp new file mode 100644 index 000000000..2ba926f8b --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp @@ -0,0 +1,22 @@ +#include "archidekt_api_response_deck_owner.h" + +void ArchidektApiResponseDeckOwner::fromJson(const QJsonObject &json) +{ + id = json.value("id").toInt(); + userName = json.value("username").toString(); + avatar = QUrl(json.value("avatar").toString()); + moderator = json.value("moderator").toBool(); + pledgeLevel = json.value("pledgeLevel").toInt(); + // TODO but not really important + // roles = {""}; +} + +void ArchidektApiResponseDeckOwner::debugPrint() const +{ + qDebug() << "Id:" << id; + qDebug() << "UserName:" << userName; + qDebug() << "Avatar:" << avatar; + qDebug() << "Moderator:" << moderator; + qDebug() << "PledgeLevel:" << pledgeLevel; + qDebug() << "Roles:" << roles; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.h new file mode 100644 index 000000000..df3f48fd2 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.h @@ -0,0 +1,36 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_OWNER_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_OWNER_H + +#include +#include +#include +#include +#include + +class ArchidektApiResponseDeckOwner +{ +public: + // Constructor + ArchidektApiResponseDeckOwner() = default; + + // Parse deck-related data from JSON + void fromJson(const QJsonObject &json); + + // Debug method for logging + void debugPrint() const; + + [[nodiscard]] QString getName() const + { + return userName; + } + +private: + int id; + QString userName; + QUrl avatar; + bool moderator; + int pledgeLevel; + QStringList roles; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_OWNER_H 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 new file mode 100644 index 000000000..33ee67a1f --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp @@ -0,0 +1,157 @@ +#include "archidekt_api_response_deck_display_widget.h" + +#include "../../../../../deck_loader/deck_loader.h" +#include "../../../../cards/card_info_picture_with_text_overlay_widget.h" +#include "../../../../cards/card_size_widget.h" +#include "../../../../cards/deck_card_zone_display_widget.h" +#include "../../../../visual_deck_editor/visual_deck_display_options_widget.h" +#include "../api_response/deck/archidekt_api_response_deck.h" + +#include +#include + +ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWidget *parent, + ArchidektApiResponseDeck _response, + CardSizeWidget *_cardSizeSlider) + : QWidget(parent), response(_response), cardSizeSlider(_cardSizeSlider) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + openInEditorButton = new QPushButton(this); + layout->addWidget(openInEditorButton); + + connect(openInEditorButton, &QPushButton::clicked, this, + &ArchidektApiResponseDeckDisplayWidget::actOpenInDeckEditor); + + displayOptionsWidget = new VisualDeckDisplayOptionsWidget(this); + layout->addWidget(displayOptionsWidget); + + connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::groupCriteriaChanged, this, + &ArchidektApiResponseDeckDisplayWidget::onGroupCriteriaChange); + + scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + + layout->addWidget(scrollArea); + + container = new QWidget(scrollArea); + + scrollArea->setWidget(container); + + containerLayout = new QVBoxLayout(container); + container->setLayout(containerLayout); + + zoneContainer = new QWidget(container); + containerLayout->addWidget(zoneContainer); + zoneContainerLayout = new QVBoxLayout(zoneContainer); + zoneContainer->setLayout(zoneContainerLayout); + + QString tempDeck; + QTextStream deckStream(&tempDeck); + + for (auto card : response.getCards()) { + QString fullName = card.getCard().getOracleCard().value("name").toString(); + // We don't really care about the second card, the card database already has it as a relation + QString cleanName = fullName.split("//").first().trimmed(); + + tempDeck += QString("%1 %2 (%3) %4\n") + .arg(card.getQuantity()) + .arg(cleanName) + .arg(card.getCard().getEdition().getEditionCode().toUpper()) + .arg(card.getCard().getCollectorNumber()); + } + + model = new DeckListModel(this); + connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset); + model->getDeckList()->loadFromStream_Plain(deckStream, false); + + DeckLoader::resolveSetNameAndNumberToProviderID(model->getDeckList()); + + model->rebuildTree(); + + retranslateUi(); +} + +void ArchidektApiResponseDeckDisplayWidget::retranslateUi() +{ + openInEditorButton->setText(tr("Open Deck in Deck Editor")); +} + +void ArchidektApiResponseDeckDisplayWidget::onGroupCriteriaChange(const QString &activeGroupCriteria) +{ + model->setActiveGroupCriteria(DeckListModelGroupCriteria::fromString(activeGroupCriteria)); + model->sort(1, Qt::AscendingOrder); +} + +void ArchidektApiResponseDeckDisplayWidget::actOpenInDeckEditor() +{ + auto loader = new DeckLoader(this); + loader->getDeckList()->loadFromString_Native(model->getDeckList()->writeToString_Native()); + + loader->getDeckList()->setName(response.getDeckName()); + + emit openInDeckEditor(loader); +} + +void ArchidektApiResponseDeckDisplayWidget::clearAllDisplayWidgets() +{ + for (auto idx : indexToWidgetMap.keys()) { + auto displayWidget = indexToWidgetMap.value(idx); + zoneContainerLayout->removeWidget(displayWidget); + indexToWidgetMap.remove(idx); + delete displayWidget; + } +} + +void ArchidektApiResponseDeckDisplayWidget::decklistModelReset() +{ + clearAllDisplayWidgets(); + constructZoneWidgetsFromDeckListModel(); +} + +void ArchidektApiResponseDeckDisplayWidget::constructZoneWidgetsFromDeckListModel() +{ + qDebug() << model->rowCount(model->getRoot()); + QSortFilterProxyModel proxy; + proxy.setSourceModel(model); + proxy.setSortRole(Qt::EditRole); + proxy.sort(1, Qt::AscendingOrder); + + for (int i = 0; i < proxy.rowCount(); ++i) { + QModelIndex proxyIndex = proxy.index(i, 0); + QModelIndex sourceIndex = proxy.mapToSource(proxyIndex); + + // Make a persistent index from the *source* model + QPersistentModelIndex persistent(sourceIndex); + + if (indexToWidgetMap.contains(persistent)) { + continue; + } + + DeckCardZoneDisplayWidget *zoneDisplayWidget = + new DeckCardZoneDisplayWidget(zoneContainer, model, nullptr, persistent, + model->data(persistent.sibling(persistent.row(), 1), Qt::EditRole).toString(), + "maintype", {"name"}, DisplayType::Overlap, 20, 10, cardSizeSlider); + + connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::sortCriteriaChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged); + connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::groupCriteriaChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged); + connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::displayTypeChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::refreshDisplayType); + zoneContainerLayout->addWidget(zoneDisplayWidget); + + indexToWidgetMap.insert(persistent, zoneDisplayWidget); + } +} + +void ArchidektApiResponseDeckDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + layout->invalidate(); + layout->activate(); + layout->update(); +} diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h new file mode 100644 index 000000000..3d624d293 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h @@ -0,0 +1,125 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_DISPLAY_WIDGET_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_DISPLAY_WIDGET_H + +#include "../../../../../deck_loader/deck_loader.h" +#include "../../../../cards/card_size_widget.h" +#include "../../../../general/layout_containers/flow_widget.h" +#include "../../../../visual_deck_editor/visual_deck_display_options_widget.h" +#include "../api_response/deck/archidekt_api_response_deck.h" +#include "deck_list_model.h" + +#include +#include +#include +#include +#include + +/** + * @class ArchidektApiResponseDeckDisplayWidget + * @brief Displays a full deck fetched from an Archidekt API response. + * + * This widget visualizes all cards in a deck retrieved from the Archidekt API. + * It supports: + * - Interactive display options via a VisualDeckDisplayOptionsWidget. + * - Scrollable display of deck zones/cards with DeckCardZoneDisplayWidget. + * - Integration with a CardSizeWidget slider for card scaling. + * - Opening the deck in a deck editor. + * + * The widget internally constructs a DeckListModel from the Archidekt API response, + * then builds zone widgets for each group of cards according to the active group + * criteria. It also responds dynamically to model resets or sorting/grouping changes. + * + * ### Signals + * - `requestNavigation(QString url)` — triggered when navigation to a deck URL is requested. + * - `openInDeckEditor(DeckLoader *loader)` — emitted when the user chooses to open the deck + * in the deck editor. + * + * ### Features + * - Automatically generates DeckCardZoneDisplayWidget instances for each card group. + * - Provides a scrollable layout for decks of arbitrary size. + * - Updates layouts dynamically when resized or when display/group/sort criteria change. + */ +class ArchidektApiResponseDeckDisplayWidget : public QWidget +{ + Q_OBJECT + +signals: + /** + * @brief Emitted when navigation to a deck URL is requested. + * @param url URL of the deck on Archidekt. + */ + void requestNavigation(QString url); + + /** + * @brief Emitted when the deck should be opened in the deck editor. + * @param loader Initialized DeckLoader containing the deck data. + */ + void openInDeckEditor(DeckLoader *loader); + +public: + /** + * @brief Constructs a display widget for an Archidekt deck. + * @param parent Parent widget. + * @param response API deck data container. + * @param cardSizeSlider Slider controlling card scaling. + */ + explicit ArchidektApiResponseDeckDisplayWidget(QWidget *parent, + ArchidektApiResponseDeck response, + CardSizeWidget *cardSizeSlider); + + /** + * @brief Updates all UI text for retranslation/localization. + * + * Called when the application language changes. + */ + void retranslateUi(); + + /** + * @brief Opens the deck in the deck editor via DeckLoader. + */ + void actOpenInDeckEditor(); + + /** + * @brief Clears all dynamically generated card zone display widgets. + */ + void clearAllDisplayWidgets(); + + /** + * @brief Handles model reset by clearing and reconstructing display widgets. + */ + void decklistModelReset(); + + /** + * @brief Builds DeckCardZoneDisplayWidget instances from the current DeckListModel. + */ + void constructZoneWidgetsFromDeckListModel(); + +private slots: + /** + * @brief Slot triggered when the active group criteria change. + * @param activeGroupCriteria Name of the new grouping criteria. + */ + void onGroupCriteriaChange(const QString &activeGroupCriteria); + +private: + ArchidektApiResponseDeck response; ///< API deck data container + CardSizeWidget *cardSizeSlider; ///< Slider for adjusting card sizes + QVBoxLayout *layout; ///< Main vertical layout + QPushButton *openInEditorButton; ///< Button to open deck in editor + VisualDeckDisplayOptionsWidget *displayOptionsWidget; ///< Controls grouping/sorting/display + QScrollArea *scrollArea; ///< Scrollable area for deck zones + QWidget *zoneContainer; ///< Container for deck zones + QVBoxLayout *zoneContainerLayout; ///< Layout for deck zones + QWidget *container; ///< Outer container for scroll area + QHash indexToWidgetMap; ///< Maps model indices to widgets + QVBoxLayout *containerLayout; ///< Layout for container + DeckListModel *model; ///< Deck list model +protected slots: + /** + * @brief Updates layout and display on resize. + * @param event Resize event. + */ + void resizeEvent(QResizeEvent *event) override; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp new file mode 100644 index 000000000..1b633c4d3 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp @@ -0,0 +1,235 @@ +#include "archidekt_api_response_deck_entry_display_widget.h" + +#include "../../../../../card_picture_loader/card_picture_loader.h" +#include "../../../../cards/card_info_picture_with_text_overlay_widget.h" +#include "../../../../general/display/background_plate_widget.h" +#include "../../../../general/display/color_bar.h" +#include "archidekt_deck_preview_image_display_widget.h" + +#include +#include +#include +#include +#include +#include + +#define ARCHIDEKT_DEFAULT_IMAGE "https://storage.googleapis.com/topdekt-user/images/archidekt_deck_card_shadow.jpg" + +QString timeAgo(const QString ×tamp) +{ + QDateTime dt = QDateTime::fromString(timestamp, Qt::ISODate); + + if (!dt.isValid()) + return timestamp; // fallback if parsing fails + + qint64 secs = dt.secsTo(QDateTime::currentDateTimeUtc()); + + if (secs < 60) + return QString("%1 seconds ago").arg(secs); + if (secs < 3600) + return QString("%1 minutes ago").arg(secs / 60); + if (secs < 86400) + return QString("%1 hours ago").arg(secs / 3600); + if (secs < 30 * 86400) + return QString("%1 days ago").arg(secs / 86400); + if (secs < 365 * 86400) + return QString("%1 months ago").arg(secs / (30 * 86400)); + + return QString("%1 years ago").arg(secs / (365 * 86400)); +} + +ArchidektApiResponseDeckEntryDisplayWidget::ArchidektApiResponseDeckEntryDisplayWidget( + QWidget *parent, + ArchidektApiResponseDeckListingContainer _response, + QNetworkAccessManager *_imageNetworkManager) + : QWidget(parent), response(_response), imageNetworkManager(_imageNetworkManager) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + this->setMaximumWidth(400); + this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + + auto headerLayout = new QVBoxLayout(); + + previewWidget = new ArchidektDeckPreviewImageDisplayWidget(this); + + previewWidget->setMaximumWidth(400); + previewWidget->setMinimumHeight(300); // consistent height + + // Set deck name (ellided) + QFontMetrics fm(previewWidget->topLeftLabel->font()); + QString elided = fm.elidedText(response.getName(), Qt::ElideRight, 280); + previewWidget->topLeftLabel->setLabelText(elided); + previewWidget->topLeftLabel->setToolTip(response.getName()); + + // Set count + previewWidget->topRightLabel->setLabelText(QString::number(response.getSize())); + + // EDH bracket (skip if 0) + if (response.getEDHBracket() != 0) { + previewWidget->bottomLeftLabel->setLabelText(QString("EDH: %1").arg(response.getEDHBracket())); + } else { + previewWidget->bottomLeftLabel->hide(); + } + + // Views + previewWidget->bottomRightLabel->setLabelText(QString("Views: %1").arg(response.getViewCount())); + + // Use preview->imageLabel for image loading + picture = previewWidget->imageLabel; + + imageUrl = response.getFeatured().isEmpty() ? QUrl(ARCHIDEKT_DEFAULT_IMAGE) : QUrl(response.getFeatured()); + + QNetworkRequest req(imageUrl); + QNetworkReply *reply = imageNetworkManager->get(req); + + // tag the reply with "this" so we know it belongs to us later + reply->setProperty("deckWidget", QVariant::fromValue(this)); + reply->setProperty("requestedUrl", imageUrl); + + connect(imageNetworkManager, &QNetworkAccessManager::finished, this, + &ArchidektApiResponseDeckEntryDisplayWidget::onPreviewImageLoadFinished); + + headerLayout->addWidget(previewWidget); + + auto colors = response.getColors(); + + ColorBar *colorBar = new ColorBar(colors, this); + colorBar->setMinPercentThreshold(3); + colorBar->setFixedHeight(22); + + headerLayout->addWidget(colorBar); + + // Create a shared plate for the labels + backgroundPlateWidget = new BackgroundPlateWidget(this); + backgroundPlateWidget->setFixedHeight(120); // Adjust height to fit all labels + + QVBoxLayout *plateLayout = new QVBoxLayout(backgroundPlateWidget); + + // Add labels to the plate layout + QLabel *ownerLabel = new QLabel(QString("Owner: %1").arg(response.getOwner().getName())); + plateLayout->addWidget(ownerLabel); + + QLabel *createdAtLabel = new QLabel(QString("Created: %1").arg(timeAgo(response.getCreatedAt()))); + plateLayout->addWidget(createdAtLabel); + + QLabel *updatedAtLabel = new QLabel(QString("Updated: %1").arg(timeAgo(response.getUpdatedAt()))); + plateLayout->addWidget(updatedAtLabel); + + // Add the shared plate to the header layout + headerLayout->addWidget(backgroundPlateWidget); + + layout->addLayout(headerLayout); +} + +void ArchidektApiResponseDeckEntryDisplayWidget::mousePressEvent(QMouseEvent *event) +{ + QWidget::mousePressEvent(event); + actRequestNavigationToDeck(); +} + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +void ArchidektApiResponseDeckEntryDisplayWidget::enterEvent(QEnterEvent *event) +#else +void ArchidektApiResponseDeckEntryDisplayWidget::enterEvent(QEvent *event) +#endif +{ + QWidget::enterEvent(event); + backgroundPlateWidget->setFocused(true); +} + +void ArchidektApiResponseDeckEntryDisplayWidget::leaveEvent(QEvent *event) +{ + QWidget::leaveEvent(event); + backgroundPlateWidget->setFocused(false); +} + +void ArchidektApiResponseDeckEntryDisplayWidget::setScaleFactor(int scale) +{ + scaleFactor = scale; + updateScaledPreview(); +} + +void ArchidektApiResponseDeckEntryDisplayWidget::onPreviewImageLoadFinished(QNetworkReply *reply) +{ + // Check if this is our reply + void *owner = reply->property("deckWidget").value(); + if (owner != this) { + return; // not our reply + } + + // Check that the requested URL matches what we asked + QUrl requestedUrl = reply->property("requestedUrl").toUrl(); + if (requestedUrl != imageUrl) { + reply->deleteLater(); + return; + } + + QPixmap loaded; + + if (reply->error() != QNetworkReply::NoError || !loaded.loadFromData(reply->readAll())) { + CardPictureLoader::getCardBackLoadingFailedPixmap(loaded, QSize(400, 400)); + } + + originalPixmap = loaded; + + // Always scale preview widget to this ratio + previewWidget->setAspectRatio(DESIGN_RATIO); + previewWidget->setPreviewWidth(400); + + // Initial scaling + updateScaledPreview(); + + reply->deleteLater(); +} + +void ArchidektApiResponseDeckEntryDisplayWidget::updateScaledPreview() +{ + if (originalPixmap.isNull()) { + return; + } + + int baseWidth = 400; + int newWidth = baseWidth * scaleFactor / 100; + int newHeight = static_cast(newWidth * DESIGN_RATIO); + + previewWidget->setFixedSize(newWidth, newHeight); + + // Scale image to fill the preview area (crop edges) + QPixmap scaled = + originalPixmap.scaled(newWidth, newHeight, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + + // Crop to exact target size + QRect cropRect((scaled.width() - newWidth) / 2, (scaled.height() - newHeight) / 2, newWidth, newHeight); + QPixmap cropped = scaled.copy(cropRect); + + picture->setPixmap(cropped); + picture->setFixedSize(newWidth, newHeight); + + // Update the elided deck name based on new width + int textMaxWidth = int(newWidth * 0.7); // allow 70% of width for text + QFontMetrics fm(previewWidget->topLeftLabel->font()); + QString elided = fm.elidedText(response.getName(), Qt::ElideRight, textMaxWidth); + previewWidget->topLeftLabel->setText(elided); + previewWidget->topLeftLabel->setToolTip(response.getName()); + + setFixedWidth(newWidth); + + layout->invalidate(); + layout->activate(); + updateGeometry(); +} + +void ArchidektApiResponseDeckEntryDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + layout->invalidate(); + layout->activate(); + layout->update(); +} + +void ArchidektApiResponseDeckEntryDisplayWidget::actRequestNavigationToDeck() +{ + emit requestNavigation(QString("https://archidekt.com/api/decks/%1/").arg(response.getId())); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.h new file mode 100644 index 000000000..365a99c9c --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.h @@ -0,0 +1,121 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_ENTRY_DISPLAY_WIDGET_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_ENTRY_DISPLAY_WIDGET_H + +#include "../../../../cards/card_info_picture_with_text_overlay_widget.h" +#include "../api_response/deck_listings/archidekt_api_response_deck_listing_container.h" +#include "archidekt_deck_preview_image_display_widget.h" + +#include +#include +#include +#include +#include + +class BackgroundPlateWidget; + +/** + * @class ArchidektApiResponseDeckEntryDisplayWidget + * @brief Displays a single Archidekt deck listing as a preview card with metadata. + * + * This widget renders a deck entry received from an Archidekt API response. It includes: + * - A scaled deck preview image loaded asynchronously via QNetworkAccessManager. + * - Elided deck name in the top-left corner. + * - Deck size, EDH bracket, and view count labels. + * - A color distribution bar summarizing deck colors. + * - Metadata labels including owner, creation date, and last update date. + * + * The widget dynamically scales the preview image and labels according to a linked + * CardSizeWidget slider. Hovering over the widget highlights the background plate, + * and clicking emits a `requestNavigation` signal pointing to the deck URL. + * + * ### Features + * - Asynchronous image loading with fallback to a default placeholder image. + * - Maintains a fixed aspect ratio for the preview image (150:267). + * - Updates text elision dynamically when resized or scaled. + * - Integrates with FlowWidget containers for scrollable deck galleries. + * + * ### Signals + * - `requestNavigation(QString url)` — emitted when the widget is clicked to request + * navigation to the deck's Archidekt page. + * + * ### Slots + * - `actRequestNavigationToDeck()` — triggers navigation. + * - `setScaleFactor(int scale)` — adjusts preview image scaling. + */ +class ArchidektApiResponseDeckEntryDisplayWidget : public QWidget +{ + Q_OBJECT + +signals: + /** + * @brief Emitted when the user requests navigation. + * @param url Full URL to the Archidekt page. + */ + void requestNavigation(QString url); + +public: + /** + * @brief Constructs a deck entry display widget. + * @param parent Parent widget. + * @param response API container holding deck listing data. + * @param imageNetworkManager Shared network manager for fetching preview images. + */ + explicit ArchidektApiResponseDeckEntryDisplayWidget(QWidget *parent, + ArchidektApiResponseDeckListingContainer response, + QNetworkAccessManager *imageNetworkManager); + + /** + * @brief Handles finished network replies for preview images. + * @param reply QNetworkReply containing image data. + * + * Validates that the reply corresponds to this widget and updates the preview image. + */ + void onPreviewImageLoadFinished(QNetworkReply *reply); + + /** + * @brief Updates the scaled preview image and adjusts layout accordingly. + */ + void updateScaledPreview(); + + /** + * @brief Ensures layout responds correctly on resize events. + * @param event Resize event. + */ + void resizeEvent(QResizeEvent *event) override; + +public slots: + /** + * @brief Emits `requestNavigation` for the deck's URL. + */ + void actRequestNavigationToDeck(); + + /** + * @brief Sets a scaling factor (percentage) for the preview image. + * @param scale Scale percentage (100 = normal size). + */ + void setScaleFactor(int scale); + +protected: + void mousePressEvent(QMouseEvent *event) override; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + void enterEvent(QEnterEvent *event) override; ///< Qt6 hover enter +#else + void enterEvent(QEvent *event) override; ///< Qt5 hover enter +#endif + void leaveEvent(QEvent *event) override; + +private: + QVBoxLayout *layout; ///< Main vertical layout + ArchidektApiResponseDeckListingContainer response; ///< Deck data + QUrl imageUrl; ///< URL of the deck's preview image + QNetworkAccessManager *imageNetworkManager; ///< Shared network manager + ArchidektDeckPreviewImageDisplayWidget *previewWidget; ///< Widget showing the deck preview + QLabel *picture; ///< QLabel displaying the scaled pixmap + QPixmap originalPixmap; ///< Original image for scaling (avoids degradation) + int scaleFactor = 100; ///< Current scaling percentage + BackgroundPlateWidget *backgroundPlateWidget; ///< Plate for metadata labels + static constexpr float DESIGN_RATIO = 150.0f / 267.0f; ///< Design aspect ratio +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_ENTRY_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp new file mode 100644 index 000000000..5747ce90d --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp @@ -0,0 +1,47 @@ +#include "archidekt_api_response_deck_listings_display_widget.h" + +#include "../../../../cards/card_info_picture_with_text_overlay_widget.h" +#include "archidekt_api_response_deck_entry_display_widget.h" + +ArchidektApiResponseDeckListingsDisplayWidget::ArchidektApiResponseDeckListingsDisplayWidget( + QWidget *parent, + ArchidektDeckListingApiResponse response, + CardSizeWidget *_cardSizeSlider) + : QWidget(parent), cardSizeSlider(_cardSizeSlider) +{ + layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + setLayout(layout); + + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + + imageNetworkManager = new QNetworkAccessManager(this); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + imageNetworkManager->setTransferTimeout(); // Use Qt's default timeout +#endif + + imageNetworkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); + + // Add widgets for deck listings + auto deckListings = response.results; + for (const auto &deckListing : deckListings) { + auto cardListDisplayWidget = + new ArchidektApiResponseDeckEntryDisplayWidget(this, deckListing, imageNetworkManager); + cardListDisplayWidget->setScaleFactor(cardSizeSlider->getSlider()->value()); + connect(cardListDisplayWidget, &ArchidektApiResponseDeckEntryDisplayWidget::requestNavigation, this, + &ArchidektApiResponseDeckListingsDisplayWidget::requestNavigation); + connect(cardSizeSlider->getSlider(), &QSlider::valueChanged, cardListDisplayWidget, + &ArchidektApiResponseDeckEntryDisplayWidget::setScaleFactor); + flowWidget->addWidget(cardListDisplayWidget); + } + + layout->addWidget(flowWidget); +} + +void ArchidektApiResponseDeckListingsDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + layout->invalidate(); + layout->activate(); + layout->update(); +} diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h new file mode 100644 index 000000000..f00941239 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h @@ -0,0 +1,97 @@ +#ifndef COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_LISTINGS_DISPLAY_WIDGET_H +#define COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_LISTINGS_DISPLAY_WIDGET_H + +#include "../../../../cards/card_size_widget.h" +#include "../../../../general/layout_containers/flow_widget.h" +#include "../api_response/archidekt_deck_listing_api_response.h" + +#include +#include +#include +#include +#include + +/** + * @class ArchidektApiResponseDeckListingsDisplayWidget + * @brief Displays a scrollable horizontal list of Archidekt deck listings with dynamic card sizing. + * + * This widget serves as a container for multiple + * ArchidektApiResponseDeckEntryDisplayWidget instances, each representing one deck listing + * returned from an Archidekt API call. + * + * ### Responsibilities + * - Creates a **FlowWidget** that arranges deck entries horizontally with wrapping. + * - Creates a shared **QNetworkAccessManager** for entry widgets to fetch card thumbnails. + * - Connects a **CardSizeWidget** slider to all deck entries to dynamically rescale their preview cards. + * - Propagates deck-navigation requests (`requestNavigation`) from children to the parent. + * + * ### Layout + * The widget uses a single `QHBoxLayout` containing the `FlowWidget`. + * The FlowWidget automatically manages child flow and scrollbar behavior (horizontal = off, + * vertical = auto), providing an efficient scrollable gallery of deck entries. + * + * ### API Integration + * The constructor consumes an `ArchidektDeckListingApiResponse`, iterates through its + * `results`, and instantiates a child entry widget for each deck. + * + * ### Signals + * - `requestNavigation(QString url)` — emitted whenever a child entry widget requests + * navigation to a deck or related Archidekt page. + * + * ### Performance Notes + * - All entry widgets share a single QNetworkAccessManager instance to reuse connections + * and avoid redundant session creation. + * - `resizeEvent()` forces layout invalidation to ensure the flow layout responds properly + * to container resizing. + */ +class ArchidektApiResponseDeckListingsDisplayWidget : public QWidget +{ + Q_OBJECT + +signals: + /** + * @brief Emitted when a child deck entry requests that the UI navigate to a particular URL. + * @param url The destination URL (typically an Archidekt deck page). + */ + void requestNavigation(QString url); + +public: + /** + * @brief Constructs a widget that displays multiple deck listing previews. + * + * @param parent Parent widget. + * @param response The Archidekt API response containing deck listings. + * @param cardSizeSlider A slider widget used to dynamically resize card previews. + * + * Each deck in `response.results` becomes its own + * ArchidektApiResponseDeckEntryDisplayWidget, added to the FlowWidget. + */ + explicit ArchidektApiResponseDeckListingsDisplayWidget(QWidget *parent, + ArchidektDeckListingApiResponse response, + CardSizeWidget *cardSizeSlider); + + /** + * @brief Ensures FlowWidget layout properly recomputes on resize. + * + * Forces the layout to invalidate and activate itself so that the + * FlowWidget recalculates wrapping and child placement. + * + * @param event Resize event. + */ + void resizeEvent(QResizeEvent *event) override; + +private: + /// Slider controlling the scale of card thumbnails in all deck entry widgets. + CardSizeWidget *cardSizeSlider; + + /// Main horizontal layout containing the FlowWidget. + QHBoxLayout *layout; + + /// Container providing scrollable multi-row flow layout of deck entries. + FlowWidget *flowWidget; + + /// Shared network manager used to download card images for all child entry widgets. + QNetworkAccessManager *imageNetworkManager; +}; + +#endif // COCKATRICE_ARCHIDEKT_API_RESPONSE_DECK_LISTINGS_DISPLAY_WIDGET_H \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp new file mode 100644 index 000000000..ced08491e --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp @@ -0,0 +1,80 @@ +#include "archidekt_deck_preview_image_display_widget.h" + +#include +#include + +ArchidektDeckPreviewImageDisplayWidget::ArchidektDeckPreviewImageDisplayWidget(QWidget *parent) : QWidget(parent) +{ + imageLabel = new QLabel(this); + imageLabel->setAlignment(Qt::AlignCenter); + + topLeftLabel = new ShadowBackgroundLabel(this, ""); + topRightLabel = new ShadowBackgroundLabel(this, ""); + bottomLeftLabel = new ShadowBackgroundLabel(this, ""); + bottomRightLabel = new ShadowBackgroundLabel(this, ""); + + QFont f; + f.setBold(true); + f.setPointSize(16); + topLeftLabel->setFont(f); + topRightLabel->setFont(f); + bottomLeftLabel->setFont(f); + bottomRightLabel->setFont(f); + + // Raise so labels appear above image + topLeftLabel->raise(); + topRightLabel->raise(); + bottomLeftLabel->raise(); + bottomRightLabel->raise(); +} + +void ArchidektDeckPreviewImageDisplayWidget::setAspectRatio(float ratio) +{ + aspectRatio = ratio; +} + +void ArchidektDeckPreviewImageDisplayWidget::setPreviewWidth(int width) +{ + int height = int(width * aspectRatio); + + setFixedSize(width, height); + updateGeometry(); + update(); +} + +void ArchidektDeckPreviewImageDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + + // Full size for the image + imageLabel->setGeometry(rect()); + + topLeftLabel->adjustSize(); + topRightLabel->adjustSize(); + bottomLeftLabel->adjustSize(); + bottomRightLabel->adjustSize(); + + // Padding settings + const int horizontalPadding = 8; + const int topPadding = 6; + const int bottomPadding = 6; + + // Left-aligned, top-aligned (Deck Name) + topLeftLabel->move(horizontalPadding, topPadding); + + // Right-aligned, top-aligned (Card Count) + topRightLabel->move(width() - topRightLabel->width() - horizontalPadding, topPadding); + + // Bottom-left, bottom-aligned (EDH bracket) + bottomLeftLabel->move(horizontalPadding, height() - bottomLeftLabel->height() - bottomPadding); + + // Bottom-right, bottom-aligned (views) + bottomRightLabel->move(width() - bottomRightLabel->width() - horizontalPadding, + height() - bottomRightLabel->height() - bottomPadding); + + // Ensure labels stay above image + topLeftLabel->raise(); + topRightLabel->raise(); + bottomLeftLabel->raise(); + bottomRightLabel->raise(); +} diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h new file mode 100644 index 000000000..053823c45 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h @@ -0,0 +1,66 @@ +#ifndef COCKATRICE_ARCHIDEKT_DECK_PREVIEW_IMAGE_DISPLAY_WIDGET_H +#define COCKATRICE_ARCHIDEKT_DECK_PREVIEW_IMAGE_DISPLAY_WIDGET_H + +#include "../../../../general/display/shadow_background_label.h" + +#include +#include + +/** + * @class ArchidektDeckPreviewImageDisplayWidget + * @brief Widget for displaying a deck preview image with overlaid metadata labels. + * + * This widget shows a deck's preview image along with several overlay labels: + * - Top-left: Deck name. + * - Top-right: Card count. + * - Bottom-left: EDH bracket (if applicable). + * - Bottom-right: View count. + * + * Labels automatically scale and position themselves relative to the widget's size. + * The image can be scaled while maintaining a specified aspect ratio. + * + * ### Features + * - Adjustable preview width and aspect ratio. + * - Labels automatically repositioned on resize. + * - Supports overlaying multiple pieces of metadata with shadowed labels for readability. + */ +class ArchidektDeckPreviewImageDisplayWidget : public QWidget +{ + Q_OBJECT +public: + /** + * @brief Constructs the deck preview display widget. + * @param parent Optional parent widget. + */ + explicit ArchidektDeckPreviewImageDisplayWidget(QWidget *parent = nullptr); + + /** + * @brief Sets the aspect ratio for the preview image (height / width). + * @param ratio Aspect ratio to maintain. + */ + void setAspectRatio(float ratio); + + /** + * @brief Sets the width of the preview image; height is adjusted according to the aspect ratio. + * @param width Desired width in pixels. + */ + void setPreviewWidth(int width); + + QLabel *imageLabel; ///< QLabel to display the deck image + ShadowBackgroundLabel *topLeftLabel; ///< Overlay label at top-left (deck name) + ShadowBackgroundLabel *topRightLabel; ///< Overlay label at top-right (card count) + ShadowBackgroundLabel *bottomLeftLabel; ///< Overlay label at bottom-left (EDH bracket) + ShadowBackgroundLabel *bottomRightLabel; ///< Overlay label at bottom-right (views) + +protected: + /** + * @brief Handles resize events to reposition the image and overlay labels. + * @param event Resize event. + */ + void resizeEvent(QResizeEvent *event) override; + +private: + float aspectRatio = 1.0f; ///< Aspect ratio to maintain for the preview image +}; + +#endif // COCKATRICE_ARCHIDEKT_DECK_PREVIEW_IMAGE_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp new file mode 100644 index 000000000..7f04987c1 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp @@ -0,0 +1,562 @@ +#include "tab_archidekt.h" + +#include "../../../../../client/settings/cache_settings.h" +#include "../../../cards/additional_info/mana_symbol_widget.h" +#include "../../tab_supervisor.h" +#include "api_response/archidekt_deck_listing_api_response.h" +#include "display/archidekt_api_response_deck_display_widget.h" +#include "display/archidekt_api_response_deck_listings_display_widget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) +{ + networkManager = new QNetworkAccessManager(this); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + networkManager->setTransferTimeout(); // Use Qt's default timeout +#endif + + networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); + connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(processApiJson(QNetworkReply *))); + + searchDebounceTimer = new QTimer(this); + searchDebounceTimer->setSingleShot(true); // We only want it to fire once after inactivity + searchDebounceTimer->setInterval(300); // 300ms debounce + + connect(searchDebounceTimer, &QTimer::timeout, this, [this]() { doSearchImmediate(); }); + + container = new QWidget(this); + mainLayout = new QVBoxLayout(container); + mainLayout->setContentsMargins(0, 0, 0, 0); + container->setLayout(mainLayout); + + navigationContainer = new QWidget(container); + navigationContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + navigationLayout = new QHBoxLayout(navigationContainer); + navigationLayout->setSpacing(3); + navigationContainer->setLayout(navigationLayout); + + // Sort by + + orderByCombo = new QComboBox(navigationContainer); + orderByCombo->addItems({"name", "updatedAt", "createdAt", "viewCount", "size", "edhBracket"}); + orderByCombo->setCurrentText("updatedAt"); // Pre-select updatedAt + + // Asc/Desc toggle + orderDirButton = new QPushButton(tr("Desc."), navigationContainer); + orderDirButton->setCheckable(true); // checked = DESC, unchecked = ASC + orderDirButton->setChecked(true); + + connect(orderByCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch); + connect(orderDirButton, &QPushButton::clicked, this, [this](bool checked) { + orderDirButton->setText(checked ? tr("Desc.") : tr("Asc.")); + doSearch(); + }); + + // Colors + QHBoxLayout *colorLayout = new QHBoxLayout(); + QString colorIdentity = "WUBRG"; // Optionally include "C" for colorless once we have a symbol for it + + for (const QChar &color : colorIdentity) { + auto *manaSymbol = new ManaSymbolWidget(navigationContainer, color, false, true); + manaSymbol->setFixedWidth(25); + colorLayout->addWidget(manaSymbol); + + connect(manaSymbol, &ManaSymbolWidget::colorToggled, this, [this](QChar c, bool active) { + if (active) { + activeColors.insert(c); + } else { + activeColors.remove(c); + } + doSearch(); + }); + } + + logicalAndCheck = new QCheckBox("Require ALL colors", navigationContainer); + + // Formats + + formatLabel = new QLabel(this); + + formatSettingsWidget = new SettingsButtonWidget(this); + + QStringList formatNames = {"Standard", "Modern", "Commander", "Legacy", "Vintage", + "Pauper", "Custom", "Frontier", "Future Std", "Penny Dreadful", + "1v1 Commander", "Dual Commander", "Brawl"}; + + for (int i = 0; i < formatNames.size(); ++i) { + QCheckBox *formatCheckBox = new QCheckBox(formatNames[i], navigationContainer); + connect(formatCheckBox, &QCheckBox::clicked, this, &TabArchidekt::doSearch); + formatChecks << formatCheckBox; + formatSettingsWidget->addSettingsWidget(formatCheckBox); + } + + // EDH Bracket + edhBracketCombo = new QComboBox(navigationContainer); + edhBracketCombo->addItem(tr("Any Bracket")); + edhBracketCombo->addItems({"1", "2", "3", "4", "5"}); + + connect(edhBracketCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch); + + // Search for Card Packages instead of Decks + packagesCheck = new QCheckBox("Packages", navigationContainer); + + connect(packagesCheck, &QCheckBox::clicked, this, [this]() { + bool disable = packagesCheck->isChecked(); + for (auto *cb : formatChecks) + cb->setEnabled(!disable); + commandersField->setEnabled(!disable); + deckTagNameField->setEnabled(!disable); + edhBracketCombo->setCurrentIndex(0); + edhBracketCombo->setEnabled(!disable); + doSearch(); + }); + + // Deck Name + nameField = new QLineEdit(navigationContainer); + nameField->setPlaceholderText(tr("Deck name contains...")); + + // Owner Name + ownerField = new QLineEdit(navigationContainer); + ownerField->setPlaceholderText(tr("Owner name contains...")); + + // Contained cards + cardsField = new QLineEdit(navigationContainer); + cardsField->setPlaceholderText("Deck contains card..."); + + // Commanders + commandersField = new QLineEdit(navigationContainer); + commandersField->setPlaceholderText("Deck has commander..."); + + // DB supplemented card search + auto cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this); + auto displayModel = new CardDatabaseDisplayModel(this); + displayModel->setSourceModel(cardDatabaseModel); + auto *searchModel = new CardSearchModel(displayModel, this); + + auto *proxyModel = new CardCompleterProxyModel(this); + proxyModel->setSourceModel(searchModel); + proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + proxyModel->setFilterRole(Qt::DisplayRole); + + auto *completer = new QCompleter(proxyModel, this); + completer->setCompletionRole(Qt::DisplayRole); + completer->setCompletionMode(QCompleter::PopupCompletion); + completer->setCaseSensitivity(Qt::CaseInsensitive); + completer->setFilterMode(Qt::MatchContains); + completer->setMaxVisibleItems(10); + + cardsField->setCompleter(completer); + commandersField->setCompleter(completer); + + connect(cardsField, &QLineEdit::textChanged, searchModel, &CardSearchModel::updateSearchResults); + + connect(cardsField, &QLineEdit::textChanged, this, [=](const QString &text) { + QString pattern = ".*" + QRegularExpression::escape(text) + ".*"; + proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption)); + if (!text.isEmpty()) + completer->complete(); + }); + + connect(commandersField, &QLineEdit::textChanged, searchModel, &CardSearchModel::updateSearchResults); + + connect(commandersField, &QLineEdit::textChanged, this, [=](const QString &text) { + QString pattern = ".*" + QRegularExpression::escape(text) + ".*"; + proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption)); + if (!text.isEmpty()) + completer->complete(); + }); + + // Tag Name + deckTagNameField = new QLineEdit(navigationContainer); + deckTagNameField->setPlaceholderText("Deck tag"); + + connect(deckTagNameField, &QLineEdit::textChanged, this, &TabArchidekt::doSearch); + + // Search button + searchPushButton = new QPushButton(navigationContainer); + searchPushButton->setText("Search"); + + connect(searchPushButton, &QPushButton::clicked, this, &TabArchidekt::doSearch); + + // Card Size settings + settingsButton = new SettingsButtonWidget(this); + cardSizeSlider = new CardSizeWidget(this, nullptr, SettingsCache::instance().getArchidektPreviewSize()); + connect(cardSizeSlider, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(), + &SettingsCache::setArchidektPreviewCardSize); + settingsButton->addSettingsWidget(cardSizeSlider); + + // Min deck size + minDeckSizeLabel = new QLabel(navigationContainer); + + minDeckSizeSpin = new QSpinBox(navigationContainer); + minDeckSizeSpin->setSpecialValueText(tr("Disabled")); + minDeckSizeSpin->setRange(0, 200); + minDeckSizeSpin->setValue(0); + + // Size logic + minDeckSizeLogicCombo = new QComboBox(navigationContainer); + minDeckSizeLogicCombo->addItems({"Exact", "≥", "≤"}); // Exact = unset, ≥ = GTE, ≤ = LTE + minDeckSizeLogicCombo->setCurrentIndex(1); // default GTE + + connect(minDeckSizeSpin, QOverload::of(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch); + connect(minDeckSizeLogicCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch); + + // Page number + pageLabel = new QLabel(navigationContainer); + + pageSpin = new QSpinBox(navigationContainer); + pageSpin->setRange(1, 9999); + pageSpin->setValue(1); + + connect(pageSpin, QOverload::of(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch); + + // Page display + currentPageDisplay = new QWidget(container); + currentPageLayout = new QVBoxLayout(currentPageDisplay); + currentPageLayout->setContentsMargins(0, 0, 0, 0); + currentPageDisplay->setLayout(currentPageLayout); + + // Layout composition + + // Sort section + navigationLayout->addWidget(orderByCombo); + navigationLayout->addWidget(orderDirButton); + + // Colors section + navigationLayout->addLayout(colorLayout); + navigationLayout->addWidget(logicalAndCheck); + + // Formats section + navigationLayout->addWidget(formatSettingsWidget); + navigationLayout->addWidget(formatLabel); + + // EDH Bracket + navigationLayout->addWidget(edhBracketCombo); + + // Packages toggle + navigationLayout->addWidget(packagesCheck); + + // Deck name + navigationLayout->addWidget(nameField); + + // Owner name + navigationLayout->addWidget(ownerField); + + // Contained cards + navigationLayout->addWidget(cardsField); + + // Commanders + navigationLayout->addWidget(commandersField); + + // Deck tag + navigationLayout->addWidget(deckTagNameField); + + // Search button + navigationLayout->addWidget(searchPushButton); + + // Card size settings + navigationLayout->addWidget(settingsButton); + + // Min. # of cards in deck + navigationLayout->addWidget(minDeckSizeLabel); + navigationLayout->addWidget(minDeckSizeSpin); + navigationLayout->addWidget(minDeckSizeLogicCombo); + + // Page number + navigationLayout->addWidget(pageLabel); + navigationLayout->addWidget(pageSpin); + + mainLayout->addWidget(navigationContainer); + mainLayout->addWidget(currentPageDisplay); + + // Ensure navigation stays at the top and currentPageDisplay takes remaining space + mainLayout->setStretch(0, 0); // navigationContainer gets minimum space + mainLayout->setStretch(1, 1); // currentPageDisplay expands as much as possible + + setCentralWidget(container); + + TabArchidekt::retranslateUi(); + + getTopDecks(); +} + +void TabArchidekt::retranslateUi() +{ + searchPushButton->setText(tr("Search")); + formatLabel->setText(tr("Formats")); + minDeckSizeLabel->setText(tr("Min. # of Cards:")); + pageLabel->setText(tr("Page:")); +} + +QString TabArchidekt::buildSearchUrl() +{ + QUrlQuery query; + + // orderBy (field + direction) + { + QString field = orderByCombo->currentText(); + if (!field.isEmpty()) { + bool desc = orderDirButton->isChecked(); + QString final = desc ? "-" + field : field; + query.addQueryItem("orderBy", final); + } + } + + // Colors + QStringList selectedColors; + for (QChar c : activeColors) { + selectedColors.append(c); + } + if (!selectedColors.isEmpty()) { + query.addQueryItem("colors", selectedColors.join(",")); + } + + // logicalAnd + if (logicalAndCheck->isChecked()) { + query.addQueryItem("logicalAnd", "true"); + } + + // Formats + if (!packagesCheck->isChecked()) { + QStringList formatIds; + for (int i = 0; i < formatChecks.size(); ++i) + if (formatChecks[i]->isChecked()) { + formatIds << QString::number(i + 1); + } + + if (!formatIds.isEmpty()) { + query.addQueryItem("deckFormat", formatIds.join(",")); + } + } + + // edhBracket + if (!packagesCheck->isChecked()) { + if (!edhBracketCombo->currentText().isEmpty()) { + if (edhBracketCombo->currentText() != tr("Any Bracket")) { + query.addQueryItem("edhBracket", edhBracketCombo->currentText()); + } + } + } + + // Search for card packages instead of decks + if (packagesCheck->isChecked()) { + query.addQueryItem("packages", "true"); + } + + // Name + if (!nameField->text().isEmpty()) { + query.addQueryItem("name", nameField->text()); + } + + // owner + if (!ownerField->text().isEmpty()) { + query.addQueryItem("ownerUsername", ownerField->text()); + } + + // cards + if (!cardsField->text().isEmpty()) { + query.addQueryItem("cardName", cardsField->text()); + } + + // Commander Name + if (!packagesCheck->isChecked()) { + if (!commandersField->text().isEmpty()) { + query.addQueryItem("commanderName", commandersField->text()); + } + } + + // deckTagName + if (!packagesCheck->isChecked()) { + if (!deckTagNameField->text().isEmpty()) { + query.addQueryItem("deckTagName", deckTagNameField->text()); + } + } + + // page number + if (pageSpin->value() <= 1) { + query.addQueryItem("page", QString::number(pageSpin->value())); + } + + // Min deck size + if (minDeckSizeSpin->value() != 0) { + query.addQueryItem("size", QString::number(minDeckSizeSpin->value())); + + QString logic = "GTE"; // default + QString selected = minDeckSizeLogicCombo->currentText(); + if (selected == "≥") + logic = "GTE"; + else if (selected == "≤") + logic = "LTE"; + else + logic = ""; // Exact = unset + + if (!logic.isEmpty()) { + query.addQueryItem("sizeLogic", logic); + } + } + + // build final URL + QUrl url("https://archidekt.com/api/decks/v3/"); + url.setQuery(query); + + return url.toString(); +} + +void TabArchidekt::doSearch() +{ + searchDebounceTimer->start(); +} + +void TabArchidekt::doSearchImmediate() +{ + QString url = buildSearchUrl(); + QNetworkRequest req{QUrl(url)}; + networkManager->get(req); +} + +void TabArchidekt::actNavigatePage(QString url) +{ + QNetworkRequest request{QUrl(url)}; + networkManager->get(request); +} + +void TabArchidekt::getTopDecks() +{ + QNetworkRequest request{QUrl(buildSearchUrl())}; + networkManager->get(request); +} + +void TabArchidekt::processApiJson(QNetworkReply *reply) +{ + if (reply->error() != QNetworkReply::NoError) { + qDebug() << "Network error occurred:" << reply->errorString(); + reply->deleteLater(); + return; + } + + QByteArray responseData = reply->readAll(); + QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData); + + if (!jsonDoc.isObject()) { + qDebug() << "Invalid JSON response received."; + reply->deleteLater(); + return; + } + + QJsonObject jsonObj = jsonDoc.object(); + + // Get the actual URL from the reply + QString responseUrl = reply->url().toString(); + + // Check if the response URL matches a commander request + if (responseUrl.startsWith("https://archidekt.com/api/decks/v3/")) { + processTopDecksResponse(jsonObj); + } else if (responseUrl.startsWith("https://archidekt.com/api/decks/")) { + processDeckResponse(jsonObj); + } else { + prettyPrintJson(jsonObj, 4); + } + + reply->deleteLater(); +} + +void TabArchidekt::processTopDecksResponse(QJsonObject reply) +{ + ArchidektDeckListingApiResponse deckData; + deckData.fromJson(reply); + + // **Remove previous page display to prevent stacking** + if (currentPageDisplay) { + mainLayout->removeWidget(currentPageDisplay); + delete currentPageDisplay; + currentPageDisplay = nullptr; + } + + // **Create new currentPageDisplay** + currentPageDisplay = new QWidget(container); + currentPageLayout = new QVBoxLayout(currentPageDisplay); + currentPageDisplay->setLayout(currentPageLayout); + + auto display = new ArchidektApiResponseDeckListingsDisplayWidget(currentPageDisplay, deckData, cardSizeSlider); + connect(display, &ArchidektApiResponseDeckListingsDisplayWidget::requestNavigation, this, + &TabArchidekt::actNavigatePage); + currentPageLayout->addWidget(display); + + mainLayout->addWidget(currentPageDisplay); + + // **Ensure layout stays correct** + mainLayout->setStretch(0, 0); // Keep navigationContainer at the top + mainLayout->setStretch(1, 1); // Make sure currentPageDisplay takes remaining space +} + +void TabArchidekt::processDeckResponse(QJsonObject reply) +{ + ArchidektApiResponseDeck deckData; + deckData.fromJson(reply); + + // **Remove previous page display to prevent stacking** + if (currentPageDisplay) { + mainLayout->removeWidget(currentPageDisplay); + delete currentPageDisplay; + currentPageDisplay = nullptr; + } + + // **Create new currentPageDisplay** + currentPageDisplay = new QWidget(container); + currentPageLayout = new QVBoxLayout(currentPageDisplay); + currentPageDisplay->setLayout(currentPageLayout); + + auto display = new ArchidektApiResponseDeckDisplayWidget(currentPageDisplay, deckData, cardSizeSlider); + connect(display, &ArchidektApiResponseDeckDisplayWidget::requestNavigation, this, &TabArchidekt::actNavigatePage); + connect(display, &ArchidektApiResponseDeckDisplayWidget::openInDeckEditor, tabSupervisor, + &TabSupervisor::openDeckInNewTab); + currentPageLayout->addWidget(display); + + mainLayout->addWidget(currentPageDisplay); + + // **Ensure layout stays correct** + mainLayout->setStretch(0, 0); // Keep navigationContainer at the top + mainLayout->setStretch(1, 1); // Make sure currentPageDisplay takes remaining space +} + +void TabArchidekt::prettyPrintJson(const QJsonValue &value, int indentLevel) +{ + const QString indent(indentLevel * 2, ' '); // Adjust spacing as needed for pretty printing + + if (value.isObject()) { + QJsonObject obj = value.toObject(); + for (auto it = obj.begin(); it != obj.end(); ++it) { + qDebug().noquote() << indent + it.key() + ":"; + prettyPrintJson(it.value(), indentLevel + 1); + } + } else if (value.isArray()) { + QJsonArray array = value.toArray(); + for (int i = 0; i < array.size(); ++i) { + qDebug().noquote() << indent + QString("[%1]:").arg(i); + prettyPrintJson(array[i], indentLevel + 1); + } + } else if (value.isString()) { + qDebug().noquote() << indent + "\"" + value.toString() + "\""; + } else if (value.isDouble()) { + qDebug().noquote() << indent + QString::number(value.toDouble()); + } else if (value.isBool()) { + qDebug().noquote() << indent + (value.toBool() ? "true" : "false"); + } else if (value.isNull()) { + qDebug().noquote() << indent + "null"; + } +} diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h new file mode 100644 index 000000000..68a5b78c9 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h @@ -0,0 +1,250 @@ +#ifndef COCKATRICE_TAB_ARCHIDEKT_H +#define COCKATRICE_TAB_ARCHIDEKT_H + +#include "../../interface/widgets/cards/card_size_widget.h" +#include "../../interface/widgets/quick_settings/settings_button_widget.h" +#include "../../tab.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** Base API link for Archidekt deck search */ +inline QString archidektApiLink = "https://archidekt.com/api/decks/v3/?name="; + +/** + * @brief Tab for browsing, searching, and filtering Archidekt decks. + * + * This class provides a comprehensive interface for querying decks from the Archidekt API. + * Users can filter decks by name, owner, included cards, commanders, deck tags, colors, EDH bracket, + * and formats. It also provides sorting and pagination, as well as a card size adjustment widget. + */ +class TabArchidekt : public Tab +{ + Q_OBJECT +public: + /** + * @brief Construct a new TabArchidekt object + * @param _tabSupervisor Parent tab supervisor responsible for tab management and callbacks + * + * Initializes the network manager, creates all UI components, sets up layouts, + * connects signals and slots, and triggers an initial fetch of top decks. + */ + explicit TabArchidekt(TabSupervisor *_tabSupervisor); + + /** + * @brief Update all UI text to reflect the current language or translation + * + * This function re-applies translations to all labels, buttons, and placeholders. + * It should be called after a language change. + */ + void retranslateUi() override; + + /** + * @brief Construct the search URL from all current filters + * @return QString Fully constructed URL including all query parameters + * + * The search URL is dynamically built using the state of all filter widgets. + * Parameters included: + * - Deck name + * - Owner + * - Included cards + * - Commander cards + * - Deck tag + * - Colors and logical AND requirement + * - Formats + * - EDH bracket + * - Packages toggle + * - Sorting field and direction + * - Minimum amount of cards in the deck + * - Pagination (page) + */ + QString buildSearchUrl(); + + /** + * @brief Retrieve the tab display text + * @return QString Human-readable title for the tab + * + * If a card is pre-selected (cardToQuery), its name is appended to the tab title. + */ + QString getTabText() const override + { + auto cardName = cardToQuery.isNull() ? QString() : cardToQuery->getName(); + return tr("Archidekt: ") + cardName; + } + + /** + * @brief Get the card size slider widget + * @return CardSizeWidget* Pointer to the card size adjustment slider + * + * Allows external code to read or manipulate the current card size or hook up the sliders signals. + */ + CardSizeWidget *getCardSizeSlider() + { + return cardSizeSlider; + } + + /** @brief Network manager for handling API requests */ + QNetworkAccessManager *networkManager; + +public slots: + /** + * @brief Trigger a search using the current filters + * + * Sends a network request to the Archidekt API using the URL generated by buildSearchUrl(). + * Updates the current page display with results asynchronously. + */ + void doSearch(); + void doSearchImmediate(); + /** + * @brief Process a network reply containing JSON data + * @param reply QNetworkReply object with the API response + * + * Determines whether the response corresponds to a top decks query or a single deck, + * and dispatches it to the appropriate handler. + */ + void processApiJson(QNetworkReply *reply); + + /** + * @brief Handle a JSON response containing multiple decks + * @param reply QJsonObject containing top deck listings + * + * Clears the previous page display and creates a new display widget for the results. + */ + void processTopDecksResponse(QJsonObject reply); + + /** + * @brief Handle a JSON response for a single deck + * @param reply QJsonObject containing deck data + * + * Clears the previous page display and creates a new display widget for the deck details. + */ + void processDeckResponse(QJsonObject reply); + + /** + * @brief Pretty-print a QJsonValue for debugging + * @param value The JSON value to print + * @param indentLevel The indentation depth (number of levels) + */ + void prettyPrintJson(const QJsonValue &value, int indentLevel); + + /** + * @brief Navigate to a specified page URL + * @param url The URL to request + * + * Typically called when a navigation button is clicked in a deck listing. + */ + void actNavigatePage(QString url); + + /** + * @brief Fetch top decks from the Archidekt API + * + * Called on initialization to populate the initial page display. + */ + void getTopDecks(); + +private: + QTimer *searchDebounceTimer; ///< Timer to debounce search requests by spin-boxes etc. + + // --------------------------------------------------------------------- + // Layout Containers + // --------------------------------------------------------------------- + + QWidget *container; ///< Root container for the entire tab + QVBoxLayout *mainLayout; ///< Outer vertical layout containing navigation and page display + QWidget *navigationContainer; ///< Container for all navigation/filter controls + QHBoxLayout *navigationLayout; ///< Layout for horizontal arrangement of filter widgets + QWidget *currentPageDisplay; ///< Widget containing the currently displayed deck(s) + QVBoxLayout *currentPageLayout; ///< Layout for deck display widgets + + // --------------------------------------------------------------------- + // Sorting Controls + // --------------------------------------------------------------------- + + QComboBox *orderByCombo; ///< Dropdown for selecting the sort field + QPushButton *orderDirButton; ///< Toggle button for ascending/descending sort + + // --------------------------------------------------------------------- + // Color Filters + // --------------------------------------------------------------------- + + QSet activeColors; ///< Set of currently active mana colors + QCheckBox *logicalAndCheck; ///< Require ALL selected colors instead of ANY + + // --------------------------------------------------------------------- + // Format Filters + // --------------------------------------------------------------------- + + QLabel *formatLabel; ///< Label displaying "Formats" + SettingsButtonWidget *formatSettingsWidget; ///< Collapsible widget containing format checkboxes + QVector formatChecks; ///< Individual checkboxes for each format + + // --------------------------------------------------------------------- + // EDH Bracket / Package Toggle + // --------------------------------------------------------------------- + + QComboBox *edhBracketCombo; ///< Dropdown for EDH bracket selection + QCheckBox *packagesCheck; ///< Toggle for searching card packages instead of full decks + + // --------------------------------------------------------------------- + // Basic Search Fields + // --------------------------------------------------------------------- + + QLineEdit *nameField; ///< Input for deck name filter + QLineEdit *ownerField; ///< Input for owner name filter + + // --------------------------------------------------------------------- + // Card Filters + // --------------------------------------------------------------------- + + QLineEdit *cardsField; ///< Input for cards included in the deck (comma-separated) + QLineEdit *commandersField; ///< Input for commander cards (comma-separated) + + // --------------------------------------------------------------------- + // Deck Tag + // --------------------------------------------------------------------- + + QLineEdit *deckTagNameField; ///< Input for deck tag filtering + + // --------------------------------------------------------------------- + // Search Trigger + // --------------------------------------------------------------------- + + QPushButton *searchPushButton; ///< Button to trigger the search manually + + // --------------------------------------------------------------------- + // UI Settings (Card Size) + // --------------------------------------------------------------------- + + SettingsButtonWidget *settingsButton; ///< Container for additional UI settings + CardSizeWidget *cardSizeSlider; ///< Slider to adjust card size in results + + // --------------------------------------------------------------------- + // Minimum Cards in Deck + // --------------------------------------------------------------------- + + QLabel *minDeckSizeLabel; ///< Label for minimum number of cards per deck + QSpinBox *minDeckSizeSpin; ///< Spinner to select minimum deck size + QComboBox *minDeckSizeLogicCombo; ///< Combo box for the size logic to apply + + // --------------------------------------------------------------------- + // Pagination + // --------------------------------------------------------------------- + + QLabel *pageLabel; ///< Label for current page selection + QSpinBox *pageSpin; ///< Spinner to select the page number for results + + // --------------------------------------------------------------------- + // Optional Context + // --------------------------------------------------------------------- + + CardInfoPtr cardToQuery; ///< Optional pre-selected card for initial filtering +}; + +#endif // COCKATRICE_TAB_ARCHIDEKT_H diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp index 18a98f5ec..df9387a6c 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp @@ -5,6 +5,7 @@ #include "../interface/widgets/server/user/user_list_manager.h" #include "../interface/widgets/server/user/user_list_widget.h" #include "../main.h" +#include "api/archidekt/tab_archidekt.h" #include "api/edhrec/tab_edhrec_main.h" #include "tab_account.h" #include "tab_admin.h" @@ -140,6 +141,9 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget * aTabEdhRec = new QAction(this); connect(aTabEdhRec, &QAction::triggered, this, [this] { addEdhrecMainTab(); }); + aTabArchidekt = new QAction(this); + connect(aTabArchidekt, &QAction::triggered, this, [this] { addArchidektTab(); }); + aTabHome = new QAction(this); aTabHome->setCheckable(true); connect(aTabHome, &QAction::triggered, this, &TabSupervisor::actTabHome); @@ -204,6 +208,7 @@ void TabSupervisor::retranslateUi() aTabDeckEditor->setText(tr("Deck Editor")); aTabVisualDeckEditor->setText(tr("Visual Deck Editor")); aTabEdhRec->setText(tr("EDHRec")); + aTabArchidekt->setText(tr("Archidekt")); aTabHome->setText(tr("Home")); aTabVisualDeckStorage->setText(tr("&Visual Deck Storage")); aTabVisualDatabaseDisplay->setText(tr("Visual Database Display")); @@ -386,6 +391,7 @@ void TabSupervisor::resetTabsMenu() tabsMenu->addAction(aTabDeckEditor); tabsMenu->addAction(aTabVisualDeckEditor); tabsMenu->addAction(aTabEdhRec); + tabsMenu->addAction(aTabArchidekt); tabsMenu->addSeparator(); tabsMenu->addAction(aTabHome); tabsMenu->addAction(aTabVisualDeckStorage); @@ -899,6 +905,15 @@ TabEdhRecMain *TabSupervisor::addEdhrecMainTab() return tab; } +TabArchidekt *TabSupervisor::addArchidektTab() +{ + auto *tab = new TabArchidekt(this); + + myAddTab(tab); + setCurrentWidget(tab); + return tab; +} + TabVisualDatabaseDisplay *TabSupervisor::addVisualDatabaseDisplayTab() { auto *tab = new TabVisualDatabaseDisplay(this); diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h index 9fd00f584..948ac6d7e 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h @@ -11,6 +11,7 @@ #include "../../deck_loader/deck_loader.h" #include "../interface/widgets/server/user/user_list_proxy.h" #include "abstract_tab_deck_editor.h" +#include "api/archidekt/tab_archidekt.h" #include "api/edhrec/tab_edhrec.h" #include "api/edhrec/tab_edhrec_main.h" #include "tab_visual_database_display.h" @@ -110,7 +111,7 @@ private: QList deckEditorTabs; bool isLocalGame; - QAction *aTabHome, *aTabDeckEditor, *aTabVisualDeckEditor, *aTabEdhRec, *aTabVisualDeckStorage, + QAction *aTabHome, *aTabDeckEditor, *aTabVisualDeckEditor, *aTabEdhRec, *aTabArchidekt, *aTabVisualDeckStorage, *aTabVisualDatabaseDisplay, *aTabServer, *aTabAccount, *aTabDeckStorage, *aTabReplays, *aTabAdmin, *aTabLog; int myAddTab(Tab *tab, QAction *manager = nullptr); @@ -172,6 +173,7 @@ public slots: TabDeckEditorVisual *addVisualDeckEditorTab(DeckLoader *deckToOpen); TabVisualDatabaseDisplay *addVisualDatabaseDisplayTab(); TabEdhRecMain *addEdhrecMainTab(); + TabArchidekt *addArchidektTab(); TabEdhRec *addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander = false); void openReplay(GameReplay *replay); void switchToFirstAvailableNetworkTab(); 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 new file mode 100644 index 000000000..c190038a5 --- /dev/null +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp @@ -0,0 +1,124 @@ +#include "visual_deck_display_options_widget.h" + +#include "../tabs/visual_deck_editor/tab_deck_editor_visual.h" + +VisualDeckDisplayOptionsWidget::VisualDeckDisplayOptionsWidget(QWidget *parent) +{ + groupAndSortLayout = new QHBoxLayout(this); + groupAndSortLayout->setAlignment(Qt::AlignLeft); + this->setLayout(groupAndSortLayout); + + groupByLabel = new QLabel(this); + + groupByComboBox = new QComboBox(this); + if (auto visualDeckEditorWidget = qobject_cast(parent)) { + if (auto tabWidget = qobject_cast(visualDeckEditorWidget)) { + // Inside a central widget QWidget container inside TabDeckEditorVisual + if (auto tab = qobject_cast(tabWidget->parent()->parent())) { + auto originalBox = tab->getDeckDockWidget()->getGroupByComboBox(); + groupByComboBox->setModel(originalBox->model()); + groupByComboBox->setModelColumn(originalBox->modelColumn()); + + // Original -> clone + connect(originalBox, QOverload::of(&QComboBox::currentIndexChanged), + [this](int index) { groupByComboBox->setCurrentIndex(index); }); + + // Clone -> original + connect(groupByComboBox, QOverload::of(&QComboBox::currentIndexChanged), + [originalBox](int index) { originalBox->setCurrentIndex(index); }); + } + } + } else { + groupByComboBox->addItem( + tr(qPrintable(DeckListModelGroupCriteria::toString(DeckListModelGroupCriteria::MAIN_TYPE))), + DeckListModelGroupCriteria::MAIN_TYPE); + groupByComboBox->addItem( + tr(qPrintable(DeckListModelGroupCriteria::toString(DeckListModelGroupCriteria::MANA_COST))), + DeckListModelGroupCriteria::MANA_COST); + groupByComboBox->addItem( + tr(qPrintable(DeckListModelGroupCriteria::toString(DeckListModelGroupCriteria::COLOR))), + DeckListModelGroupCriteria::COLOR); + groupByComboBox->setMinimumWidth(300); + connect(groupByComboBox, QOverload::of(&QComboBox::currentTextChanged), this, + &VisualDeckDisplayOptionsWidget::groupCriteriaChanged); + emit groupCriteriaChanged(groupByComboBox->currentText()); + } + + sortByLabel = new QLabel(this); + + sortCriteriaButton = new SettingsButtonWidget(this); + + sortLabel = new QLabel(sortCriteriaButton); + sortLabel->setWordWrap(true); + + QStringList sortProperties = {"colors", "cmc", "name", "maintype"}; + sortByListWidget = new QListWidget(); + sortByListWidget->setSelectionMode(QAbstractItemView::SingleSelection); + sortByListWidget->setDragDropMode(QAbstractItemView::InternalMove); + sortByListWidget->setDefaultDropAction(Qt::MoveAction); + + for (const QString &property : sortProperties) { + QListWidgetItem *item = new QListWidgetItem(property, sortByListWidget); + item->setFlags(item->flags() | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled); + } + + connect(sortByListWidget->model(), &QAbstractItemModel::rowsMoved, this, + &VisualDeckDisplayOptionsWidget::onSortCriteriaChange); + onSortCriteriaChange(); + + sortByListWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + + sortCriteriaButton->addSettingsWidget(sortLabel); + sortCriteriaButton->addSettingsWidget(sortByListWidget); + + displayTypeButton = new QPushButton(this); + connect(displayTypeButton, &QPushButton::clicked, this, &VisualDeckDisplayOptionsWidget::updateDisplayType); + + groupAndSortLayout->addWidget(groupByLabel); + groupAndSortLayout->addWidget(groupByComboBox); + groupAndSortLayout->addWidget(sortByLabel); + groupAndSortLayout->addWidget(sortCriteriaButton); + groupAndSortLayout->addWidget(displayTypeButton); + + retranslateUi(); +} + +void VisualDeckDisplayOptionsWidget::retranslateUi() +{ + groupByLabel->setText(tr("Group by:")); + groupByComboBox->setToolTip(tr("Change how cards are divided into categories/groups.")); + sortByLabel->setText(tr("Sort by:")); + 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->setText(tr("Toggle Layout: Overlap")); + displayTypeButton->setToolTip( + tr("Change how cards are displayed within zones (i.e. overlapped or fully visible.)")); +} + +void VisualDeckDisplayOptionsWidget::onSortCriteriaChange() +{ + QStringList selectedCriteria; + for (int i = 0; i < sortByListWidget->count(); ++i) { + QListWidgetItem *item = sortByListWidget->item(i); + selectedCriteria.append(item->text()); // Collect user-defined sort order + } + + emit sortCriteriaChanged(selectedCriteria); +} + +void VisualDeckDisplayOptionsWidget::updateDisplayType() +{ + // Toggle the display type + currentDisplayType = (currentDisplayType == DisplayType::Overlap) ? DisplayType::Flat : DisplayType::Overlap; + + // Update UI and emit signal + switch (currentDisplayType) { + case DisplayType::Flat: + displayTypeButton->setText(tr("Toggle Layout: Flat")); + break; + case DisplayType::Overlap: + displayTypeButton->setText(tr("Toggle Layout: Overlap")); + break; + } + emit displayTypeChanged(currentDisplayType); +} diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.h new file mode 100644 index 000000000..7a447753f --- /dev/null +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.h @@ -0,0 +1,138 @@ +#ifndef COCKATRICE_VISUAL_DECK_DISPLAY_OPTIONS_WIDGET_H +#define COCKATRICE_VISUAL_DECK_DISPLAY_OPTIONS_WIDGET_H + +#include "visual_deck_editor_widget.h" + +#include +#include +#include +#include +#include +#include + +/** + * @class VisualDeckDisplayOptionsWidget + * @brief A widget that controls how deck cards are displayed in the visual deck editor. + * + * This widget provides: + * - A **group-by** selector (QComboBox) + * - A **sort-by** multi-criteria, draggable list (QListWidget within a SettingsButtonWidget) + * - A **display-type toggler** (flat vs. overlap layout) + * + * Depending on whether the parent is a VisualDeckEditorWidget, this widget can mirror the + * original group by checkbox from the main deck editor UI to maintain synchronization. + * + * It emits signals whenever the grouping criterion, sorting criteria, or display mode changes. + */ +class VisualDeckDisplayOptionsWidget : public QWidget +{ + Q_OBJECT +signals: + /** + * @brief Emitted when the display type (flat or overlapping layout) changes. + * @param displayType The newly selected display layout. + */ + void displayTypeChanged(const DisplayType &displayType); + + /** + * @brief Emitted when a new grouping criterion is selected. + * @param activeGroupCriteria Name of the selected group-by criterion. + */ + void groupCriteriaChanged(const QString &activeGroupCriteria); + + /** + * @brief Emitted when the order of sort criteria changes. + * @param activeSortCriteria Ordered list of sorting keys. + */ + void sortCriteriaChanged(const QStringList &activeSortCriteria); + +public slots: + /** + * @brief Updates all UI text for retranslation/localization. + * + * Called when the application language changes. + */ + void retranslateUi(); + +public: + /** + * @brief Constructs a new VisualDeckDisplayOptionsWidget. + * @param parent The parent QWidget—may trigger cloning of models if the parent is a visual deck editor. + */ + explicit VisualDeckDisplayOptionsWidget(QWidget *parent); + + /** + * @brief Gets the current display type (Overlap or Flat). + */ + DisplayType getDisplayType() const + { + return currentDisplayType; + } + + /** + * @brief Gets the currently active group-by criterion. + */ + QString getActiveGroupCriteria() const + { + return activeGroupCriteria; + } + + /** + * @brief Gets the currently active ordered sort criteria. + */ + QStringList getActiveSortCriteria() const + { + return activeSortCriteria; + } + +private slots: + /** + * @brief Slot triggered whenever the sort list is reordered. + * + * Reads the QListWidget’s order and emits `sortCriteriaChanged()`. + */ + void onSortCriteriaChange(); + + /** + * @brief Toggles the display layout between flat and overlapping modes. + * + * Emits `displayTypeChanged()`. + */ + void updateDisplayType(); + +private: + /// Layout for grouping and sorting UI elements. + QHBoxLayout *groupAndSortLayout; + + /// Current deck display type. + DisplayType currentDisplayType = DisplayType::Overlap; + + /// Button used to toggle the display layout. + QPushButton *displayTypeButton; + + /// Label for the group-by selector. + QLabel *groupByLabel; + + /// Combo box listing group-by criteria. + QComboBox *groupByComboBox; + + /// Currently active group-by criterion. + QString activeGroupCriteria = "maintype"; + + /// Encapsulates the sort settings widgets (label + list). + SettingsButtonWidget *sortCriteriaButton; + + /// Label for “Sort by”. + QLabel *sortByLabel; + + /// Descriptive label inside the sort criteria button. + QLabel *sortLabel; + + /// Draggable list of sort criteria. + QListWidget *sortByListWidget; + + /// Ordered list of current sort criteria. + QStringList activeSortCriteria = {"name", "cmc", "colors", "maintype"}; +}; + +#endif // COCKATRICE_VISUAL_DECK_DISPLAY_OPTIONS_WIDGET_H 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 5acad9df1..76cfe8c8e 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 @@ -9,6 +9,7 @@ #include "../general/layout_containers/flow_widget.h" #include "../tabs/visual_deck_editor/tab_deck_editor_visual.h" #include "../tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h" +#include "visual_deck_display_options_widget.h" #include #include @@ -109,75 +110,22 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, } }); - groupAndSortContainer = new QWidget(this); - groupAndSortLayout = new QHBoxLayout(groupAndSortContainer); - groupAndSortLayout->setAlignment(Qt::AlignLeft); - groupAndSortContainer->setLayout(groupAndSortLayout); + displayOptionsAndSearch = new QWidget(this); + displayOptionsAndSearchLayout = new QHBoxLayout(displayOptionsAndSearch); + displayOptionsAndSearchLayout->setAlignment(Qt::AlignLeft); + displayOptionsAndSearch->setLayout(displayOptionsAndSearchLayout); - groupByLabel = new QLabel(groupAndSortContainer); + displayOptionsWidget = new VisualDeckDisplayOptionsWidget(this); + connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::displayTypeChanged, this, + &VisualDeckEditorWidget::displayTypeChanged); + connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::groupCriteriaChanged, this, + &VisualDeckEditorWidget::activeGroupCriteriaChanged); + connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::sortCriteriaChanged, this, + &VisualDeckEditorWidget::activeSortCriteriaChanged); - groupByComboBox = new QComboBox(this); - if (auto tabWidget = qobject_cast(parent)) { - // Inside a central widget QWidget container inside TabDeckEditorVisual - if (auto tab = qobject_cast(tabWidget->parent()->parent())) { - auto originalBox = tab->getDeckDockWidget()->getGroupByComboBox(); - groupByComboBox->setModel(originalBox->model()); - groupByComboBox->setModelColumn(originalBox->modelColumn()); - - // Original -> clone - connect(originalBox, QOverload::of(&QComboBox::currentIndexChanged), - [this](int index) { groupByComboBox->setCurrentIndex(index); }); - - // Clone -> original - connect(groupByComboBox, QOverload::of(&QComboBox::currentIndexChanged), - [originalBox](int index) { originalBox->setCurrentIndex(index); }); - } - } else { - QStringList groupProperties = {"maintype", "colors", "cmc", "name"}; - groupByComboBox->addItems(groupProperties); - groupByComboBox->setMinimumWidth(300); - connect(groupByComboBox, QOverload::of(&QComboBox::currentTextChanged), this, - &VisualDeckEditorWidget::actChangeActiveGroupCriteria); - actChangeActiveGroupCriteria(); - } - - sortByLabel = new QLabel(groupAndSortContainer); - - sortCriteriaButton = new SettingsButtonWidget(this); - - sortLabel = new QLabel(sortCriteriaButton); - sortLabel->setWordWrap(true); - - QStringList sortProperties = {"colors", "cmc", "name", "maintype"}; - sortByListWidget = new QListWidget(); - sortByListWidget->setSelectionMode(QAbstractItemView::SingleSelection); - sortByListWidget->setDragDropMode(QAbstractItemView::InternalMove); - sortByListWidget->setDefaultDropAction(Qt::MoveAction); - - for (const QString &property : sortProperties) { - QListWidgetItem *item = new QListWidgetItem(property, sortByListWidget); - item->setFlags(item->flags() | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled); - } - - connect(sortByListWidget->model(), &QAbstractItemModel::rowsMoved, this, - &VisualDeckEditorWidget::actChangeActiveSortCriteria); - actChangeActiveSortCriteria(); - - sortByListWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - - sortCriteriaButton->addSettingsWidget(sortLabel); - sortCriteriaButton->addSettingsWidget(sortByListWidget); - - displayTypeButton = new QPushButton(this); - connect(displayTypeButton, &QPushButton::clicked, this, &VisualDeckEditorWidget::updateDisplayType); - - groupAndSortLayout->addWidget(groupByLabel); - groupAndSortLayout->addWidget(groupByComboBox); - groupAndSortLayout->addWidget(sortByLabel); - groupAndSortLayout->addWidget(sortCriteriaButton); - groupAndSortLayout->addWidget(displayTypeButton); - groupAndSortLayout->addWidget(searchBar); - groupAndSortLayout->addWidget(searchPushButton); + displayOptionsAndSearchLayout->addWidget(displayOptionsWidget); + displayOptionsAndSearchLayout->addWidget(searchBar); + displayOptionsAndSearchLayout->addWidget(searchPushButton); scrollArea = new QScrollArea(this); scrollArea->setWidgetResizable(true); @@ -197,7 +145,7 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(), &SettingsCache::setVisualDeckEditorCardSize); - mainLayout->addWidget(groupAndSortContainer); + mainLayout->addWidget(displayOptionsAndSearch); mainLayout->addWidget(scrollArea); mainLayout->addWidget(cardSizeWidget); @@ -218,17 +166,9 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, void VisualDeckEditorWidget::retranslateUi() { searchBar->setPlaceholderText(tr("Type a card name here for suggestions from the database...")); - groupByLabel->setText(tr("Group by:")); - groupByComboBox->setToolTip(tr("Change how cards are divided into categories/groups.")); - sortByLabel->setText(tr("Sort by:")); - sortLabel->setText(tr("Click and drag to change the sort order within the groups")); searchPushButton->setText(tr("Quick search and add card")); searchPushButton->setToolTip(tr("Search for closest match in the database (with auto-suggestions) and add " "preferred printing to the deck on pressing enter")); - sortCriteriaButton->setToolTip(tr("Configure how cards are sorted within their groups")); - displayTypeButton->setText(tr("Toggle Layout: Overlap")); - displayTypeButton->setToolTip( - tr("Change how cards are displayed within zones (i.e. overlapped or fully visible.)")); } void VisualDeckEditorWidget::setSelectionModel(QItemSelectionModel *model) @@ -326,8 +266,9 @@ void VisualDeckEditorWidget::constructZoneWidgetForIndex(QPersistentModelIndex p { DeckCardZoneDisplayWidget *zoneDisplayWidget = new DeckCardZoneDisplayWidget( zoneContainer, deckListModel, selectionModel, persistent, - deckListModel->data(persistent.sibling(persistent.row(), 1), Qt::EditRole).toString(), activeGroupCriteria, - activeSortCriteria, currentDisplayType, 20, 10, cardSizeWidget); + deckListModel->data(persistent.sibling(persistent.row(), 1), Qt::EditRole).toString(), + displayOptionsWidget->getActiveGroupCriteria(), displayOptionsWidget->getActiveSortCriteria(), + displayOptionsWidget->getDisplayType(), 20, 10, cardSizeWidget); connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover); connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardClicked, this, &VisualDeckEditorWidget::onCardClick); connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::requestCleanup, this, @@ -338,7 +279,7 @@ void VisualDeckEditorWidget::constructZoneWidgetForIndex(QPersistentModelIndex p &DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged); connect(this, &VisualDeckEditorWidget::displayTypeChanged, zoneDisplayWidget, &DeckCardZoneDisplayWidget::refreshDisplayType); - zoneDisplayWidget->refreshDisplayType(currentDisplayType); + zoneDisplayWidget->refreshDisplayType(displayOptionsWidget->getDisplayType()); zoneContainerLayout->addWidget(zoneDisplayWidget); indexToWidgetMap.insert(persistent, zoneDisplayWidget); @@ -370,48 +311,12 @@ void VisualDeckEditorWidget::updateZoneWidgets() { } -void VisualDeckEditorWidget::updateDisplayType() -{ - // Toggle the display type - currentDisplayType = (currentDisplayType == DisplayType::Overlap) ? DisplayType::Flat : DisplayType::Overlap; - - // Update UI and emit signal - switch (currentDisplayType) { - case DisplayType::Flat: - displayTypeButton->setText(tr("Toggle Layout: Flat")); - break; - case DisplayType::Overlap: - displayTypeButton->setText(tr("Toggle Layout: Overlap")); - break; - } - emit displayTypeChanged(currentDisplayType); -} - void VisualDeckEditorWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); zoneContainer->setMaximumWidth(scrollArea->viewport()->width()); } -void VisualDeckEditorWidget::actChangeActiveGroupCriteria() -{ - activeGroupCriteria = groupByComboBox->currentText(); - emit activeGroupCriteriaChanged(activeGroupCriteria); -} - -void VisualDeckEditorWidget::actChangeActiveSortCriteria() -{ - QStringList selectedCriteria; - for (int i = 0; i < sortByListWidget->count(); ++i) { - QListWidgetItem *item = sortByListWidget->item(i); - selectedCriteria.append(item->text()); // Collect user-defined sort order - } - - activeSortCriteria = selectedCriteria; - - emit activeSortCriteriaChanged(selectedCriteria); -} - void VisualDeckEditorWidget::decklistModelReset() { clearAllDisplayWidgets(); diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h index 524762d89..3a60d09a7 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h @@ -23,6 +23,7 @@ #include #include +class VisualDeckDisplayOptionsWidget; class DeckCardZoneDisplayWidget; enum class DisplayType { @@ -55,7 +56,6 @@ public: public slots: void decklistDataChanged(QModelIndex topLeft, QModelIndex bottomRight); void updateZoneWidgets(); - void updateDisplayType(); void cleanupInvalidZones(DeckCardZoneDisplayWidget *displayWidget); void onCardAddition(const QModelIndex &parent, int first, int last); void onCardRemoval(const QModelIndex &parent, int first, int last); @@ -73,8 +73,6 @@ signals: protected slots: void onHover(const ExactCard &hoveredCard); void onCardClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName); - void actChangeActiveGroupCriteria(); - void actChangeActiveSortCriteria(); void decklistModelReset(); private: @@ -85,19 +83,10 @@ private: CardDatabaseDisplayModel *cardDatabaseDisplayModel; CardCompleterProxyModel *proxyModel; QCompleter *completer; + QWidget *displayOptionsAndSearch; + QHBoxLayout *displayOptionsAndSearchLayout; + VisualDeckDisplayOptionsWidget *displayOptionsWidget; QPushButton *searchPushButton; - DisplayType currentDisplayType = DisplayType::Overlap; - QPushButton *displayTypeButton; - QWidget *groupAndSortContainer; - QHBoxLayout *groupAndSortLayout; - QLabel *groupByLabel; - QComboBox *groupByComboBox; - QString activeGroupCriteria = "maintype"; - SettingsButtonWidget *sortCriteriaButton; - QLabel *sortByLabel; - QLabel *sortLabel; - QListWidget *sortByListWidget; - QStringList activeSortCriteria = {"name", "cmc", "colors", "maintype"}; QScrollArea *scrollArea; QWidget *zoneContainer; QVBoxLayout *zoneContainerLayout; 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 9b96c0ce6..1e4a53553 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -74,6 +74,29 @@ enum Type MANA_COST, /**< Group cards by their total mana cost. */ COLOR /**< Group cards by their color identity. */ }; +static inline QString toString(Type t) +{ + switch (t) { + case MAIN_TYPE: + return "Main Type"; + case MANA_COST: + return "Mana Cost"; + case COLOR: + return "Colors"; + } + return {}; +} + +static inline Type fromString(const QString &s) +{ + if (s == "Main Type") + return MAIN_TYPE; + if (s == "Mana Cost") + return MANA_COST; + if (s == "Colors") + return COLOR; + return MAIN_TYPE; // default +} } // namespace DeckListModelGroupCriteria /** @@ -234,8 +257,8 @@ public: */ [[nodiscard]] QModelIndex findCard(const QString &cardName, const QString &zoneName, - const QString &providerId = "", - const QString &cardNumber = "") const; + const QString &providerId = "", + const QString &cardNumber = "") const; /** * @brief Adds a card using the preferred printing if available. @@ -296,8 +319,8 @@ private: QModelIndex nodeToIndex(AbstractDecklistNode *node) const; [[nodiscard]] DecklistModelCardNode *findCardNode(const QString &cardName, const QString &zoneName, - const QString &providerId = "", - const QString &cardNumber = "") const; + const QString &providerId = "", + const QString &cardNumber = "") const; void emitRecursiveUpdates(const QModelIndex &index); void sortHelper(InnerDecklistNode *node, Qt::SortOrder order); From 2b64e65f450191df37023db78e65c813da10261f Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sun, 30 Nov 2025 13:03:18 +0100 Subject: [PATCH 036/325] [CardInfoPictureEnlargedWidget] Fix DPR scaling. (#6382) --- .../card_info_picture_enlarged_widget.cpp | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp index 1024315a1..185bb20d1 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp @@ -33,10 +33,13 @@ CardInfoPictureEnlargedWidget::CardInfoPictureEnlargedWidget(QWidget *parent) : */ void CardInfoPictureEnlargedWidget::loadPixmap(const QSize &size) { + // Handle DPI scaling + qreal dpr = devicePixelRatio(); // Get the actual scaling factor + QSize availableSize = size * dpr; // Convert to physical pixel size if (card) { - CardPictureLoader::getPixmap(enlargedPixmap, card, size); + CardPictureLoader::getPixmap(enlargedPixmap, card, availableSize); } else { - CardPictureLoader::getCardBackPixmap(enlargedPixmap, size); + CardPictureLoader::getCardBackPixmap(enlargedPixmap, availableSize); } pixmapDirty = false; } @@ -77,25 +80,35 @@ void CardInfoPictureEnlargedWidget::paintEvent(QPaintEvent *event) loadPixmap(size()); } - // Scale the size of the pixmap to fit the widget while maintaining the aspect ratio - QSize scaledSize = enlargedPixmap.size().scaled(size().width(), size().height(), Qt::KeepAspectRatio); + qreal dpr = enlargedPixmap.devicePixelRatio(); - // Calculate the position to center the scaled pixmap - QPoint topLeft{(width() - scaledSize.width()) / 2, (height() - scaledSize.height()) / 2}; + QSize logicalPixmapSize(enlargedPixmap.width() / dpr, enlargedPixmap.height() / dpr); - // Define the radius for rounded corners - // Adjust the radius as needed for rounded corners - qreal radius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * scaledSize.width() : 0.; + // Scale the pixmap to fit the widget (logical → logical) + QSize scaledLogicalSize = logicalPixmapSize.scaled(size(), Qt::KeepAspectRatio); + + // Convert scaled logical size → physical size for scaled() + QSize scaledPhysicalSize = scaledLogicalSize * dpr; + + // Pixmap scaled in PHYSICAL pixels + QPixmap finalPixmap = enlargedPixmap.scaled(scaledPhysicalSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + finalPixmap.setDevicePixelRatio(dpr); + + // Center inside widget + QPoint topLeft{(width() - scaledLogicalSize.width()) / 2, (height() - scaledLogicalSize.height()) / 2}; + + // Rounded corner radius based on logical width + qreal radius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * scaledLogicalSize.width() : 0.0; QStylePainter painter(this); // Fill the background with transparent color to ensure rounded corners are rendered properly painter.fillRect(rect(), Qt::transparent); // Use the transparent background QPainterPath shape; - shape.addRoundedRect(QRect(topLeft, scaledSize), radius, radius); + shape.addRoundedRect(QRect(topLeft, scaledLogicalSize), radius, radius); painter.setClipPath(shape); // Set the clipping path // Draw the pixmap scaled to the calculated size - painter.drawItemPixmap(QRect(topLeft, scaledSize), Qt::AlignCenter, - enlargedPixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + painter.drawPixmap(QRect(topLeft, scaledLogicalSize), finalPixmap); } From d57bec8ec600c725379c3e312e120c1919c91006 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 30 Nov 2025 04:05:49 -0800 Subject: [PATCH 037/325] [DeckList] Move decklist node classes into new folder (#6381) * [DeckList] Move decklist node classes into new folder * reformat * fix --- .../network/interfaces/deck_stats_interface.cpp | 2 +- .../network/interfaces/tapped_out_interface.cpp | 2 +- cockatrice/src/game/deckview/deck_view.cpp | 2 +- cockatrice/src/game/player/menu/utility_menu.cpp | 3 ++- .../src/interface/deck_loader/deck_loader.cpp | 2 +- libcockatrice_deck_list/CMakeLists.txt | 16 ++++++++-------- .../libcockatrice/deck_list/deck_list.cpp | 6 +++--- .../libcockatrice/deck_list/deck_list.h | 2 +- .../{ => tree}/abstract_deck_list_card_node.cpp | 0 .../{ => tree}/abstract_deck_list_card_node.h | 0 .../{ => tree}/abstract_deck_list_node.cpp | 0 .../{ => tree}/abstract_deck_list_node.h | 0 .../deck_list/{ => tree}/deck_list_card_node.cpp | 0 .../deck_list/{ => tree}/deck_list_card_node.h | 0 .../{ => tree}/inner_deck_list_node.cpp | 0 .../deck_list/{ => tree}/inner_deck_list_node.h | 0 .../models/deck_list/deck_list_model.h | 4 ++-- .../network/server/remote/game/server_player.cpp | 2 +- .../loading_from_clipboard/clipboard_testing.cpp | 2 +- 19 files changed, 22 insertions(+), 21 deletions(-) rename libcockatrice_deck_list/libcockatrice/deck_list/{ => tree}/abstract_deck_list_card_node.cpp (100%) rename libcockatrice_deck_list/libcockatrice/deck_list/{ => tree}/abstract_deck_list_card_node.h (100%) rename libcockatrice_deck_list/libcockatrice/deck_list/{ => tree}/abstract_deck_list_node.cpp (100%) rename libcockatrice_deck_list/libcockatrice/deck_list/{ => tree}/abstract_deck_list_node.h (100%) rename libcockatrice_deck_list/libcockatrice/deck_list/{ => tree}/deck_list_card_node.cpp (100%) rename libcockatrice_deck_list/libcockatrice/deck_list/{ => tree}/deck_list_card_node.h (100%) rename libcockatrice_deck_list/libcockatrice/deck_list/{ => tree}/inner_deck_list_node.cpp (100%) rename libcockatrice_deck_list/libcockatrice/deck_list/{ => tree}/inner_deck_list_node.h (100%) diff --git a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp index eeb48959c..6c295beb9 100644 --- a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp +++ b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include DeckStatsInterface::DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent) : QObject(parent), cardDatabase(_cardDatabase) diff --git a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp index cebbce8b8..4bfeba7b1 100644 --- a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp +++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include TappedOutInterface::TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent) : QObject(parent), cardDatabase(_cardDatabase) diff --git a/cockatrice/src/game/deckview/deck_view.cpp b/cockatrice/src/game/deckview/deck_view.cpp index 64e9abc46..edef3fad4 100644 --- a/cockatrice/src/game/deckview/deck_view.cpp +++ b/cockatrice/src/game/deckview/deck_view.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include DeckViewCardDragItem::DeckViewCardDragItem(DeckViewCard *_item, const QPointF &_hotSpot, diff --git a/cockatrice/src/game/player/menu/utility_menu.cpp b/cockatrice/src/game/player/menu/utility_menu.cpp index ca2832ec7..a3117abf8 100644 --- a/cockatrice/src/game/player/menu/utility_menu.cpp +++ b/cockatrice/src/game/player/menu/utility_menu.cpp @@ -3,9 +3,10 @@ #include "../../../interface/deck_loader/deck_loader.h" #include "../player.h" #include "../player_actions.h" -#include "libcockatrice/deck_list/inner_deck_list_node.h" #include "player_menu.h" +#include + UtilityMenu::UtilityMenu(Player *_player, QMenu *playerMenu) : QMenu(playerMenu), player(_player) { PlayerActions *playerActions = player->getPlayerActions(); diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index 372aa0aa8..eea906a97 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -18,7 +18,7 @@ #include #include #include -#include +#include const QStringList DeckLoader::ACCEPTED_FILE_EXTENSIONS = {"*.cod", "*.dec", "*.dek", "*.txt", "*.mwDeck"}; diff --git a/libcockatrice_deck_list/CMakeLists.txt b/libcockatrice_deck_list/CMakeLists.txt index d9b27354b..5dffff3f7 100644 --- a/libcockatrice_deck_list/CMakeLists.txt +++ b/libcockatrice_deck_list/CMakeLists.txt @@ -3,13 +3,13 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) set(HEADERS - libcockatrice/deck_list/abstract_deck_list_card_node.h - libcockatrice/deck_list/abstract_deck_list_node.h + libcockatrice/deck_list/tree/abstract_deck_list_card_node.h + libcockatrice/deck_list/tree/abstract_deck_list_node.h + libcockatrice/deck_list/tree/deck_list_card_node.h + libcockatrice/deck_list/tree/inner_deck_list_node.h libcockatrice/deck_list/deck_list.h - libcockatrice/deck_list/deck_list_card_node.h libcockatrice/deck_list/deck_list_history_manager.h libcockatrice/deck_list/deck_list_memento.h - libcockatrice/deck_list/inner_deck_list_node.h ) if(Qt6_FOUND) @@ -21,12 +21,12 @@ endif() add_library( libcockatrice_deck_list STATIC ${MOC_SOURCES} - libcockatrice/deck_list/abstract_deck_list_card_node.cpp - libcockatrice/deck_list/abstract_deck_list_node.cpp + libcockatrice/deck_list/tree/abstract_deck_list_card_node.cpp + libcockatrice/deck_list/tree/abstract_deck_list_node.cpp + libcockatrice/deck_list/tree/deck_list_card_node.cpp + libcockatrice/deck_list/tree/inner_deck_list_node.cpp libcockatrice/deck_list/deck_list.cpp - libcockatrice/deck_list/deck_list_card_node.cpp libcockatrice/deck_list/deck_list_history_manager.cpp - libcockatrice/deck_list/inner_deck_list_node.cpp ) add_dependencies(libcockatrice_deck_list libcockatrice_protocol) diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index f06652d7c..c44012486 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -1,9 +1,9 @@ #include "deck_list.h" -#include "abstract_deck_list_node.h" -#include "deck_list_card_node.h" #include "deck_list_memento.h" -#include "inner_deck_list_node.h" +#include "tree/abstract_deck_list_node.h" +#include "tree/deck_list_card_node.h" +#include "tree/inner_deck_list_node.h" #include #include diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index f6bd54382..cbab9ce5d 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -11,7 +11,7 @@ #define DECKLIST_H #include "deck_list_memento.h" -#include "inner_deck_list_node.h" +#include "tree/inner_deck_list_node.h" #include #include diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_card_node.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.cpp similarity index 100% rename from libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_card_node.cpp rename to libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.cpp diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_card_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.h similarity index 100% rename from libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_card_node.h rename to libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.h diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_node.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_node.cpp similarity index 100% rename from libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_node.cpp rename to libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_node.cpp diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_node.h similarity index 100% rename from libcockatrice_deck_list/libcockatrice/deck_list/abstract_deck_list_node.h rename to libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_node.h diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_card_node.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.cpp similarity index 100% rename from libcockatrice_deck_list/libcockatrice/deck_list/deck_list_card_node.cpp rename to libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.cpp diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_card_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.h similarity index 100% rename from libcockatrice_deck_list/libcockatrice/deck_list/deck_list_card_node.h rename to libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.h diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/inner_deck_list_node.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.cpp similarity index 100% rename from libcockatrice_deck_list/libcockatrice/deck_list/inner_deck_list_node.cpp rename to libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.cpp diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/inner_deck_list_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.h similarity index 100% rename from libcockatrice_deck_list/libcockatrice/deck_list/inner_deck_list_node.h rename to libcockatrice_deck_list/libcockatrice/deck_list/tree/inner_deck_list_node.h 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 1e4a53553..a480c606b 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -1,12 +1,12 @@ #ifndef DECKLISTMODEL_H #define DECKLISTMODEL_H +#include <../../../../libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.h> +#include <../../../../libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.h> #include #include #include -#include #include -#include class CardDatabase; class QPrinter; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp index b59d89ed8..8190155e9 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/tests/loading_from_clipboard/clipboard_testing.cpp b/tests/loading_from_clipboard/clipboard_testing.cpp index df2806585..2342bc088 100644 --- a/tests/loading_from_clipboard/clipboard_testing.cpp +++ b/tests/loading_from_clipboard/clipboard_testing.cpp @@ -1,7 +1,7 @@ #include "clipboard_testing.h" #include -#include +#include void testEmpty(const QString &clipboard) { From 3ff2df279681be9e690e30b07dfb9fe17a939d91 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 30 Nov 2025 04:09:09 -0800 Subject: [PATCH 038/325] [DeckList] Move metadata into struct (#6380) * [DeckList] Move metadata into struct * wipe metadata if preserveMetadata is false --- .../libcockatrice/deck_list/deck_list.cpp | 59 +++++++++++-------- .../libcockatrice/deck_list/deck_list.h | 56 ++++++++++++------ .../libcockatrice/utility/card_ref.h | 5 ++ 3 files changed, 76 insertions(+), 44 deletions(-) diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index c44012486..37f537249 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -77,6 +77,11 @@ void SideboardPlan::write(QXmlStreamWriter *xml) xml->writeEndElement(); } +bool DeckList::Metadata::isEmpty() const +{ + return name.isEmpty() && comments.isEmpty() && bannerCard.isEmpty() && tags.isEmpty(); +} + DeckList::DeckList() { root = new InnerDecklistNode; @@ -122,20 +127,20 @@ bool DeckList::readElement(QXmlStreamReader *xml) const QString childName = xml->name().toString(); if (xml->isStartElement()) { if (childName == "lastLoadedTimestamp") { - lastLoadedTimestamp = xml->readElementText(); + metadata.lastLoadedTimestamp = xml->readElementText(); } else if (childName == "deckname") { - name = xml->readElementText(); + metadata.name = xml->readElementText(); } else if (childName == "comments") { - comments = xml->readElementText(); + metadata.comments = xml->readElementText(); } else if (childName == "bannerCard") { QString providerId = xml->attributes().value("providerId").toString(); QString cardName = xml->readElementText(); - bannerCard = {cardName, providerId}; + metadata.bannerCard = {cardName, providerId}; } else if (childName == "tags") { - tags.clear(); // Clear existing tags + metadata.tags.clear(); // Clear existing tags while (xml->readNextStartElement()) { if (xml->name().toString() == "tag") { - tags.append(xml->readElementText()); + metadata.tags.append(xml->readElementText()); } } } else if (childName == "zone") { @@ -155,24 +160,30 @@ bool DeckList::readElement(QXmlStreamReader *xml) return true; } +void writeMetadata(QXmlStreamWriter *xml, const DeckList::Metadata &metadata) +{ + xml->writeTextElement("lastLoadedTimestamp", metadata.lastLoadedTimestamp); + xml->writeTextElement("deckname", metadata.name); + xml->writeStartElement("bannerCard"); + xml->writeAttribute("providerId", metadata.bannerCard.providerId); + xml->writeCharacters(metadata.bannerCard.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"); - xml->writeTextElement("lastLoadedTimestamp", lastLoadedTimestamp); - xml->writeTextElement("deckname", name); - xml->writeStartElement("bannerCard"); - xml->writeAttribute("providerId", bannerCard.providerId); - xml->writeCharacters(bannerCard.name); - xml->writeEndElement(); - xml->writeTextElement("comments", comments); - // Write tags - xml->writeStartElement("tags"); - for (const QString &tag : tags) { - xml->writeTextElement("tag", tag); - } - xml->writeEndElement(); + writeMetadata(xml, metadata); // Write zones for (int i = 0; i < root->size(); i++) { @@ -329,7 +340,7 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata) const auto ¤t = inputs.at(index++); if (!current.contains(reEmpty)) { match = reComment.match(current); - name = match.captured(); + metadata.name = match.captured(); break; } } @@ -337,10 +348,10 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata) const auto ¤t = inputs.at(index++); if (!current.contains(reEmpty)) { match = reComment.match(current); - comments += match.captured() + '\n'; + metadata.comments += match.captured() + '\n'; } } - comments.chop(1); + metadata.comments.chop(1); // Discard empty lines while (index < max_line && inputs.at(index).contains(reEmpty)) { @@ -504,9 +515,7 @@ void DeckList::cleanList(bool preserveMetadata) { root->clearTree(); if (!preserveMetadata) { - setName(); - setComments(); - setTags(); + metadata = {}; } refreshDeckHash(); } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index cbab9ce5d..5340d69fe 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -126,12 +126,24 @@ public: class DeckList : public QObject { Q_OBJECT + +public: + struct Metadata + { + QString name; ///< User-defined deck name. + QString comments; ///< Free-form comments or notes. + CardRef bannerCard; ///< Optional representative card for the deck. + QStringList tags; ///< User-defined tags for deck classification. + QString lastLoadedTimestamp; ///< Timestamp string of last load. + + /** + * @brief Checks if all values (except for lastLoadedTimestamp) in the metadata is empty. + */ + bool isEmpty() const; + }; + private: - QString name; ///< User-defined deck name. - QString comments; ///< Free-form comments or notes. - CardRef bannerCard; ///< Optional representative card for the deck. - QString lastLoadedTimestamp; ///< Timestamp string of last load. - QStringList tags; ///< User-defined tags for deck classification. + Metadata metadata; ///< Deck metadata that is stored in the deck file QMap sideboardPlans; ///< Named sideboard plans. InnerDecklistNode *root; ///< Root of the deck tree (zones + cards). @@ -181,34 +193,34 @@ public slots: ///@{ void setName(const QString &_name = QString()) { - name = _name; + metadata.name = _name; } void setComments(const QString &_comments = QString()) { - comments = _comments; + metadata.comments = _comments; } void setTags(const QStringList &_tags = QStringList()) { - tags = _tags; + metadata.tags = _tags; emit deckTagsChanged(); } void addTag(const QString &_tag) { - tags.append(_tag); + metadata.tags.append(_tag); emit deckTagsChanged(); } void clearTags() { - tags.clear(); + metadata.tags.clear(); emit deckTagsChanged(); } void setBannerCard(const CardRef &_bannerCard = {}) { - bannerCard = _bannerCard; + metadata.bannerCard = _bannerCard; } void setLastLoadedTimestamp(const QString &_lastLoadedTimestamp = QString()) { - lastLoadedTimestamp = _lastLoadedTimestamp; + metadata.lastLoadedTimestamp = _lastLoadedTimestamp; } ///@} @@ -223,32 +235,38 @@ public: ~DeckList() override; /// @name Metadata getters + /// The individual metadata getters still exist for backwards compatibility. + /// TODO: Figure out when we can remove them. ///@{ + const Metadata &getMetadata() const + { + return metadata; + } QString getName() const { - return name; + return metadata.name; } QString getComments() const { - return comments; + return metadata.comments; } QStringList getTags() const { - return tags; + return metadata.tags; } CardRef getBannerCard() const { - return bannerCard; + return metadata.bannerCard; } QString getLastLoadedTimestamp() const { - return lastLoadedTimestamp; + return metadata.lastLoadedTimestamp; } ///@} bool isBlankDeck() const { - return name.isEmpty() && comments.isEmpty() && getCardList().isEmpty(); + return metadata.isEmpty() && getCardList().isEmpty(); } /// @name Sideboard plans @@ -286,7 +304,7 @@ public: void cleanList(bool preserveMetadata = false); bool isEmpty() const { - return root->isEmpty() && name.isEmpty() && comments.isEmpty() && sideboardPlans.isEmpty(); + return root->isEmpty() && metadata.isEmpty() && sideboardPlans.isEmpty(); } QStringList getCardList() const; QList getCardRefList() const; diff --git a/libcockatrice_utility/libcockatrice/utility/card_ref.h b/libcockatrice_utility/libcockatrice/utility/card_ref.h index d29eee0bb..d89fe590b 100644 --- a/libcockatrice_utility/libcockatrice/utility/card_ref.h +++ b/libcockatrice_utility/libcockatrice/utility/card_ref.h @@ -19,6 +19,11 @@ struct CardRef { return name == other.name && providerId == other.providerId; } + + bool isEmpty() const + { + return name.isEmpty() && providerId.isEmpty(); + } }; #endif // CARD_REF_H From 364d0ca52b701f4cde78d4ed1eb462ca69cb5ddb Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Mon, 1 Dec 2025 09:37:48 +0100 Subject: [PATCH 039/325] [EDHRec] Add background plate and "selection highlight" to card display widgets (#6390) --- .../general/display/background_plate_widget.h | 2 +- ...i_response_card_details_display_widget.cpp | 33 +++++++++++++++++-- ...api_response_card_details_display_widget.h | 12 +++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/cockatrice/src/interface/widgets/general/display/background_plate_widget.h b/cockatrice/src/interface/widgets/general/display/background_plate_widget.h index d529cb6fa..1fc1bc3ba 100644 --- a/cockatrice/src/interface/widgets/general/display/background_plate_widget.h +++ b/cockatrice/src/interface/widgets/general/display/background_plate_widget.h @@ -8,7 +8,7 @@ class BackgroundPlateWidget : public QWidget Q_OBJECT public: - explicit BackgroundPlateWidget(QWidget *parent = nullptr); + explicit BackgroundPlateWidget(QWidget *parent); void setFocused(bool focused); diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp index 108694d8d..26ade96dd 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp @@ -1,5 +1,6 @@ #include "edhrec_api_response_card_details_display_widget.h" +#include "../../../../../general/display/background_plate_widget.h" #include "../../tab_edhrec_main.h" #include @@ -25,8 +26,14 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi layout->addWidget(nameLabel); layout->addWidget(cardPictureWidget); - layout->addWidget(inclusionDisplayWidget); - layout->addWidget(synergyDisplayWidget); + + backgroundPlateWidget = new BackgroundPlateWidget(this); + auto plateLayout = new QVBoxLayout(backgroundPlateWidget); + + plateLayout->addWidget(inclusionDisplayWidget); + plateLayout->addWidget(synergyDisplayWidget); + + layout->addWidget(backgroundPlateWidget); QWidget *currentParent = parentWidget(); TabEdhRecMain *parentTab = nullptr; @@ -49,6 +56,28 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi } } +void EdhrecApiResponseCardDetailsDisplayWidget::mousePressEvent(QMouseEvent *event) +{ + QWidget::mousePressEvent(event); + actRequestPageNavigation(); +} + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +void EdhrecApiResponseCardDetailsDisplayWidget::enterEvent(QEnterEvent *event) +#else +void EdhrecApiResponseCardDetailsDisplayWidget::enterEvent(QEvent *event) +#endif +{ + QWidget::enterEvent(event); + backgroundPlateWidget->setFocused(true); +} + +void EdhrecApiResponseCardDetailsDisplayWidget::leaveEvent(QEvent *event) +{ + QWidget::leaveEvent(event); + backgroundPlateWidget->setFocused(false); +} + void EdhrecApiResponseCardDetailsDisplayWidget::actRequestPageNavigation() { emit requestUrl(toDisplay.url); diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h index 548cdc0af..82dcfb8b0 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h @@ -8,6 +8,7 @@ #define EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H #include "../../../../../cards/card_info_picture_widget.h" +#include "../../../../../general/display/background_plate_widget.h" #include "../../api_response/cards/edhrec_api_response_card_details.h" #include "edhrec_api_response_card_inclusion_display_widget.h" #include "edhrec_api_response_card_synergy_display_widget.h" @@ -30,9 +31,20 @@ private: EdhrecApiResponseCardDetails toDisplay; QVBoxLayout *layout; CardInfoPictureWidget *cardPictureWidget; + BackgroundPlateWidget *backgroundPlateWidget; ///< Plate for metadata labels QLabel *nameLabel; EdhrecApiResponseCardInclusionDisplayWidget *inclusionDisplayWidget; EdhrecApiResponseCardSynergyDisplayWidget *synergyDisplayWidget; + +protected slots: + void mousePressEvent(QMouseEvent *event) override; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + void enterEvent(QEnterEvent *event) override; ///< Qt6 hover enter +#else + void enterEvent(QEvent *event) override; ///< Qt5 hover enter +#endif + void leaveEvent(QEvent *event) override; }; #endif // EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H From f0ebd281487ffb53e80aa775bf1b103cf5cf30de Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:51:08 +0100 Subject: [PATCH 040/325] [VDE] Consolidate statistical analysis into a separate object (#6392) * [VDE] Consolidate statistical analysis into a separate object so multiple widgets can re-use calculations and calculation is only performed once on data change. * [VDE] Lint. * [VDE] Move struct up to not confuse compiler. * [VDE] NoDiscards * [VDE] Move variables * [VDE] Lint. --- cockatrice/CMakeLists.txt | 1 + .../deck_analytics/deck_analytics_widget.cpp | 15 +-- .../deck_analytics/deck_analytics_widget.h | 3 +- .../deck_list_statistics_analyzer.cpp | 120 ++++++++++++++++++ .../deck_list_statistics_analyzer.h | 60 +++++++++ .../deck_analytics/mana_base_widget.cpp | 68 +--------- .../widgets/deck_analytics/mana_base_widget.h | 11 +- .../deck_analytics/mana_curve_widget.cpp | 36 +----- .../deck_analytics/mana_curve_widget.h | 8 +- .../deck_analytics/mana_devotion_widget.cpp | 82 +----------- .../deck_analytics/mana_devotion_widget.h | 11 +- .../tab_deck_editor_visual.cpp | 2 +- 12 files changed, 215 insertions(+), 202 deletions(-) create mode 100644 cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index eacbc8825..1dbeddc5e 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -142,6 +142,7 @@ set(cockatrice_SOURCES src/interface/widgets/cards/deck_card_zone_display_widget.cpp src/interface/widgets/cards/deck_preview_card_picture_widget.cpp src/interface/widgets/deck_analytics/deck_analytics_widget.cpp + src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp src/interface/widgets/deck_analytics/mana_base_widget.cpp src/interface/widgets/deck_analytics/mana_curve_widget.cpp src/interface/widgets/deck_analytics/mana_devotion_widget.cpp diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp index 2131700fd..45393bb5d 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp @@ -17,20 +17,19 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListModel *_deckLi container->setLayout(containerLayout); scrollArea->setWidget(container); - manaCurveWidget = new ManaCurveWidget(this, deckListModel); + deckListStatisticsAnalyzer = new DeckListStatisticsAnalyzer(this, deckListModel); + + manaCurveWidget = new ManaCurveWidget(this, deckListStatisticsAnalyzer); containerLayout->addWidget(manaCurveWidget); - manaDevotionWidget = new ManaDevotionWidget(this, deckListModel); + manaDevotionWidget = new ManaDevotionWidget(this, deckListStatisticsAnalyzer); containerLayout->addWidget(manaDevotionWidget); - manaBaseWidget = new ManaBaseWidget(this, deckListModel); + manaBaseWidget = new ManaBaseWidget(this, deckListStatisticsAnalyzer); containerLayout->addWidget(manaBaseWidget); } -void DeckAnalyticsWidget::refreshDisplays(DeckListModel *_deckModel) +void DeckAnalyticsWidget::refreshDisplays() { - deckListModel = _deckModel; - manaCurveWidget->setDeckModel(_deckModel); - manaDevotionWidget->setDeckModel(_deckModel); - manaBaseWidget->setDeckModel(_deckModel); + deckListStatisticsAnalyzer->update(); } diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h index e2f00bf0d..524362aed 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h @@ -24,10 +24,11 @@ public: explicit DeckAnalyticsWidget(QWidget *parent, DeckListModel *deckListModel); void setDeckList(const DeckList &_deckListModel); std::map analyzeManaCurve(); - void refreshDisplays(DeckListModel *_deckListModel); + void refreshDisplays(); private: DeckListModel *deckListModel; + DeckListStatisticsAnalyzer *deckListStatisticsAnalyzer; QVBoxLayout *mainLayout; QWidget *container; diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp new file mode 100644 index 000000000..a5ddf37a1 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp @@ -0,0 +1,120 @@ +#include "deck_list_statistics_analyzer.h" + +#include "deck_list_model.h" +#include "deck_list_statistics_analyzer.h" + +#include +#include +#include + +DeckListStatisticsAnalyzer::DeckListStatisticsAnalyzer(QObject *parent, + DeckListModel *_model, + DeckListStatisticsAnalyzerConfig cfg) + : QObject(parent), model(_model), config(cfg) +{ + connect(model, &DeckListModel::dataChanged, this, &DeckListStatisticsAnalyzer::update); +} + +void DeckListStatisticsAnalyzer::update() +{ + manaBaseMap.clear(); + manaCurveMap.clear(); + manaDevotionMap.clear(); + + auto nodes = model->getDeckList()->getCardNodes(); + + for (auto *node : nodes) { + CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName()); + if (!info) + continue; + + for (int i = 0; i < node->getNumber(); ++i) { + // ---- Mana curve ---- + if (config.computeManaCurve) { + manaCurveMap[info->getCmc().toInt()]++; + } + + // ---- Mana base ---- + if (config.computeManaBase) { + auto mana = determineManaProduction(info->getText()); + for (auto it = mana.begin(); it != mana.end(); ++it) + manaBaseMap[it.key()] += it.value(); + } + + // ---- Devotion ---- + if (config.computeDevotion) { + auto devo = countManaSymbols(info->getManaCost()); + for (auto &d : devo) + manaDevotionMap[d.first] += d.second; + } + } + } + + emit statsUpdated(); +} + +QHash DeckListStatisticsAnalyzer::determineManaProduction(const QString &rulesText) +{ + QHash manaCounts = {{"W", 0}, {"U", 0}, {"B", 0}, {"R", 0}, {"G", 0}, {"C", 0}}; + + QString text = rulesText.toLower(); // Normalize case for matching + + // Quick keyword-based checks for any color and colorless mana + if (text.contains("{t}: add one mana of any color") || text.contains("add one mana of any color")) { + for (const auto &color : {QStringLiteral("W"), QStringLiteral("U"), QStringLiteral("B"), QStringLiteral("R"), + QStringLiteral("G")}) { + manaCounts[color]++; + } + } + if (text.contains("{t}: add {c}") || text.contains("add one colorless mana")) { + manaCounts["C"]++; + } + + // Optimized regex for specific mana symbols + static const QRegularExpression specificColorRegex(R"(\{T\}:\s*Add\s*\{([WUBRG])\})"); + QRegularExpressionMatch match = specificColorRegex.match(rulesText); + if (match.hasMatch()) { + manaCounts[match.captured(1)]++; + } + + return manaCounts; +} + +std::unordered_map DeckListStatisticsAnalyzer::countManaSymbols(const QString &manaString) +{ + std::unordered_map manaCounts = {{'W', 0}, {'U', 0}, {'B', 0}, {'R', 0}, {'G', 0}}; + + int len = manaString.length(); + for (int i = 0; i < len; ++i) { + if (manaString[i] == '{') { + ++i; // Move past '{' + if (i < len && manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) { + char mana1 = manaString[i].toLatin1(); + ++i; // Move to next character + if (i < len && manaString[i] == '/') { + ++i; // Move past '/' + if (i < len && manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) { + char mana2 = manaString[i].toLatin1(); + manaCounts[mana1]++; + manaCounts[mana2]++; + } else { + // Handle cases like "{W/}" where second part is invalid + manaCounts[mana1]++; + } + } else { + manaCounts[mana1]++; + } + } + // Ensure we always skip to the closing '}' + while (i < len && manaString[i] != '}') { + ++i; + } + } + // Check if the character is a standalone mana symbol (not inside {}) + else if (manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) { + manaCounts[manaString[i].toLatin1()]++; + } + } + + return manaCounts; +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h new file mode 100644 index 000000000..8fa033971 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h @@ -0,0 +1,60 @@ +#ifndef COCKATRICE_DECK_LIST_STATISTICS_ANALYZER_H +#define COCKATRICE_DECK_LIST_STATISTICS_ANALYZER_H + +#include "deck_list_model.h" + +#include +#include +#include + +class DeckListModel; + +struct DeckListStatisticsAnalyzerConfig +{ + bool computeManaBase = true; + bool computeManaCurve = true; + bool computeDevotion = true; +}; + +class DeckListStatisticsAnalyzer : public QObject +{ + Q_OBJECT + +public: + explicit DeckListStatisticsAnalyzer(QObject *parent, + DeckListModel *model, + DeckListStatisticsAnalyzerConfig cfg = DeckListStatisticsAnalyzerConfig()); + + void update(); + + [[nodiscard]] const QHash &getManaBase() const + { + return manaBaseMap; + } + [[nodiscard]] const std::unordered_map &getManaCurve() const + { + return manaCurveMap; + } + [[nodiscard]] const std::unordered_map &getDevotion() const + { + return manaDevotionMap; + } + +signals: + void statsUpdated(); + +private: + DeckListModel *model; + DeckListStatisticsAnalyzerConfig config; + + // Internal result containers + QHash manaBaseMap; + std::unordered_map manaCurveMap; + std::unordered_map manaDevotionMap; + + // Internal helper functions + QHash determineManaProduction(const QString &); + std::unordered_map countManaSymbols(const QString &); +}; + +#endif // COCKATRICE_DECK_LIST_STATISTICS_ANALYZER_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.cpp index 568583af5..2e530db0b 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.cpp @@ -10,8 +10,8 @@ #include #include -ManaBaseWidget::ManaBaseWidget(QWidget *parent, DeckListModel *_deckListModel) - : QWidget(parent), deckListModel(_deckListModel) +ManaBaseWidget::ManaBaseWidget(QWidget *parent, DeckListStatisticsAnalyzer *_deckStatAnalyzer) + : QWidget(parent), deckStatAnalyzer(_deckStatAnalyzer) { layout = new QVBoxLayout(this); setLayout(layout); @@ -24,7 +24,7 @@ ManaBaseWidget::ManaBaseWidget(QWidget *parent, DeckListModel *_deckListModel) barLayout = new QHBoxLayout(barContainer); layout->addWidget(barContainer); - connect(deckListModel, &DeckListModel::dataChanged, this, &ManaBaseWidget::analyzeManaBase); + connect(deckStatAnalyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaBaseWidget::updateDisplay); retranslateUi(); } @@ -34,13 +34,6 @@ void ManaBaseWidget::retranslateUi() bannerWidget->setText(tr("Mana Base")); } -void ManaBaseWidget::setDeckModel(DeckListModel *deckModel) -{ - deckListModel = deckModel; - connect(deckListModel, &DeckListModel::dataChanged, this, &ManaBaseWidget::analyzeManaBase); - analyzeManaBase(); -} - void ManaBaseWidget::updateDisplay() { // Clear the layout first @@ -50,6 +43,8 @@ void ManaBaseWidget::updateDisplay() delete item; } + auto manaBaseMap = deckStatAnalyzer->getManaBase(); + int highestEntry = 0; for (auto entry : manaBaseMap) { if (entry > highestEntry) { @@ -74,56 +69,3 @@ void ManaBaseWidget::updateDisplay() update(); } - -QHash ManaBaseWidget::analyzeManaBase() -{ - manaBaseMap.clear(); - QList cardsInDeck = deckListModel->getDeckList()->getCardNodes(); - - for (auto currentCard : cardsInDeck) { - for (int k = 0; k < currentCard->getNumber(); ++k) { - CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(currentCard->getName()); - if (info) { - auto devotion = determineManaProduction(info->getText()); - mergeManaCounts(manaBaseMap, devotion); - } - } - } - - updateDisplay(); - return manaBaseMap; -} - -QHash ManaBaseWidget::determineManaProduction(const QString &rulesText) -{ - QHash manaCounts = {{"W", 0}, {"U", 0}, {"B", 0}, {"R", 0}, {"G", 0}, {"C", 0}}; - - QString text = rulesText.toLower(); // Normalize case for matching - - // Quick keyword-based checks for any color and colorless mana - if (text.contains("{t}: add one mana of any color") || text.contains("add one mana of any color")) { - for (const auto &color : {QStringLiteral("W"), QStringLiteral("U"), QStringLiteral("B"), QStringLiteral("R"), - QStringLiteral("G")}) { - manaCounts[color]++; - } - } - if (text.contains("{t}: add {c}") || text.contains("add one colorless mana")) { - manaCounts["C"]++; - } - - // Optimized regex for specific mana symbols - static const QRegularExpression specificColorRegex(R"(\{T\}:\s*Add\s*\{([WUBRG])\})"); - QRegularExpressionMatch match = specificColorRegex.match(rulesText); - if (match.hasMatch()) { - manaCounts[match.captured(1)]++; - } - - return manaCounts; -} - -void ManaBaseWidget::mergeManaCounts(QHash &manaCounts1, const QHash &manaCounts2) -{ - for (auto it = manaCounts2.constBegin(); it != manaCounts2.constEnd(); ++it) { - manaCounts1[it.key()] += it.value(); - } -} diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.h b/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.h index e88fe3b85..079449353 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.h +++ b/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.h @@ -8,6 +8,7 @@ #define MANA_BASE_WIDGET_H #include "../general/display/banner_widget.h" +#include "deck_list_statistics_analyzer.h" #include #include @@ -20,21 +21,15 @@ class ManaBaseWidget : public QWidget Q_OBJECT public: - explicit ManaBaseWidget(QWidget *parent, DeckListModel *deckListModel); - QHash analyzeManaBase(); + explicit ManaBaseWidget(QWidget *parent, DeckListStatisticsAnalyzer *deckStatAnalyzer); void updateDisplay(); - QHash determineManaProduction(const QString &manaString); - void mergeManaCounts(QHash &manaCounts1, const QHash &manaCounts2); - public slots: - void setDeckModel(DeckListModel *deckModel); void retranslateUi(); private: - DeckListModel *deckListModel; + DeckListStatisticsAnalyzer *deckStatAnalyzer; BannerWidget *bannerWidget; - QHash manaBaseMap; QVBoxLayout *layout; QWidget *barContainer; QHBoxLayout *barLayout; diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.cpp index 4515b723b..c094eb590 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.cpp @@ -10,8 +10,8 @@ #include #include -ManaCurveWidget::ManaCurveWidget(QWidget *parent, DeckListModel *_deckListModel) - : QWidget(parent), deckListModel(_deckListModel) +ManaCurveWidget::ManaCurveWidget(QWidget *parent, DeckListStatisticsAnalyzer *_deckStatAnalyzer) + : QWidget(parent), deckStatAnalyzer(_deckStatAnalyzer) { layout = new QVBoxLayout(this); setLayout(layout); @@ -24,7 +24,7 @@ ManaCurveWidget::ManaCurveWidget(QWidget *parent, DeckListModel *_deckListModel) barLayout = new QHBoxLayout(barContainer); layout->addWidget(barContainer); - connect(deckListModel, &DeckListModel::dataChanged, this, &ManaCurveWidget::analyzeManaCurve); + connect(deckStatAnalyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaCurveWidget::updateDisplay); retranslateUi(); } @@ -34,34 +34,6 @@ void ManaCurveWidget::retranslateUi() bannerWidget->setText(tr("Mana Curve")); } -void ManaCurveWidget::setDeckModel(DeckListModel *deckModel) -{ - deckListModel = deckModel; - connect(deckListModel, &DeckListModel::dataChanged, this, &ManaCurveWidget::analyzeManaCurve); - analyzeManaCurve(); -} - -std::unordered_map ManaCurveWidget::analyzeManaCurve() -{ - manaCurveMap.clear(); - - QList cardsInDeck = deckListModel->getDeckList()->getCardNodes(); - - for (auto currentCard : cardsInDeck) { - for (int k = 0; k < currentCard->getNumber(); ++k) { - CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(currentCard->getName()); - if (info) { - int cmc = info->getCmc().toInt(); - manaCurveMap[cmc]++; - } - } - } - - updateDisplay(); - - return manaCurveMap; -} - void ManaCurveWidget::updateDisplay() { // Clear the layout first @@ -73,6 +45,8 @@ void ManaCurveWidget::updateDisplay() } } + auto manaCurveMap = deckStatAnalyzer->getManaCurve(); + int highestEntry = 0; for (const auto &entry : manaCurveMap) { if (entry.second > highestEntry) { diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.h b/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.h index 7362e0c72..fad1fb0f8 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.h +++ b/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.h @@ -8,6 +8,7 @@ #define MANA_CURVE_WIDGET_H #include "../general/display/banner_widget.h" +#include "deck_list_statistics_analyzer.h" #include #include @@ -19,17 +20,14 @@ class ManaCurveWidget : public QWidget Q_OBJECT public: - explicit ManaCurveWidget(QWidget *parent, DeckListModel *deckListModel); + explicit ManaCurveWidget(QWidget *parent, DeckListStatisticsAnalyzer *deckStatAnalyzer); void updateDisplay(); public slots: - void setDeckModel(DeckListModel *deckModel); - std::unordered_map analyzeManaCurve(); void retranslateUi(); private: - DeckListModel *deckListModel; - std::unordered_map manaCurveMap; + DeckListStatisticsAnalyzer *deckStatAnalyzer; QVBoxLayout *layout; BannerWidget *bannerWidget; QWidget *barContainer; diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp index 63e4255b5..476bb8077 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp @@ -10,8 +10,8 @@ #include #include -ManaDevotionWidget::ManaDevotionWidget(QWidget *parent, DeckListModel *_deckListModel) - : QWidget(parent), deckListModel(_deckListModel) +ManaDevotionWidget::ManaDevotionWidget(QWidget *parent, DeckListStatisticsAnalyzer *_deckStatAnalyzer) + : QWidget(parent), deckStatAnalyzer(_deckStatAnalyzer) { layout = new QVBoxLayout(this); setLayout(layout); @@ -23,7 +23,7 @@ ManaDevotionWidget::ManaDevotionWidget(QWidget *parent, DeckListModel *_deckList barLayout = new QHBoxLayout(); layout->addLayout(barLayout); - connect(deckListModel, &DeckListModel::dataChanged, this, &ManaDevotionWidget::analyzeManaDevotion); + connect(deckStatAnalyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaDevotionWidget::updateDisplay); retranslateUi(); } @@ -33,33 +33,6 @@ void ManaDevotionWidget::retranslateUi() bannerWidget->setText(tr("Mana Devotion")); } -void ManaDevotionWidget::setDeckModel(DeckListModel *deckModel) -{ - deckListModel = deckModel; - connect(deckListModel, &DeckListModel::dataChanged, this, &ManaDevotionWidget::analyzeManaDevotion); - analyzeManaDevotion(); -} - -std::unordered_map ManaDevotionWidget::analyzeManaDevotion() -{ - manaDevotionMap.clear(); - - QList cardsInDeck = deckListModel->getDeckList()->getCardNodes(); - - for (auto currentCard : cardsInDeck) { - for (int k = 0; k < currentCard->getNumber(); ++k) { - CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(currentCard->getName()); - if (info) { - auto devotion = countManaSymbols(info->getManaCost()); - mergeManaCounts(manaDevotionMap, devotion); - } - } - } - - updateDisplay(); - return manaDevotionMap; -} - void ManaDevotionWidget::updateDisplay() { // Clear the layout first @@ -69,6 +42,8 @@ void ManaDevotionWidget::updateDisplay() delete item; } + auto manaDevotionMap = deckStatAnalyzer->getDevotion(); + int highestEntry = 0; for (auto entry : manaDevotionMap) { if (highestEntry < entry.second) { @@ -89,50 +64,3 @@ void ManaDevotionWidget::updateDisplay() update(); // Update the widget display } - -std::unordered_map ManaDevotionWidget::countManaSymbols(const QString &manaString) -{ - std::unordered_map manaCounts = {{'W', 0}, {'U', 0}, {'B', 0}, {'R', 0}, {'G', 0}}; - - int len = manaString.length(); - for (int i = 0; i < len; ++i) { - if (manaString[i] == '{') { - ++i; // Move past '{' - if (i < len && manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) { - char mana1 = manaString[i].toLatin1(); - ++i; // Move to next character - if (i < len && manaString[i] == '/') { - ++i; // Move past '/' - if (i < len && manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) { - char mana2 = manaString[i].toLatin1(); - manaCounts[mana1]++; - manaCounts[mana2]++; - } else { - // Handle cases like "{W/}" where second part is invalid - manaCounts[mana1]++; - } - } else { - manaCounts[mana1]++; - } - } - // Ensure we always skip to the closing '}' - while (i < len && manaString[i] != '}') { - ++i; - } - } - // Check if the character is a standalone mana symbol (not inside {}) - else if (manaCounts.find(manaString[i].toLatin1()) != manaCounts.end()) { - manaCounts[manaString[i].toLatin1()]++; - } - } - - return manaCounts; -} - -void ManaDevotionWidget::mergeManaCounts(std::unordered_map &manaCounts1, - const std::unordered_map &manaCounts2) -{ - for (const auto &pair : manaCounts2) { - manaCounts1[pair.first] += pair.second; // Add values for matching keys - } -} diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.h b/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.h index b8ec7950a..ff2e86159 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.h +++ b/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.h @@ -8,6 +8,7 @@ #define MANA_DEVOTION_WIDGET_H #include "../general/display/banner_widget.h" +#include "deck_list_statistics_analyzer.h" #include #include @@ -20,21 +21,15 @@ class ManaDevotionWidget : public QWidget Q_OBJECT public: - explicit ManaDevotionWidget(QWidget *parent, DeckListModel *deckListModel); + explicit ManaDevotionWidget(QWidget *parent, DeckListStatisticsAnalyzer *deckStatAnalyzer); void updateDisplay(); - std::unordered_map countManaSymbols(const QString &manaString); - void mergeManaCounts(std::unordered_map &manaCounts1, const std::unordered_map &manaCounts2); - public slots: - void setDeckModel(DeckListModel *deckModel); - std::unordered_map analyzeManaDevotion(); void retranslateUi(); private: - DeckListModel *deckListModel; + DeckListStatisticsAnalyzer *deckStatAnalyzer; BannerWidget *bannerWidget; - std::unordered_map manaDevotionMap; QVBoxLayout *layout; QHBoxLayout *barLayout; }; diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 1540df0bf..6dd55d545 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -84,7 +84,7 @@ void TabDeckEditorVisual::onDeckChanged() { AbstractTabDeckEditor::onDeckModified(); tabContainer->visualDeckView->constructZoneWidgetsFromDeckListModel(); - tabContainer->deckAnalytics->refreshDisplays(deckDockWidget->deckModel); + tabContainer->deckAnalytics->refreshDisplays(); tabContainer->sampleHandWidget->setDeckModel(deckDockWidget->deckModel); } From 30cc8ad6f9c03e4d376572d5d039d535007beaa5 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Tue, 2 Dec 2025 22:19:44 +0100 Subject: [PATCH 041/325] [DeckEditor] Expand DeckDock TreeView when adding card through deck editor (#6388) * [DeckEditor] Expand DeckDock TreeView when adding card through deck editor * [DeckEditor] Expand first, then set selection. --- .../widgets/deck_editor/deck_editor_deck_dock_widget.h | 2 +- .../src/interface/widgets/tabs/abstract_tab_deck_editor.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index 852ce7164..37891072d 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -69,6 +69,7 @@ public slots: void actSwapCard(); void actRemoveCard(); void offsetCountAtIndex(const QModelIndex &idx, int offset); + void expandAll(); signals: void nameChanged(); @@ -117,7 +118,6 @@ private slots: void updateShowBannerCardComboBox(bool visible); void updateShowTagsWidget(bool visible); void syncBannerCardComboBoxSelectionWithDeck(); - void expandAll(); }; #endif // DECK_EDITOR_DECK_DOCK_WIDGET_H 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 6d05e656d..a03b79e3f 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -153,6 +153,7 @@ void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, QString zoneNam card.getPrinting().getProperty("num"))); QModelIndex newCardIndex = deckDockWidget->deckModel->addCard(card, zoneName); + deckDockWidget->expandAll(); deckDockWidget->deckView->clearSelection(); deckDockWidget->deckView->setCurrentIndex(newCardIndex); setModified(true); From d29e72ce7212e7c9e90d60398719eca969d87f82 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Tue, 2 Dec 2025 22:24:39 +0100 Subject: [PATCH 042/325] [CardInfo] Display set short name and collector number in info widget. (#6378) * [CardInfo] Display set short name and collector number in info widget. * Lint. * Use reference. --- .../widgets/cards/card_info_display_widget.cpp | 2 +- .../widgets/cards/card_info_frame_widget.cpp | 2 +- .../widgets/cards/card_info_text_widget.cpp | 12 +++++++++++- .../interface/widgets/cards/card_info_text_widget.h | 4 +++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp index b13a085bd..819bc57d0 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp @@ -49,7 +49,7 @@ void CardInfoDisplayWidget::setCard(const ExactCard &card) if (exactCard) connect(exactCard.getCardPtr().data(), &QObject::destroyed, this, &CardInfoDisplayWidget::clear); - text->setCard(exactCard.getCardPtr()); + text->setCard(exactCard); pic->setCard(exactCard); } diff --git a/cockatrice/src/interface/widgets/cards/card_info_frame_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_frame_widget.cpp index 829292b53..21bee8f54 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_frame_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_frame_widget.cpp @@ -154,7 +154,7 @@ void CardInfoFrameWidget::setCard(const ExactCard &card) setViewTransformationButtonVisibility(hasTransformation(exactCard.getInfo())); - text->setCard(exactCard.getCardPtr()); + text->setCard(exactCard); pic->setCard(exactCard); } 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 775f6fb9e..456e1533a 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp @@ -50,8 +50,9 @@ void CardInfoTextWidget::setTexts(const QString &propsText, const QString &textT textLabel->setText(textText); } -void CardInfoTextWidget::setCard(CardInfoPtr card) +void CardInfoTextWidget::setCard(const ExactCard &exactCard) { + auto card = exactCard.getCardPtr(); if (card == nullptr) { setTexts("", ""); return; @@ -61,6 +62,15 @@ void CardInfoTextWidget::setCard(CardInfoPtr card) text += QString("%1%2") .arg(tr("Name:"), card->getName().toHtmlEscaped()); + if (exactCard.getPrinting() != PrintingInfo()) { + QString setShort = exactCard.getPrinting().getSet()->getShortName().toHtmlEscaped(); + QString cardNum = exactCard.getPrinting().getProperty("num").toHtmlEscaped(); + + text += QString("%1%2").arg(tr("Set:"), setShort); + + text += QString("%1%2").arg(tr("Collector Number:"), cardNum); + } + QStringList cardProps = card->getProperties(); for (const QString &key : cardProps) { if (key.contains("-")) 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 8711422b0..95b588882 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_text_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_text_widget.h @@ -7,6 +7,8 @@ #ifndef CARDINFOTEXT_H #define CARDINFOTEXT_H +#include "libcockatrice/card/printing/exact_card.h" + #include #include class QLabel; @@ -32,7 +34,7 @@ public: signals: void linkActivated(const QString &link); public slots: - void setCard(CardInfoPtr card); + void setCard(const ExactCard &card); }; #endif From 658ae83157d0d9566683da3a477cbd8875911f08 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 2 Dec 2025 21:18:46 -0800 Subject: [PATCH 043/325] [DeckList] Make DeckList not a QObject (#6383) --- .../deck_editor_deck_dock_widget.cpp | 5 +- .../deck_preview_deck_tags_display_widget.cpp | 12 ++-- .../deck_preview_deck_tags_display_widget.h | 2 +- .../libcockatrice/deck_list/deck_list.cpp | 14 +++-- .../libcockatrice/deck_list/deck_list.h | 55 +++---------------- .../models/deck_list/deck_list_model.cpp | 7 ++- 6 files changed, 28 insertions(+), 67 deletions(-) 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 4db7b8774..075b73216 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 @@ -424,7 +424,6 @@ void DeckEditorDeckDockWidget::setDeck(DeckLoader *_deck) deckLoader->setParent(this); deckModel->setDeckList(deckLoader->getDeckList()); connect(deckLoader, &DeckLoader::deckLoaded, deckModel, &DeckListModel::rebuildTree); - connect(deckLoader->getDeckList(), &DeckList::deckHashChanged, deckModel, &DeckListModel::deckHashChanged); emit requestDeckHistoryClear(); historyManagerWidget->setDeckListModel(deckModel); @@ -452,7 +451,7 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel() sortDeckModelToDeckView(); expandAll(); - deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList()); + deckTagsDisplayWidget->setDeckList(deckModel->getDeckList()); } void DeckEditorDeckDockWidget::sortDeckModelToDeckView() @@ -485,7 +484,7 @@ void DeckEditorDeckDockWidget::cleanDeck() emit deckModified(); emit deckChanged(); updateBannerCardComboBox(); - deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList()); + deckTagsDisplayWidget->setDeckList(deckModel->getDeckList()); } void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index) diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp index 706444545..a29f3bfc4 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp @@ -28,21 +28,15 @@ DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_par flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); if (_deckList) { - connectDeckList(_deckList); + setDeckList(_deckList); } layout->addWidget(flowWidget); } -void DeckPreviewDeckTagsDisplayWidget::connectDeckList(DeckList *_deckList) +void DeckPreviewDeckTagsDisplayWidget::setDeckList(DeckList *_deckList) { - if (deckList) { - disconnect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags); - } - deckList = _deckList; - connect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags); - refreshTags(); } @@ -150,6 +144,7 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() QStringList updatedTags = dialog.getActiveTags(); deckList->setTags(updatedTags); deckPreviewWidget->deckLoader->saveToFile(deckPreviewWidget->filePath, DeckLoader::CockatriceFormat); + refreshTags(); } } } else if (parentWidget()) { @@ -181,6 +176,7 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() QStringList updatedTags = dialog.getActiveTags(); deckList->setTags(updatedTags); deckEditor->setModified(true); + refreshTags(); } } } diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h index 4270e38e5..b1b1117ae 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h @@ -21,7 +21,7 @@ class DeckPreviewDeckTagsDisplayWidget : public QWidget public: explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList); - void connectDeckList(DeckList *_deckList); + void setDeckList(DeckList *_deckList); void refreshTags(); DeckList *deckList; FlowWidget *flowWidget; diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index 37f537249..cea759428 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -87,6 +87,12 @@ DeckList::DeckList() root = new InnerDecklistNode; } +DeckList::DeckList(const DeckList &other) + : metadata(other.metadata), sideboardPlans(other.sideboardPlans), root(new InnerDecklistNode(other.getRoot())), + cachedDeckHash(other.cachedDeckHash) +{ +} + DeckList::DeckList(const QString &nativeString) { root = new InnerDecklistNode; @@ -443,11 +449,8 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata) cardName.replace(diff.key(), diff.value()); } - // Resolve complete card name, this function does nothing if the name is not found - cardName = getCompleteCardName(cardName); - // Determine the zone (mainboard/sideboard) - QString zoneName = getCardZoneFromName(cardName, sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN); + QString zoneName = sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN; // make new entry in decklist new DecklistCardNode(cardName, amount, getZoneObjFromName(zoneName), -1, setCode, collectorNumber); @@ -708,12 +711,11 @@ QString DeckList::getDeckHash() const } /** - * Invalidates the cached deckHash and emits the deckHashChanged signal. + * Invalidates the cached deckHash. */ void DeckList::refreshDeckHash() { cachedDeckHash = QString(); - emit deckHashChanged(); } /** diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index 5340d69fe..d4d1204e6 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -1,5 +1,5 @@ /** - * @file decklist.h + * @file deck_list.h * @brief Defines the DeckList class and supporting types for managing a full * deck structure including cards, zones, sideboard plans, and * serialization to/from multiple formats. This is a logic class which @@ -93,7 +93,7 @@ public: * @brief Represents a complete deck, including metadata, zones, cards, * and sideboard plans. * - * A DeckList is a QObject wrapper around an `InnerDecklistNode` tree, + * A DeckList is a wrapper around an `InnerDecklistNode` tree, * enriched with metadata like deck name, comments, tags, banner card, * and multiple sideboard plans. * @@ -110,10 +110,6 @@ public: * - Owns the root `InnerDecklistNode` tree. * - Owns `SideboardPlan` instances stored in `sideboardPlans`. * - * ### Signals: - * - @c deckHashChanged() — emitted when the deck contents change. - * - @c deckTagsChanged() — emitted when tags are added/removed. - * * ### Example workflow: * ``` * DeckList deck; @@ -123,10 +119,8 @@ public: * deck.saveToFile_Native(device); * ``` */ -class DeckList : public QObject +class DeckList { - Q_OBJECT - public: struct Metadata { @@ -158,37 +152,7 @@ private: static void getCardRefListHelper(InnerDecklistNode *item, QList &result); InnerDecklistNode *getZoneObjFromName(const QString &zoneName); -protected: - /** - * @brief Map a card name to its zone. - * Override in subclasses for format-specific logic. - * @param cardName Card being placed. - * @param currentZoneName Zone candidate. - * @return Zone name to use. - */ - virtual QString getCardZoneFromName(const QString /*cardName*/, QString currentZoneName) - { - return currentZoneName; - } - - /** - * @brief Produce the complete display name of a card. - * Override in subclasses to add set suffixes or annotations. - * @param cardName Base name. - * @return Full display name. - */ - virtual QString getCompleteCardName(const QString &cardName) const - { - return cardName; - } - -signals: - /// Emitted when the deck hash changes. - void deckHashChanged(); - /// Emitted when the deck tags are modified. - void deckTagsChanged(); - -public slots: +public: /// @name Metadata setters ///@{ void setName(const QString &_name = QString()) @@ -202,17 +166,14 @@ public slots: void setTags(const QStringList &_tags = QStringList()) { metadata.tags = _tags; - emit deckTagsChanged(); } void addTag(const QString &_tag) { metadata.tags.append(_tag); - emit deckTagsChanged(); } void clearTags() { metadata.tags.clear(); - emit deckTagsChanged(); } void setBannerCard(const CardRef &_bannerCard = {}) { @@ -224,15 +185,13 @@ public slots: } ///@} -public: /// @brief Construct an empty deck. explicit DeckList(); - /// @brief Delete copy constructor. - DeckList(const DeckList &) = delete; - DeckList &operator=(const DeckList &) = delete; + /// @brief Copy constructor (deep copies the node tree) + DeckList(const DeckList &other); /// @brief Construct from a serialized native-format string. explicit DeckList(const QString &nativeString); - ~DeckList() override; + virtual ~DeckList(); /// @name Metadata getters /// The individual metadata getters still exist for backwards compatibility. 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 e3e2de58d..bd16ab3d2 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -5,8 +5,11 @@ DeckListModel::DeckListModel(QObject *parent) : QAbstractItemModel(parent), lastKnownColumn(1), lastKnownOrder(Qt::AscendingOrder) { + // This class will leak the decklist object. We cannot safely delete it in the dtor because the deckList field is a + // non-owning pointer and another deckList might have been assigned to it. + // `DeckListModel::cleanList` also leaks for the same reason. + // TODO: fix the leak deckList = new DeckList; - deckList->setParent(this); root = new InnerDecklistNode; } @@ -284,6 +287,7 @@ bool DeckListModel::setData(const QModelIndex &index, const QVariant &value, con emitRecursiveUpdates(index); deckList->refreshDeckHash(); + emit deckHashChanged(); emit dataChanged(index, index); return true; @@ -422,6 +426,7 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam cardNode->setCardCollectorNumber(printingInfo.getProperty("num")); cardNode->setCardProviderId(printingInfo.getProperty("uuid")); deckList->refreshDeckHash(); + emit deckHashChanged(); } sort(lastKnownColumn, lastKnownOrder); emitRecursiveUpdates(parentIndex); From b4e3f2cba989554c97bd383842520545f1e48fee Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 2 Dec 2025 21:19:56 -0800 Subject: [PATCH 044/325] [Oracle] Support importing tokens and spoilers from local file (#6387) --- oracle/src/pages.cpp | 20 +++++++- oracle/src/pages.h | 2 + oracle/src/pagetemplates.cpp | 97 ++++++++++++++++++++++++++++-------- oracle/src/pagetemplates.h | 11 +++- 4 files changed, 105 insertions(+), 25 deletions(-) diff --git a/oracle/src/pages.cpp b/oracle/src/pages.cpp index 9b2f5ce7c..46edf1cf8 100644 --- a/oracle/src/pages.cpp +++ b/oracle/src/pages.cpp @@ -672,13 +672,21 @@ QString LoadTokensPage::getFileType() return tr("XML; token database (*.xml)"); } +QString LoadTokensPage::getFilePromptName() +{ + return tr("tokens"); +} + void LoadTokensPage::retranslateUi() { setTitle(tr("Tokens import")); setSubTitle(tr("Please specify a compatible source for token data.")); - urlLabel->setText(tr("Download URL:")); + urlRadioButton->setText(tr("Download URL:")); + fileRadioButton->setText(tr("Local file:")); urlButton->setText(tr("Restore default URL")); + fileButton->setText(tr("Choose file...")); + pathLabel->setText(tr("The token database will be saved at the following location:") + "
" + SettingsCache::instance().getTokenDatabasePath()); defaultPathCheckBox->setText(tr("Save to a custom path (not recommended)")); @@ -709,13 +717,21 @@ QString LoadSpoilersPage::getFileType() return tr("XML; spoiler database (*.xml)"); } +QString LoadSpoilersPage::getFilePromptName() +{ + return tr("spoiler"); +} + void LoadSpoilersPage::retranslateUi() { setTitle(tr("Spoilers import")); setSubTitle(tr("Please specify a compatible source for spoiler data.")); - urlLabel->setText(tr("Download URL:")); + urlRadioButton->setText(tr("Download URL:")); + fileRadioButton->setText(tr("Local file:")); urlButton->setText(tr("Restore default URL")); + fileButton->setText(tr("Choose file...")); + 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)")); diff --git a/oracle/src/pages.h b/oracle/src/pages.h index 6a88b6099..066cc2e1b 100644 --- a/oracle/src/pages.h +++ b/oracle/src/pages.h @@ -131,6 +131,7 @@ protected: QString getDefaultSavePath() override; QString getWindowTitle() override; QString getFileType() override; + QString getFilePromptName() override; }; class LoadTokensPage : public SimpleDownloadFilePage @@ -148,6 +149,7 @@ protected: QString getDefaultSavePath() override; QString getWindowTitle() override; QString getFileType() override; + QString getFilePromptName() override; void initializePage() override; }; diff --git a/oracle/src/pagetemplates.cpp b/oracle/src/pagetemplates.cpp index d9ce3300f..4a27fef30 100644 --- a/oracle/src/pagetemplates.cpp +++ b/oracle/src/pagetemplates.cpp @@ -12,32 +12,43 @@ #include #include #include +#include #include SimpleDownloadFilePage::SimpleDownloadFilePage(QWidget *parent) : OracleWizardPage(parent) { - urlLabel = new QLabel(this); + urlRadioButton = new QRadioButton(this); + fileRadioButton = new QRadioButton(this); + urlLineEdit = new QLineEdit(this); + fileLineEdit = new QLineEdit(this); progressLabel = new QLabel(this); progressBar = new QProgressBar(this); + urlRadioButton->setChecked(true); + urlButton = new QPushButton(this); connect(urlButton, &QPushButton::clicked, this, &SimpleDownloadFilePage::actRestoreDefaultUrl); - defaultPathCheckBox = new QCheckBox(this); + fileButton = new QPushButton(this); + connect(fileButton, &QPushButton::clicked, this, &SimpleDownloadFilePage::actLoadCardFile); + defaultPathCheckBox = new QCheckBox(this); pathLabel = new QLabel(this); pathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); auto *layout = new QGridLayout(this); - layout->addWidget(urlLabel, 0, 0); + layout->addWidget(urlRadioButton, 0, 0); layout->addWidget(urlLineEdit, 0, 1); layout->addWidget(urlButton, 1, 1, Qt::AlignRight); - layout->addWidget(pathLabel, 2, 0, 1, 2); - layout->addWidget(defaultPathCheckBox, 3, 0, 1, 2); - layout->addWidget(progressLabel, 4, 0); - layout->addWidget(progressBar, 4, 1); + layout->addWidget(fileRadioButton, 2, 0); + layout->addWidget(fileLineEdit, 2, 1); + layout->addWidget(fileButton, 3, 1, Qt::AlignRight); + layout->addWidget(pathLabel, 4, 0, 1, 2); + layout->addWidget(defaultPathCheckBox, 5, 0, 1, 2); + layout->addWidget(progressLabel, 6, 0); + layout->addWidget(progressBar, 6, 1); setLayout(layout); } @@ -56,6 +67,31 @@ void SimpleDownloadFilePage::actRestoreDefaultUrl() urlLineEdit->setText(getDefaultUrl()); } +void SimpleDownloadFilePage::actLoadCardFile() +{ + QFileDialog dialog(this, tr("Load %1 file").arg(getFilePromptName())); + dialog.setFileMode(QFileDialog::ExistingFile); + + QString extensions = "*.json *.xml"; +#ifdef HAS_ZLIB + extensions += " *.zip"; +#endif +#ifdef HAS_LZMA + extensions += " *.xz"; +#endif + dialog.setNameFilter(tr("%1 file (%1)").arg(getFilePromptName(), extensions)); + + if (!fileLineEdit->text().isEmpty() && QFile::exists(fileLineEdit->text())) { + dialog.selectFile(fileLineEdit->text()); + } + + if (!dialog.exec()) { + return; + } + + fileLineEdit->setText(dialog.selectedFiles().at(0)); +} + bool SimpleDownloadFilePage::validatePage() { // if data has already been downloaded, pass directly to the "save" step @@ -68,22 +104,41 @@ bool SimpleDownloadFilePage::validatePage() } } - QUrl url = QUrl::fromUserInput(urlLineEdit->text()); - if (!url.isValid()) { - QMessageBox::critical(this, tr("Error"), tr("The provided URL is not valid: ") + url.toString()); - return false; + // else, try to import sets + if (urlRadioButton->isChecked()) { + QUrl url = QUrl::fromUserInput(urlLineEdit->text()); + if (!url.isValid()) { + QMessageBox::critical(this, tr("Error"), tr("The provided URL is not valid: ") + url.toString()); + return false; + } + + progressLabel->setText(tr("Downloading (0MB)")); + // show an infinite progressbar + progressBar->setMaximum(0); + progressBar->setMinimum(0); + progressBar->setValue(0); + progressLabel->show(); + progressBar->show(); + + wizard()->disableButtons(); + downloadFile(url); + + } else if (fileRadioButton->isChecked()) { + QFile cardFile(fileLineEdit->text()); + if (!cardFile.exists()) { + QMessageBox::critical(this, tr("Error"), tr("Please choose a file.")); + return false; + } + + if (!cardFile.open(QIODevice::ReadOnly)) { + QMessageBox::critical(nullptr, tr("Error"), tr("Cannot open file '%1'.").arg(fileLineEdit->text())); + return false; + } + + downloadData = cardFile.readAll(); + wizard()->next(); } - progressLabel->setText(tr("Downloading (0MB)")); - // show an infinite progressbar - progressBar->setMaximum(0); - progressBar->setMinimum(0); - progressBar->setValue(0); - progressLabel->show(); - progressBar->show(); - - wizard()->disableButtons(); - downloadFile(url); return false; } diff --git a/oracle/src/pagetemplates.h b/oracle/src/pagetemplates.h index 372aa2fef..79dcdd632 100644 --- a/oracle/src/pagetemplates.h +++ b/oracle/src/pagetemplates.h @@ -3,6 +3,8 @@ #include +class QFile; +class QRadioButton; class OracleWizard; class QCheckBox; class QLabel; @@ -43,15 +45,19 @@ protected: virtual QString getDefaultSavePath() = 0; virtual QString getWindowTitle() = 0; virtual QString getFileType() = 0; + virtual QString getFilePromptName() = 0; bool saveToFile(); bool internalSaveToFile(const QString &fileName); protected: QByteArray downloadData; - QLabel *urlLabel; - QLabel *pathLabel; + QRadioButton *urlRadioButton; + QRadioButton *fileRadioButton; QLineEdit *urlLineEdit; + QLineEdit *fileLineEdit; QPushButton *urlButton; + QPushButton *fileButton; + QLabel *pathLabel; QLabel *progressLabel; QProgressBar *progressBar; QCheckBox *defaultPathCheckBox; @@ -60,6 +66,7 @@ signals: void parsedDataReady(); private slots: void actRestoreDefaultUrl(); + void actLoadCardFile(); void actDownloadProgress(qint64 received, qint64 total); void actDownloadFinished(); }; From a799cd097af44651279e8ce80420c3c4a92f9e6f Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Wed, 3 Dec 2025 08:23:34 +0100 Subject: [PATCH 045/325] [PrintingSelector] Sync modified and history state on bulk selection (#6379) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [PrintingSelector] Emit deckModified when using bulk selection * [PrintingSelector] Hook up history manager. * [PrintingSelector] Remember card amount. * Return early. Took 18 minutes --------- Co-authored-by: Lukas Brübach --- .../dialogs/dlg_select_set_for_cards.cpp | 20 ++++++++++++++++++- .../dialogs/dlg_select_set_for_cards.h | 2 ++ .../printing_selector/printing_selector.h | 5 +++++ ...rinting_selector_card_selection_widget.cpp | 5 +++++ 4 files changed, 31 insertions(+), 1 deletion(-) 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 893c84512..905ac5db0 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 @@ -145,31 +145,49 @@ void DlgSelectSetForCards::retranslateUi() void DlgSelectSetForCards::actOK() { QMap modifiedSetsAndCardsMap = getModifiedCards(); + + if (modifiedSetsAndCardsMap.isEmpty()) { + accept(); // Nothing to do + } else { + emit deckAboutToBeModified(tr("Bulk modified printings.")); + } + for (QString modifiedSet : modifiedSetsAndCardsMap.keys()) { for (QString card : modifiedSetsAndCardsMap.value(modifiedSet)) { QModelIndex find_card = model->findCard(card, DECK_ZONE_MAIN); if (!find_card.isValid()) { continue; } + int amount = + model->data(find_card.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT), Qt::DisplayRole).toInt(); model->removeRow(find_card.row(), find_card.parent()); CardInfoPtr cardInfo = CardDatabaseManager::query()->getCardInfo(card); PrintingInfo printing = CardDatabaseManager::query()->getSpecificPrinting(card, modifiedSet, ""); - model->addCard(ExactCard(cardInfo, printing), DECK_ZONE_MAIN); + for (int i = 0; i < amount; i++) { + model->addCard(ExactCard(cardInfo, printing), DECK_ZONE_MAIN); + } } } + if (!modifiedSetsAndCardsMap.isEmpty()) { + emit deckModified(); + } accept(); } void DlgSelectSetForCards::actClear() { + emit deckAboutToBeModified(tr("Cleared all printing information.")); DeckLoader::clearSetNamesAndNumbers(model->getDeckList()); + emit deckModified(); accept(); } void DlgSelectSetForCards::actSetAllToPreferred() { + emit deckAboutToBeModified(tr("Set all printings to preferred.")); DeckLoader::clearSetNamesAndNumbers(model->getDeckList()); DeckLoader::setProviderIdToPreferredPrinting(model->getDeckList()); + emit deckModified(); accept(); } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h index 335b86d2c..5cdef5a30 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h @@ -37,6 +37,8 @@ public: signals: void widgetOrderChanged(); void orderChanged(); + void deckAboutToBeModified(const QString &reason); + void deckModified(); public slots: void actOK(); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h index 7130e436c..fbe3b5b06 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h @@ -40,6 +40,11 @@ public: return deckModel; } + [[nodiscard]] AbstractTabDeckEditor *getDeckEditor() const + { + return deckEditor; + } + public slots: void retranslateUi(); void updateDisplay(); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp index 57d5fd895..fc17cecd0 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp @@ -1,6 +1,7 @@ #include "printing_selector_card_selection_widget.h" #include "../../../interface/widgets/dialogs/dlg_select_set_for_cards.h" +#include "../tabs/abstract_tab_deck_editor.h" /** * @brief Constructs a PrintingSelectorCardSelectionWidget for navigating through cards in the deck. @@ -48,6 +49,10 @@ void PrintingSelectorCardSelectionWidget::connectSignals() void PrintingSelectorCardSelectionWidget::selectSetForCards() { auto *setSelectionDialog = new DlgSelectSetForCards(nullptr, parent->getDeckModel()); + connect(setSelectionDialog, &DlgSelectSetForCards::deckAboutToBeModified, parent->getDeckEditor(), + &AbstractTabDeckEditor::onDeckHistorySaveRequested); + connect(setSelectionDialog, &DlgSelectSetForCards::deckModified, parent->getDeckEditor(), + &AbstractTabDeckEditor::onDeckModified); if (!setSelectionDialog->exec()) { return; } From f0be6972cccd4b030710ac605109ff1bc8c5903d Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 4 Dec 2025 09:40:24 -0800 Subject: [PATCH 046/325] [TagsDisplayWidget] cleanup refactor (#6394) * Make fields private * Move method to static * clean up code * move code --- .../deck_preview_deck_tags_display_widget.cpp | 155 ++++++++++-------- .../deck_preview_deck_tags_display_widget.h | 10 +- 2 files changed, 96 insertions(+), 69 deletions(-) diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp index a29f3bfc4..28b786951 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp @@ -71,74 +71,36 @@ static QStringList getAllFiles(const QString &filePath) return allFiles; } -bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath) +/** + * Gets all tags that appear in the deck folder + */ +static QStringList findAllKnownTags() { - QFileInfo fileInfo(filePath); - QString newFileName = QDir::toNativeSeparators(fileInfo.path() + "/" + fileInfo.completeBaseName() + ".cod"); + QStringList allFiles = getAllFiles(SettingsCache::instance().getDeckPath()); - if (QFile::exists(newFileName)) { - QMessageBox::StandardButton reply = - QMessageBox::question(parent, QObject::tr("Overwrite Existing File?"), - QObject::tr("A .cod version of this deck already exists. Overwrite it?"), - QMessageBox::Yes | QMessageBox::No); - return reply == QMessageBox::Yes; + QStringList knownTags; + auto loader = DeckLoader(nullptr); + for (const QString &file : allFiles) { + loader.loadFromFile(file, DeckLoader::getFormatFromName(file), false); + QStringList tags = loader.getDeckList()->getTags(); + knownTags.append(tags); + knownTags.removeDuplicates(); } - return true; // Safe to proceed + + return knownTags; } void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() { if (qobject_cast(parentWidget())) { auto *deckPreviewWidget = qobject_cast(parentWidget()); - QStringList knownTags = deckPreviewWidget->visualDeckStorageWidget->tagFilterWidget->getAllKnownTags(); - QStringList activeTags = deckList->getTags(); - bool canAddTags = true; - - if (DeckLoader::getFormatFromName(deckPreviewWidget->filePath) != DeckLoader::CockatriceFormat) { - canAddTags = false; - // Retrieve saved preference if the prompt is disabled - if (!SettingsCache::instance().getVisualDeckStoragePromptForConversion()) { - if (SettingsCache::instance().getVisualDeckStorageAlwaysConvert()) { - - if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath)) - return; - - deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath); - deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastLoadInfo().fileName; - deckPreviewWidget->refreshBannerCardText(); - canAddTags = true; - } - } else { - // Show the dialog to the user - DialogConvertDeckToCodFormat conversionDialog(parentWidget()); - if (conversionDialog.exec() == QDialog::Accepted) { - - if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath)) - return; - - deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath); - deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastLoadInfo().fileName; - deckPreviewWidget->refreshBannerCardText(); - canAddTags = true; - - if (conversionDialog.dontAskAgain()) { - SettingsCache::instance().setVisualDeckStoragePromptForConversion(false); - SettingsCache::instance().setVisualDeckStorageAlwaysConvert(true); - } - } else { - SettingsCache::instance().setVisualDeckStorageAlwaysConvert(false); - - if (conversionDialog.dontAskAgain()) { - SettingsCache::instance().setVisualDeckStoragePromptForConversion(false); - } else { - SettingsCache::instance().setVisualDeckStoragePromptForConversion(true); - } - } - } - } + bool canAddTags = promptFileConversionIfRequired(deckPreviewWidget); if (canAddTags) { + QStringList knownTags = deckPreviewWidget->visualDeckStorageWidget->tagFilterWidget->getAllKnownTags(); + QStringList activeTags = deckList->getTags(); + DeckPreviewTagDialog dialog(knownTags, activeTags); if (dialog.exec() == QDialog::Accepted) { QStringList updatedTags = dialog.getActiveTags(); @@ -159,16 +121,8 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() } if (qobject_cast(currentParent)) { auto *deckEditor = qobject_cast(currentParent); - QStringList knownTags; - QStringList allFiles = getAllFiles(SettingsCache::instance().getDeckPath()); - DeckLoader loader(this); - for (const QString &file : allFiles) { - loader.loadFromFile(file, DeckLoader::getFormatFromName(file), false); - QStringList tags = loader.getDeckList()->getTags(); - knownTags.append(tags); - knownTags.removeDuplicates(); - } + QStringList knownTags = findAllKnownTags(); QStringList activeTags = deckList->getTags(); DeckPreviewTagDialog dialog(knownTags, activeTags); @@ -181,3 +135,74 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() } } } + +static bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath) +{ + QFileInfo fileInfo(filePath); + QString newFileName = QDir::toNativeSeparators(fileInfo.path() + "/" + fileInfo.completeBaseName() + ".cod"); + + if (QFile::exists(newFileName)) { + QMessageBox::StandardButton reply = + QMessageBox::question(parent, QObject::tr("Overwrite Existing File?"), + QObject::tr("A .cod version of this deck already exists. Overwrite it?"), + QMessageBox::Yes | QMessageBox::No); + return reply == QMessageBox::Yes; + } + return true; // Safe to proceed +} + +static void convertFileToCockatriceFormat(DeckPreviewWidget *deckPreviewWidget) +{ + deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath); + deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastLoadInfo().fileName; + deckPreviewWidget->refreshBannerCardText(); +} + +/** + * Checks if the deck's file format supports tags. + * If not, then prompt the user for file conversion. + * @return whether the resulting file can support adding tags + */ +bool DeckPreviewDeckTagsDisplayWidget::promptFileConversionIfRequired(DeckPreviewWidget *deckPreviewWidget) +{ + if (DeckLoader::getFormatFromName(deckPreviewWidget->filePath) == DeckLoader::CockatriceFormat) { + return true; + } + + // Retrieve saved preference if the prompt is disabled + if (!SettingsCache::instance().getVisualDeckStoragePromptForConversion()) { + if (!SettingsCache::instance().getVisualDeckStorageAlwaysConvert()) { + return false; + } + + if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath)) { + return false; + } + + convertFileToCockatriceFormat(deckPreviewWidget); + return true; + } + + // Show the dialog to the user + DialogConvertDeckToCodFormat conversionDialog(parentWidget()); + if (conversionDialog.exec() != QDialog::Accepted) { + SettingsCache::instance().setVisualDeckStoragePromptForConversion(!conversionDialog.dontAskAgain()); + SettingsCache::instance().setVisualDeckStorageAlwaysConvert(false); + + return false; + } + + // Try to convert file + if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath)) { + return false; + } + + convertFileToCockatriceFormat(deckPreviewWidget); + + if (conversionDialog.dontAskAgain()) { + SettingsCache::instance().setVisualDeckStoragePromptForConversion(false); + SettingsCache::instance().setVisualDeckStorageAlwaysConvert(true); + } + + return true; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h index b1b1117ae..85b0bb9f8 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h @@ -12,21 +12,23 @@ #include -inline bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath); - class DeckPreviewWidget; class DeckPreviewDeckTagsDisplayWidget : public QWidget { Q_OBJECT + DeckList *deckList; + FlowWidget *flowWidget; + public: explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList); void setDeckList(DeckList *_deckList); void refreshTags(); - DeckList *deckList; - FlowWidget *flowWidget; public slots: void openTagEditDlg(); + +private: + bool promptFileConversionIfRequired(DeckPreviewWidget *deckPreviewWidget); }; #endif // DECK_PREVIEW_DECK_TAGS_DISPLAY_WIDGET_H From 95c34342053bb49392f35b6971d3508190c73b9b Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 4 Dec 2025 10:26:39 -0800 Subject: [PATCH 047/325] [TagDisplayWidget] Refactor to just store tags and use signals (#6395) --- .../deck_editor_deck_dock_widget.cpp | 15 ++++- .../deck_editor_deck_dock_widget.h | 1 + .../deck_preview_deck_tags_display_widget.cpp | 67 +++++++------------ .../deck_preview_deck_tags_display_widget.h | 14 +++- .../deck_preview/deck_preview_widget.cpp | 9 ++- .../deck_preview/deck_preview_widget.h | 2 + 6 files changed, 59 insertions(+), 49 deletions(-) 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 075b73216..7e4287ac5 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 @@ -154,8 +154,10 @@ void DeckEditorDeckDockWidget::createDeckDock() &DeckEditorDeckDockWidget::setBannerCard); bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); - deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList()); + deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList()->getTags()); deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible()); + connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this, + &DeckEditorDeckDockWidget::setTags); activeGroupCriteriaLabel = new QLabel(this); @@ -383,6 +385,13 @@ void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */) emit deckModified(); } +void DeckEditorDeckDockWidget::setTags(const QStringList &tags) +{ + deckModel->getDeckList()->setTags(tags); + deckEditor->setModified(true); + emit deckModified(); +} + void DeckEditorDeckDockWidget::syncDeckListBannerCardWithComboBox() { auto [name, id] = bannerCardComboBox->currentData().value>(); @@ -451,7 +460,7 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel() sortDeckModelToDeckView(); expandAll(); - deckTagsDisplayWidget->setDeckList(deckModel->getDeckList()); + deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags()); } void DeckEditorDeckDockWidget::sortDeckModelToDeckView() @@ -484,7 +493,7 @@ void DeckEditorDeckDockWidget::cleanDeck() emit deckModified(); emit deckChanged(); updateBannerCardComboBox(); - deckTagsDisplayWidget->setDeckList(deckModel->getDeckList()); + deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags()); } void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index) diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index 37891072d..d1f1da300 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -112,6 +112,7 @@ private slots: void updateName(const QString &name); void updateComments(); void setBannerCard(int); + void setTags(const QStringList &tags); void syncDeckListBannerCardWithComboBox(); void updateHash(); void refreshShortcuts(); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp index 28b786951..fce0916bb 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp @@ -13,8 +13,8 @@ #include #include -DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList) - : QWidget(_parent), deckList(nullptr) +DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, const QStringList &_tags) + : QWidget(_parent), currentTags(_tags) { setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); @@ -27,16 +27,14 @@ DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_par flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); - if (_deckList) { - setDeckList(_deckList); - } - layout->addWidget(flowWidget); + + refreshTags(); } -void DeckPreviewDeckTagsDisplayWidget::setDeckList(DeckList *_deckList) +void DeckPreviewDeckTagsDisplayWidget::setTags(const QStringList &_tags) { - deckList = _deckList; + currentTags = _tags; refreshTags(); } @@ -44,7 +42,7 @@ void DeckPreviewDeckTagsDisplayWidget::refreshTags() { flowWidget->clearLayout(); - for (const QString &tag : deckList->getTags()) { + for (const QString &tag : currentTags) { flowWidget->addWidget(new DeckPreviewTagDisplayWidget(this, tag)); } @@ -93,46 +91,19 @@ static QStringList findAllKnownTags() void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() { if (qobject_cast(parentWidget())) { + // If we're the child of a DeckPreviewWidget, then we need to handle conversion auto *deckPreviewWidget = qobject_cast(parentWidget()); bool canAddTags = promptFileConversionIfRequired(deckPreviewWidget); if (canAddTags) { QStringList knownTags = deckPreviewWidget->visualDeckStorageWidget->tagFilterWidget->getAllKnownTags(); - QStringList activeTags = deckList->getTags(); - - DeckPreviewTagDialog dialog(knownTags, activeTags); - if (dialog.exec() == QDialog::Accepted) { - QStringList updatedTags = dialog.getActiveTags(); - deckList->setTags(updatedTags); - deckPreviewWidget->deckLoader->saveToFile(deckPreviewWidget->filePath, DeckLoader::CockatriceFormat); - refreshTags(); - } - } - } else if (parentWidget()) { - // If we're the child of an AbstractTabDeckEditor, we are buried under a ton of childWidgets in the - // DeckInfoDock. - QWidget *currentParent = parentWidget(); - while (currentParent) { - if (qobject_cast(currentParent)) { - break; - } - currentParent = currentParent->parentWidget(); - } - if (qobject_cast(currentParent)) { - auto *deckEditor = qobject_cast(currentParent); - - QStringList knownTags = findAllKnownTags(); - QStringList activeTags = deckList->getTags(); - - DeckPreviewTagDialog dialog(knownTags, activeTags); - if (dialog.exec() == QDialog::Accepted) { - QStringList updatedTags = dialog.getActiveTags(); - deckList->setTags(updatedTags); - deckEditor->setModified(true); - refreshTags(); - } + execTagDialog(knownTags); } + } else { + // If we're the child of an AbstractTabDeckEditor, then we don't bother with conversion + QStringList knownTags = findAllKnownTags(); + execTagDialog(knownTags); } } @@ -205,4 +176,16 @@ bool DeckPreviewDeckTagsDisplayWidget::promptFileConversionIfRequired(DeckPrevie } return true; +} + +void DeckPreviewDeckTagsDisplayWidget::execTagDialog(const QStringList &knownTags) +{ + DeckPreviewTagDialog dialog(knownTags, currentTags); + if (dialog.exec() == QDialog::Accepted) { + QStringList updatedTags = dialog.getActiveTags(); + if (updatedTags != currentTags) { + setTags(updatedTags); + emit tagsChanged(updatedTags); + } + } } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h index 85b0bb9f8..74ea1cbdf 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h @@ -17,12 +17,12 @@ class DeckPreviewDeckTagsDisplayWidget : public QWidget { Q_OBJECT - DeckList *deckList; + QStringList currentTags; FlowWidget *flowWidget; public: - explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList); - void setDeckList(DeckList *_deckList); + explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, const QStringList &_tags); + void setTags(const QStringList &_tags); void refreshTags(); public slots: @@ -30,5 +30,13 @@ public slots: private: bool promptFileConversionIfRequired(DeckPreviewWidget *deckPreviewWidget); + void execTagDialog(const QStringList &knownTags); + +signals: + /** + * Emitted when the tags have changed due to user interaction. + * @param tags The new list of tags. + */ + void tagsChanged(const QStringList &tags); }; #endif // DECK_PREVIEW_DECK_TAGS_DISPLAY_WIDGET_H 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 2735b4856..bf915bbea 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 @@ -83,7 +83,8 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess) setFilePath(deckLoader->getLastLoadInfo().fileName); colorIdentityWidget = new ColorIdentityWidget(this, getColorIdentity()); - deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeckList()); + deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeckList()->getTags()); + connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this, &DeckPreviewWidget::setTags); bannerCardLabel = new QLabel(this); bannerCardLabel->setObjectName("bannerCardLabel"); @@ -307,6 +308,12 @@ void DeckPreviewWidget::imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewC emit deckLoadRequested(filePath); } +void DeckPreviewWidget::setTags(const QStringList &tags) +{ + deckLoader->getDeckList()->setTags(tags); + deckLoader->saveToFile(filePath, DeckLoader::CockatriceFormat); +} + QMenu *DeckPreviewWidget::createRightClickMenu() { auto *menu = new QMenu(this); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h index 76db9e569..6e6fdb9af 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h @@ -72,6 +72,8 @@ private: void addSetBannerCardMenu(QMenu *menu); private slots: + void setTags(const QStringList &tags); + void actRenameDeck(); void actRenameFile(); void actDeleteFile(); From 0a239712dd4a16aba3f7bf7f0b624df210cb9815 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 4 Dec 2025 23:14:19 +0100 Subject: [PATCH 048/325] [VDD] Add search bar for filters. (#6389) * [VDD] Add search bar for filters. * Update cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com> --------- Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com> --- ...tabase_display_filter_save_load_widget.cpp | 78 ++++++++++--------- ...database_display_filter_save_load_widget.h | 5 +- 2 files changed, 46 insertions(+), 37 deletions(-) diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp index 4d8eb5c57..44b275afd 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp @@ -18,6 +18,16 @@ VisualDatabaseDisplayFilterSaveLoadWidget::VisualDatabaseDisplayFilterSaveLoadWi layout = new QVBoxLayout(this); setLayout(layout); + // Filter search input + searchInput = new QLineEdit(this); + layout->addWidget(searchInput); + + connect(searchInput, &QLineEdit::textChanged, this, &VisualDatabaseDisplayFilterSaveLoadWidget::applySearchFilter); + + // File list container + fileListWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + layout->addWidget(fileListWidget); + // Input for filter filename filenameInput = new QLineEdit(this); layout->addWidget(filenameInput); @@ -25,11 +35,12 @@ VisualDatabaseDisplayFilterSaveLoadWidget::VisualDatabaseDisplayFilterSaveLoadWi // Save button saveButton = new QPushButton(this); layout->addWidget(saveButton); - connect(saveButton, &QPushButton::clicked, this, &VisualDatabaseDisplayFilterSaveLoadWidget::saveFilter); + // Disable save if empty + saveButton->setEnabled(false); + connect(filenameInput, &QLineEdit::textChanged, this, + [this](const QString &text) { saveButton->setEnabled(!text.trimmed().isEmpty()); }); - // File list container - fileListWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); - layout->addWidget(fileListWidget); + connect(saveButton, &QPushButton::clicked, this, &VisualDatabaseDisplayFilterSaveLoadWidget::saveFilter); refreshFilterList(); // Populate the file list on startup retranslateUi(); @@ -37,6 +48,7 @@ VisualDatabaseDisplayFilterSaveLoadWidget::VisualDatabaseDisplayFilterSaveLoadWi void VisualDatabaseDisplayFilterSaveLoadWidget::retranslateUi() { + searchInput->setPlaceholderText(tr("Search filter...")); saveButton->setText(tr("Save Filter")); saveButton->setToolTip(tr("Save all currently applied filters to a file")); filenameInput->setPlaceholderText(tr("Enter filename...")); @@ -112,42 +124,36 @@ void VisualDatabaseDisplayFilterSaveLoadWidget::loadFilter(const QString &filena emit filterModel->layoutChanged(); } +void VisualDatabaseDisplayFilterSaveLoadWidget::applySearchFilter(const QString &text) +{ + fileListWidget->clearLayout(); + + QString filter = text.trimmed(); + QStringList filtered = allFilterFiles; + + if (!filter.isEmpty()) { + filtered = filtered.filter(QRegularExpression(filter, QRegularExpression::CaseInsensitiveOption)); + } + + for (const QString &filename : filtered) { + FilterDisplayWidget *filterWidget = new FilterDisplayWidget(this, filename, filterModel); + fileListWidget->addWidget(filterWidget); + + connect(filterWidget, &FilterDisplayWidget::filterLoadRequested, this, + &VisualDatabaseDisplayFilterSaveLoadWidget::loadFilter); + + connect(filterWidget, &FilterDisplayWidget::filterDeleted, this, + &VisualDatabaseDisplayFilterSaveLoadWidget::refreshFilterList); + } +} + void VisualDatabaseDisplayFilterSaveLoadWidget::refreshFilterList() { fileListWidget->clearLayout(); - // Clear existing widgets - for (auto buttonPair : fileButtons) { - buttonPair.first->deleteLater(); - buttonPair.second->deleteLater(); - } - fileButtons.clear(); // Clear the list of buttons + fileButtons.clear(); - // Refresh the filter file list QDir dir(SettingsCache::instance().getFiltersPath()); - QStringList filterFiles = dir.entryList(QStringList() << "*.json", QDir::Files, QDir::Name); + allFilterFiles = dir.entryList({"*.json"}, QDir::Files, QDir::Name); - // Loop through the filter files and create widgets for them - for (const QString &filename : filterFiles) { - bool alreadyAdded = false; - - // Check if the widget for this filter file already exists to avoid duplicates - for (const auto &pair : fileButtons) { - if (pair.first->text() == filename) { - alreadyAdded = true; - break; - } - } - - if (!alreadyAdded) { - // Create a new custom widget for the filter - FilterDisplayWidget *filterWidget = new FilterDisplayWidget(this, filename, filterModel); - fileListWidget->addWidget(filterWidget); - - // Connect signals to handle loading and deletion - connect(filterWidget, &FilterDisplayWidget::filterLoadRequested, this, - &VisualDatabaseDisplayFilterSaveLoadWidget::loadFilter); - connect(filterWidget, &FilterDisplayWidget::filterDeleted, this, - &VisualDatabaseDisplayFilterSaveLoadWidget::refreshFilterList); - } - } + applySearchFilter(searchInput->text()); } diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h index 48e6f9498..bb5921a02 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.h @@ -27,6 +27,7 @@ public: void saveFilter(); void loadFilter(const QString &filename); + void applySearchFilter(const QString &text); void refreshFilterList(); void deleteFilter(const QString &filename, QPushButton *deleteButton); @@ -37,9 +38,11 @@ private: FilterTreeModel *filterModel; QVBoxLayout *layout; + QLineEdit *searchInput; + FlowWidget *fileListWidget; QLineEdit *filenameInput; QPushButton *saveButton; - FlowWidget *fileListWidget; + QStringList allFilterFiles; QMap> fileButtons; }; From edb0a954e2b9cf57cdc15ea5a582971a50494d39 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Fri, 5 Dec 2025 17:26:35 +0100 Subject: [PATCH 049/325] [GameInformation] Check for existence of room for create as judge checkbox (#6398) --- cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp index 55b999ee9..7f109f600 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp @@ -109,7 +109,7 @@ void DlgCreateGame::sharedCtor() gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0); gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1); gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 0); - if (room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) { + if (room && room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) { gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 2, 0); } else { createGameAsJudgeCheckBox->setChecked(false); From 7c7f2dd8d5c4196ed1562d1b6c8773796c96c6c4 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Fri, 5 Dec 2025 18:42:45 +0100 Subject: [PATCH 050/325] [Doxygen] Logging (#6399) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Doxygen] Logging Took 50 minutes Took 36 seconds * [Doxygen] Newline. Took 2 minutes * [Doxygen] Add another example. Took 7 minutes * [Doxygen] \note and \warning Took 4 minutes Took 32 seconds --------- Co-authored-by: Lukas Brübach --- cockatrice/resources/config/qtlogging.ini | 4 + .../developer_documentation/index.md | 2 + .../developer_documentation/logging.md | 184 ++++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 doc/doxygen/extra-pages/developer_documentation/logging.md diff --git a/cockatrice/resources/config/qtlogging.ini b/cockatrice/resources/config/qtlogging.ini index 083d7234e..20aa206ce 100644 --- a/cockatrice/resources/config/qtlogging.ini +++ b/cockatrice/resources/config/qtlogging.ini @@ -1,6 +1,10 @@ [Rules] # The default log level is info *.debug = false +#*.info = true +#*.warning = true +#*.critical = true +#*.fatal = true # Uncomment a rule to see debug level logs for that category, # or set = false to disable logging diff --git a/doc/doxygen/extra-pages/developer_documentation/index.md b/doc/doxygen/extra-pages/developer_documentation/index.md index b26faf836..da2d444d8 100644 --- a/doc/doxygen/extra-pages/developer_documentation/index.md +++ b/doc/doxygen/extra-pages/developer_documentation/index.md @@ -1,5 +1,7 @@ @page developer_reference Developer Reference +- @subpage logging + - @subpage primer_cards - @subpage card_database_schema_and_parsing diff --git a/doc/doxygen/extra-pages/developer_documentation/logging.md b/doc/doxygen/extra-pages/developer_documentation/logging.md new file mode 100644 index 000000000..8fd5cb015 --- /dev/null +++ b/doc/doxygen/extra-pages/developer_documentation/logging.md @@ -0,0 +1,184 @@ +@page logging Logging + +Cockatrice uses QtLogging from the QtCore module for its logging. See +the [official documentation](https://doc.qt.io/qt-6/qtlogging.html) for further details. + +# Log Message Pattern + +Any message logged through the QtLogging system automatically conforms to this message pattern: + +Generic: + +``` +[ ] [] - [:] +``` + +Example: + +``` +[2025-12-05 14:48:25.908 I] [MainWindow::startupConfigCheck] - Startup: found config with current version [window_main.cpp:951] +``` + +For more information, see [Logging Setup](#logging-setup). + +# Log Level and Categories + +\note The default log level for the application is info. + +This means that you should only use qInfo() in production-level code if you are truly sure that this message is +beneficial to end-users and other developers. As a general rule, if your functionality logs to info more than twice in +response to a user interaction, you are advised to consider moving some of these logs down to the debug level. + +\warning You are strongly advised to avoid the use of the generic logging macros (e.g. qDebug(), qInfo(), qWarn()). + +\note You should instead use the corresponding category logging macros (qCDebug(), qCInfo(), qCWarn()) and define +logging +categories for your log statements. + +Example: + +```c++ +in .h + +inline Q_LOGGING_CATEGORY(ExampleCategory, "cockatrice_example_category"); +inline Q_LOGGING_CATEGORY(ExampleSubCategory, "cockatrice_example_category.sub_category"); + +in .cpp + +qCInfo(ExampleCategory) << "Info level logs are usually sent through the main category" +qCDebug(ExampleSubCategory) << "Debug level logs are permitted their own category to allow selective silencing" +``` + +For more information on how to enable or disable logging categories, +see [Logging Configuration](#logging-configuration). + +# Logging Configuration + +For configuring our logging, we use the qtlogging.ini, located under cockatrice/resources/config/qtlogging.ini, which is +baked into the application in release version and set as the QT_LOGGING_CONF environment variable in main.cpp. + +```c++ +#ifdef Q_OS_APPLE + // /cockatrice/cockatrice.app/Contents/MacOS/cockatrice + const QByteArray configPath = "../../../qtlogging.ini"; +#elif defined(Q_OS_UNIX) + // /cockatrice/cockatrice + const QByteArray configPath = "./qtlogging.ini"; +#elif defined(Q_OS_WIN) + // /cockatrice/Debug/cockatrice.exe + const QByteArray configPath = "../qtlogging.ini"; +#else + const QByteArray configPath = ""; +#endif + + if (!qEnvironmentVariableIsSet(("QT_LOGGING_CONF"))) { + // Set the QT_LOGGING_CONF environment variable + qputenv("QT_LOGGING_CONF", configPath); + } +``` + +For more information on how to use this file and on how Qt evaluates which logging rules/file to use, please +see the [official Qt documentation](https://doc.qt.io/qt-6/qloggingcategory.html#configuring-categories). + +Some examples: + +``` +# Turn off all logging except everything from card_picture_loader and all sub categories + +[Rules] +# The default log level is info +*.debug = false +*.info = false +*.warning = false +*.critical = false +*.fatal = false + +card_picture_loader.* = true +``` + +``` +# Turn off all logging except info level logs from card_picture_loader and all sub categories + +[Rules] +# The default log level is info +*.debug = false +*.info = false +*.warning = false +*.critical = false +*.fatal = false + +card_picture_loader.*.info = true +``` + +``` +[Rules] +# Turn on debug level logs for card_picture_loader but keep logging for sub categories suppressed +*.debug = false + +card_picture_loader.debug = true +``` + +``` +[Rules] +# Turn on all logs for worker subcategory of card_picture_loader +*.debug = false + +card_picture_loader.worker = true +``` + +``` +[Rules] +# Turn off some noisy and irrelevant startup logging for local development +*.debug = false + +qt_translator = false +window_main.* = false +release_channel = false +spoiler_background_updater = false +theme_manager = false +sound_engine = false +tapped_out_interface = false +card_database = false +card_database.loading = false +card_database.loading.success_or_failure = true +cockatrice_xml.* = false +``` + +# Logging Setup + +This is achieved through our logging setup in @ref main.cpp, where we set the message pattern and install a custom +logger which replaces the full file path at the end with just the file name (Qt shows the full and quite lengthy path by +default). + +```c++ + qSetMessagePattern( + "\033[0m[%{time yyyy-MM-dd h:mm:ss.zzz} " + "%{if-debug}\033[36mD%{endif}%{if-info}\033[32mI%{endif}%{if-warning}\033[33mW%{endif}%{if-critical}\033[31mC%{" + "endif}%{if-fatal}\033[1;31mF%{endif}\033[0m] [%{function}] - %{message} [%{file}:%{line}]"); + QApplication app(argc, argv); + + QObject::connect(&app, &QApplication::lastWindowClosed, &app, &QApplication::quit); + + qInstallMessageHandler(CockatriceLogger); +``` + +```c++ +static void CockatriceLogger(QtMsgType type, const QMessageLogContext &ctx, const QString &message) +{ + QString logMessage = qFormatLogMessage(type, ctx, message); + + // Regular expression to match the full path in the square brackets and extract only the filename and line number + QRegularExpression regex(R"(\[(?:.:)?[\/\\].*[\/\\]([^\/\\]+\:\d+)\])"); + QRegularExpressionMatch match = regex.match(logMessage); + + if (match.hasMatch()) { + // Extract the filename and line number (e.g., "main.cpp:211") + QString filenameLine = match.captured(1); + + // Replace the full path in square brackets with just the filename and line number + logMessage.replace(match.captured(0), QString("[%1]").arg(filenameLine)); + } + + Logger::getInstance().log(type, ctx, logMessage); +} +``` \ No newline at end of file From dde36183ce4c1532e216e56eac2d41f5da134d41 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Fri, 5 Dec 2025 23:27:27 +0100 Subject: [PATCH 051/325] [VDE] Proper parent lookup syncs group-by box again (#6396) * [VDE] Proper parent lookup syncs group-by box again * [VDE] Proper lib inclusion. * [VDE] Lint. --- .../visual_deck_display_options_widget.cpp | 30 +++++++++---------- .../libcockatrice/utility/qt_utils.h | 20 +++++++++++++ 2 files changed, 34 insertions(+), 16 deletions(-) create mode 100644 libcockatrice_utility/libcockatrice/utility/qt_utils.h 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 c190038a5..b169e73cb 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 @@ -2,7 +2,9 @@ #include "../tabs/visual_deck_editor/tab_deck_editor_visual.h" -VisualDeckDisplayOptionsWidget::VisualDeckDisplayOptionsWidget(QWidget *parent) +#include + +VisualDeckDisplayOptionsWidget::VisualDeckDisplayOptionsWidget(QWidget *parent) : QWidget(parent) { groupAndSortLayout = new QHBoxLayout(this); groupAndSortLayout->setAlignment(Qt::AlignLeft); @@ -11,23 +13,19 @@ VisualDeckDisplayOptionsWidget::VisualDeckDisplayOptionsWidget(QWidget *parent) groupByLabel = new QLabel(this); groupByComboBox = new QComboBox(this); - if (auto visualDeckEditorWidget = qobject_cast(parent)) { - if (auto tabWidget = qobject_cast(visualDeckEditorWidget)) { - // Inside a central widget QWidget container inside TabDeckEditorVisual - if (auto tab = qobject_cast(tabWidget->parent()->parent())) { - auto originalBox = tab->getDeckDockWidget()->getGroupByComboBox(); - groupByComboBox->setModel(originalBox->model()); - groupByComboBox->setModelColumn(originalBox->modelColumn()); - // Original -> clone - connect(originalBox, QOverload::of(&QComboBox::currentIndexChanged), - [this](int index) { groupByComboBox->setCurrentIndex(index); }); + if (auto tab = QtUtils::findParentOfType(this)) { + auto originalBox = tab->getDeckDockWidget()->getGroupByComboBox(); + groupByComboBox->setModel(originalBox->model()); + groupByComboBox->setModelColumn(originalBox->modelColumn()); - // Clone -> original - connect(groupByComboBox, QOverload::of(&QComboBox::currentIndexChanged), - [originalBox](int index) { originalBox->setCurrentIndex(index); }); - } - } + // Original -> clone + connect(originalBox, QOverload::of(&QComboBox::currentIndexChanged), + [this](int index) { groupByComboBox->setCurrentIndex(index); }); + + // Clone -> original + connect(groupByComboBox, QOverload::of(&QComboBox::currentIndexChanged), + [originalBox](int index) { originalBox->setCurrentIndex(index); }); } else { groupByComboBox->addItem( tr(qPrintable(DeckListModelGroupCriteria::toString(DeckListModelGroupCriteria::MAIN_TYPE))), diff --git a/libcockatrice_utility/libcockatrice/utility/qt_utils.h b/libcockatrice_utility/libcockatrice/utility/qt_utils.h new file mode 100644 index 000000000..855cc8b18 --- /dev/null +++ b/libcockatrice_utility/libcockatrice/utility/qt_utils.h @@ -0,0 +1,20 @@ +#ifndef COCKATRICE_QT_UTILS_H +#define COCKATRICE_QT_UTILS_H +#include + +namespace QtUtils +{ +template T *findParentOfType(const QObject *obj) +{ + const QObject *p = obj ? obj->parent() : nullptr; + while (p) { + if (auto casted = qobject_cast(const_cast(p))) { + return casted; + } + p = p->parent(); + } + return nullptr; +} +} // namespace QtUtils + +#endif // COCKATRICE_QT_UTILS_H From 5c1bb27d5cb900bbe60985c2ceb596fff3317a28 Mon Sep 17 00:00:00 2001 From: tooomm Date: Fri, 5 Dec 2025 23:28:25 +0100 Subject: [PATCH 052/325] README: Add code docs + flathub repo links (#6384) * Add code docs + flathub repo links * Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e415fa23..b1233f749 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,8 @@ Latest beta version: - [Magic-Token](https://github.com/Cockatrice/Magic-Token): MtG token data to use in Cockatrice - [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Script to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) to use in Cockatrice -- [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official webpage of the Cockatrice project +- [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official Cockatrice webpage +- [Cockatrice @Flathub](https://github.com/flathub/io.github.Cockatrice.cockatrice): Configuration for our Linux `flatpak` package # Community Resources [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA) @@ -54,6 +55,7 @@ Latest beta version: Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other projet contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out. - [Official Website](https://cockatrice.github.io) - [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki) +- [Official Code Documentation](https://cockatrice.github.io/docs) - [Official Discord](https://discord.gg/3Z9yzmA) - [reddit r/Cockatrice](https://reddit.com/r/cockatrice) From d3302d521fbc5f2b164e5ab97c7e0afda2812c84 Mon Sep 17 00:00:00 2001 From: Lily Huang <112970249+lilyhuang-github@users.noreply.github.com> Date: Sat, 6 Dec 2025 08:09:55 -0500 Subject: [PATCH 053/325] Fix flipped svg for donator/judge/vip (#6400) --- cockatrice/resources/usericons/pawn_donator_double.svg | 4 ++-- cockatrice/resources/usericons/pawn_judge_double.svg | 4 ++-- cockatrice/resources/usericons/pawn_vip_double.svg | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cockatrice/resources/usericons/pawn_donator_double.svg b/cockatrice/resources/usericons/pawn_donator_double.svg index f0dba08b2..30b353652 100644 --- a/cockatrice/resources/usericons/pawn_donator_double.svg +++ b/cockatrice/resources/usericons/pawn_donator_double.svg @@ -350,11 +350,11 @@ style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z " transform="translate(0,952.36218)" - id="right" /> + id="left" /> + id="left" /> + id="left" /> Date: Sun, 7 Dec 2025 06:03:52 -0800 Subject: [PATCH 054/325] [DeckLoader] Extract LoadedDeck struct (#6406) * [DeckLoader] Extract LoadedDeck struct * update usages * Move enum to separate namespace * format * format * format --- cockatrice/CMakeLists.txt | 2 + .../src/game/deckview/deck_view_container.cpp | 2 +- .../deck_loader/deck_file_format.cpp | 9 ++++ .../interface/deck_loader/deck_file_format.h | 36 +++++++++++++++ .../src/interface/deck_loader/deck_loader.cpp | 42 +++++++----------- .../src/interface/deck_loader/deck_loader.h | 44 +++++-------------- .../src/interface/deck_loader/loaded_deck.cpp | 11 +++++ .../src/interface/deck_loader/loaded_deck.h | 39 ++++++++++++++++ .../interface/widgets/general/home_widget.cpp | 4 +- .../widgets/tabs/abstract_tab_deck_editor.cpp | 6 +-- .../widgets/tabs/tab_deck_storage.cpp | 6 +-- .../tab_deck_storage_visual.cpp | 2 +- .../deck_preview_deck_tags_display_widget.cpp | 4 +- .../deck_preview/deck_preview_widget.cpp | 10 ++--- 14 files changed, 143 insertions(+), 74 deletions(-) create mode 100644 cockatrice/src/interface/deck_loader/deck_file_format.cpp create mode 100644 cockatrice/src/interface/deck_loader/deck_file_format.h create mode 100644 cockatrice/src/interface/deck_loader/loaded_deck.cpp create mode 100644 cockatrice/src/interface/deck_loader/loaded_deck.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 1dbeddc5e..f531bc2c8 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -19,7 +19,9 @@ set(cockatrice_SOURCES src/client/settings/card_counter_settings.cpp src/client/settings/shortcut_treeview.cpp src/client/settings/shortcuts_settings.cpp + src/interface/deck_loader/deck_file_format.cpp src/interface/deck_loader/deck_loader.cpp + src/interface/deck_loader/loaded_deck.cpp src/interface/widgets/dialogs/dlg_connect.cpp src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.cpp src/interface/widgets/dialogs/dlg_create_game.cpp diff --git a/cockatrice/src/game/deckview/deck_view_container.cpp b/cockatrice/src/game/deckview/deck_view_container.cpp index cf6ab00b8..5d4a07bdf 100644 --- a/cockatrice/src/game/deckview/deck_view_container.cpp +++ b/cockatrice/src/game/deckview/deck_view_container.cpp @@ -259,7 +259,7 @@ void DeckViewContainer::loadLocalDeck() void DeckViewContainer::loadDeckFromFile(const QString &filePath) { - DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(filePath); + DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(filePath); DeckLoader deck(this); bool success = deck.loadFromFile(filePath, fmt, true); diff --git a/cockatrice/src/interface/deck_loader/deck_file_format.cpp b/cockatrice/src/interface/deck_loader/deck_file_format.cpp new file mode 100644 index 000000000..3c88cde4e --- /dev/null +++ b/cockatrice/src/interface/deck_loader/deck_file_format.cpp @@ -0,0 +1,9 @@ +#include "deck_file_format.h" + +DeckFileFormat::Format DeckFileFormat::getFormatFromName(const QString &fileName) +{ + if (fileName.endsWith(".cod", Qt::CaseInsensitive)) { + return Cockatrice; + } + return PlainText; +} \ No newline at end of file diff --git a/cockatrice/src/interface/deck_loader/deck_file_format.h b/cockatrice/src/interface/deck_loader/deck_file_format.h new file mode 100644 index 000000000..995de32c0 --- /dev/null +++ b/cockatrice/src/interface/deck_loader/deck_file_format.h @@ -0,0 +1,36 @@ +#ifndef COCKATRICE_DECK_FILE_FORMAT_H +#define COCKATRICE_DECK_FILE_FORMAT_H +#include + +namespace DeckFileFormat +{ + +/** + * The deck file formats that Cockatrice supports. + */ +enum Format +{ + /** + * Plaintext deck files, a format that is intended to be widely supported among different programs. + * This format does not support Cockatrice specific features such as banner cards or tags. + */ + PlainText, + + /** + * This is cockatrice's native deck file format, and supports deck metadata such as banner cards and tags. + * Stored as .cod files. + */ + Cockatrice +}; + +/** + * Determines what deck file format the given filename corresponds to. + * + * @param fileName The filename + * @return The deck format + */ +Format getFormatFromName(const QString &fileName); + +} // namespace DeckFileFormat + +#endif // COCKATRICE_DECK_FILE_FORMAT_H diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index eea906a97..e3d1ce965 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -33,7 +33,7 @@ DeckLoader::DeckLoader(QObject *parent, DeckList *_deckList) : QObject(parent), { } -bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest) +bool DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -42,17 +42,17 @@ bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool user bool result = false; switch (fmt) { - case PlainTextFormat: + case DeckFileFormat::PlainText: result = deckList->loadFromFile_Plain(&file); break; - case CockatriceFormat: { + case DeckFileFormat::Cockatrice: { result = deckList->loadFromFile_Native(&file); qCInfo(DeckLoaderLog) << "Loaded from" << fileName << "-" << result; if (!result) { qCInfo(DeckLoaderLog) << "Retrying as plain format"; file.seek(0); result = deckList->loadFromFile_Plain(&file); - fmt = PlainTextFormat; + fmt = DeckFileFormat::PlainText; } break; } @@ -77,7 +77,7 @@ bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool user return result; } -bool DeckLoader::loadFromFileAsync(const QString &fileName, FileFormat fmt, bool userRequest) +bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest) { auto *watcher = new QFutureWatcher(this); @@ -106,9 +106,9 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, FileFormat fmt, bool } switch (fmt) { - case PlainTextFormat: + case DeckFileFormat::PlainText: return deckList->loadFromFile_Plain(&file); - case CockatriceFormat: { + case DeckFileFormat::Cockatrice: { bool result = false; result = deckList->loadFromFile_Native(&file); if (!result) { @@ -140,7 +140,7 @@ bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId) return result; } -bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt) +bool DeckLoader::saveToFile(const QString &fileName, DeckFileFormat::Format fmt) { QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { @@ -149,10 +149,10 @@ bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt) bool result = false; switch (fmt) { - case PlainTextFormat: + case DeckFileFormat::PlainText: result = deckList->saveToFile_Plain(&file); break; - case CockatriceFormat: + case DeckFileFormat::Cockatrice: result = deckList->saveToFile_Native(&file); qCInfo(DeckLoaderLog) << "Saving to " << fileName << "-" << result; break; @@ -172,7 +172,7 @@ bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt) return result; } -bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt) +bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, DeckFileFormat::Format fmt) { QFileInfo fileInfo(fileName); if (!fileInfo.exists()) { @@ -193,10 +193,10 @@ bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, FileFormat f // Perform file modifications switch (fmt) { - case PlainTextFormat: + case DeckFileFormat::PlainText: result = deckList->saveToFile_Plain(&file); break; - case CockatriceFormat: + case DeckFileFormat::Cockatrice: deckList->setLastLoadedTimestamp(QDateTime::currentDateTime().toString()); result = deckList->saveToFile_Native(&file); break; @@ -414,14 +414,6 @@ void DeckLoader::clearSetNamesAndNumbers(const DeckList *deckList) deckList->forEachCard(clearSetNameAndNumber); } -DeckLoader::FileFormat DeckLoader::getFormatFromName(const QString &fileName) -{ - if (fileName.endsWith(".cod", Qt::CaseInsensitive)) { - return CockatriceFormat; - } - return PlainTextFormat; -} - void DeckLoader::saveToClipboard(const DeckList *deckList, bool addComments, bool addSetNameAndNumber) { QString buffer; @@ -564,12 +556,12 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName) bool result = false; // Perform file modifications based on the detected format - switch (getFormatFromName(fileName)) { - case PlainTextFormat: + switch (DeckFileFormat::getFormatFromName(fileName)) { + case DeckFileFormat::PlainText: // Save in Cockatrice's native format result = deckList->saveToFile_Native(&file); break; - case CockatriceFormat: + case DeckFileFormat::Cockatrice: qCInfo(DeckLoaderLog) << "File is already in Cockatrice format. No conversion needed."; result = true; break; @@ -590,7 +582,7 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName) } lastLoadInfo = { .fileName = newFileName, - .fileFormat = CockatriceFormat, + .fileFormat = DeckFileFormat::Cockatrice, }; } diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h index 00136e6bb..d356f255d 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.h +++ b/cockatrice/src/interface/deck_loader/deck_loader.h @@ -7,14 +7,16 @@ #ifndef DECK_LOADER_H #define DECK_LOADER_H +#include "loaded_deck.h" + #include #include #include #include -inline Q_LOGGING_CATEGORY(DeckLoaderLog, "deck_loader") +inline Q_LOGGING_CATEGORY(DeckLoaderLog, "deck_loader"); - class DeckLoader : public QObject +class DeckLoader : public QObject { Q_OBJECT signals: @@ -22,27 +24,6 @@ signals: void loadFinished(bool success); public: - enum FileFormat - { - PlainTextFormat, - CockatriceFormat - }; - - /** - * @brief Information about where the deck was loaded from. - * - * For local decks, the remoteDeckId field will always be -1. - * For remote decks, fileName will be empty and fileFormat will always be CockatriceFormat - */ - struct LoadInfo - { - static constexpr int NON_REMOTE_ID = -1; - - QString fileName = ""; - FileFormat fileFormat = CockatriceFormat; - int remoteDeckId = NON_REMOTE_ID; - }; - /** * Supported file extensions for decklist files */ @@ -61,7 +42,7 @@ public: private: DeckList *deckList; - LoadInfo lastLoadInfo; + LoadedDeck::LoadInfo lastLoadInfo; public: DeckLoader(QObject *parent); @@ -69,29 +50,28 @@ public: DeckLoader(const DeckLoader &) = delete; DeckLoader &operator=(const DeckLoader &) = delete; - const LoadInfo &getLastLoadInfo() const + const LoadedDeck::LoadInfo &getLastLoadInfo() const { return lastLoadInfo; } - void setLastLoadInfo(const LoadInfo &info) + void setLastLoadInfo(const LoadedDeck::LoadInfo &info) { lastLoadInfo = info; } [[nodiscard]] bool hasNotBeenLoaded() const { - return lastLoadInfo.fileName.isEmpty() && lastLoadInfo.remoteDeckId == LoadInfo::NON_REMOTE_ID; + return lastLoadInfo.isEmpty(); } static void clearSetNamesAndNumbers(const DeckList *deckList); - static FileFormat getFormatFromName(const QString &fileName); - bool loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest = false); - bool loadFromFileAsync(const QString &fileName, FileFormat fmt, bool userRequest); + bool loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest = false); + bool loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest); bool loadFromRemote(const QString &nativeString, int remoteDeckId); - bool saveToFile(const QString &fileName, FileFormat fmt); - bool updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt); + bool saveToFile(const QString &fileName, DeckFileFormat::Format fmt); + bool updateLastLoadedTimestamp(const QString &fileName, DeckFileFormat::Format fmt); static QString exportDeckToDecklist(const DeckList *deckList, DecklistWebsite website); diff --git a/cockatrice/src/interface/deck_loader/loaded_deck.cpp b/cockatrice/src/interface/deck_loader/loaded_deck.cpp new file mode 100644 index 000000000..633545718 --- /dev/null +++ b/cockatrice/src/interface/deck_loader/loaded_deck.cpp @@ -0,0 +1,11 @@ +#include "loaded_deck.h" + +bool LoadedDeck::LoadInfo::isEmpty() const +{ + return fileName.isEmpty() && remoteDeckId == NON_REMOTE_ID; +} + +bool LoadedDeck::isEmpty() const +{ + return deckList.isEmpty() && lastLoadInfo.isEmpty(); +} \ No newline at end of file diff --git a/cockatrice/src/interface/deck_loader/loaded_deck.h b/cockatrice/src/interface/deck_loader/loaded_deck.h new file mode 100644 index 000000000..90f3853cf --- /dev/null +++ b/cockatrice/src/interface/deck_loader/loaded_deck.h @@ -0,0 +1,39 @@ +#ifndef COCKATRICE_LOADED_DECK_H +#define COCKATRICE_LOADED_DECK_H + +#include "deck_file_format.h" +#include "libcockatrice/deck_list/deck_list.h" + +#include + +/** + * @brief Represents a deck that was loaded from somewhere. + * Contains the DeckList itself, as well as info about where it was loaded from. + */ +struct LoadedDeck +{ + + /** + * @brief Information about where the deck was loaded from. + * + * For local decks, the remoteDeckId field will always be -1. + * For remote decks, fileName will be empty and fileFormat will always be CockatriceFormat + */ + struct LoadInfo + { + static constexpr int NON_REMOTE_ID = -1; + + QString fileName = ""; + DeckFileFormat::Format fileFormat = DeckFileFormat::Cockatrice; + int remoteDeckId = NON_REMOTE_ID; + + bool isEmpty() const; + }; + + DeckList deckList; ///< The decklist itself + LoadInfo lastLoadInfo; ///< info about where the deck was loaded from + + bool isEmpty() const; +}; + +#endif // COCKATRICE_LOADED_DECK_H diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index 88eec230c..7aa6b40c7 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -23,7 +23,7 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor) backgroundSourceDeck = new DeckLoader(this); backgroundSourceDeck->loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod", - DeckLoader::CockatriceFormat, false); + DeckFileFormat::Cockatrice, false); gradientColors = extractDominantColors(background); @@ -73,7 +73,7 @@ void HomeWidget::initializeBackgroundFromSource() break; case BackgroundSources::DeckFileArt: backgroundSourceDeck->loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod", - DeckLoader::CockatriceFormat, false); + DeckFileFormat::Cockatrice, false); cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000); break; } 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 a03b79e3f..55fa0b95c 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -380,7 +380,7 @@ void AbstractTabDeckEditor::actOpenRecent(const QString &fileName) */ void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation) { - DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName); + DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName); auto *l = new DeckLoader(this); if (l->loadFromFile(fileName, fmt, true)) { @@ -406,7 +406,7 @@ void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLo bool AbstractTabDeckEditor::actSaveDeck() { DeckLoader *const deck = getDeckLoader(); - if (deck->getLastLoadInfo().remoteDeckId != DeckLoader::LoadInfo::NON_REMOTE_ID) { + if (deck->getLastLoadInfo().remoteDeckId != LoadedDeck::LoadInfo::NON_REMOTE_ID) { QString deckString = deck->getDeckList()->writeToString_Native(); if (deckString.length() > MAX_FILE_LENGTH) { QMessageBox::critical(this, tr("Error"), tr("Could not save remote deck")); @@ -452,7 +452,7 @@ bool AbstractTabDeckEditor::actSaveDeckAs() return false; QString fileName = dialog.selectedFiles().at(0); - DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName); + DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName); if (!getDeckLoader()->saveToFile(fileName, fmt)) { QMessageBox::critical( diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp index 854990e19..a54c16258 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp @@ -242,7 +242,7 @@ void TabDeckStorage::actOpenLocalDeck() QString filePath = localDirModel->filePath(curLeft); auto deckLoader = new DeckLoader(this); - if (!deckLoader->loadFromFile(filePath, DeckLoader::CockatriceFormat, true)) + if (!deckLoader->loadFromFile(filePath, DeckFileFormat::Cockatrice, true)) continue; emit openDeckEditor(deckLoader); @@ -308,7 +308,7 @@ void TabDeckStorage::uploadDeck(const QString &filePath, const QString &targetPa QFileInfo deckFileInfo(deckFile); DeckLoader deck(this); - if (!deck.loadFromFile(filePath, DeckLoader::CockatriceFormat)) { + if (!deck.loadFromFile(filePath, DeckFileFormat::Cockatrice)) { QMessageBox::critical(this, tr("Error"), tr("Invalid deck file")); return; } @@ -493,7 +493,7 @@ void TabDeckStorage::downloadFinished(const Response &r, QString filePath = extraData.toString(); DeckLoader deck(this, new DeckList(QString::fromStdString(resp.deck()))); - deck.saveToFile(filePath, DeckLoader::CockatriceFormat); + deck.saveToFile(filePath, DeckFileFormat::Cockatrice); } void TabDeckStorage::actNewFolder() diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp index 975c011dc..69c80bb35 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp @@ -25,7 +25,7 @@ TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor) void TabDeckStorageVisual::actOpenLocalDeck(const QString &filePath) { auto deckLoader = new DeckLoader(this); - if (!deckLoader->loadFromFile(filePath, DeckLoader::getFormatFromName(filePath), true)) { + if (!deckLoader->loadFromFile(filePath, DeckFileFormat::getFormatFromName(filePath), true)) { QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(filePath)); return; } diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp index fce0916bb..d3858a135 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp @@ -79,7 +79,7 @@ static QStringList findAllKnownTags() QStringList knownTags; auto loader = DeckLoader(nullptr); for (const QString &file : allFiles) { - loader.loadFromFile(file, DeckLoader::getFormatFromName(file), false); + loader.loadFromFile(file, DeckFileFormat::getFormatFromName(file), false); QStringList tags = loader.getDeckList()->getTags(); knownTags.append(tags); knownTags.removeDuplicates(); @@ -136,7 +136,7 @@ static void convertFileToCockatriceFormat(DeckPreviewWidget *deckPreviewWidget) */ bool DeckPreviewDeckTagsDisplayWidget::promptFileConversionIfRequired(DeckPreviewWidget *deckPreviewWidget) { - if (DeckLoader::getFormatFromName(deckPreviewWidget->filePath) == DeckLoader::CockatriceFormat) { + if (DeckFileFormat::getFormatFromName(deckPreviewWidget->filePath) == DeckFileFormat::Cockatrice) { return true; } 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 bf915bbea..1db0feb63 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 @@ -32,7 +32,7 @@ DeckPreviewWidget::DeckPreviewWidget(QWidget *_parent, many deck loads have finished already and if we've loaded all decks and THEN load all the tags at once. */ connect(deckLoader, &DeckLoader::loadFinished, visualDeckStorageWidget->tagFilterWidget, &VisualDeckStorageTagFilterWidget::refreshTags); - deckLoader->loadFromFileAsync(filePath, DeckLoader::getFormatFromName(filePath), false); + deckLoader->loadFromFileAsync(filePath, DeckFileFormat::getFormatFromName(filePath), false); bannerCardDisplayWidget = new DeckPreviewCardPictureWidget(this, false, visualDeckStorageWidget->deckPreviewSelectionAnimationEnabled); @@ -288,7 +288,7 @@ void DeckPreviewWidget::setBannerCard(int /* changedIndex */) auto [name, id] = bannerCardComboBox->currentData().value>(); CardRef cardRef = {name, id}; deckLoader->getDeckList()->setBannerCard(cardRef); - deckLoader->saveToFile(filePath, DeckLoader::getFormatFromName(filePath)); + deckLoader->saveToFile(filePath, DeckFileFormat::getFormatFromName(filePath)); bannerCardDisplayWidget->setCard(CardDatabaseManager::query()->getCard(cardRef)); } @@ -311,7 +311,7 @@ void DeckPreviewWidget::imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewC void DeckPreviewWidget::setTags(const QStringList &tags) { deckLoader->getDeckList()->setTags(tags); - deckLoader->saveToFile(filePath, DeckLoader::CockatriceFormat); + deckLoader->saveToFile(filePath, DeckFileFormat::Cockatrice); } QMenu *DeckPreviewWidget::createRightClickMenu() @@ -386,7 +386,7 @@ void DeckPreviewWidget::actRenameDeck() // write change deckLoader->getDeckList()->setName(newName); - deckLoader->saveToFile(filePath, DeckLoader::getFormatFromName(filePath)); + deckLoader->saveToFile(filePath, DeckFileFormat::getFormatFromName(filePath)); // update VDS refreshBannerCardText(); @@ -416,7 +416,7 @@ void DeckPreviewWidget::actRenameFile() return; } - DeckLoader::LoadInfo lastLoadInfo = deckLoader->getLastLoadInfo(); + LoadedDeck::LoadInfo lastLoadInfo = deckLoader->getLastLoadInfo(); lastLoadInfo.fileName = newFilePath; deckLoader->setLastLoadInfo(lastLoadInfo); From 2b690f8c87038031dcc473fda2165bbfde9275c8 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Mon, 8 Dec 2025 00:47:24 -0800 Subject: [PATCH 055/325] [DeckLoader] Extract cardNode functions to own file (#6408) * [DeckLoader] Extract cardNode functions to own file * update usages --- cockatrice/CMakeLists.txt | 1 + .../parsers/interface_json_deck_parser.h | 6 +- .../deck_loader/card_node_function.cpp | 40 ++++++++ .../deck_loader/card_node_function.h | 39 ++++++++ .../src/interface/deck_loader/deck_loader.cpp | 98 ------------------- .../src/interface/deck_loader/deck_loader.h | 5 - .../dialogs/dlg_load_deck_from_clipboard.cpp | 5 +- .../dialogs/dlg_load_deck_from_website.cpp | 2 +- .../dialogs/dlg_select_set_for_cards.cpp | 7 +- ...idekt_api_response_deck_display_widget.cpp | 3 +- 10 files changed, 94 insertions(+), 112 deletions(-) create mode 100644 cockatrice/src/interface/deck_loader/card_node_function.cpp create mode 100644 cockatrice/src/interface/deck_loader/card_node_function.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index f531bc2c8..dec1b3d62 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -19,6 +19,7 @@ set(cockatrice_SOURCES src/client/settings/card_counter_settings.cpp src/client/settings/shortcut_treeview.cpp src/client/settings/shortcuts_settings.cpp + src/interface/deck_loader/card_node_function.cpp src/interface/deck_loader/deck_file_format.cpp src/interface/deck_loader/deck_loader.cpp src/interface/deck_loader/loaded_deck.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 8ac05a84b..e0fe4967a 100644 --- a/cockatrice/src/client/network/parsers/interface_json_deck_parser.h +++ b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h @@ -6,6 +6,8 @@ #ifndef INTERFACE_JSON_DECK_PARSER_H #define INTERFACE_JSON_DECK_PARSER_H + +#include "../../../interface/deck_loader/card_node_function.h" #include "../../../interface/deck_loader/deck_loader.h" #include @@ -48,7 +50,7 @@ public: } loader->getDeckList()->loadFromStream_Plain(outStream, false); - DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList()); + loader->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); return loader; } @@ -95,7 +97,7 @@ public: } loader->getDeckList()->loadFromStream_Plain(outStream, false); - DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList()); + loader->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); QJsonObject commandersObj = obj.value("commanders").toObject(); if (!commandersObj.isEmpty()) { diff --git a/cockatrice/src/interface/deck_loader/card_node_function.cpp b/cockatrice/src/interface/deck_loader/card_node_function.cpp new file mode 100644 index 000000000..9441fe428 --- /dev/null +++ b/cockatrice/src/interface/deck_loader/card_node_function.cpp @@ -0,0 +1,40 @@ +#include "card_node_function.h" + +#include +#include +#include + +void CardNodeFunction::SetProviderIdToPreferred::operator()(const InnerDecklistNode *node, DecklistCardNode *card) const +{ + Q_UNUSED(node); + PrintingInfo preferredPrinting = CardDatabaseManager::query()->getPreferredPrinting(card->getName()); + QString providerId = preferredPrinting.getUuid(); + QString setShortName = preferredPrinting.getSet()->getShortName(); + QString collectorNumber = preferredPrinting.getProperty("num"); + + card->setCardProviderId(providerId); + card->setCardCollectorNumber(collectorNumber); + card->setCardSetShortName(setShortName); +} + +void CardNodeFunction::ClearPrintingData::operator()(const InnerDecklistNode *node, DecklistCardNode *card) const +{ + Q_UNUSED(node); + card->setCardSetShortName(nullptr); + card->setCardCollectorNumber(nullptr); + card->setCardProviderId(nullptr); +} + +void CardNodeFunction::ResolveProviderId::operator()(const InnerDecklistNode *node, DecklistCardNode *card) const +{ + Q_UNUSED(node); + // Retrieve the providerId based on setName and collectorNumber + QString providerId = + CardDatabaseManager::getInstance() + ->query() + ->getSpecificPrinting(card->getName(), card->getCardSetShortName(), card->getCardCollectorNumber()) + .getUuid(); + + // Set the providerId on the card + card->setCardProviderId(providerId); +} \ No newline at end of file diff --git a/cockatrice/src/interface/deck_loader/card_node_function.h b/cockatrice/src/interface/deck_loader/card_node_function.h new file mode 100644 index 000000000..398dc0c0e --- /dev/null +++ b/cockatrice/src/interface/deck_loader/card_node_function.h @@ -0,0 +1,39 @@ +#ifndef COCKATRICE_DECK_FUNCTION_H +#define COCKATRICE_DECK_FUNCTION_H + +class DecklistCardNode; +class InnerDecklistNode; + +/** + * Functions to be used with DeckList::forEachCard + */ +namespace CardNodeFunction +{ + +/** + * @brief Sets the providerId of the card to the preferred printing. + */ +struct SetProviderIdToPreferred +{ + void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const; +}; + +/** + * @brief Clears all fields on the card related to the printing + */ +struct ClearPrintingData +{ + void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const; +}; + +/** + * @brief Sets the providerId of the card based on its set name and collector number. + */ +struct ResolveProviderId +{ + void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const; +}; + +} // namespace CardNodeFunction + +#endif // COCKATRICE_DECK_FUNCTION_H diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index e3d1ce965..d866b684b 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -316,104 +316,6 @@ QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsi return deckString; } -// This struct is here to support the forEachCard function call, defined in decklist. -// It requires a function to be called for each card, and it will set the providerId to the preferred printing. -struct SetProviderIdToPreferred -{ - // Main operator for struct, allowing the foreachcard to work. - SetProviderIdToPreferred() - { - } - - void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const - { - Q_UNUSED(node); - PrintingInfo preferredPrinting = CardDatabaseManager::query()->getPreferredPrinting(card->getName()); - QString providerId = preferredPrinting.getUuid(); - QString setShortName = preferredPrinting.getSet()->getShortName(); - QString collectorNumber = preferredPrinting.getProperty("num"); - - card->setCardProviderId(providerId); - card->setCardCollectorNumber(collectorNumber); - card->setCardSetShortName(setShortName); - } -}; - -/** - * 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(const DeckList *deckList) -{ - // Set up the struct to call. - SetProviderIdToPreferred setProviderIdToPreferred; - - // Call the forEachCard method for each card in the deck - deckList->forEachCard(setProviderIdToPreferred); -} - -/** - * 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(const DeckList *deckList) -{ - auto setProviderId = [](const auto node, const auto card) { - Q_UNUSED(node); - // Retrieve the providerId based on setName and collectorNumber - QString providerId = - CardDatabaseManager::getInstance() - ->query() - ->getSpecificPrinting(card->getName(), card->getCardSetShortName(), card->getCardCollectorNumber()) - .getUuid(); - - // Set the providerId on the card - card->setCardProviderId(providerId); - }; - - deckList->forEachCard(setProviderId); -} - -// This struct is here to support the forEachCard function call, defined in decklist. -// It requires a function to be called for each card, and it will set the providerId. -struct ClearSetNameNumberAndProviderId -{ - // Main operator for struct, allowing the foreachcard to work. - ClearSetNameNumberAndProviderId() - { - } - - void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const - { - Q_UNUSED(node); - // Set the providerId on the card - card->setCardSetShortName(nullptr); - card->setCardCollectorNumber(nullptr); - card->setCardProviderId(nullptr); - } -}; - -/** - * Clears the set name and numbers on each card in the decklist. - * - * @param deckList The decklist to modify - */ -void DeckLoader::clearSetNamesAndNumbers(const DeckList *deckList) -{ - auto clearSetNameAndNumber = [](const auto node, auto card) { - Q_UNUSED(node) - // Set the providerId on the card - card->setCardSetShortName(nullptr); - card->setCardCollectorNumber(nullptr); - card->setCardProviderId(nullptr); - }; - - deckList->forEachCard(clearSetNameAndNumber); -} - void DeckLoader::saveToClipboard(const DeckList *deckList, bool addComments, bool addSetNameAndNumber) { QString buffer; diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h index d356f255d..474eee149 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.h +++ b/cockatrice/src/interface/deck_loader/deck_loader.h @@ -65,8 +65,6 @@ public: return lastLoadInfo.isEmpty(); } - static void clearSetNamesAndNumbers(const DeckList *deckList); - bool loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest = false); bool loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest); bool loadFromRemote(const QString &nativeString, int remoteDeckId); @@ -75,9 +73,6 @@ public: static QString exportDeckToDecklist(const DeckList *deckList, DecklistWebsite website); - 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, 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 2a276479f..952364e37 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 @@ -1,6 +1,7 @@ #include "dlg_load_deck_from_clipboard.h" #include "../../../client/settings/cache_settings.h" +#include "../../deck_loader/card_node_function.h" #include "../../deck_loader/deck_loader.h" #include "dlg_settings.h" @@ -82,9 +83,9 @@ bool AbstractDlgDeckTextEdit::loadIntoDeck(DeckLoader *deckLoader) const if (deckLoader->getDeckList()->loadFromStream_Plain(stream, true)) { if (loadSetNameAndNumberCheckBox->isChecked()) { - DeckLoader::resolveSetNameAndNumberToProviderID(deckLoader->getDeckList()); + deckLoader->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); } else { - DeckLoader::clearSetNamesAndNumbers(deckLoader->getDeckList()); + deckLoader->getDeckList()->forEachCard(CardNodeFunction::ClearPrintingData()); } return true; } 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 f29984ab0..20eb00083 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); - DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList()); + loader->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); 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 905ac5db0..5b6eff923 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 @@ -1,5 +1,6 @@ #include "dlg_select_set_for_cards.h" +#include "../../deck_loader/card_node_function.h" #include "../../deck_loader/deck_loader.h" #include "../interface/widgets/cards/card_info_picture_widget.h" #include "../interface/widgets/general/layout_containers/flow_widget.h" @@ -177,7 +178,7 @@ void DlgSelectSetForCards::actOK() void DlgSelectSetForCards::actClear() { emit deckAboutToBeModified(tr("Cleared all printing information.")); - DeckLoader::clearSetNamesAndNumbers(model->getDeckList()); + model->getDeckList()->forEachCard(CardNodeFunction::ClearPrintingData()); emit deckModified(); accept(); } @@ -185,8 +186,8 @@ void DlgSelectSetForCards::actClear() void DlgSelectSetForCards::actSetAllToPreferred() { emit deckAboutToBeModified(tr("Set all printings to preferred.")); - DeckLoader::clearSetNamesAndNumbers(model->getDeckList()); - DeckLoader::setProviderIdToPreferredPrinting(model->getDeckList()); + model->getDeckList()->forEachCard(CardNodeFunction::ClearPrintingData()); + model->getDeckList()->forEachCard(CardNodeFunction::SetProviderIdToPreferred()); emit deckModified(); accept(); } 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 33ee67a1f..63d732c9a 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,5 +1,6 @@ #include "archidekt_api_response_deck_display_widget.h" +#include "../../../../../deck_loader/card_node_function.h" #include "../../../../../deck_loader/deck_loader.h" #include "../../../../cards/card_info_picture_with_text_overlay_widget.h" #include "../../../../cards/card_size_widget.h" @@ -68,7 +69,7 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset); model->getDeckList()->loadFromStream_Plain(deckStream, false); - DeckLoader::resolveSetNameAndNumberToProviderID(model->getDeckList()); + model->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); model->rebuildTree(); From da703445479ce07a8eea5c7bf4fd4d01a4a666ef Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Fri, 12 Dec 2025 03:57:18 +0100 Subject: [PATCH 056/325] [VDD] Implement ExactMatch Name filter (#6409) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [VDD] Implement ExactMatch Name filter Took 7 minutes Took 4 minutes * Update cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com> --------- Co-authored-by: Lukas Brübach Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com> --- .../visual_database_display_name_filter_widget.cpp | 8 ++++---- .../libcockatrice/filters/filter_card.cpp | 2 ++ libcockatrice_filters/libcockatrice/filters/filter_card.h | 1 + .../libcockatrice/filters/filter_tree.cpp | 7 +++++++ libcockatrice_filters/libcockatrice/filters/filter_tree.h | 1 + 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp index 64eed8b16..28c4e5ab9 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp @@ -51,7 +51,7 @@ VisualDatabaseDisplayNameFilterWidget::VisualDatabaseDisplayNameFilterWidget(QWi void VisualDatabaseDisplayNameFilterWidget::retranslateUi() { - searchBox->setPlaceholderText(tr("Filter by name...")); + searchBox->setPlaceholderText(tr("Filter by name... (Exact match)")); loadFromDeckButton->setText(tr("Load from Deck")); loadFromDeckButton->setToolTip(tr("Apply all card names in currently loaded deck as exact match name filters")); loadFromClipboardButton->setText(tr("Load from Clipboard")); @@ -123,14 +123,14 @@ void VisualDatabaseDisplayNameFilterWidget::updateFilterModel() { // Clear existing name filters emit filterModel->layoutAboutToBeChanged(); - filterModel->clearFiltersOfType(CardFilter::Attr::AttrName); + filterModel->clearFiltersOfType(CardFilter::Attr::AttrNameExact); filterModel->blockSignals(true); filterModel->filterTree()->blockSignals(true); for (const auto &name : activeFilters.keys()) { QString nameString = name; - filterModel->addFilter(new CardFilter(nameString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrName)); + filterModel->addFilter(new CardFilter(nameString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrNameExact)); } filterModel->blockSignals(false); @@ -146,7 +146,7 @@ void VisualDatabaseDisplayNameFilterWidget::updateFilterModel() void VisualDatabaseDisplayNameFilterWidget::syncWithFilterModel() { QStringList currentFilters; - for (const auto &filter : filterModel->getFiltersOfType(CardFilter::Attr::AttrName)) { + for (const auto &filter : filterModel->getFiltersOfType(CardFilter::Attr::AttrNameExact)) { if (filter->type() == CardFilter::Type::TypeOr) { currentFilters.append(filter->term()); } diff --git a/libcockatrice_filters/libcockatrice/filters/filter_card.cpp b/libcockatrice_filters/libcockatrice/filters/filter_card.cpp index d49a1b9cb..5fdce7ae0 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_card.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_card.cpp @@ -60,6 +60,8 @@ const QString CardFilter::attrName(Attr a) switch (a) { case AttrName: return tr("Name"); + case AttrNameExact: + return tr("Name (Exact)"); case AttrType: return tr("Type"); case AttrColor: diff --git a/libcockatrice_filters/libcockatrice/filters/filter_card.h b/libcockatrice_filters/libcockatrice/filters/filter_card.h index 4392a4630..732e43a09 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_card.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_card.h @@ -33,6 +33,7 @@ public: AttrLoyalty, AttrManaCost, AttrName, + AttrNameExact, AttrPow, AttrRarity, AttrSet, diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp index 89b772b8e..afb2d8563 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp @@ -153,6 +153,11 @@ bool FilterItem::acceptName(const CardInfoPtr info) const return info->getName().contains(term, Qt::CaseInsensitive); } +bool FilterItem::acceptNameExact(const CardInfoPtr info) const +{ + return info->getName() == term; +} + bool FilterItem::acceptType(const CardInfoPtr info) const { return info->getCardType().contains(term, Qt::CaseInsensitive); @@ -401,6 +406,8 @@ bool FilterItem::acceptCardAttr(const CardInfoPtr info, CardFilter::Attr attr) c switch (attr) { case CardFilter::AttrName: return acceptName(info); + case CardFilter::AttrNameExact: + return acceptNameExact(info); case CardFilter::AttrType: return acceptType(info); case CardFilter::AttrColor: diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.h b/libcockatrice_filters/libcockatrice/filters/filter_tree.h index af91d2dbf..75d4241a5 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.h @@ -203,6 +203,7 @@ public: } [[nodiscard]] bool acceptName(CardInfoPtr info) const; + [[nodiscard]] bool acceptNameExact(CardInfoPtr info) const; [[nodiscard]] bool acceptType(CardInfoPtr info) const; [[nodiscard]] bool acceptMainType(CardInfoPtr info) const; [[nodiscard]] bool acceptSubType(CardInfoPtr info) const; From a390c8ada7cded393de291569406c4b96924b8e4 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Fri, 12 Dec 2025 11:29:18 +0100 Subject: [PATCH 057/325] [NetworkManager] Set Version string as user agent (#6411) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [NetworkManager] Set Version string as user agent Took 13 minutes * Update in oracle. Took 14 minutes --------- Co-authored-by: Lukas Brübach --- .../client/network/interfaces/deck_stats_interface.cpp | 2 ++ .../client/network/interfaces/tapped_out_interface.cpp | 2 ++ .../update/card_spoiler/spoiler_background_updater.cpp | 5 ++++- .../card_picture_loader/card_picture_loader_worker.cpp | 2 ++ .../widgets/dialogs/dlg_load_deck_from_website.cpp | 2 ++ .../archidekt_api_response_deck_entry_display_widget.cpp | 2 ++ .../widgets/tabs/api/archidekt/tab_archidekt.cpp | 4 ++++ .../interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp | 7 ++++++- oracle/src/pages.cpp | 8 ++++++-- 9 files changed, 30 insertions(+), 4 deletions(-) diff --git a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp index 6c295beb9..5e9b874d0 100644 --- a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp +++ b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp @@ -8,6 +8,7 @@ #include #include #include +#include DeckStatsInterface::DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent) : QObject(parent), cardDatabase(_cardDatabase) @@ -62,6 +63,7 @@ void DeckStatsInterface::analyzeDeck(DeckList *deck) QNetworkRequest request(QUrl("https://deckstats.net/index.php")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); manager->post(request, data); } diff --git a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp index 4bfeba7b1..137c7728c 100644 --- a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp +++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp @@ -8,6 +8,7 @@ #include #include #include +#include TappedOutInterface::TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent) : QObject(parent), cardDatabase(_cardDatabase) @@ -87,6 +88,7 @@ void TappedOutInterface::analyzeDeck(DeckList *deck) QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); manager->post(request, data); } diff --git a/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.cpp b/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.cpp index f6f851037..7194c46e5 100644 --- a/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.cpp +++ b/cockatrice/src/client/network/update/card_spoiler/spoiler_background_updater.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #define SPOILERS_STATUS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/SpoilerSeasonEnabled" #define SPOILERS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/spoiler.xml" @@ -39,7 +40,9 @@ void SpoilerBackgroundUpdater::startSpoilerDownloadProcess(QString url, bool sav void SpoilerBackgroundUpdater::downloadFromURL(QUrl url, bool saveResults) { auto *nam = new QNetworkAccessManager(this); - QNetworkReply *reply = nam->get(QNetworkRequest(url)); + auto request = QNetworkRequest(url); + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); + QNetworkReply *reply = nam->get(request); if (saveResults) { // This will write out to the file (used for spoiler.xml) 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 30c979cb5..77fddc9de 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 @@ -10,6 +10,7 @@ #include #include #include +#include static constexpr int MAX_REQUESTS_PER_SEC = 10; @@ -86,6 +87,7 @@ QNetworkReply *CardPictureLoaderWorker::makeRequest(const QUrl &url, CardPicture } QNetworkRequest req(url); + req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); if (!picDownload) { req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache); } 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 20eb00083..2b63af1a4 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 @@ -8,6 +8,7 @@ #include #include #include +#include DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent) { @@ -67,6 +68,7 @@ void DlgLoadDeckFromWebsite::accept() } QNetworkRequest request(QUrl(info.fullUrl)); + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); QNetworkReply *reply = nam->get(request); QEventLoop loop; diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp index 1b633c4d3..7a838d1ff 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #define ARCHIDEKT_DEFAULT_IMAGE "https://storage.googleapis.com/topdekt-user/images/archidekt_deck_card_shadow.jpg" @@ -82,6 +83,7 @@ ArchidektApiResponseDeckEntryDisplayWidget::ArchidektApiResponseDeckEntryDisplay imageUrl = response.getFeatured().isEmpty() ? QUrl(ARCHIDEKT_DEFAULT_IMAGE) : QUrl(response.getFeatured()); QNetworkRequest req(imageUrl); + req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); QNetworkReply *reply = imageNetworkManager->get(req); // tag the reply with "this" so we know it belongs to us later 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 7f04987c1..1376efd47 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp @@ -22,6 +22,7 @@ #include #include #include +#include TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) { @@ -426,18 +427,21 @@ void TabArchidekt::doSearchImmediate() { QString url = buildSearchUrl(); QNetworkRequest req{QUrl(url)}; + req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); networkManager->get(req); } void TabArchidekt::actNavigatePage(QString url) { QNetworkRequest request{QUrl(url)}; + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); networkManager->get(request); } void TabArchidekt::getTopDecks() { QNetworkRequest request{QUrl(buildSearchUrl())}; + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); networkManager->get(request); } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp index 06ce88f06..92163997c 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp @@ -25,6 +25,7 @@ #include #include #include +#include static bool canBeCommander(const CardInfoPtr &cardInfo) { @@ -166,6 +167,7 @@ void TabEdhRecMain::setCard(CardInfoPtr _cardToQuery, bool isCommander) } QNetworkRequest request{QUrl(url)}; + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); networkManager->get(request); } @@ -173,6 +175,7 @@ void TabEdhRecMain::setCard(CardInfoPtr _cardToQuery, bool isCommander) void TabEdhRecMain::actNavigatePage(QString url) { QNetworkRequest request{QUrl("https://json.edhrec.com/pages" + url + ".json")}; + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); networkManager->get(request); } @@ -180,6 +183,7 @@ void TabEdhRecMain::actNavigatePage(QString url) void TabEdhRecMain::getTopCards() { QNetworkRequest request{QUrl("https://json.edhrec.com/pages/top/year.json")}; + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); networkManager->get(request); } @@ -187,6 +191,7 @@ void TabEdhRecMain::getTopCards() void TabEdhRecMain::getTopCommanders() { QNetworkRequest request{QUrl("https://json.edhrec.com/pages/commanders/year.json")}; + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); networkManager->get(request); } @@ -194,7 +199,7 @@ void TabEdhRecMain::getTopCommanders() void TabEdhRecMain::getTopTags() { QNetworkRequest request{QUrl("https://json.edhrec.com/pages/tags.json")}; - + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); networkManager->get(request); } diff --git a/oracle/src/pages.cpp b/oracle/src/pages.cpp index 46edf1cf8..7629a291b 100644 --- a/oracle/src/pages.cpp +++ b/oracle/src/pages.cpp @@ -302,7 +302,9 @@ void LoadSetsPage::downloadSetsFile(const QUrl &url) const auto urlString = url.toString(); if (urlString == ALLSETS_URL || urlString == ALLSETS_URL_FALLBACK) { const auto versionUrl = QUrl::fromUserInput(MTGJSON_VERSION_URL); - auto *versionReply = wizard()->nam->get(QNetworkRequest(versionUrl)); + QNetworkRequest request = QNetworkRequest(versionUrl); + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); + auto *versionReply = wizard()->nam->get(request); connect(versionReply, &QNetworkReply::finished, [this, versionReply]() { if (versionReply->error() == QNetworkReply::NoError) { auto data = versionReply->readAll(); @@ -326,7 +328,9 @@ void LoadSetsPage::downloadSetsFile(const QUrl &url) wizard()->setCardSourceUrl(url.toString()); - auto *reply = wizard()->nam->get(QNetworkRequest(url)); + QNetworkRequest request = QNetworkRequest(url); + request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); + auto *reply = wizard()->nam->get(request); connect(reply, &QNetworkReply::finished, this, &LoadSetsPage::actDownloadFinishedSetsFile); connect(reply, &QNetworkReply::downloadProgress, this, &LoadSetsPage::actDownloadProgressSetsFile); From 2e2682aad47909d2c374bc4cf283a5b956635d51 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:37:44 -0800 Subject: [PATCH 058/325] [DeckList] Refactor and cleanup methods that iterate over nodes (#6407) * remove helpers * create getZoneNodes method * replace direct calls to getRoot and forEachCard * remove more non-const uses of forEachCard * make node getter return const lists * one more usage * address comment * address comment again * fix hash * fix hashes (for real this time) --- cockatrice/src/filters/deck_filter_string.cpp | 5 +- cockatrice/src/game/deckview/deck_view.cpp | 5 +- .../src/game/player/menu/utility_menu.cpp | 13 +- .../src/interface/deck_loader/deck_loader.cpp | 49 ++++---- .../src/interface/deck_loader/deck_loader.h | 2 +- .../deck_editor_deck_dock_widget.cpp | 2 +- .../dialogs/dlg_select_set_for_cards.cpp | 6 +- .../printing_selector/card_amount_widget.cpp | 2 +- ...al_database_display_name_filter_widget.cpp | 2 +- .../visual_deck_editor_sample_hand_widget.cpp | 2 +- .../deck_preview/deck_preview_widget.cpp | 2 +- .../libcockatrice/deck_list/deck_list.cpp | 113 ++++++++---------- .../libcockatrice/deck_list/deck_list.h | 5 +- .../models/deck_list/deck_list_model.cpp | 89 ++++---------- .../models/deck_list/deck_list_model.h | 2 +- .../server/remote/game/server_player.cpp | 28 ++--- 16 files changed, 125 insertions(+), 202 deletions(-) diff --git a/cockatrice/src/filters/deck_filter_string.cpp b/cockatrice/src/filters/deck_filter_string.cpp index 52b6a38d1..dfc54afe0 100644 --- a/cockatrice/src/filters/deck_filter_string.cpp +++ b/cockatrice/src/filters/deck_filter_string.cpp @@ -118,12 +118,13 @@ static void setupParserRules() return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) -> bool { int count = 0; - deck->deckLoader->getDeckList()->forEachCard([&](InnerDecklistNode *, const DecklistCardNode *node) { + auto cardNodes = deck->deckLoader->getDeckList()->getCardNodes(); + for (auto node : cardNodes) { auto cardInfoPtr = CardDatabaseManager::query()->getCardInfo(node->getName()); if (!cardInfoPtr.isNull() && cardFilter.check(cardInfoPtr)) { count += node->getNumber(); } - }); + } return numberMatcher(count); }; }; diff --git a/cockatrice/src/game/deckview/deck_view.cpp b/cockatrice/src/game/deckview/deck_view.cpp index edef3fad4..383545cf3 100644 --- a/cockatrice/src/game/deckview/deck_view.cpp +++ b/cockatrice/src/game/deckview/deck_view.cpp @@ -343,10 +343,7 @@ void DeckViewScene::rebuildTree() if (!deck) return; - InnerDecklistNode *listRoot = deck->getRoot(); - for (int i = 0; i < listRoot->size(); i++) { - auto *currentZone = dynamic_cast(listRoot->at(i)); - + for (auto *currentZone : deck->getZoneNodes()) { DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0); if (!container) { container = new DeckViewCardContainer(currentZone->getName()); diff --git a/cockatrice/src/game/player/menu/utility_menu.cpp b/cockatrice/src/game/player/menu/utility_menu.cpp index a3117abf8..ab14d629a 100644 --- a/cockatrice/src/game/player/menu/utility_menu.cpp +++ b/cockatrice/src/game/player/menu/utility_menu.cpp @@ -5,6 +5,7 @@ #include "../player_actions.h" #include "player_menu.h" +#include #include UtilityMenu::UtilityMenu(Player *_player, QMenu *playerMenu) : QMenu(playerMenu), player(_player) @@ -65,15 +66,13 @@ void UtilityMenu::populatePredefinedTokensMenu() return; } - InnerDecklistNode *tokenZone = - dynamic_cast(_deck->getDeckList()->getRoot()->findChild(DECK_ZONE_TOKENS)); + auto tokenCardNodes = _deck->getDeckList()->getCardNodes({DECK_ZONE_TOKENS}); - if (tokenZone) { - if (!tokenZone->empty()) - setEnabled(true); + if (!tokenCardNodes.isEmpty()) { + setEnabled(true); - for (int i = 0; i < tokenZone->size(); ++i) { - const QString tokenName = tokenZone->at(i)->getName(); + for (int i = 0; i < tokenCardNodes.size(); ++i) { + const QString tokenName = tokenCardNodes[i]->getName(); predefinedTokens.append(tokenName); QAction *a = addAction(tokenName); if (i < 10) { diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index d866b684b..a3f0ff619 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -269,6 +269,20 @@ static QString toDecklistExportString(const DecklistCardNode *card) return cardString; } +/** + * Converts all cards in the list to their decklist export string and joins them into one string + */ +static QString toDecklistExportString(const QList &cardNodes) +{ + QString result; + + for (auto cardNode : cardNodes) { + result += toDecklistExportString(cardNode); + } + + return result; +} + /** * Export deck to decklist function, called to format the deck in a way to be sent to a server * @@ -279,29 +293,11 @@ QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsi { // Add the base url QString deckString = "https://" + getDomainForWebsite(website) + "/?"; - // Create two strings to pass to function - QString mainBoardCards, sideBoardCards; - // Set up the function to call - auto formatDeckListForExport = [&mainBoardCards, &sideBoardCards](const auto *node, const auto *card) { - // Get the card name - CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName()); - if (!dbCard || dbCard->getIsToken()) { - // If it's a token, we don't care about the card. - return; - } + // export all cards in zone + QString mainBoardCards = toDecklistExportString(deckList->getCardNodes({DECK_ZONE_MAIN})); + QString sideBoardCards = toDecklistExportString(deckList->getCardNodes({DECK_ZONE_SIDE})); - // Check if it's a sideboard card. - if (node->getName() == DECK_ZONE_SIDE) { - sideBoardCards += toDecklistExportString(card); - } else { - // If it's a mainboard card, do the same thing, but for the mainboard card string - mainBoardCards += toDecklistExportString(card); - } - }; - - // call our struct function for each card in the deck - deckList->forEachCard(formatDeckListForExport); // Remove the extra return at the end of the last cards mainBoardCards.chop(3); sideBoardCards.chop(3); @@ -335,9 +331,7 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out, } // loop zones - for (int i = 0; i < deckList->getRoot()->size(); i++) { - const auto *zoneNode = dynamic_cast(deckList->getRoot()->at(i)); - + for (auto zoneNode : deckList->getZoneNodes()) { saveToStream_DeckZone(out, zoneNode, addComments, addSetNameAndNumber); // end of zone @@ -514,7 +508,7 @@ QString DeckLoader::getCompleteCardName(const QString &cardName) return cardName; } -void DeckLoader::printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node) +void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node) { const int totalColumns = 2; @@ -597,12 +591,11 @@ void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList) cursor.insertText(deckList->getComments()); cursor.insertBlock(headerBlockFormat, headerCharFormat); - for (int i = 0; i < deckList->getRoot()->size(); i++) { + for (auto zoneNode : deckList->getZoneNodes()) { cursor.insertHtml("
"); - // cursor.insertHtml("


"); cursor.insertBlock(headerBlockFormat, headerCharFormat); - printDeckListNode(&cursor, dynamic_cast(deckList->getRoot()->at(i))); + printDeckListNode(&cursor, zoneNode); } doc.print(printer); diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h index 474eee149..ae3088ff8 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.h +++ b/cockatrice/src/interface/deck_loader/deck_loader.h @@ -94,7 +94,7 @@ public: } private: - static void printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node); + static void printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node); static void saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList); static void saveToStream_DeckZone(QTextStream &out, 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 7e4287ac5..08ebf23ba 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 @@ -337,7 +337,7 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() // Collect unique (name, providerId) pairs QSet> bannerCardSet; - QList cardsInDeck = deckModel->getDeckList()->getCardNodes(); + QList cardsInDeck = deckModel->getDeckList()->getCardNodes(); for (auto currentCard : cardsInDeck) { if (!CardDatabaseManager::query()->getCard(currentCard->toCardRef())) { 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 5b6eff923..2bfe650c3 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 @@ -228,7 +228,7 @@ QMap DlgSelectSetForCards::getSetsForCards() if (!decklist) return setCounts; - QList cardsInDeck = decklist->getCardNodes(); + QList cardsInDeck = decklist->getCardNodes(); for (auto currentCard : cardsInDeck) { CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(currentCard->getName()); @@ -271,7 +271,7 @@ void DlgSelectSetForCards::updateCardLists() if (!decklist) return; - QList cardsInDeck = decklist->getCardNodes(); + QList cardsInDeck = decklist->getCardNodes(); for (auto currentCard : cardsInDeck) { bool found = false; @@ -360,7 +360,7 @@ QMap DlgSelectSetForCards::getCardsForSets() if (!decklist) return setCards; - QList cardsInDeck = decklist->getCardNodes(); + QList cardsInDeck = decklist->getCardNodes(); for (auto currentCard : cardsInDeck) { CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(currentCard->getName()); diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index 80258be61..0d0195d34 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -311,7 +311,7 @@ int CardAmountWidget::countCardsInZone(const QString &deckZone) return -1; } - QList cardsInDeck = decklist->getCardNodes({deckZone}); + QList cardsInDeck = decklist->getCardNodes({deckZone}); int count = 0; for (auto currentCard : cardsInDeck) { diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp index 28c4e5ab9..9bc506056 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp @@ -68,7 +68,7 @@ void VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck() if (!decklist) return; - QList cardsInDeck = decklist->getCardNodes(); + QList cardsInDeck = decklist->getCardNodes(); for (auto currentCard : cardsInDeck) { createNameFilter(currentCard->getName()); diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp index b1db1cf37..341b94a3b 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp @@ -84,7 +84,7 @@ QList VisualDeckEditorSampleHandWidget::getRandomCards(int amountToGe if (!decklist) return randomCards; - QList cardsInDeck = decklist->getCardNodes({DECK_ZONE_MAIN}); + QList cardsInDeck = decklist->getCardNodes({DECK_ZONE_MAIN}); // Collect all cards in the main deck, allowing duplicates based on their count for (auto currentCard : cardsInDeck) { 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 1db0feb63..68243100c 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 @@ -235,7 +235,7 @@ void DeckPreviewWidget::updateBannerCardComboBox() // Prepare the new items with deduplication QSet> bannerCardSet; - QList cardsInDeck = deckLoader->getDeckList()->getCardNodes(); + QList cardsInDeck = deckLoader->getDeckList()->getCardNodes(); for (auto currentCard : cardsInDeck) { for (int k = 0; k < currentCard->getNumber(); ++k) { diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index cea759428..333be017b 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -523,56 +523,34 @@ void DeckList::cleanList(bool preserveMetadata) refreshDeckHash(); } -void DeckList::getCardListHelper(InnerDecklistNode *item, QSet &result) -{ - for (int i = 0; i < item->size(); ++i) { - auto *node = dynamic_cast(item->at(i)); - - if (node) { - result.insert(node->getName()); - } else { - getCardListHelper(dynamic_cast(item->at(i)), result); - } - } -} - -void DeckList::getCardRefListHelper(InnerDecklistNode *item, QList &result) -{ - for (int i = 0; i < item->size(); ++i) { - auto *node = dynamic_cast(item->at(i)); - - if (node) { - result.append(node->toCardRef()); - } else { - getCardRefListHelper(dynamic_cast(item->at(i)), result); - } - } -} - QStringList DeckList::getCardList() const { - QSet result; - getCardListHelper(root, result); - return result.values(); + auto nodes = getCardNodes(); + + QStringList result; + std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(result), [](auto node) { return node->getName(); }); + + return result; } QList DeckList::getCardRefList() const { + auto nodes = getCardNodes(); + QList result; - getCardRefListHelper(root, result); + std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(result), + [](auto node) { return node->toCardRef(); }); + return result; } -QList DeckList::getCardNodes(const QStringList &restrictToZones) const +QList DeckList::getCardNodes(const QStringList &restrictToZones) const { - QList result; + QList result; - for (auto *node : *root) { - auto *zoneNode = dynamic_cast(node); - if (zoneNode == nullptr) { - continue; - } - if (!restrictToZones.isEmpty() && !restrictToZones.contains(node->getName())) { + auto zoneNodes = getZoneNodes(); + for (auto *zoneNode : zoneNodes) { + if (!restrictToZones.isEmpty() && !restrictToZones.contains(zoneNode->getName())) { continue; } for (auto *cardNode : *zoneNode) { @@ -586,20 +564,28 @@ QList DeckList::getCardNodes(const QStringList &restrictToZo return result; } +QList DeckList::getZoneNodes() const +{ + QList zones; + for (auto *node : *root) { + InnerDecklistNode *currentZone = dynamic_cast(node); + if (!currentZone) + continue; + zones.append(currentZone); + } + + return zones; +} + int DeckList::getSideboardSize() const { - int size = 0; - for (int i = 0; i < root->size(); ++i) { - auto *node = dynamic_cast(root->at(i)); - if (node->getName() != DECK_ZONE_SIDE) { - continue; - } + auto cards = getCardNodes({DECK_ZONE_SIDE}); - for (int j = 0; j < node->size(); j++) { - auto *card = dynamic_cast(node->at(j)); - size += card->getNumber(); - } + int size = 0; + for (auto card : cards) { + size += card->getNumber(); } + return size; } @@ -665,26 +651,23 @@ bool DeckList::deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNod return false; } -static QString computeDeckHash(const InnerDecklistNode *root) +static QString computeDeckHash(const DeckList &deckList) { - QStringList cardList; - QSet hashZones, optionalZones; + auto mainDeckNodes = deckList.getCardNodes({DECK_ZONE_MAIN}); + auto sideDeckNodes = deckList.getCardNodes({DECK_ZONE_SIDE}); - hashZones << DECK_ZONE_MAIN << DECK_ZONE_SIDE; // Zones in deck to be included in hashing process - optionalZones << DECK_ZONE_TOKENS; // Optional zones in deck not included in hashing process - - for (int i = 0; i < root->size(); i++) { - auto *node = dynamic_cast(root->at(i)); - for (int j = 0; j < node->size(); j++) { - if (hashZones.contains(node->getName())) // Mainboard or Sideboard - { - auto *card = dynamic_cast(node->at(j)); - for (int k = 0; k < card->getNumber(); ++k) { - cardList.append((node->getName() == DECK_ZONE_SIDE ? "SB:" : "") + card->getName().toLower()); - } + static auto nodesToCardList = [](const QList &nodes, const QString &prefix = {}) { + QStringList result; + for (auto node : nodes) { + for (int i = 0; i < node->getNumber(); ++i) { + result.append(prefix + node->getName().toLower()); } } - } + return result; + }; + + QStringList cardList = nodesToCardList(mainDeckNodes) + nodesToCardList(sideDeckNodes, "SB:"); + cardList.sort(); QByteArray deckHashArray = QCryptographicHash::hash(cardList.join(";").toUtf8(), QCryptographicHash::Sha1); quint64 number = (((quint64)(unsigned char)deckHashArray[0]) << 32) + @@ -706,7 +689,7 @@ QString DeckList::getDeckHash() const return cachedDeckHash; } - cachedDeckHash = computeDeckHash(root); + cachedDeckHash = computeDeckHash(*this); return cachedDeckHash; } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index d4d1204e6..6f90b6b14 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -148,8 +148,6 @@ private: mutable QString cachedDeckHash; // Helpers for traversing the tree - static void getCardListHelper(InnerDecklistNode *node, QSet &result); - static void getCardRefListHelper(InnerDecklistNode *item, QList &result); InnerDecklistNode *getZoneObjFromName(const QString &zoneName); public: @@ -267,7 +265,8 @@ public: } QStringList getCardList() const; QList getCardRefList() const; - QList getCardNodes(const QStringList &restrictToZones = QStringList()) const; + QList getCardNodes(const QStringList &restrictToZones = QStringList()) const; + QList getZoneNodes() const; int getSideboardSize() const; InnerDecklistNode *getRoot() const { 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 bd16ab3d2..2856198b8 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -550,86 +550,49 @@ void DeckListModel::setDeckList(DeckList *_deck) QList DeckListModel::getCards() const { - QList cards; - DeckList *decklist = getDeckList(); - if (!decklist) { - return cards; - } - InnerDecklistNode *listRoot = decklist->getRoot(); - if (!listRoot) - return cards; + auto nodes = deckList->getCardNodes(); - for (int i = 0; i < listRoot->size(); i++) { - InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); - if (!currentZone) - continue; - for (int j = 0; j < currentZone->size(); j++) { - DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); - if (!currentCard) - continue; - for (int k = 0; k < currentCard->getNumber(); ++k) { - ExactCard card = CardDatabaseManager::query()->getCard(currentCard->toCardRef()); - if (card) { - cards.append(card); - } else { - qDebug() << "Card not found in database!"; - } + QList cards; + for (auto node : nodes) { + ExactCard card = CardDatabaseManager::query()->getCard(node->toCardRef()); + if (card) { + for (int k = 0; k < node->getNumber(); ++k) { + cards.append(card); } + } else { + qDebug() << "Card not found in database!"; } } + return cards; } QList DeckListModel::getCardsForZone(const QString &zoneName) const { - QList cards; - DeckList *decklist = getDeckList(); - if (!decklist) { - return cards; - } - InnerDecklistNode *listRoot = decklist->getRoot(); - if (!listRoot) - return cards; + auto nodes = deckList->getCardNodes({zoneName}); - for (int i = 0; i < listRoot->size(); i++) { - InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); - if (!currentZone) - continue; - if (currentZone->getName() == zoneName) { - for (int j = 0; j < currentZone->size(); j++) { - DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); - if (!currentCard) - continue; - for (int k = 0; k < currentCard->getNumber(); ++k) { - ExactCard card = CardDatabaseManager::query()->getCard(currentCard->toCardRef()); - if (card) { - cards.append(card); - } else { - qDebug() << "Card not found in database!"; - } - } + QList cards; + for (auto node : nodes) { + ExactCard card = CardDatabaseManager::query()->getCard(node->toCardRef()); + if (card) { + for (int k = 0; k < node->getNumber(); ++k) { + cards.append(card); } + } else { + qDebug() << "Card not found in database!"; } } + return cards; } -QList *DeckListModel::getZones() const +QList DeckListModel::getZones() const { - QList *zones = new QList(); - DeckList *decklist = getDeckList(); - if (!decklist) { - return zones; - } - InnerDecklistNode *listRoot = decklist->getRoot(); - if (!listRoot) - return zones; + auto zoneNodes = deckList->getZoneNodes(); + + QList zones; + std::transform(zoneNodes.cbegin(), zoneNodes.cend(), std::back_inserter(zones), + [](auto zoneNode) { return zoneNode->getName(); }); - for (int i = 0; i < listRoot->size(); i++) { - InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); - if (!currentZone) - continue; - zones->append(currentZone->getName()); - } return zones; } \ No newline at end of file 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 a480c606b..0b47faed4 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -300,7 +300,7 @@ public: [[nodiscard]] QList getCards() const; [[nodiscard]] QList getCardsForZone(const QString &zoneName) const; - [[nodiscard]] QList *getZones() const; + [[nodiscard]] QList getZones() const; /** * @brief Sets the criteria used to group cards in the model. diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp index 8190155e9..e62f861a9 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp @@ -102,28 +102,16 @@ void Server_Player::setupZones() // ------------------------------------------------------------------ // Assign card ids and create deck from deck list - InnerDecklistNode *listRoot = deck->getRoot(); - for (int i = 0; i < listRoot->size(); ++i) { - auto *currentZone = dynamic_cast(listRoot->at(i)); - Server_CardZone *z; - if (currentZone->getName() == DECK_ZONE_MAIN) { - z = deckZone; - } else if (currentZone->getName() == DECK_ZONE_SIDE) { - z = sbZone; - } else { - continue; + auto insertCardsIntoZone = [this](auto cards, auto *zone) { + for (auto card : cards) { + for (int k = 0; k < card->getNumber(); ++k) { + zone->insertCard(new Server_Card(card->toCardRef(), nextCardId++, 0, 0, zone), -1, 0); + } } + }; - for (int j = 0; j < currentZone->size(); ++j) { - auto *currentCard = dynamic_cast(currentZone->at(j)); - if (!currentCard) { - continue; - } - for (int k = 0; k < currentCard->getNumber(); ++k) { - z->insertCard(new Server_Card(currentCard->toCardRef(), nextCardId++, 0, 0, z), -1, 0); - } - } - } + insertCardsIntoZone(deck->getCardNodes({DECK_ZONE_MAIN}), deckZone); + insertCardsIntoZone(deck->getCardNodes({DECK_ZONE_SIDE}), sbZone); const QList &sideboardPlan = deck->getCurrentSideboardPlan(); for (const auto &m : sideboardPlan) { From ccdda39e78fc24d7cf5e2ea610f1faf611aa5197 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sat, 13 Dec 2025 15:17:55 +0100 Subject: [PATCH 059/325] Deck format legality checker (#6166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Deck legality checker. Took 51 seconds Took 1 minute Took 1 minute Took 5 minutes Took 3 minutes * Adjust format parsing. Took 8 minutes Took 3 seconds * toString() the xmlName Took 4 minutes * more toStrings() Took 5 minutes * Comments Took 3 minutes * Layout Took 2 minutes * Layout part 2: Electric boogaloo Took 59 seconds * Update cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com> * Move layout. Took 4 minutes Took 10 seconds * Emit deckModified Took 6 minutes * Fix qOverloads Took 4 minutes * Fix qOverloads Took 12 seconds * Consider text and name in a special way. Took 11 minutes * Adjust "Any number of" oracle text Took 5 minutes * Store allowedCounts by format Took 15 minutes Took 6 seconds * Only restrict vintage. Took 2 minutes * Adjust for DBConverter. Took 6 minutes --------- Co-authored-by: Lukas Brübach Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com> --- cockatrice/CMakeLists.txt | 1 + .../deck_editor_deck_dock_widget.cpp | 67 +++++- .../deck_editor_deck_dock_widget.h | 3 + .../deck_editor/deck_list_style_proxy.cpp | 3 +- .../tabs/api/archidekt/tab_archidekt.cpp | 4 +- ..._display_format_legality_filter_widget.cpp | 205 ++++++++++++++++++ ...se_display_format_legality_filter_widget.h | 43 ++++ ...tabase_display_main_type_filter_widget.cpp | 2 +- ...ual_database_display_set_filter_widget.cpp | 2 +- ...atabase_display_sub_type_filter_widget.cpp | 2 +- .../visual_database_display_widget.cpp | 2 + .../visual_database_display_widget.h | 2 + .../visual_deck_editor_sample_hand_widget.cpp | 4 +- ...ual_deck_storage_quick_settings_widget.cpp | 6 +- dbconverter/src/main.h | 72 +++++- libcockatrice_card/CMakeLists.txt | 2 + .../libcockatrice/card/card_info.h | 3 + .../card/database/card_database.cpp | 5 + .../card/database/card_database.h | 4 + .../card/database/card_database_loader.cpp | 3 +- .../card/database/card_database_querier.cpp | 23 ++ .../card/database/card_database_querier.h | 2 + .../database/parser/card_database_parser.h | 6 +- .../card/database/parser/cockatrice_xml_3.cpp | 5 +- .../card/database/parser/cockatrice_xml_3.h | 3 +- .../card/database/parser/cockatrice_xml_4.cpp | 179 ++++++++++++++- .../card/database/parser/cockatrice_xml_4.h | 4 +- .../card/format/format_legality_rules.cpp | 53 +++++ .../card/format/format_legality_rules.h | 73 +++++++ .../libcockatrice/deck_list/deck_list.cpp | 10 +- .../libcockatrice/deck_list/deck_list.h | 12 +- .../tree/abstract_deck_list_card_node.h | 6 + .../deck_list/tree/deck_list_card_node.h | 19 +- .../models/deck_list/deck_list_model.cpp | 105 ++++++++- .../models/deck_list/deck_list_model.h | 14 ++ oracle/src/oracleimporter.cpp | 72 +++++- oracle/src/oracleimporter.h | 1 + 37 files changed, 987 insertions(+), 35 deletions(-) create mode 100644 cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp create mode 100644 cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.h create mode 100644 libcockatrice_card/libcockatrice/card/format/format_legality_rules.cpp create mode 100644 libcockatrice_card/libcockatrice/card/format/format_legality_rules.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index dec1b3d62..c9fa80e6b 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -202,6 +202,7 @@ set(cockatrice_SOURCES src/interface/widgets/utility/sequence_edit.cpp src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp + src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp 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 08ebf23ba..268a13d5f 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 @@ -124,6 +124,12 @@ void DeckEditorDeckDockWidget::createDeckDock() quickSettingsWidget->addSettingsWidget(showBannerCardCheckBox); quickSettingsWidget->addSettingsWidget(showTagsWidgetCheckBox); + formatLabel = new QLabel(this); + + formatComboBox = new QComboBox(this); + formatComboBox->addItem(tr("Loading Database...")); + formatComboBox->setEnabled(false); // Disable until loaded + commentsLabel = new QLabel(); commentsLabel->setObjectName("commentsLabel"); commentsEdit = new QTextEdit; @@ -208,13 +214,16 @@ void DeckEditorDeckDockWidget::createDeckDock() upperLayout->addWidget(commentsLabel, 1, 0); upperLayout->addWidget(commentsEdit, 1, 1); - upperLayout->addWidget(bannerCardLabel, 2, 0); - upperLayout->addWidget(bannerCardComboBox, 2, 1); + upperLayout->addWidget(formatLabel, 2, 0); + upperLayout->addWidget(formatComboBox, 2, 1); - upperLayout->addWidget(deckTagsDisplayWidget, 3, 1); + upperLayout->addWidget(bannerCardLabel, 3, 0); + upperLayout->addWidget(bannerCardComboBox, 3, 1); - upperLayout->addWidget(activeGroupCriteriaLabel, 4, 0); - upperLayout->addWidget(activeGroupCriteriaComboBox, 4, 1); + upperLayout->addWidget(deckTagsDisplayWidget, 4, 1); + + upperLayout->addWidget(activeGroupCriteriaLabel, 5, 0); + upperLayout->addWidget(activeGroupCriteriaComboBox, 5, 1); hashLabel1 = new QLabel(); hashLabel1->setObjectName("hashLabel1"); @@ -263,6 +272,46 @@ void DeckEditorDeckDockWidget::createDeckDock() refreshShortcuts(); retranslateUi(); + + connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this, + &DeckEditorDeckDockWidget::initializeFormats); + + if (CardDatabaseManager::getInstance()->getLoadStatus() == LoadStatus::Ok) { + initializeFormats(); + } +} + +void DeckEditorDeckDockWidget::initializeFormats() +{ + QMap allFormats = CardDatabaseManager::query()->getAllFormatsWithCount(); + + formatComboBox->clear(); // Remove "Loading Database..." + formatComboBox->setEnabled(true); + + // Populate with formats + formatComboBox->addItem("", ""); + for (auto it = allFormats.constBegin(); it != allFormats.constEnd(); ++it) { + QString displayText = QString("%1").arg(it.key()); + formatComboBox->addItem(displayText, it.key()); // store the raw key in itemData + } + + if (!deckModel->getDeckList()->getGameFormat().isEmpty()) { + deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat()); + formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat())); + } else { + // Ensure no selection is visible initially + formatComboBox->setCurrentIndex(-1); + } + + connect(formatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { + if (index >= 0) { + QString formatKey = formatComboBox->itemData(index).toString(); + deckModel->setActiveFormat(formatKey); + } else { + deckModel->setActiveFormat(QString()); // clear format if deselected + } + emit deckModified(); + }); } ExactCard DeckEditorDeckDockWidget::getCurrentCard() @@ -466,6 +515,12 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel() void DeckEditorDeckDockWidget::sortDeckModelToDeckView() { deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); + deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat()); + formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat())); + deckView->expandAll(); + deckView->expandAll(); + + emit deckChanged(); } DeckLoader *DeckEditorDeckDockWidget::getDeckLoader() @@ -724,6 +779,8 @@ void DeckEditorDeckDockWidget::retranslateUi() showTagsWidgetCheckBox->setText(tr("Show tags selection menu")); commentsLabel->setText(tr("&Comments:")); activeGroupCriteriaLabel->setText(tr("Group by:")); + formatLabel->setText(tr("Format:")); + hashLabel1->setText(tr("Hash:")); aIncrement->setText(tr("&Increment number")); diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index d1f1da300..08aa38e5e 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -69,6 +69,7 @@ public slots: void actSwapCard(); void actRemoveCard(); void offsetCountAtIndex(const QModelIndex &idx, int offset); + void initializeFormats(); void expandAll(); signals: @@ -100,6 +101,8 @@ private: LineEditUnfocusable *hashLabel; QLabel *activeGroupCriteriaLabel; QComboBox *activeGroupCriteriaComboBox; + QLabel *formatLabel; + QComboBox *formatComboBox; QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard; diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp index 9c2265dfd..14c5faae7 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp @@ -23,8 +23,7 @@ QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const if (role == Qt::BackgroundRole) { if (isCard) { - const bool legal = - true; // TODO: Not implemented yet. QIdentityProxyModel::data(index, DeckRoles::IsLegalRole).toBool(); + const bool legal = QIdentityProxyModel::data(index, DeckRoles::IsLegalRole).toBool(); int base = 255 - (index.row() % 2) * 30; return legal ? QBrush(QColor(base, base, base)) : QBrush(QColor(255, base / 3, base / 3)); } else { 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 1376efd47..e6615fa7b 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp @@ -214,7 +214,7 @@ TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) minDeckSizeLogicCombo->addItems({"Exact", "≥", "≤"}); // Exact = unset, ≥ = GTE, ≤ = LTE minDeckSizeLogicCombo->setCurrentIndex(1); // default GTE - connect(minDeckSizeSpin, QOverload::of(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch); + connect(minDeckSizeSpin, qOverload(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch); connect(minDeckSizeLogicCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch); // Page number @@ -224,7 +224,7 @@ TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) pageSpin->setRange(1, 9999); pageSpin->setValue(1); - connect(pageSpin, QOverload::of(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch); + connect(pageSpin, qOverload(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch); // Page display currentPageDisplay = new QWidget(container); diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp new file mode 100644 index 000000000..1f1b7b94c --- /dev/null +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp @@ -0,0 +1,205 @@ +#include "visual_database_display_format_legality_filter_widget.h" + +#include "../../../filters/filter_tree_model.h" + +#include +#include +#include +#include +#include + +VisualDatabaseDisplayFormatLegalityFilterWidget::VisualDatabaseDisplayFormatLegalityFilterWidget( + QWidget *parent, + FilterTreeModel *_filterModel) + : QWidget(parent), filterModel(_filterModel) +{ + allFormatsWithCount = CardDatabaseManager::query()->getAllFormatsWithCount(); + + setMaximumHeight(75); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); + + layout = new QHBoxLayout(this); + setLayout(layout); + layout->setContentsMargins(0, 1, 0, 1); + layout->setSpacing(1); + layout->setAlignment(Qt::AlignTop); + + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + layout->addWidget(flowWidget); + + // Create the spinbox + spinBox = new QSpinBox(this); + spinBox->setMinimum(1); + spinBox->setMaximum(getMaxMainTypeCount()); // Set the max value dynamically + spinBox->setValue(150); + layout->addWidget(spinBox); + connect(spinBox, qOverload(&QSpinBox::valueChanged), this, + &VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatButtonsVisibility); + + // Create the toggle button for Exact Match/Includes mode + toggleButton = new QPushButton(this); + toggleButton->setCheckable(true); + layout->addWidget(toggleButton); + connect(toggleButton, &QPushButton::toggled, this, + &VisualDatabaseDisplayFormatLegalityFilterWidget::updateFilterMode); + connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() { + QTimer::singleShot(100, this, &VisualDatabaseDisplayFormatLegalityFilterWidget::syncWithFilterModel); + }); + + createFormatButtons(); // Populate buttons initially + updateFilterMode(false); // Initialize toggle button text + + retranslateUi(); +} + +void VisualDatabaseDisplayFormatLegalityFilterWidget::retranslateUi() +{ + spinBox->setToolTip(tr("Do not display formats with less than this amount of cards in the database")); + toggleButton->setToolTip(tr("Filter mode (AND/OR/NOT conjunctions of filters)")); +} + +void VisualDatabaseDisplayFormatLegalityFilterWidget::createFormatButtons() +{ + // Iterate through main types and create buttons + for (auto it = allFormatsWithCount.begin(); it != allFormatsWithCount.end(); ++it) { + auto *button = new QPushButton(it.key(), flowWidget); + button->setCheckable(true); + button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }" + "QPushButton:checked { background-color: green; color: white; }"); + + flowWidget->addWidget(button); + formatButtons[it.key()] = button; + + // Connect toggle signal + connect(button, &QPushButton::toggled, this, + [this, mainType = it.key()](bool checked) { handleFormatToggled(mainType, checked); }); + } + updateFormatButtonsVisibility(); // Ensure visibility is updated initially +} + +void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatButtonsVisibility() +{ + int threshold = spinBox->value(); // Get the current spinbox value + + // Iterate through buttons and hide/disable those below the threshold + for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) { + bool visible = allFormatsWithCount[it.key()] >= threshold; + it.value()->setVisible(visible); + it.value()->setEnabled(visible); + } +} + +int VisualDatabaseDisplayFormatLegalityFilterWidget::getMaxMainTypeCount() const +{ + int maxCount = 1; + for (auto it = allFormatsWithCount.begin(); it != allFormatsWithCount.end(); ++it) { + maxCount = qMax(maxCount, it.value()); + } + return maxCount; +} + +void VisualDatabaseDisplayFormatLegalityFilterWidget::handleFormatToggled(const QString &format, bool active) +{ + activeFormats[format] = active; + + if (formatButtons.contains(format)) { + formatButtons[format]->setChecked(active); + } + + updateFormatFilter(); +} + +void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatFilter() +{ + // Clear existing filters related to main type + filterModel->blockSignals(true); + filterModel->filterTree()->blockSignals(true); + filterModel->clearFiltersOfType(CardFilter::Attr::AttrFormat); + + if (exactMatchMode) { + // Exact Match: Only selected main types are allowed + QSet selectedTypes; + for (const auto &type : activeFormats.keys()) { + if (activeFormats[type]) { + selectedTypes.insert(type); + } + } + + if (!selectedTypes.isEmpty()) { + // Require all selected types (TypeAnd) + for (const auto &type : selectedTypes) { + QString typeString = type; + filterModel->addFilter( + new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrFormat)); + } + + // Exclude any other types (TypeAndNot) + for (const auto &type : formatButtons.keys()) { + if (!selectedTypes.contains(type)) { + QString typeString = type; + filterModel->addFilter( + new CardFilter(typeString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrFormat)); + } + } + } + } else { + // Default Includes Mode (TypeOr) - match any selected main types + for (const auto &type : activeFormats.keys()) { + if (activeFormats[type]) { + QString typeString = type; + filterModel->addFilter( + new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrFormat)); + } + } + } + + filterModel->blockSignals(false); + filterModel->filterTree()->blockSignals(false); + + emit filterModel->filterTree()->changed(); + emit filterModel->layoutChanged(); +} + +void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFilterMode(bool checked) +{ + exactMatchMode = checked; + toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes")); + updateFormatFilter(); +} + +void VisualDatabaseDisplayFormatLegalityFilterWidget::syncWithFilterModel() +{ + // Temporarily block signals for each button to prevent toggling while updating button states + for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) { + it.value()->blockSignals(true); + } + + // Uncheck all buttons + for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) { + it.value()->setChecked(false); + } + + // Get active filters for main types + QSet activeTypes; + for (const auto &filter : filterModel->getFiltersOfType(CardFilter::AttrFormat)) { + if (filter->type() == CardFilter::Type::TypeAnd) { + activeTypes.insert(filter->term()); + } + } + + // Check the buttons for active types + for (const auto &type : activeTypes) { + activeFormats[type] = true; + if (formatButtons.contains(type)) { + formatButtons[type]->setChecked(true); + } + } + + // Re-enable signal emissions for each button + for (auto it = formatButtons.begin(); it != formatButtons.end(); ++it) { + it.value()->blockSignals(false); + } + + // Update the visibility of buttons + updateFormatButtonsVisibility(); +} diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.h new file mode 100644 index 000000000..a2a00b740 --- /dev/null +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.h @@ -0,0 +1,43 @@ +#ifndef COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H +#define COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H + +#include "../../../filters/filter_tree_model.h" +#include "../general/layout_containers/flow_widget.h" + +#include +#include +#include +#include +#include +#include + +class VisualDatabaseDisplayFormatLegalityFilterWidget : public QWidget +{ + Q_OBJECT +public: + explicit VisualDatabaseDisplayFormatLegalityFilterWidget(QWidget *parent, FilterTreeModel *filterModel); + void retranslateUi(); + void createFormatButtons(); + void updateFormatButtonsVisibility(); + int getMaxMainTypeCount() const; + + void handleFormatToggled(const QString &format, bool active); + void updateFormatFilter(); + void updateFilterMode(bool checked); + void syncWithFilterModel(); + +private: + FilterTreeModel *filterModel; + QMap allFormatsWithCount; + QSpinBox *spinBox; + QHBoxLayout *layout; + FlowWidget *flowWidget; + QPushButton *toggleButton; // Mode switch button + + QMap activeFormats; // Track active filters + QMap formatButtons; // Store toggle buttons + + bool exactMatchMode = false; // Toggle between "Exact Match" and "Includes" +}; + +#endif // COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp index 32652c346..368ac8719 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp @@ -33,7 +33,7 @@ VisualDatabaseDisplayMainTypeFilterWidget::VisualDatabaseDisplayMainTypeFilterWi spinBox->setMaximum(getMaxMainTypeCount()); // Set the max value dynamically spinBox->setValue(150); layout->addWidget(spinBox); - connect(spinBox, QOverload::of(&QSpinBox::valueChanged), this, + connect(spinBox, qOverload(&QSpinBox::valueChanged), this, &VisualDatabaseDisplayMainTypeFilterWidget::updateMainTypeButtonsVisibility); // Create the toggle button for Exact Match/Includes mode diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp index 1ea6e8e67..dee89f06a 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp @@ -27,7 +27,7 @@ VisualDatabaseDisplayRecentSetFilterSettingsWidget::VisualDatabaseDisplayRecentS filterToMostRecentSetsAmount->setMaximum(100); filterToMostRecentSetsAmount->setValue( SettingsCache::instance().getVisualDatabaseDisplayFilterToMostRecentSetsAmount()); - connect(filterToMostRecentSetsAmount, QOverload::of(&QSpinBox::valueChanged), &SettingsCache::instance(), + connect(filterToMostRecentSetsAmount, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), &SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsAmount); layout->addWidget(filterToMostRecentSetsCheckBox); diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp index 7a88b51cf..b34bb65d3 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp @@ -26,7 +26,7 @@ VisualDatabaseDisplaySubTypeFilterWidget::VisualDatabaseDisplaySubTypeFilterWidg spinBox->setMaximum(getMaxSubTypeCount()); spinBox->setValue(150); layout->addWidget(spinBox); - connect(spinBox, QOverload::of(&QSpinBox::valueChanged), this, + connect(spinBox, qOverload(&QSpinBox::valueChanged), this, &VisualDatabaseDisplaySubTypeFilterWidget::updateSubTypeButtonsVisibility); // Create search box 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 cc0fc8033..c5eaa171a 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 @@ -206,6 +206,7 @@ void VisualDatabaseDisplayWidget::initialize() saveLoadWidget = new VisualDatabaseDisplayFilterSaveLoadWidget(this, filterModel); nameFilterWidget = new VisualDatabaseDisplayNameFilterWidget(this, deckEditor, filterModel); mainTypeFilterWidget = new VisualDatabaseDisplayMainTypeFilterWidget(this, filterModel); + formatLegalityWidget = new VisualDatabaseDisplayFormatLegalityFilterWidget(this, filterModel); subTypeFilterWidget = new VisualDatabaseDisplaySubTypeFilterWidget(this, filterModel); setFilterWidget = new VisualDatabaseDisplaySetFilterWidget(this, filterModel); @@ -223,6 +224,7 @@ void VisualDatabaseDisplayWidget::initialize() filterContainerLayout->addWidget(quickFilterSubTypeWidget); filterContainerLayout->addWidget(quickFilterSetWidget); filterContainerLayout->addWidget(mainTypeFilterWidget); + filterContainerLayout->addWidget(formatLegalityWidget); searchLayout->addWidget(colorFilterWidget); searchLayout->addWidget(clearFilterWidget); diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h index 5cab2ca9b..24990f8e5 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h @@ -17,6 +17,7 @@ #include "../utility/custom_line_edit.h" #include "visual_database_display_color_filter_widget.h" #include "visual_database_display_filter_save_load_widget.h" +#include "visual_database_display_format_legality_filter_widget.h" #include "visual_database_display_main_type_filter_widget.h" #include "visual_database_display_name_filter_widget.h" #include "visual_database_display_set_filter_widget.h" @@ -91,6 +92,7 @@ private: SettingsButtonWidget *quickFilterNameWidget; VisualDatabaseDisplayNameFilterWidget *nameFilterWidget; VisualDatabaseDisplayMainTypeFilterWidget *mainTypeFilterWidget; + VisualDatabaseDisplayFormatLegalityFilterWidget *formatLegalityWidget; SettingsButtonWidget *quickFilterSubTypeWidget; VisualDatabaseDisplaySubTypeFilterWidget *subTypeFilterWidget; SettingsButtonWidget *quickFilterSetWidget; diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp index 341b94a3b..778706f9a 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp @@ -24,9 +24,9 @@ VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *pare handSizeSpinBox = new QSpinBox(this); handSizeSpinBox->setValue(SettingsCache::instance().getVisualDeckEditorSampleHandSize()); handSizeSpinBox->setMinimum(1); - connect(handSizeSpinBox, QOverload::of(&QSpinBox::valueChanged), &SettingsCache::instance(), + connect(handSizeSpinBox, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), &SettingsCache::setVisualDeckEditorSampleHandSize); - connect(handSizeSpinBox, QOverload::of(&QSpinBox::valueChanged), this, + connect(handSizeSpinBox, qOverload(&QSpinBox::valueChanged), this, &VisualDeckEditorSampleHandWidget::updateDisplay); resetAndHandSizeLayout->addWidget(handSizeSpinBox); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp index c1e9bfa6a..a396b8cd3 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp @@ -61,10 +61,10 @@ VisualDeckStorageQuickSettingsWidget::VisualDeckStorageQuickSettingsWidget(QWidg unusedColorIdentitiesOpacitySpinBox->setMaximum(100); unusedColorIdentitiesOpacitySpinBox->setValue( SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity()); - connect(unusedColorIdentitiesOpacitySpinBox, QOverload::of(&QSpinBox::valueChanged), this, + connect(unusedColorIdentitiesOpacitySpinBox, qOverload(&QSpinBox::valueChanged), this, &VisualDeckStorageQuickSettingsWidget::unusedColorIdentitiesOpacityChanged); - connect(unusedColorIdentitiesOpacitySpinBox, QOverload::of(&QSpinBox::valueChanged), - &SettingsCache::instance(), &SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity); + connect(unusedColorIdentitiesOpacitySpinBox, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity); unusedColorIdentitiesOpacityLabel->setBuddy(unusedColorIdentitiesOpacitySpinBox); diff --git a/dbconverter/src/main.h b/dbconverter/src/main.h index 7a5809b1b..d6e734316 100644 --- a/dbconverter/src/main.h +++ b/dbconverter/src/main.h @@ -5,6 +5,10 @@ #include #include +static const QList kConstructedCounts = {{4, "legal"}, {0, "banned"}}; + +static const QList kSingletonCounts = {{1, "legal"}, {0, "banned"}}; + class CardDatabaseConverter : public CardDatabase { public: @@ -23,7 +27,73 @@ public: bool saveCardDatabase(const QString &fileName) { CockatriceXml4Parser parser(new NoopCardPreferenceProvider()); - return parser.saveToFile(sets, cards, fileName); + + return parser.saveToFile(createDefaultMagicFormats(), sets, cards, fileName); + } + + FormatRulesNameMap createDefaultMagicFormats() + { + // Predefined common exceptions + CardCondition superTypeIsBasic; + superTypeIsBasic.field = "type"; + superTypeIsBasic.matchType = "contains"; + superTypeIsBasic.value = "Basic Land"; + + ExceptionRule basicLands; + basicLands.conditions.append(superTypeIsBasic); + + CardCondition anyNumberAllowed; + anyNumberAllowed.field = "text"; + anyNumberAllowed.matchType = "contains"; + anyNumberAllowed.value = "A deck can have any number of"; + + ExceptionRule mayContainAnyNumber; + mayContainAnyNumber.conditions.append(anyNumberAllowed); + + // Map to store default rules + FormatRulesNameMap defaultFormatRulesNameMap; + + // ----------------- Helper lambda to create format ----------------- + auto makeFormat = [&](const QString &name, int minDeck = 60, int maxDeck = -1, int maxSideboardSize = 15, + const QList &allowedCounts = kConstructedCounts) -> FormatRulesPtr { + FormatRulesPtr f(new FormatRules); + f->formatName = name; + f->allowedCounts = allowedCounts; + f->minDeckSize = minDeck; + f->maxDeckSize = maxDeck; + f->maxSideboardSize = maxSideboardSize; + f->exceptions.append(basicLands); + f->exceptions.append(mayContainAnyNumber); + defaultFormatRulesNameMap.insert(name.toLower(), f); + return f; + }; + + // ----------------- Standard formats ----------------- + makeFormat("Standard"); + makeFormat("Modern"); + makeFormat("Legacy"); + makeFormat("Pioneer"); + makeFormat("Historic"); + makeFormat("Timeless"); + makeFormat("Future"); + makeFormat("OldSchool"); + makeFormat("Premodern"); + makeFormat("Pauper"); + makeFormat("Penny"); + + // ----------------- Singleton formats ----------------- + makeFormat("Commander", 100, 100, 15, kSingletonCounts); + makeFormat("Duel", 100, 100, 15, kSingletonCounts); + makeFormat("Brawl", 60, 60, 15, kSingletonCounts); + makeFormat("StandardBrawl", 60, 60, 15, kSingletonCounts); + makeFormat("Oathbreaker", 60, 60, 15, kSingletonCounts); + makeFormat("PauperCommander", 100, 100, 15, kSingletonCounts); + makeFormat("Predh", 100, 100, 15, kSingletonCounts); + + // ----------------- Restricted formats ----------------- + makeFormat("Vintage", 60, -1, 15, {{4, "legal"}, {1, "restricted"}, {0, "banned"}}); + + return defaultFormatRulesNameMap; } }; diff --git a/libcockatrice_card/CMakeLists.txt b/libcockatrice_card/CMakeLists.txt index 857376163..f516cde00 100644 --- a/libcockatrice_card/CMakeLists.txt +++ b/libcockatrice_card/CMakeLists.txt @@ -41,6 +41,8 @@ add_library( libcockatrice/card/relation/card_relation.cpp libcockatrice/card/set/card_set.cpp libcockatrice/card/set/card_set_list.cpp + libcockatrice/card/format/format_legality_rules.cpp + libcockatrice/card/format/format_legality_rules.h ) target_include_directories( diff --git a/libcockatrice_card/libcockatrice/card/card_info.h b/libcockatrice_card/libcockatrice/card/card_info.h index 7bd51356c..00e8fec37 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.h +++ b/libcockatrice_card/libcockatrice/card/card_info.h @@ -1,6 +1,7 @@ #ifndef CARD_INFO_H #define CARD_INFO_H +#include "format/format_legality_rules.h" #include "printing/printing_info.h" #include @@ -22,10 +23,12 @@ class ICardDatabaseParser; typedef QSharedPointer CardInfoPtr; typedef QSharedPointer CardSetPtr; +typedef QSharedPointer FormatRulesPtr; typedef QMap> SetToPrintingsMap; typedef QHash CardNameMap; typedef QHash SetNameMap; +typedef QHash FormatRulesNameMap; Q_DECLARE_METATYPE(CardInfoPtr) diff --git a/libcockatrice_card/libcockatrice/card/database/card_database.cpp b/libcockatrice_card/libcockatrice/card/database/card_database.cpp index 2e977901f..de84ad814 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database.cpp @@ -199,3 +199,8 @@ void CardDatabase::notifyEnabledSetsChanged() // inform the carddatabasemodels that they need to re-check their list of cards emit cardDatabaseEnabledSetsChanged(); } + +void CardDatabase::addFormat(FormatRulesPtr format) +{ + formats.insert(format->formatName.toLower(), format); +} \ No newline at end of file diff --git a/libcockatrice_card/libcockatrice/card/database/card_database.h b/libcockatrice_card/libcockatrice/card/database/card_database.h index 6028cce86..7f8fc39db 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database.h +++ b/libcockatrice_card/libcockatrice/card/database/card_database.h @@ -42,6 +42,8 @@ protected: /// Sets indexed by short name SetNameMap sets; + FormatRulesNameMap formats; + /// Loader responsible for file discovery and parsing CardDatabaseLoader *loader; @@ -141,6 +143,8 @@ public slots: */ void addSet(CardSetPtr set); + void addFormat(FormatRulesPtr format); + /** @brief Loads card databases from configured paths. */ void loadCardDatabases(); diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp b/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp index f56f6a0b5..91bb4c741 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp @@ -23,6 +23,7 @@ CardDatabaseLoader::CardDatabaseLoader(QObject *parent, // connect parser outputs to the database adders connect(p, &ICardDatabaseParser::addCard, database, &CardDatabase::addCard, Qt::DirectConnection); connect(p, &ICardDatabaseParser::addSet, database, &CardDatabase::addSet, Qt::DirectConnection); + connect(p, &ICardDatabaseParser::addFormat, database, &CardDatabase::addFormat, Qt::DirectConnection); } // when SettingsCache's path changes, trigger reloads @@ -149,6 +150,6 @@ bool CardDatabaseLoader::saveCustomTokensToFile() } } - availableParsers.first()->saveToFile(tmpSets, tmpCards, fileName); + availableParsers.first()->saveToFile(FormatRulesNameMap(), tmpSets, tmpCards, fileName); return true; } diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp b/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp index 65999e590..b2a675b99 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp @@ -341,4 +341,27 @@ QMap CardDatabaseQuerier::getAllSubCardTypesWithCount() const } return typeCounts; +} + +FormatRulesPtr CardDatabaseQuerier::getFormat(const QString &formatName) const +{ + return db->formats.value(formatName.toLower()); +} + +QMap CardDatabaseQuerier::getAllFormatsWithCount() const +{ + QMap formatCounts; + + for (const auto &card : db->cards.values()) { + QStringList allProps = card->getProperties(); + + for (const QString &prop : allProps) { + if (prop.startsWith("format-")) { + QString formatName = prop.mid(QStringLiteral("format-").size()); + formatCounts[formatName]++; + } + } + } + + return formatCounts; } \ No newline at end of file diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_querier.h b/libcockatrice_card/libcockatrice/card/database/card_database_querier.h index d9a980f06..ff8d7958b 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_querier.h +++ b/libcockatrice_card/libcockatrice/card/database/card_database_querier.h @@ -214,6 +214,8 @@ public: * @return Map of subtype string to count. */ [[nodiscard]] QMap getAllSubCardTypesWithCount() const; + FormatRulesPtr getFormat(const QString &formatName) const; + QMap getAllFormatsWithCount() const; private: const CardDatabase *db; //!< Card database used for all lookups. diff --git a/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.h b/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.h index 35012dbc5..93d46a3cc 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.h +++ b/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.h @@ -38,6 +38,7 @@ public: /** * @brief Saves card and set data to a file. + * @param _formats * @param sets Map of sets to save. * @param cards Map of cards to save. * @param fileName Target file path. @@ -45,7 +46,8 @@ public: * @param sourceVersion Optional version string of the source. * @return true if save succeeded. */ - virtual bool saveToFile(SetNameMap sets, + virtual bool saveToFile(FormatRulesNameMap _formats, + SetNameMap sets, CardNameMap cards, const QString &fileName, const QString &sourceUrl = "unknown", @@ -79,6 +81,8 @@ signals: /** Emitted when a set is loaded from the database. */ void addSet(CardSetPtr set); + + void addFormat(FormatRulesPtr format); }; Q_DECLARE_INTERFACE(ICardDatabaseParser, "ICardDatabaseParser") 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 35e2f3d83..9df396c74 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp @@ -438,12 +438,15 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in return xml; } -bool CockatriceXml3Parser::saveToFile(SetNameMap _sets, +bool CockatriceXml3Parser::saveToFile(FormatRulesNameMap _formats, + SetNameMap _sets, CardNameMap cards, const QString &fileName, const QString &sourceUrl, const QString &sourceVersion) { + Q_UNUSED(_formats); + QFile file(fileName); if (!file.open(QIODevice::WriteOnly)) { return false; diff --git a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.h b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.h index a727561ea..c3a261739 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.h +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.h @@ -46,7 +46,8 @@ public: /** * @brief Save sets and cards back to an XML3 file. */ - bool saveToFile(SetNameMap _sets, + bool saveToFile(FormatRulesNameMap _formats, + SetNameMap _sets, CardNameMap cards, const QString &fileName, const QString &sourceUrl = "unknown", 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 ab4c8002d..0d46aad0f 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #define COCKATRICE_XML4_TAGNAME "cockatrice_carddatabase" @@ -60,7 +61,9 @@ void CockatriceXml4Parser::parseFile(QIODevice &device) } auto xmlName = xml.name().toString(); - if (xmlName == "sets") { + if (xmlName == "formats") { + loadFormats(xml); + } else if (xmlName == "sets") { loadSetsFromXml(xml); } else if (xmlName == "cards") { loadCardsFromXml(xml); @@ -78,6 +81,116 @@ void CockatriceXml4Parser::parseFile(QIODevice &device) } } +static QSharedPointer parseFormat(QXmlStreamReader &xml) +{ + auto rulesPtr = FormatRulesPtr(new FormatRules()); + + if (xml.attributes().hasAttribute("formatName")) { + rulesPtr->formatName = xml.attributes().value("formatName").toString(); + } + + while (!xml.atEnd()) { + auto token = xml.readNext(); + + if (token == QXmlStreamReader::EndElement && xml.name().toString() == "format") { + break; + } + + if (token != QXmlStreamReader::StartElement) { + continue; + } + + QString xmlName = xml.name().toString(); + + if (xmlName == "minDeckSize") { + rulesPtr->minDeckSize = xml.readElementText().toInt(); + } else if (xmlName == "maxDeckSize") { + QString text = xml.readElementText(); + rulesPtr->maxDeckSize = text.toInt(); + } else if (xmlName == "maxSideboardSize") { + rulesPtr->maxSideboardSize = xml.readElementText().toInt(); + } else if (xmlName == "allowedCounts") { + while (!xml.atEnd()) { + token = xml.readNext(); + + if (token == QXmlStreamReader::EndElement && xml.name().toString() == "allowedCounts") { + break; + } + + if (token == QXmlStreamReader::StartElement && xml.name().toString() == "count") { + + AllowedCount c; + + QString maxAttr = xml.attributes().value("max").toString(); + c.max = (maxAttr == "unlimited") ? -1 : maxAttr.toInt(); + + c.label = xml.readElementText().trimmed(); + + rulesPtr->allowedCounts.append(c); + } + } + } else if (xmlName == "exceptions") { + while (!xml.atEnd()) { + token = xml.readNext(); + + if (token == QXmlStreamReader::EndElement && xml.name().toString() == "exceptions") { + break; + } + + if (token == QXmlStreamReader::StartElement && xml.name().toString() == "exception") { + ExceptionRule ex; + + while (!xml.atEnd()) { + token = xml.readNext(); + + if (token == QXmlStreamReader::EndElement && xml.name().toString() == "exception") { + break; + } + + if (token == QXmlStreamReader::StartElement) { + QString ename = xml.name().toString(); + + if (ename == "maxCopies") { + QString text = xml.readElementText(); + ex.maxCopies = (text == "unlimited") ? -1 : text.toInt(); + } else if (ename == "cardCondition") { + CardCondition cond; + cond.field = xml.attributes().value("field").toString(); + cond.matchType = xml.attributes().value("match").toString(); + cond.value = xml.attributes().value("value").toString(); + ex.conditions.append(cond); + xml.skipCurrentElement(); + } else { + xml.skipCurrentElement(); + } + } + } + + rulesPtr->exceptions.append(ex); + } + } + } else { + xml.skipCurrentElement(); + } + } + + return rulesPtr; +} + +void CockatriceXml4Parser::loadFormats(QXmlStreamReader &xml) +{ + while (!xml.atEnd()) { + if (xml.readNext() == QXmlStreamReader::EndElement) { + break; + } + + if (xml.name().toString() == "format") { + auto rulesPtr = parseFormat(xml); + emit addFormat(rulesPtr); + } + } +} + void CockatriceXml4Parser::loadSetsFromXml(QXmlStreamReader &xml) { while (!xml.atEnd()) { @@ -273,6 +386,59 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) } } +static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const QSharedPointer &rulesPtr) +{ + if (rulesPtr.isNull()) { + qCWarning(CockatriceXml4Log) << "&operator<< FormatRules is nullptr"; + return xml; + } + + const FormatRules &rules = *rulesPtr; + + xml.writeStartElement("format"); + if (!rules.formatName.isEmpty()) { + xml.writeAttribute("formatName", rules.formatName); + } + + xml.writeTextElement("minDeckSize", QString::number(rules.minDeckSize)); + xml.writeTextElement("maxDeckSize", rules.maxDeckSize >= 0 ? QString::number(rules.maxDeckSize) : "0"); + xml.writeTextElement("maxSideboardSize", QString::number(rules.maxSideboardSize)); + if (!rules.allowedCounts.isEmpty()) { + xml.writeStartElement("allowedCounts"); + + for (const AllowedCount &c : rules.allowedCounts) { + xml.writeStartElement("count"); + xml.writeAttribute("max", c.max == -1 ? "unlimited" : QString::number(c.max)); + xml.writeCharacters(c.label); + xml.writeEndElement(); // count + } + + xml.writeEndElement(); // allowedCounts + } + + if (!rules.exceptions.isEmpty()) { + xml.writeStartElement("exceptions"); + for (const ExceptionRule &ex : rules.exceptions) { + xml.writeStartElement("exception"); + xml.writeTextElement("maxCopies", ex.maxCopies == -1 ? "unlimited" : QString::number(ex.maxCopies)); + + for (const CardCondition &cond : ex.conditions) { + xml.writeStartElement("cardCondition"); + xml.writeAttribute("field", cond.field); + xml.writeAttribute("match", cond.matchType); + xml.writeAttribute("value", cond.value); + xml.writeEndElement(); // cardCondition + } + + xml.writeEndElement(); // exception + } + xml.writeEndElement(); // exceptions + } + + xml.writeEndElement(); // format + return xml; +} + static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set) { if (set.isNull()) { @@ -399,7 +565,8 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in return xml; } -bool CockatriceXml4Parser::saveToFile(SetNameMap _sets, +bool CockatriceXml4Parser::saveToFile(FormatRulesNameMap _formats, + SetNameMap _sets, CardNameMap cards, const QString &fileName, const QString &sourceUrl, @@ -426,6 +593,14 @@ bool CockatriceXml4Parser::saveToFile(SetNameMap _sets, xml.writeTextElement("sourceVersion", sourceVersion); xml.writeEndElement(); + if (_formats.count() > 0) { + xml.writeStartElement("formats"); + for (FormatRulesPtr format : _formats) { + xml << format; + } + xml.writeEndElement(); + } + if (_sets.count() > 0) { xml.writeStartElement("sets"); for (CardSetPtr set : _sets) { diff --git a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.h b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.h index b83f6929e..5b47e9090 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.h +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.h @@ -49,7 +49,8 @@ public: /** * @brief Save sets and cards back to an XML4 file. */ - bool saveToFile(SetNameMap _sets, + bool saveToFile(FormatRulesNameMap _formats, + SetNameMap _sets, CardNameMap cards, const QString &fileName, const QString &sourceUrl = "unknown", @@ -72,6 +73,7 @@ private: */ void loadCardsFromXml(QXmlStreamReader &xml); + void loadFormats(QXmlStreamReader &xml); /** * @brief Load all elements from the XML stream. * @param xml The open QXmlStreamReader positioned at the element. diff --git a/libcockatrice_card/libcockatrice/card/format/format_legality_rules.cpp b/libcockatrice_card/libcockatrice/card/format/format_legality_rules.cpp new file mode 100644 index 000000000..a6b3bb038 --- /dev/null +++ b/libcockatrice_card/libcockatrice/card/format/format_legality_rules.cpp @@ -0,0 +1,53 @@ +#include "format_legality_rules.h" + +#include + +bool cardMatchesCondition(const CardInfo &card, const CardCondition &cond) +{ + CardMatchType type = matchTypeFromString(cond.matchType); + QString fieldValue; + if (cond.field == "name") { + fieldValue = card.getName(); + } else if (cond.field == "text") { + fieldValue = card.getText(); + } else { + fieldValue = card.getProperty(cond.field); + } + + switch (type) { + case CardMatchType::Equals: + return fieldValue == cond.value; + case CardMatchType::NotEquals: + return fieldValue != cond.value; + case CardMatchType::Contains: + return fieldValue.contains(cond.value, Qt::CaseInsensitive); + case CardMatchType::NotContains: + return !fieldValue.contains(cond.value, Qt::CaseInsensitive); + case CardMatchType::Regex: { + QRegularExpression re(cond.value, QRegularExpression::CaseInsensitiveOption); + return re.match(fieldValue).hasMatch(); + } + default: + return false; + } +} + +bool exceptionAppliesToCard(const CardInfo &card, const ExceptionRule &rule) +{ + for (const CardCondition &cond : rule.conditions) { + if (!cardMatchesCondition(card, cond)) { + return false; // all conditions must match + } + } + return true; +} + +bool cardHasAnyException(const CardInfo &card, const FormatRules &format) +{ + for (const ExceptionRule &rule : format.exceptions) { + if (exceptionAppliesToCard(card, rule)) { + return true; + } + } + return false; +} \ No newline at end of file diff --git a/libcockatrice_card/libcockatrice/card/format/format_legality_rules.h b/libcockatrice_card/libcockatrice/card/format/format_legality_rules.h new file mode 100644 index 000000000..16f2359ab --- /dev/null +++ b/libcockatrice_card/libcockatrice/card/format/format_legality_rules.h @@ -0,0 +1,73 @@ +#ifndef COCKATRICE_FORMAT_LEGALITY_RULES_H +#define COCKATRICE_FORMAT_LEGALITY_RULES_H + +#include +#include +#include + +class CardInfo; +using CardInfoPtr = QSharedPointer; + +struct CardCondition +{ + QString field; // e.g. "type", "maintype", "text" + QString matchType; // "contains", "equals", "regex", "notContains", etc. + QString value; // e.g. "Basic Land" +}; + +struct AllowedCount +{ + int max = 0; // 4, 1, 0, or -1 for unlimited + QString label; // "legal", "restricted", "banned" +}; + +struct ExceptionRule +{ + QList conditions; // All must match + int maxCopies = -1; // -1 = unlimited +}; + +struct FormatRules +{ + QString formatName; + int minDeckSize = 60; + int maxDeckSize = -1; // -1 = unlimited + int maxSideboardSize = 15; + + QList allowedCounts; + + QList exceptions; // Cards allowed to break maxCopies +}; + +enum class CardMatchType +{ + Equals, + NotEquals, + Contains, + NotContains, + Regex +}; + +// convert string to enum +inline CardMatchType matchTypeFromString(const QString &str) +{ + if (str == "equals") + return CardMatchType::Equals; + if (str == "notEquals") + return CardMatchType::NotEquals; + if (str == "contains") + return CardMatchType::Contains; + if (str == "notContains") + return CardMatchType::NotContains; + if (str == "regex") + return CardMatchType::Regex; + return CardMatchType::Equals; // fallback default +} + +bool cardMatchesCondition(const CardInfo &card, const CardCondition &cond); + +bool exceptionAppliesToCard(const CardInfo &card, const ExceptionRule &rule); + +bool cardHasAnyException(const CardInfo &card, const FormatRules &format); + +#endif // COCKATRICE_FORMAT_LEGALITY_RULES_H diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index 333be017b..09326e619 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -136,6 +136,8 @@ bool DeckList::readElement(QXmlStreamReader *xml) 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") { @@ -170,6 +172,7 @@ 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); @@ -594,15 +597,16 @@ DecklistCardNode *DeckList::addCard(const QString &cardName, const int position, const QString &cardSetName, const QString &cardSetCollectorNumber, - const QString &cardProviderId) + const QString &cardProviderId, + const bool formatLegal) { auto *zoneNode = dynamic_cast(root->findChild(zoneName)); if (zoneNode == nullptr) { zoneNode = new InnerDecklistNode(zoneName, root); } - auto *node = - new DecklistCardNode(cardName, 1, zoneNode, position, cardSetName, cardSetCollectorNumber, cardProviderId); + auto *node = new DecklistCardNode(cardName, 1, zoneNode, position, cardSetName, cardSetCollectorNumber, + cardProviderId, formatLegal); refreshDeckHash(); return node; diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index 6f90b6b14..e4206ebc6 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -126,6 +126,7 @@ public: { QString name; ///< User-defined deck name. QString comments; ///< Free-form comments or notes. + QString gameFormat; ///< The name of the game format this deck contains legal cards for CardRef bannerCard; ///< Optional representative card for the deck. QStringList tags; ///< User-defined tags for deck classification. QString lastLoadedTimestamp; ///< Timestamp string of last load. @@ -181,6 +182,10 @@ public: { metadata.lastLoadedTimestamp = _lastLoadedTimestamp; } + void setGameFormat(const QString &_gameFormat = QString()) + { + metadata.gameFormat = _gameFormat; + } ///@} /// @brief Construct an empty deck. @@ -219,6 +224,10 @@ public: { return metadata.lastLoadedTimestamp; } + QString getGameFormat() const + { + return metadata.gameFormat; + } ///@} bool isBlankDeck() const @@ -277,7 +286,8 @@ public: int position, const QString &cardSetName = QString(), const QString &cardSetCollectorNumber = QString(), - const QString &cardProviderId = QString()); + const QString &cardProviderId = QString(), + const bool formatLegal = true); bool deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode = nullptr); ///@} 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 1d201a4c1..88d8b0930 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 @@ -88,6 +88,12 @@ public: /// @param _cardSetNumber Set the collector number. virtual void setCardCollectorNumber(const QString &_cardSetNumber) = 0; + /// @return The format legality of the card + virtual bool getFormatLegality() const = 0; + + /// @param _formatLegal If the card is considered legal + virtual void setFormatLegality(const bool _formatLegal) = 0; + /** * @brief Get the height of this node in the tree. * diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.h b/libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.h index c2fe038e6..b3d42b89a 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.h @@ -51,6 +51,7 @@ class DecklistCardNode : public AbstractDecklistCardNode QString cardSetShortName; ///< Short set code (e.g., "NEO"). QString cardSetNumber; ///< Collector number within the set. QString cardProviderId; ///< External provider identifier (e.g., UUID). + bool formatLegal; ///< Format legality public: /** @@ -63,6 +64,7 @@ public: * @param _cardSetShortName Short set code (e.g., "NEO"). * @param _cardSetNumber Collector number within the set. * @param _cardProviderId External provider ID (e.g., UUID). + * @param _formatLegality If the card is legal in the format * * On construction, if a parent is provided, this node is inserted into * the parent’s children list automatically. @@ -73,10 +75,11 @@ public: int position = -1, QString _cardSetShortName = QString(), QString _cardSetNumber = QString(), - QString _cardProviderId = QString()) + QString _cardProviderId = QString(), + bool _formatLegality = true) : AbstractDecklistCardNode(_parent, position), name(std::move(_name)), number(_number), cardSetShortName(std::move(_cardSetShortName)), cardSetNumber(std::move(_cardSetNumber)), - cardProviderId(std::move(_cardProviderId)) + cardProviderId(std::move(_cardProviderId)), formatLegal(_formatLegality) { } @@ -150,6 +153,18 @@ public: cardSetNumber = _cardSetNumber; } + /// @return The format legality of the card + [[nodiscard]] bool getFormatLegality() const override + { + return formatLegal; + } + + /// @param _formatLegal If the card is considered legal + void setFormatLegality(const bool _formatLegal) override + { + formatLegal = _formatLegal; + } + /// @return Always false; card nodes are not deck headers. [[nodiscard]] bool isDeckHeader() const override { 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 2856198b8..05ee480a5 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -168,6 +168,10 @@ QVariant DeckListModel::data(const QModelIndex &index, int role) const return card->depth(); } + case DeckRoles::IsLegalRole: { + return card->getFormatLegality(); + } + default: { return {}; } @@ -268,6 +272,7 @@ bool DeckListModel::setData(const QModelIndex &index, const QVariant &value, con switch (index.column()) { case DeckListModelColumns::CARD_AMOUNT: node->setNumber(value.toInt()); + refreshCardFormatLegalities(); break; case DeckListModelColumns::CARD_NAME: node->setName(value.toString()); @@ -414,8 +419,9 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam // Determine the correct index int insertRow = findSortedInsertRow(groupNode, cardInfo); - auto *decklistCard = deckList->addCard(cardInfo->getName(), zoneName, insertRow, cardSetName, - printingInfo.getProperty("num"), printingInfo.getProperty("uuid")); + auto *decklistCard = + deckList->addCard(cardInfo->getName(), zoneName, insertRow, cardSetName, printingInfo.getProperty("num"), + printingInfo.getProperty("uuid"), isCardLegalForCurrentFormat(cardInfo)); beginInsertRows(parentIndex, insertRow, insertRow); cardNode = new DecklistModelCardNode(decklistCard, groupNode, insertRow); @@ -532,6 +538,13 @@ void DeckListModel::setActiveGroupCriteria(DeckListModelGroupCriteria::Type newC rebuildTree(); } +void DeckListModel::setActiveFormat(const QString &_format) +{ + deckList->setGameFormat(_format); + refreshCardFormatLegalities(); + emitBackgroundUpdates(QModelIndex()); // start from root +} + void DeckListModel::cleanList() { setDeckList(new DeckList); @@ -595,4 +608,90 @@ QList DeckListModel::getZones() const [](auto zoneNode) { return zoneNode->getName(); }); return zones; -} \ No newline at end of file +} + +bool DeckListModel::isCardLegalForCurrentFormat(const CardInfoPtr cardInfo) +{ + if (!deckList->getGameFormat().isEmpty()) { + if (cardInfo->getProperties().contains("format-" + deckList->getGameFormat())) { + QString formatLegality = cardInfo->getProperty("format-" + deckList->getGameFormat()); + return formatLegality == "legal" || formatLegality == "restricted"; + } + return false; + } + return true; +} + +int maxAllowedForLegality(const FormatRules &format, const QString &legality) +{ + for (const AllowedCount &c : format.allowedCounts) { + if (c.label == legality) { + return c.max; + } + } + return -1; // unknown legality → treat as illegal +} + + +bool DeckListModel::isCardQuantityLegalForCurrentFormat(const CardInfoPtr cardInfo, int quantity) +{ + auto formatRules = CardDatabaseManager::query()->getFormat(deckList->getGameFormat()); + + if (!formatRules) { + return true; + } + + // Exceptions always win + if (cardHasAnyException(*cardInfo, *formatRules)) { + return true; + } + + const QString legalityProp = "format-" + deckList->getGameFormat(); + if (!cardInfo->getProperties().contains(legalityProp)) { + return false; + } + + const QString legality = cardInfo->getProperty(legalityProp); + + int maxAllowed = maxAllowedForLegality(*formatRules, legality); + + if (maxAllowed == -1) { + return false; + } + + if (maxAllowed < 0) { // unlimited + return true; + } + + return quantity <= maxAllowed; +} + +void DeckListModel::refreshCardFormatLegalities() +{ + InnerDecklistNode *listRoot = deckList->getRoot(); + + for (int i = 0; i < listRoot->size(); i++) { + auto *currentZone = static_cast(listRoot->at(i)); + for (int j = 0; j < currentZone->size(); j++) { + auto *currentCard = static_cast(currentZone->at(j)); + + // TODO: better sanity checking + if (currentCard == nullptr) { + continue; + } + + ExactCard exactCard = CardDatabaseManager::query()->getCard(currentCard->toCardRef()); + if (!exactCard) { + continue; + } + + bool legal = isCardLegalForCurrentFormat(exactCard.getCardPtr()); + + if (legal) { + legal = isCardQuantityLegalForCurrentFormat(exactCard.getCardPtr(), currentCard->getNumber()); + } + + currentCard->setFormatLegality(legal); + } + } +} 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 0b47faed4..c03497bd5 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -164,6 +164,14 @@ public: { dataNode->setCardCollectorNumber(_cardSetNumber); } + bool getFormatLegality() const override + { + return dataNode->getFormatLegality(); + } + void setFormatLegality(const bool _formatLegal) override + { + dataNode->setFormatLegality(_formatLegal); + } /** * @brief Returns the underlying data node. @@ -209,6 +217,9 @@ public slots: */ void rebuildTree(); +public slots: + void setActiveFormat(const QString &_format); + signals: /** * @brief Emitted whenever the deck hash changes due to modifications in the model. @@ -301,6 +312,9 @@ public: [[nodiscard]] QList getCards() const; [[nodiscard]] QList getCardsForZone(const QString &zoneName) const; [[nodiscard]] QList getZones() const; + bool isCardLegalForCurrentFormat(CardInfoPtr cardInfo); + bool isCardQuantityLegalForCurrentFormat(CardInfoPtr cardInfo, int quantity); + void refreshCardFormatLegalities(); /** * @brief Sets the criteria used to group cards in the model. diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index f316e439d..813df49ae 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -13,6 +13,10 @@ #include #include +static const QList kConstructedCounts = {{4, "legal"}, {0, "banned"}}; + +static const QList kSingletonCounts = {{1, "legal"}, {0, "banned"}}; + SplitCardPart::SplitCardPart(const QString &_name, const QString &_text, const QVariantHash &_properties, @@ -463,6 +467,71 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList return numCards; } +FormatRulesNameMap OracleImporter::createDefaultMagicFormats() +{ + // Predefined common exceptions + CardCondition superTypeIsBasic; + superTypeIsBasic.field = "type"; + superTypeIsBasic.matchType = "contains"; + superTypeIsBasic.value = "Basic Land"; + + ExceptionRule basicLands; + basicLands.conditions.append(superTypeIsBasic); + + CardCondition anyNumberAllowed; + anyNumberAllowed.field = "text"; + anyNumberAllowed.matchType = "contains"; + anyNumberAllowed.value = "A deck can have any number of"; + + ExceptionRule mayContainAnyNumber; + mayContainAnyNumber.conditions.append(anyNumberAllowed); + + // Map to store default rules + FormatRulesNameMap defaultFormatRulesNameMap; + + // ----------------- Helper lambda to create format ----------------- + auto makeFormat = [&](const QString &name, int minDeck = 60, int maxDeck = -1, int maxSideboardSize = 15, + const QList &allowedCounts = kConstructedCounts) -> FormatRulesPtr { + FormatRulesPtr f(new FormatRules); + f->formatName = name; + f->allowedCounts = allowedCounts; + f->minDeckSize = minDeck; + f->maxDeckSize = maxDeck; + f->maxSideboardSize = maxSideboardSize; + f->exceptions.append(basicLands); + f->exceptions.append(mayContainAnyNumber); + defaultFormatRulesNameMap.insert(name.toLower(), f); + return f; + }; + + // ----------------- Standard formats ----------------- + makeFormat("Standard"); + makeFormat("Modern"); + makeFormat("Legacy"); + makeFormat("Pioneer"); + makeFormat("Historic"); + makeFormat("Timeless"); + makeFormat("Future"); + makeFormat("OldSchool"); + makeFormat("Premodern"); + makeFormat("Pauper"); + makeFormat("Penny"); + + // ----------------- Singleton formats ----------------- + makeFormat("Commander", 100, 100, 15, kSingletonCounts); + makeFormat("Duel", 100, 100, 15, kSingletonCounts); + makeFormat("Brawl", 60, 60, 15, kSingletonCounts); + makeFormat("StandardBrawl", 60, 60, 15, kSingletonCounts); + makeFormat("Oathbreaker", 60, 60, 15, kSingletonCounts); + makeFormat("PauperCommander", 100, 100, 15, kSingletonCounts); + makeFormat("Predh", 100, 100, 15, kSingletonCounts); + + // ----------------- Restricted formats ----------------- + makeFormat("Vintage", 60, -1, 15, {{4, "legal"}, {1, "restricted"}, {0, "banned"}}); + + return defaultFormatRulesNameMap; +} + int OracleImporter::startImport() { static ICardSetPriorityController *noOpController = new NoopCardSetPriorityController(); @@ -497,7 +566,8 @@ int OracleImporter::startImport() bool OracleImporter::saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion) { CockatriceXml4Parser parser(new NoopCardPreferenceProvider()); - return parser.saveToFile(sets, cards, fileName, sourceUrl, sourceVersion); + + return parser.saveToFile(createDefaultMagicFormats(), sets, cards, fileName, sourceUrl, sourceVersion); } void OracleImporter::clear() diff --git a/oracle/src/oracleimporter.h b/oracle/src/oracleimporter.h index 3ec6da6e1..e83958d73 100644 --- a/oracle/src/oracleimporter.h +++ b/oracle/src/oracleimporter.h @@ -154,6 +154,7 @@ public: int startImport(); bool saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion); int importCardsFromSet(const CardSetPtr ¤tSet, const QList &cardsList); + FormatRulesNameMap createDefaultMagicFormats(); const CardNameMap &getCardList() const { return cards; From 5d9d7d3aa55222471a629ecbeb3b18957a1e26b4 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 14 Dec 2025 14:26:06 -0800 Subject: [PATCH 060/325] [DeckLoader] remove unused private methods (#6417) --- .../src/interface/deck_loader/deck_loader.cpp | 23 ------------------- .../src/interface/deck_loader/deck_loader.h | 3 --- 2 files changed, 26 deletions(-) diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index a3f0ff619..bd5fe3ef2 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -485,29 +485,6 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName) return result; } -QString DeckLoader::getCardZoneFromName(const QString &cardName, QString currentZoneName) -{ - CardInfoPtr card = CardDatabaseManager::query()->getCardInfo(cardName); - - if (card && card->getIsToken()) { - return DECK_ZONE_TOKENS; - } - - return currentZoneName; -} - -QString DeckLoader::getCompleteCardName(const QString &cardName) -{ - if (CardDatabaseManager::getInstance()) { - ExactCard temp = CardDatabaseManager::query()->guessCard({cardName}); - if (temp) { - return temp.getName(); - } - } - - return cardName; -} - void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node) { const int totalColumns = 2; diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h index ae3088ff8..75eb38423 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.h +++ b/cockatrice/src/interface/deck_loader/deck_loader.h @@ -106,9 +106,6 @@ private: 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 From 8485bbe575bc9c6f06cd3cebfb2416b08c3d5d36 Mon Sep 17 00:00:00 2001 From: tooomm Date: Mon, 15 Dec 2025 00:13:16 +0100 Subject: [PATCH 061/325] Docs: Use Doxygen \todo comments (#6421) * format todo comments for doxygen * Mention Todo List & link search --- README.md | 2 ++ cockatrice/src/game/player/player.cpp | 2 +- .../interface/widgets/dialogs/dlg_edit_password.cpp | 2 +- .../widgets/dialogs/dlg_forgot_password_reset.cpp | 2 +- .../src/interface/widgets/dialogs/dlg_register.cpp | 2 +- .../src/interface/widgets/dialogs/dlg_settings.cpp | 4 ++-- .../interface/widgets/server/chat_view/chat_view.cpp | 4 ++-- .../api_response/card/archidekt_api_response_card.cpp | 10 +++++----- .../libcockatrice/deck_list/deck_list.h | 2 +- .../network/server/remote/game/server_game.cpp | 2 +- libcockatrice_utility/libcockatrice/utility/peglib.h | 6 +++--- oracle/src/zip/unzip.cpp | 2 +- servatrice/src/serversocketinterface.cpp | 4 ++-- servatrice/src/smtp/qxtmailmessage.cpp | 3 +-- 14 files changed, 24 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index b1233f749..87629d2c9 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,8 @@ This tag is used for issues that we are looking for somebody to pick up. Often t For both tags, we're willing to provide help to contributors in showing them where and how they can make changes, as well as code reviews for submitted changes.
We'll happily advice on how best to implement a feature, or we can show you where the codebase is doing something similar before you get too far along - put a note on an issue you want to discuss more on! +You can also have a look at our `Todo List` in our [Code Documentation](https://cockatrice.github.io/docs) or search the repo for [`\todo` comments](https://github.com/search?q=repo%3ACockatrice%2FCockatrice%20%5Ctodo&type=code). + Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide.
diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index efce40cd7..a61bb5c00 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -263,7 +263,7 @@ void Player::deleteCard(CardItem *card) } } -// TODO: Does a player need a DeckLoader? +//! \todo Does a player need a DeckLoader? void Player::setDeck(DeckLoader &_deck) { deck = new DeckLoader(this, _deck.getDeckList()); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.cpp index 998dafb66..e3bbc7435 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_password.cpp @@ -59,7 +59,7 @@ DlgEditPassword::DlgEditPassword(QWidget *parent) : QDialog(parent) void DlgEditPassword::actOk() { - // TODO this stuff should be using qvalidators + //! \todo this stuff should be using qvalidators if (newPasswordEdit->text().length() < 8) { QMessageBox::critical(this, tr("Error"), tr("Your password is too short.")); return; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp index e0d855d7e..c9c41722e 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp @@ -121,7 +121,7 @@ void DlgForgotPasswordReset::actOk() return; } - // TODO this stuff should be using qvalidators + //! \todo this stuff should be using qvalidators if (newpasswordEdit->text().length() < 8) { QMessageBox::critical(this, tr("Error"), tr("Your password is too short.")); return; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp index 4be4cdf78..a3f232d9b 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp @@ -356,7 +356,7 @@ DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent) void DlgRegister::actOk() { - // TODO this stuff should be using qvalidators + //! \todo this stuff should be using qvalidators if (passwordEdit->text().length() < 8) { QMessageBox::critical(this, tr("Registration Warning"), tr("Your password is too short.")); return; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp index c0b80c6c1..191cc5d0b 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp @@ -1913,7 +1913,7 @@ void DlgSettings::closeEvent(QCloseEvent *event) } if (!QDir(SettingsCache::instance().getDeckPath()).exists() || SettingsCache::instance().getDeckPath().isEmpty()) { - // TODO: Prompt to create it + //! \todo Prompt to create it if (QMessageBox::critical( this, tr("Error"), tr("The path to your deck directory is invalid. Would you like to go back and set the correct path?"), @@ -1924,7 +1924,7 @@ void DlgSettings::closeEvent(QCloseEvent *event) } if (!QDir(SettingsCache::instance().getPicsPath()).exists() || SettingsCache::instance().getPicsPath().isEmpty()) { - // TODO: Prompt to create it + //! \todo Prompt to create it if (QMessageBox::critical(this, tr("Error"), tr("The path to your card pictures directory is invalid. Would you like to go back " "and set the correct path?"), diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp index c97d32d44..694085771 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp @@ -206,7 +206,7 @@ void ChatView::appendMessage(QString message, defaultFormat = QTextCharFormat(); if (!isUserMessage) { if (messageType == Event_RoomSay::ChatHistory) { - defaultFormat.setForeground(Qt::gray); // FIXME : hardcoded color + defaultFormat.setForeground(Qt::gray); //! \todo hardcoded color defaultFormat.setFontWeight(QFont::Light); defaultFormat.setFontItalic(true); static const QRegularExpression userNameRegex("^(\\[[^\\]]*\\]\\s)(\\S+):\\s"); @@ -229,7 +229,7 @@ void ChatView::appendMessage(QString message, message.remove(0, pos.relativePosition - 2); // do not remove semicolon } } else { - defaultFormat.setForeground(Qt::darkGreen); // FIXME : hardcoded color + defaultFormat.setForeground(Qt::darkGreen); //! \todo hardcoded color defaultFormat.setFontWeight(QFont::Bold); } } diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp index 909c4d9eb..5b879f8ae 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp @@ -21,16 +21,16 @@ void ArchidektApiResponseCard::fromJson(const QJsonObject &json) edition.fromJson(json.value("edition").toObject()); flavor = json.value("flavor").toString(); - // TODO but not really important - // games = {""}; - // options = {""}; + //! \todo but not really important + //! \todo games = {""}; + //! \todo options = {""}; scryfallImageHash = json.value("scryfallImageHash").toString(); oracleCard = json.value("oracleCard").toObject(); owned = json.value("owned").toInt(); pinnedStatus = json.value("pinnedStatus").toInt(); rarity = json.value("rarity").toString(); - // TODO but not really important - // globalCategories = {""}; + //! \todo but not really important + //! \todo globalCategories = {""}; } void ArchidektApiResponseCard::debugPrint() const diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index e4206ebc6..0325b029a 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -198,8 +198,8 @@ public: /// @name Metadata getters /// The individual metadata getters still exist for backwards compatibility. - /// TODO: Figure out when we can remove them. ///@{ + //! \todo Figure out when we can remove them. const Metadata &getMetadata() const { return metadata; 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 5ec7b3c5d..e53838695 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -501,7 +501,7 @@ void Server_Game::addPlayer(Server_AbstractUserInterface *userInterface, allPlayersEver.insert(playerName); // if the original creator of the game joins, give them host status back - // FIXME: transferring host to spectators has side effects + //! \todo transferring host to spectators has side effects if (newParticipant->getUserInfo()->name() == creatorInfo->name()) { hostId = newParticipant->getPlayerId(); sendGameEventContainer(prepareGameEvent(Event_GameHostChanged(), hostId)); diff --git a/libcockatrice_utility/libcockatrice/utility/peglib.h b/libcockatrice_utility/libcockatrice/utility/peglib.h index 459fa5a27..6a5b87b2d 100644 --- a/libcockatrice_utility/libcockatrice/utility/peglib.h +++ b/libcockatrice_utility/libcockatrice/utility/peglib.h @@ -441,8 +441,8 @@ private: size_t id; }; - // TODO: Use unordered_map when heterogeneous lookup is supported in C++20 - // std::unordered_map dic_; + //! \todo Use unordered_map when heterogeneous lookup is supported in C++20 + //! \todo std::unordered_map dic_; std::map> dic_; bool ignore_case_; @@ -3068,7 +3068,7 @@ inline size_t Recovery::parse_core(const char *s, size_t n, c.cut_stack.back() = true; if (c.cut_stack.size() == 1) { - // TODO: Remove unneeded entries in packrat memoise table + //! \todo Remove unneeded entries in packrat memoise table } } diff --git a/oracle/src/zip/unzip.cpp b/oracle/src/zip/unzip.cpp index 1e5910051..8e52ece18 100755 --- a/oracle/src/zip/unzip.cpp +++ b/oracle/src/zip/unzip.cpp @@ -245,7 +245,6 @@ UnZip::ErrorCode UnzipPrivate::openArchive(QIODevice* dev) \internal Parses a local header record and makes some consistency check with the information stored in the Central Directory record for this entry that has been previously parsed. - \todo Optional consistency check (as a ExtractionOptions flag) local file header signature 4 bytes (0x04034b50) version needed to extract 2 bytes @@ -262,6 +261,7 @@ UnZip::ErrorCode UnzipPrivate::openArchive(QIODevice* dev) file name (variable size) extra field (variable size) */ +//! \todo Optional consistency check (as a ExtractionOptions flag) UnZip::ErrorCode UnzipPrivate::parseLocalHeaderRecord(const QString& path, const ZipEntryP& entry) { Q_ASSERT(device); diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index 4b33aad12..bc686ad28 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -1207,7 +1207,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C return Response::RespEmailBlackListed; } - // TODO: Move this method outside of the db interface + //! \todo Move this method outside of the db interface QString errorString; if (!sqlInterface->usernameIsValid(userName, errorString)) { if (servatrice->getEnableRegistrationAudit()) @@ -1330,7 +1330,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C bool AbstractServerSocketInterface::tooManyRegistrationAttempts(const QString &ipAddress) { - // TODO: implement + //! \todo implement Q_UNUSED(ipAddress); return false; } diff --git a/servatrice/src/smtp/qxtmailmessage.cpp b/servatrice/src/smtp/qxtmailmessage.cpp index ca003d875..ff376c1a9 100644 --- a/servatrice/src/smtp/qxtmailmessage.cpp +++ b/servatrice/src/smtp/qxtmailmessage.cpp @@ -27,9 +27,8 @@ * \class QxtMailMessage * \inmodule QxtNetwork * \brief The QxtMailMessage class encapsulates an e-mail according to RFC 2822 and related specifications - * TODO: {implicitshared} */ - +//! \todo {implicitshared} #include "qxtmailmessage.h" #include "qxtmail_p.h" From c218a66bcd295d9013e6899c1fd9694587bbe254 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 14 Dec 2025 15:14:11 -0800 Subject: [PATCH 062/325] [DeckFilterString] Rename file search expression (#6413) --- cockatrice/resources/help/deck_search.md | 8 ++++---- cockatrice/src/filters/deck_filter_string.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cockatrice/resources/help/deck_search.md b/cockatrice/resources/help/deck_search.md index 6d58ee590..9ae7164e2 100644 --- a/cockatrice/resources/help/deck_search.md +++ b/cockatrice/resources/help/deck_search.md @@ -15,10 +15,10 @@ searches are case insensitive.
[n:red n:deck n:wins](#n:red n:deck n:wins) (Any deck with a name containing the words red, deck, and wins)
[n:"red deck wins"](#n:%22red deck wins%22) (Any deck with a name containing the exact phrase "red deck wins")
-
File Name:
-
[f:aggro](#f:aggro) (Any deck with a filename containing the word aggro)
-
[f:red f:deck f:wins](#f:red f:deck f:wins) (Any deck with a filename containing the words red, deck, and wins)
-
[f:"red deck wins"](#f:%22red deck wins%22) (Any deck with a filename containing the exact phrase "red deck wins")
+
File Name:
+
[fn:aggro](#fn:aggro) (Any deck with a filename containing the word aggro)
+
[fn:red fn:deck fn:wins](#fn:red fn:deck fn:wins) (Any deck with a filename containing the words red, deck, and wins)
+
[fn:"red deck wins"](#fn:%22red deck wins%22) (Any deck with a filename containing the exact phrase "red deck wins")
Relative Path (starting from the deck folder):
[p:aggro](#p:aggro) (Any deck that has "aggro" somewhere in its relative path)
diff --git a/cockatrice/src/filters/deck_filter_string.cpp b/cockatrice/src/filters/deck_filter_string.cpp index dfc54afe0..186ee0b90 100644 --- a/cockatrice/src/filters/deck_filter_string.cpp +++ b/cockatrice/src/filters/deck_filter_string.cpp @@ -22,7 +22,7 @@ CardSearch <- '[[' CardFilterString ']]' CardFilterString <- (!']]'.)* DeckNameQuery <- ([Dd] 'eck')? [Nn] 'ame'? [:] String -FileNameQuery <- [Ff] ('ile' 'name'?)? [:] String +FileNameQuery <- [Ff] ([Nn] / 'ile' ([Nn] 'ame')?) [:] String PathQuery <- [Pp] 'ath'? [:] String GenericQuery <- String From 589e9a15a6b1f23f1a094cec66542124e957884f Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 14 Dec 2025 15:14:19 -0800 Subject: [PATCH 063/325] [DeckFilterString] Add search query for format (#6414) * [DeckFilterString] Rename file search expression * [DeckFilterString] Add search query for format --- cockatrice/resources/help/deck_search.md | 3 +++ cockatrice/src/filters/deck_filter_string.cpp | 11 ++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/cockatrice/resources/help/deck_search.md b/cockatrice/resources/help/deck_search.md index 9ae7164e2..c42e41c42 100644 --- a/cockatrice/resources/help/deck_search.md +++ b/cockatrice/resources/help/deck_search.md @@ -24,6 +24,9 @@ searches are case insensitive.
[p:aggro](#p:aggro) (Any deck that has "aggro" somewhere in its relative path)
[p:edh/](#p:edh/) (Any deck with "edh/" in its relative path, A.K.A. decks in the "edh" folder)
+
Format:
+
[f:standard](#f:standard) (Any deck with format set to standard)
+
Deck Contents (Uses [card search expressions](#cardSearchSyntaxHelp)):
[[plains]] (Any deck that contains at least one card with "plains" in its name)
[[t:legendary]] (Any deck that contains at least one legendary)
diff --git a/cockatrice/src/filters/deck_filter_string.cpp b/cockatrice/src/filters/deck_filter_string.cpp index 186ee0b90..ed1a24322 100644 --- a/cockatrice/src/filters/deck_filter_string.cpp +++ b/cockatrice/src/filters/deck_filter_string.cpp @@ -13,7 +13,7 @@ QueryPartList <- ComplexQueryPart ( ws ("AND" ws)? ComplexQueryPart)* ws* ComplexQueryPart <- SomewhatComplexQueryPart ws "OR" ws ComplexQueryPart / SomewhatComplexQueryPart SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart -QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / GenericQuery +QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / FormatQuery / GenericQuery NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart @@ -24,6 +24,7 @@ CardFilterString <- (!']]'.)* DeckNameQuery <- ([Dd] 'eck')? [Nn] 'ame'? [:] String FileNameQuery <- [Ff] ([Nn] / 'ile' ([Nn] 'ame')?) [:] String PathQuery <- [Pp] 'ath'? [:] String +FormatQuery <- [Ff] 'ormat'? [:] String GenericQuery <- String @@ -157,6 +158,14 @@ static void setupParserRules() }; }; + search["FormatQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { + auto format = std::any_cast(sv[0]); + return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) { + auto gameFormat = deck->deckLoader->getDeckList()->getGameFormat(); + return QString::compare(format, gameFormat, Qt::CaseInsensitive) == 0; + }; + }; + search["GenericQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { auto name = std::any_cast(sv[0]); return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) { From b29909bdbec03d1cb9566bc0b73e0026e908f427 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 14 Dec 2025 15:56:58 -0800 Subject: [PATCH 064/325] [DeckList] Refactor: Create class to RAII underlying tree (#6412) * [DeckList] Create class to RAII underlying tree * Update usages * fixes after rebase * update docs --- libcockatrice_deck_list/CMakeLists.txt | 2 + .../libcockatrice/deck_list/deck_list.cpp | 170 +++------------- .../libcockatrice/deck_list/deck_list.h | 38 ++-- .../deck_list/deck_list_node_tree.cpp | 186 ++++++++++++++++++ .../deck_list/deck_list_node_tree.h | 87 ++++++++ .../models/deck_list/deck_list_model.cpp | 6 +- .../models/deck_list/deck_list_model.h | 4 +- 7 files changed, 325 insertions(+), 168 deletions(-) create mode 100644 libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp create mode 100644 libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h diff --git a/libcockatrice_deck_list/CMakeLists.txt b/libcockatrice_deck_list/CMakeLists.txt index 5dffff3f7..a7dd01702 100644 --- a/libcockatrice_deck_list/CMakeLists.txt +++ b/libcockatrice_deck_list/CMakeLists.txt @@ -27,6 +27,8 @@ add_library( libcockatrice/deck_list/tree/inner_deck_list_node.cpp 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_node_tree.h ) add_dependencies(libcockatrice_deck_list libcockatrice_protocol) diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index 09326e619..54eaa3096 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -83,26 +83,23 @@ bool DeckList::Metadata::isEmpty() const } DeckList::DeckList() -{ - root = new InnerDecklistNode; -} - -DeckList::DeckList(const DeckList &other) - : metadata(other.metadata), sideboardPlans(other.sideboardPlans), root(new InnerDecklistNode(other.getRoot())), - cachedDeckHash(other.cachedDeckHash) { } DeckList::DeckList(const QString &nativeString) { - root = new InnerDecklistNode; loadFromString_Native(nativeString); } +DeckList::DeckList(const Metadata &metadata, + const DecklistNodeTree &tree, + const QMap &sideboardPlans) + : metadata(metadata), sideboardPlans(sideboardPlans), tree(tree) +{ +} + DeckList::~DeckList() { - delete root; - QMapIterator i(sideboardPlans); while (i.hasNext()) delete i.next().value(); @@ -152,8 +149,7 @@ bool DeckList::readElement(QXmlStreamReader *xml) } } } else if (childName == "zone") { - InnerDecklistNode *newZone = getZoneObjFromName(xml->attributes().value("name").toString()); - newZone->readElement(xml); + tree.readZoneElement(xml); } else if (childName == "sideboard_plan") { SideboardPlan *newSideboardPlan = new SideboardPlan; if (newSideboardPlan->readElement(xml)) { @@ -195,9 +191,7 @@ void DeckList::write(QXmlStreamWriter *xml) const writeMetadata(xml, metadata); // Write zones - for (int i = 0; i < root->size(); i++) { - root->at(i)->writeElement(xml); - } + tree.write(xml); // Write sideboard plans QMapIterator i(sideboardPlans); @@ -456,25 +450,13 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata) QString zoneName = sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN; // make new entry in decklist - new DecklistCardNode(cardName, amount, getZoneObjFromName(zoneName), -1, setCode, collectorNumber); + tree.addCard(cardName, amount, zoneName, -1, setCode, collectorNumber); } refreshDeckHash(); return true; } -InnerDecklistNode *DeckList::getZoneObjFromName(const QString &zoneName) -{ - for (int i = 0; i < root->size(); i++) { - auto *node = dynamic_cast(root->at(i)); - if (node->getName() == zoneName) { - return node; - } - } - - return new InnerDecklistNode(zoneName, root); -} - bool DeckList::loadFromFile_Plain(QIODevice *device) { QTextStream in(device); @@ -519,7 +501,7 @@ QString DeckList::writeToString_Plain(bool prefixSideboardCards, bool slashTappe */ void DeckList::cleanList(bool preserveMetadata) { - root->clearTree(); + tree.clear(); if (!preserveMetadata) { metadata = {}; } @@ -528,7 +510,7 @@ void DeckList::cleanList(bool preserveMetadata) QStringList DeckList::getCardList() const { - auto nodes = getCardNodes(); + auto nodes = tree.getCardNodes(); QStringList result; std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(result), [](auto node) { return node->getName(); }); @@ -538,7 +520,7 @@ QStringList DeckList::getCardList() const QList DeckList::getCardRefList() const { - auto nodes = getCardNodes(); + auto nodes = tree.getCardNodes(); QList result; std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(result), @@ -547,42 +529,19 @@ QList DeckList::getCardRefList() const return result; } -QList DeckList::getCardNodes(const QStringList &restrictToZones) const +QList DeckList::getCardNodes(const QSet &restrictToZones) const { - QList result; - - auto zoneNodes = getZoneNodes(); - for (auto *zoneNode : zoneNodes) { - if (!restrictToZones.isEmpty() && !restrictToZones.contains(zoneNode->getName())) { - continue; - } - for (auto *cardNode : *zoneNode) { - auto *cardCardNode = dynamic_cast(cardNode); - if (cardCardNode != nullptr) { - result.append(cardCardNode); - } - } - } - - return result; + return tree.getCardNodes(restrictToZones); } QList DeckList::getZoneNodes() const { - QList zones; - for (auto *node : *root) { - InnerDecklistNode *currentZone = dynamic_cast(node); - if (!currentZone) - continue; - zones.append(currentZone); - } - - return zones; + return tree.getZoneNodes(); } int DeckList::getSideboardSize() const { - auto cards = getCardNodes({DECK_ZONE_SIDE}); + auto cards = tree.getCardNodes({DECK_ZONE_SIDE}); int size = 0; for (auto card : cards) { @@ -594,93 +553,18 @@ int DeckList::getSideboardSize() const DecklistCardNode *DeckList::addCard(const QString &cardName, const QString &zoneName, - const int position, + int position, const QString &cardSetName, const QString &cardSetCollectorNumber, const QString &cardProviderId, - const bool formatLegal) + bool formatLegal) { - auto *zoneNode = dynamic_cast(root->findChild(zoneName)); - if (zoneNode == nullptr) { - zoneNode = new InnerDecklistNode(zoneName, root); - } - - auto *node = new DecklistCardNode(cardName, 1, zoneNode, position, cardSetName, cardSetCollectorNumber, - cardProviderId, formatLegal); + auto node = + tree.addCard(cardName, 1, zoneName, position, cardSetName, cardSetCollectorNumber, cardProviderId, formatLegal); refreshDeckHash(); - return node; } -bool DeckList::deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode) -{ - if (node == root) { - return true; - } - - bool updateHash = false; - if (rootNode == nullptr) { - rootNode = root; - updateHash = true; - } - - int index = rootNode->indexOf(node); - if (index != -1) { - delete rootNode->takeAt(index); - - if (rootNode->empty()) { - deleteNode(rootNode, rootNode->getParent()); - } - - if (updateHash) { - refreshDeckHash(); - } - - return true; - } - - for (int i = 0; i < rootNode->size(); i++) { - auto *inner = dynamic_cast(rootNode->at(i)); - if (inner) { - if (deleteNode(node, inner)) { - if (updateHash) { - refreshDeckHash(); - } - - return true; - } - } - } - - return false; -} - -static QString computeDeckHash(const DeckList &deckList) -{ - auto mainDeckNodes = deckList.getCardNodes({DECK_ZONE_MAIN}); - auto sideDeckNodes = deckList.getCardNodes({DECK_ZONE_SIDE}); - - static auto nodesToCardList = [](const QList &nodes, const QString &prefix = {}) { - QStringList result; - for (auto node : nodes) { - for (int i = 0; i < node->getNumber(); ++i) { - result.append(prefix + node->getName().toLower()); - } - } - return result; - }; - - QStringList cardList = nodesToCardList(mainDeckNodes) + nodesToCardList(sideDeckNodes, "SB:"); - - cardList.sort(); - QByteArray deckHashArray = QCryptographicHash::hash(cardList.join(";").toUtf8(), QCryptographicHash::Sha1); - quint64 number = (((quint64)(unsigned char)deckHashArray[0]) << 32) + - (((quint64)(unsigned char)deckHashArray[1]) << 24) + - (((quint64)(unsigned char)deckHashArray[2] << 16)) + - (((quint64)(unsigned char)deckHashArray[3]) << 8) + (quint64)(unsigned char)deckHashArray[4]; - return QString::number(number, 32).rightJustified(8, '0'); -} - /** * Gets the deck hash. * The hash is computed on the first call to this method, and is cached until the decklist is modified. @@ -693,7 +577,7 @@ QString DeckList::getDeckHash() const return cachedDeckHash; } - cachedDeckHash = computeDeckHash(*this); + cachedDeckHash = tree.computeDeckHash(); return cachedDeckHash; } @@ -710,15 +594,7 @@ void DeckList::refreshDeckHash() */ void DeckList::forEachCard(const std::function &func) const { - // Support for this is only possible if the internal structure - // doesn't get more complicated. - for (int i = 0; i < root->size(); i++) { - InnerDecklistNode *node = dynamic_cast(root->at(i)); - for (int j = 0; j < node->size(); j++) { - DecklistCardNode *card = dynamic_cast(node->at(j)); - func(node, card); - } - } + tree.forEachCard(func); } DeckListMemento DeckList::createMemento(const QString &reason) const diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index 0325b029a..e8c57be67 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -11,6 +11,7 @@ #define DECKLIST_H #include "deck_list_memento.h" +#include "deck_list_node_tree.h" #include "tree/inner_deck_list_node.h" #include @@ -107,14 +108,14 @@ public: * - Provide hashing for deck identity (deck hash). * * ### Ownership: - * - Owns the root `InnerDecklistNode` tree. + * - Owns the `DecklistNodeTree`. * - Owns `SideboardPlan` instances stored in `sideboardPlans`. * * ### Example workflow: * ``` * DeckList deck; * deck.setName("Mono Red Aggro"); - * deck.addCard("Lightning Bolt", "main", -1); + * deck.addCard("Lightning Bolt", "main"); * deck.addTag("Aggro"); * deck.saveToFile_Native(device); * ``` @@ -140,7 +141,7 @@ public: private: Metadata metadata; ///< Deck metadata that is stored in the deck file QMap sideboardPlans; ///< Named sideboard plans. - InnerDecklistNode *root; ///< Root of the deck tree (zones + cards). + DecklistNodeTree tree; ///< The deck tree (zones + cards). /** * @brief Cached deck hash, recalculated lazily. @@ -148,9 +149,6 @@ private: */ mutable QString cachedDeckHash; - // Helpers for traversing the tree - InnerDecklistNode *getZoneObjFromName(const QString &zoneName); - public: /// @name Metadata setters ///@{ @@ -190,12 +188,24 @@ public: /// @brief Construct an empty deck. explicit DeckList(); - /// @brief Copy constructor (deep copies the node tree) - DeckList(const DeckList &other); /// @brief Construct from a serialized native-format string. explicit DeckList(const QString &nativeString); + /// @brief Construct from components + DeckList(const Metadata &metadata, + const DecklistNodeTree &tree, + const QMap &sideboardPlans = {}); virtual ~DeckList(); + /** + * @brief Gets a pointer to the underlying node tree. + * Note: DO NOT call this method unless the object needs to have access to the underlying model. + * For now, only the DeckListModel should be calling this. + */ + DecklistNodeTree *getTree() + { + return &tree; + } + /// @name Metadata getters /// The individual metadata getters still exist for backwards compatibility. ///@{ @@ -270,25 +280,21 @@ public: void cleanList(bool preserveMetadata = false); bool isEmpty() const { - return root->isEmpty() && metadata.isEmpty() && sideboardPlans.isEmpty(); + return tree.isEmpty() && metadata.isEmpty() && sideboardPlans.isEmpty(); } QStringList getCardList() const; QList getCardRefList() const; - QList getCardNodes(const QStringList &restrictToZones = QStringList()) const; + QList getCardNodes(const QSet &restrictToZones = {}) const; QList getZoneNodes() const; int getSideboardSize() const; - InnerDecklistNode *getRoot() const - { - return root; - } + DecklistCardNode *addCard(const QString &cardName, const QString &zoneName, - int position, + int position = -1, const QString &cardSetName = QString(), const QString &cardSetCollectorNumber = QString(), const QString &cardProviderId = QString(), const bool formatLegal = true); - bool deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode = nullptr); ///@} /// @name Deck identity 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 new file mode 100644 index 000000000..8f651a061 --- /dev/null +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.cpp @@ -0,0 +1,186 @@ +#include "deck_list_node_tree.h" + +#include "tree/deck_list_card_node.h" + +#include +#include + +DecklistNodeTree::DecklistNodeTree() : root(new InnerDecklistNode()) +{ +} + +DecklistNodeTree::DecklistNodeTree(const DecklistNodeTree &other) : root(new InnerDecklistNode(other.root)) +{ +} + +DecklistNodeTree &DecklistNodeTree::operator=(const DecklistNodeTree &other) +{ + if (this != &other) { + delete root; + root = new InnerDecklistNode(other.root); + } + return *this; +} + +DecklistNodeTree::~DecklistNodeTree() +{ + delete root; +} + +bool DecklistNodeTree::isEmpty() const +{ + return root->isEmpty(); +} + +void DecklistNodeTree::clear() +{ + root->clearTree(); +} + +QList DecklistNodeTree::getCardNodes(const QSet &restrictToZones) const +{ + QList result; + + for (auto *zoneNode : getZoneNodes()) { + if (!restrictToZones.isEmpty() && !restrictToZones.contains(zoneNode->getName())) { + continue; + } + for (auto *cardNode : *zoneNode) { + auto *cardCardNode = dynamic_cast(cardNode); + if (cardCardNode) { + result.append(cardCardNode); + } + } + } + + return result; +} + +QList DecklistNodeTree::getZoneNodes() const +{ + QList zones; + for (auto *node : *root) { + InnerDecklistNode *currentZone = dynamic_cast(node); + if (!currentZone) + continue; + zones.append(currentZone); + } + + return zones; +} + +QString DecklistNodeTree::computeDeckHash() const +{ + auto mainDeckNodes = getCardNodes({DECK_ZONE_MAIN}); + auto sideDeckNodes = getCardNodes({DECK_ZONE_SIDE}); + + static auto nodesToCardList = [](const QList &nodes, const QString &prefix = {}) { + QStringList result; + for (auto node : nodes) { + for (int i = 0; i < node->getNumber(); ++i) { + result.append(prefix + node->getName().toLower()); + } + } + return result; + }; + + QStringList cardList = nodesToCardList(mainDeckNodes) + nodesToCardList(sideDeckNodes, "SB:"); + + cardList.sort(); + QByteArray deckHashArray = QCryptographicHash::hash(cardList.join(";").toUtf8(), QCryptographicHash::Sha1); + quint64 number = (((quint64)(unsigned char)deckHashArray[0]) << 32) + + (((quint64)(unsigned char)deckHashArray[1]) << 24) + + (((quint64)(unsigned char)deckHashArray[2] << 16)) + + (((quint64)(unsigned char)deckHashArray[3]) << 8) + (quint64)(unsigned char)deckHashArray[4]; + return QString::number(number, 32).rightJustified(8, '0'); +} + +void DecklistNodeTree::write(QXmlStreamWriter *xml) const +{ + for (int i = 0; i < root->size(); i++) { + root->at(i)->writeElement(xml); + } +} + +void DecklistNodeTree::readZoneElement(QXmlStreamReader *xml) +{ + QString zoneName = xml->attributes().value("name").toString(); + InnerDecklistNode *newZone = getZoneObjFromName(zoneName); + newZone->readElement(xml); +} + +DecklistCardNode *DecklistNodeTree::addCard(const QString &cardName, + int amount, + const QString &zoneName, + int position, + const QString &cardSetName, + const QString &cardSetCollectorNumber, + const QString &cardProviderId, + const bool formatLegal) +{ + auto *zoneNode = getZoneObjFromName(zoneName); + auto *node = new DecklistCardNode(cardName, amount, zoneNode, position, cardSetName, cardSetCollectorNumber, + cardProviderId, formatLegal); + return node; +} + +bool DecklistNodeTree::deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode) +{ + if (node == root) { + return true; + } + + if (rootNode == nullptr) { + rootNode = root; + } + + int index = rootNode->indexOf(node); + if (index != -1) { + delete rootNode->takeAt(index); + + if (rootNode->empty()) { + deleteNode(rootNode, rootNode->getParent()); + } + + return true; + } + + for (int i = 0; i < rootNode->size(); i++) { + auto *inner = dynamic_cast(rootNode->at(i)); + if (inner) { + if (deleteNode(node, inner)) { + return true; + } + } + } + + return false; +} + +void DecklistNodeTree::forEachCard(const std::function &func) const +{ + // Support for this is only possible if the internal structure + // doesn't get more complicated. + for (int i = 0; i < root->size(); i++) { + InnerDecklistNode *node = dynamic_cast(root->at(i)); + for (int j = 0; j < node->size(); j++) { + DecklistCardNode *card = dynamic_cast(node->at(j)); + func(node, card); + } + } +} + +/** + * Gets the InnerDecklistNode that is the root node for the given zone, creating a new node if it doesn't exist. + */ +InnerDecklistNode *DecklistNodeTree::getZoneObjFromName(const QString &zoneName) const +{ + for (int i = 0; i < root->size(); i++) { + auto *node = dynamic_cast(root->at(i)); + if (node->getName() == zoneName) { + return node; + } + } + + return new InnerDecklistNode(zoneName, root); +} 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 new file mode 100644 index 000000000..5cfd4944d --- /dev/null +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_node_tree.h @@ -0,0 +1,87 @@ +#ifndef COCKATRICE_DECKLIST_NODE_TREE_H +#define COCKATRICE_DECKLIST_NODE_TREE_H + +#include "libcockatrice/utility/card_ref.h" +#include "tree/deck_list_card_node.h" +#include "tree/inner_deck_list_node.h" + +#include + +class DecklistNodeTree +{ + InnerDecklistNode *root; ///< Root of the deck tree (zones + cards). + +public: + /// @brief Constructs an empty DecklistNodeTree + explicit DecklistNodeTree(); + /// @brief Copy constructor. Deep copies the tree + explicit DecklistNodeTree(const DecklistNodeTree &other); + /// @brief Copy-assignment operator. Deep copies the tree + DecklistNodeTree &operator=(const DecklistNodeTree &other); + + virtual ~DecklistNodeTree(); + + /** + * @brief Gets a pointer to the underlying root node. + * Note: DO NOT call this method unless the object needs to have access to the underlying model. + * For now, only the DeckListModel should be calling this. + */ + InnerDecklistNode *getRoot() const + { + return root; + } + + bool isEmpty() const; + + /** + * @brief Deletes all nodes except the root. + */ + void clear(); + + /** + * Gets all card nodes in the tree + * @param restrictToZones Only get the nodes in these zones + * @return A QList containing all the card nodes in the zone. + */ + QList getCardNodes(const QSet &restrictToZones = {}) const; + + QList getZoneNodes() const; + + /** + * @brief Computes the deck hash + */ + QString computeDeckHash() const; + + /** + *@brief Writes the contents of the deck to xml + */ + void write(QXmlStreamWriter *xml) const; + + /** + * @brief Reads a "zone" section of the xml to this tree + */ + void readZoneElement(QXmlStreamReader *xml); + + DecklistCardNode *addCard(const QString &cardName, + int amount, + const QString &zoneName, + int position, + const QString &cardSetName = QString(), + const QString &cardSetCollectorNumber = QString(), + const QString &cardProviderId = QString(), + const bool formatLegal = true); + bool deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode = nullptr); + + /** + * @brief Apply a function to every card in the deck tree. This can modify the cards. + * + * @param func Function taking (zone node, card node). + */ + void forEachCard(const std::function &func) const; + +private: + // Helpers for traversing the tree + InnerDecklistNode *getZoneObjFromName(const QString &zoneName) const; +}; + +#endif // COCKATRICE_DECKLIST_NODE_TREE_H diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp index 05ee480a5..c06fb8268 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -41,7 +41,7 @@ void DeckListModel::rebuildTree() beginResetModel(); root->clearTree(); - InnerDecklistNode *listRoot = deckList->getRoot(); + InnerDecklistNode *listRoot = deckList->getTree()->getRoot(); for (int i = 0; i < listRoot->size(); i++) { auto *currentZone = dynamic_cast(listRoot->at(i)); @@ -313,7 +313,7 @@ bool DeckListModel::removeRows(int row, int count, const QModelIndex &parent) for (int i = 0; i < count; i++) { AbstractDecklistNode *toDelete = node->takeAt(row); if (auto *temp = dynamic_cast(toDelete)) { - deckList->deleteNode(temp->getDataNode()); + deckList->getTree()->deleteNode(temp->getDataNode()); } delete toDelete; } @@ -668,7 +668,7 @@ bool DeckListModel::isCardQuantityLegalForCurrentFormat(const CardInfoPtr cardIn void DeckListModel::refreshCardFormatLegalities() { - InnerDecklistNode *listRoot = deckList->getRoot(); + InnerDecklistNode *listRoot = deckList->getTree()->getRoot(); for (int i = 0; i < listRoot->size(); i++) { auto *currentZone = static_cast(listRoot->at(i)); 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 c03497bd5..7d8265d7a 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -202,7 +202,7 @@ public: * affects its hash. * * Slots: - * - rebuildTree(): rebuilds the model structure from the underlying DeckLoader. + * - rebuildTree(): rebuilds the model structure from the underlying node tree. */ class DeckListModel : public QAbstractItemModel { @@ -210,7 +210,7 @@ class DeckListModel : public QAbstractItemModel public slots: /** - * @brief Rebuilds the model tree from the underlying DeckLoader. + * @brief Rebuilds the model tree from the underlying node tree. * * This updates all indices and ensures the model reflects the current * state of the deck. From 9471adb4f7a4ec9de73a4430b2a20f5c1d8f5dfc Mon Sep 17 00:00:00 2001 From: tooomm Date: Mon, 15 Dec 2025 22:55:57 +0100 Subject: [PATCH 065/325] Add repo activity with top contributors acknowledgment (#6420) --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 87629d2c9..47fb3a159 100644 --- a/README.md +++ b/README.md @@ -81,8 +81,11 @@ You can also have a look at our `Todo List` in our [Code Documentation](https:// Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide. +#### Repository Activity +![Cockatrice Repo Analytics](https://repobeats.axiom.co/api/embed/c7cec938789a5bbaeb4182a028b4dbb96db8f181.svg "Cockatrice Repo Analytics by Repobeats") +
-Kudos to our amazing contributors ❤️ +Kudos to all our amazing contributors ❤️
From 1198db889109ff9d83df9f074b8a43807e6692a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 23:35:18 +0100 Subject: [PATCH 066/325] Bump actions/cache from 4 to 5 (#6424) --- .github/workflows/desktop-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 58b246071..06549702d 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -166,7 +166,7 @@ jobs: - name: Restore compiler cache (ccache) id: ccache_restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 env: BRANCH_NAME: ${{ github.head_ref || github.ref_name }} with: @@ -205,7 +205,7 @@ jobs: - name: Save compiler cache (ccache) if: github.ref == 'refs/heads/master' - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 with: path: ${{env.CACHE}} key: ${{ steps.ccache_restore.outputs.cache-primary-key }} From 64bb5355ff5da70b788d0203988a1faf192c8efc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 23:38:49 +0100 Subject: [PATCH 067/325] Bump peter-evans/create-pull-request from 7 to 8 (#6423) --- .github/workflows/translations-pull.yml | 2 +- .github/workflows/translations-push.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/translations-pull.yml b/.github/workflows/translations-pull.yml index 81d6f4e75..ed61e3b19 100644 --- a/.github/workflows/translations-pull.yml +++ b/.github/workflows/translations-pull.yml @@ -33,7 +33,7 @@ jobs: - name: Create pull request if: github.event_name != 'pull_request' id: create_pr - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 with: add-paths: | cockatrice/translations/*.ts diff --git a/.github/workflows/translations-push.yml b/.github/workflows/translations-push.yml index d201e7ad8..777e9e6ac 100644 --- a/.github/workflows/translations-push.yml +++ b/.github/workflows/translations-push.yml @@ -57,7 +57,7 @@ jobs: - name: Create pull request if: github.event_name != 'pull_request' id: create_pr - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 with: add-paths: | cockatrice/cockatrice_en@source.ts From cd443928663cdc7d7b05894af0959d41f44e5255 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Tue, 16 Dec 2025 13:03:14 +0100 Subject: [PATCH 068/325] Static helpers. (#6425) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 2 minutes Took 29 seconds Co-authored-by: Lukas Brübach --- .../archidekt_api_response_deck_entry_display_widget.cpp | 2 +- libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp | 2 +- .../libcockatrice/models/deck_list/deck_list_model.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp index 7a838d1ff..2998a03bc 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp @@ -16,7 +16,7 @@ #define ARCHIDEKT_DEFAULT_IMAGE "https://storage.googleapis.com/topdekt-user/images/archidekt_deck_card_shadow.jpg" -QString timeAgo(const QString ×tamp) +static QString timeAgo(const QString ×tamp) { QDateTime dt = QDateTime::fromString(timestamp, Qt::ISODate); diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index 54eaa3096..b453a57ef 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -164,7 +164,7 @@ bool DeckList::readElement(QXmlStreamReader *xml) return true; } -void writeMetadata(QXmlStreamWriter *xml, const DeckList::Metadata &metadata) +static void writeMetadata(QXmlStreamWriter *xml, const DeckList::Metadata &metadata) { xml->writeTextElement("lastLoadedTimestamp", metadata.lastLoadedTimestamp); xml->writeTextElement("deckname", metadata.name); 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 c06fb8268..992c33961 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -622,7 +622,7 @@ bool DeckListModel::isCardLegalForCurrentFormat(const CardInfoPtr cardInfo) return true; } -int maxAllowedForLegality(const FormatRules &format, const QString &legality) +static int maxAllowedForLegality(const FormatRules &format, const QString &legality) { for (const AllowedCount &c : format.allowedCounts) { if (c.label == legality) { From 41aca8467ab6898864c279eff9f0db9f14125b6d Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Tue, 16 Dec 2025 13:12:51 +0100 Subject: [PATCH 069/325] [CardInfoPicture] Defer enlargedPixmap creation until needed (#6426) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [CardInfoPicture] Defer enlargedPixmap creation until needed Took 4 minutes Took 1 minute * Disregard const_cast Took 2 minutes --------- Co-authored-by: Lukas Brübach --- .../cards/card_info_picture_widget.cpp | 30 ++++++++++++------- .../widgets/cards/card_info_picture_widget.h | 3 +- 2 files changed, 22 insertions(+), 11 deletions(-) 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 fc1b9ebe2..85d210e1a 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp @@ -39,10 +39,6 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool _hoverT setMouseTracking(true); } - enlargedPixmapWidget = new CardInfoPictureEnlargedWidget(this->window()); - enlargedPixmapWidget->hide(); - connect(this, &QObject::destroyed, enlargedPixmapWidget, &CardInfoPictureEnlargedWidget::deleteLater); - hoverTimer = new QTimer(this); hoverTimer->setSingleShot(true); connect(hoverTimer, &QTimer::timeout, this, &CardInfoPictureWidget::showEnlargedPixmap); @@ -277,7 +273,7 @@ void CardInfoPictureWidget::leaveEvent(QEvent *event) if (hoverToZoomEnabled) { hoverTimer->stop(); - enlargedPixmapWidget->hide(); + destroyEnlargedPixmapWidget(); } if (raiseOnEnter) { @@ -294,7 +290,7 @@ void CardInfoPictureWidget::moveEvent(QMoveEvent *event) QWidget::moveEvent(event); hoverTimer->stop(); - enlargedPixmapWidget->hide(); + destroyEnlargedPixmapWidget(); if (animation->state() == QAbstractAnimation::Running) { return; @@ -310,7 +306,7 @@ void CardInfoPictureWidget::mouseMoveEvent(QMouseEvent *event) { QWidget::mouseMoveEvent(event); - if (hoverToZoomEnabled && enlargedPixmapWidget->isVisible()) { + if (hoverToZoomEnabled && enlargedPixmapWidget && enlargedPixmapWidget->isVisible()) { const QPoint cursorPos = QCursor::pos(); const QRect screenGeometry = QGuiApplication::screenAt(cursorPos)->geometry(); const QSize widgetSize = enlargedPixmapWidget->size(); @@ -344,7 +340,7 @@ void CardInfoPictureWidget::mousePressEvent(QMouseEvent *event) void CardInfoPictureWidget::hideEvent(QHideEvent *event) { - enlargedPixmapWidget->hide(); + destroyEnlargedPixmapWidget(); QWidget::hideEvent(event); } @@ -444,12 +440,19 @@ QMenu *CardInfoPictureWidget::createAddToOpenDeckMenu() * If card information is available, the enlarged pixmap is loaded, positioned near the cursor, * and displayed. */ -void CardInfoPictureWidget::showEnlargedPixmap() const +void CardInfoPictureWidget::showEnlargedPixmap() { if (!exactCard) { return; } + // Lazy creation of the enlarged widget + if (!enlargedPixmapWidget) { + enlargedPixmapWidget = new CardInfoPictureEnlargedWidget(const_cast(this)->window()); + enlargedPixmapWidget->hide(); + connect(this, &QObject::destroyed, enlargedPixmapWidget, &CardInfoPictureEnlargedWidget::deleteLater); + } + const QSize enlargedSize(static_cast(size().width() * 2), static_cast(size().width() * aspectRatio * 2)); enlargedPixmapWidget->setCardPixmap(exactCard, enlargedSize); @@ -460,7 +463,6 @@ void CardInfoPictureWidget::showEnlargedPixmap() const int newX = cursorPos.x() + enlargedPixmapOffset; int newY = cursorPos.y() + enlargedPixmapOffset; - // Adjust if out of bounds if (newX + widgetSize.width() > screenGeometry.right()) { newX = cursorPos.x() - widgetSize.width() - enlargedPixmapOffset; } @@ -472,3 +474,11 @@ void CardInfoPictureWidget::showEnlargedPixmap() const enlargedPixmapWidget->show(); } + +void CardInfoPictureWidget::destroyEnlargedPixmapWidget() +{ + if (enlargedPixmapWidget) { + enlargedPixmapWidget->deleteLater(); + enlargedPixmapWidget = nullptr; + } +} diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h index 980d700f6..c9dbc47ab 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h @@ -63,7 +63,8 @@ protected: { return resizedPixmap; } - void showEnlargedPixmap() const; + void showEnlargedPixmap(); + void destroyEnlargedPixmapWidget(); private: ExactCard exactCard; From d47dc358856d21f26af246378badd5a52b5c5ce1 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Tue, 16 Dec 2025 13:39:19 +0100 Subject: [PATCH 070/325] [TabArchidekt] Set game format when importing (#6416) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [TabArchidekt] Set game format when importing Took 5 minutes * Move formats to file. Took 9 minutes Took 4 seconds --------- Co-authored-by: Lukas Brübach --- cockatrice/CMakeLists.txt | 1 + .../api_response/archidekt_formats.h | 227 ++++++++++++++++++ .../deck/archidekt_api_response_deck.h | 5 + ...idekt_api_response_deck_display_widget.cpp | 3 + 4 files changed, 236 insertions(+) create mode 100644 cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_formats.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index c9fa80e6b..4c3679011 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -230,6 +230,7 @@ set(cockatrice_SOURCES src/interface/widgets/tabs/abstract_tab_deck_editor.cpp src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp + src/interface/widgets/tabs/api/archidekt/api_response/archidekt_formats.h src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_formats.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_formats.h new file mode 100644 index 000000000..ac1b66e14 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/archidekt_formats.h @@ -0,0 +1,227 @@ +#ifndef COCKATRICE_ARCHIDEKT_FORMATS_H +#define COCKATRICE_ARCHIDEKT_FORMATS_H +#include +#include + +namespace ArchidektFormats +{ +enum class DeckFormat +{ + Standard = 0, + Modern = 1, + Commander = 2, + Legacy = 3, + Vintage = 4, + Pauper = 5, + Custom = 6, + Frontier = 7, + FutureStandard = 8, + PennyDreadful = 9, + Commander1v1 = 10, + DualCommander = 11, + Brawl = 12, + + // Values outside Archidekt range + Alchemy = 1000, + Historic = 1001, + Gladiator = 1002, + Oathbreaker = 1003, + OldSchool = 1004, + PauperCommander = 1005, + Pioneer = 1006, + PreDH = 1007, + Premodern = 1008, + StandardBrawl = 1009, + Timeless = 1010, + Unknown = 1011 +}; + +inline static QString formatToApiName(DeckFormat format) +{ + switch (format) { + case DeckFormat::Standard: + return "Standard"; + case DeckFormat::Modern: + return "Modern"; + case DeckFormat::Commander: + return "Commander"; + case DeckFormat::Legacy: + return "Legacy"; + case DeckFormat::Vintage: + return "Vintage"; + case DeckFormat::Pauper: + return "Pauper"; + case DeckFormat::Custom: + return "Custom"; + case DeckFormat::Frontier: + return "Frontier"; + case DeckFormat::FutureStandard: + return "Future Std"; + case DeckFormat::PennyDreadful: + return "Penny Dreadful"; + case DeckFormat::Commander1v1: + return "1v1 Commander"; + case DeckFormat::DualCommander: + return "Dual Commander"; + case DeckFormat::Brawl: + return "Brawl"; + + case DeckFormat::Alchemy: + return "Alchemy"; + case DeckFormat::Historic: + return "Historic"; + case DeckFormat::Gladiator: + return "Gladiator"; + case DeckFormat::Oathbreaker: + return "Oathbreaker"; + case DeckFormat::OldSchool: + return "Old School"; + case DeckFormat::PauperCommander: + return "Pauper Commander"; + case DeckFormat::Pioneer: + return "Pioneer"; + case DeckFormat::PreDH: + return "PreDH"; + case DeckFormat::Premodern: + return "Premodern"; + case DeckFormat::StandardBrawl: + return "Standard Brawl"; + case DeckFormat::Timeless: + return "Timeless"; + + default: + return "Unknown"; + } +} + +inline static DeckFormat apiNameToFormat(const QString &name) +{ + const QString n = name.trimmed(); + + if (n.compare("Standard", Qt::CaseInsensitive) == 0) + return DeckFormat::Standard; + if (n.compare("Modern", Qt::CaseInsensitive) == 0) + return DeckFormat::Modern; + if (n.compare("Commander", Qt::CaseInsensitive) == 0) + return DeckFormat::Commander; + if (n.compare("Legacy", Qt::CaseInsensitive) == 0) + return DeckFormat::Legacy; + if (n.compare("Vintage", Qt::CaseInsensitive) == 0) + return DeckFormat::Vintage; + if (n.compare("Pauper", Qt::CaseInsensitive) == 0) + return DeckFormat::Pauper; + if (n.compare("Custom", Qt::CaseInsensitive) == 0) + return DeckFormat::Custom; + if (n.compare("Frontier", Qt::CaseInsensitive) == 0) + return DeckFormat::Frontier; + if (n.compare("Future Std", Qt::CaseInsensitive) == 0) + return DeckFormat::FutureStandard; + if (n.compare("Penny Dreadful", Qt::CaseInsensitive) == 0) + return DeckFormat::PennyDreadful; + if (n.compare("1v1 Commander", Qt::CaseInsensitive) == 0) + return DeckFormat::Commander1v1; + if (n.compare("Dual Commander", Qt::CaseInsensitive) == 0) + return DeckFormat::DualCommander; + if (n.compare("Brawl", Qt::CaseInsensitive) == 0) + return DeckFormat::Brawl; + + if (n.compare("Alchemy", Qt::CaseInsensitive) == 0) + return DeckFormat::Alchemy; + if (n.compare("Historic", Qt::CaseInsensitive) == 0) + return DeckFormat::Historic; + if (n.compare("Gladiator", Qt::CaseInsensitive) == 0) + return DeckFormat::Gladiator; + if (n.compare("Oathbreaker", Qt::CaseInsensitive) == 0) + return DeckFormat::Oathbreaker; + if (n.compare("Old School", Qt::CaseInsensitive) == 0) + return DeckFormat::OldSchool; + if (n.compare("Pauper Commander", Qt::CaseInsensitive) == 0) + return DeckFormat::PauperCommander; + if (n.compare("Pioneer", Qt::CaseInsensitive) == 0) + return DeckFormat::Pioneer; + if (n.compare("PreDH", Qt::CaseInsensitive) == 0) + return DeckFormat::PreDH; + if (n.compare("Premodern", Qt::CaseInsensitive) == 0) + return DeckFormat::Premodern; + if (n.compare("Standard Brawl", Qt::CaseInsensitive) == 0) + return DeckFormat::StandardBrawl; + if (n.compare("Timeless", Qt::CaseInsensitive) == 0) + return DeckFormat::Timeless; + + return DeckFormat::Unknown; +} + +inline static QString formatToCockatriceName(DeckFormat format) +{ + switch (format) { + case DeckFormat::Standard: + return "standard"; + case DeckFormat::Modern: + return "modern"; + case DeckFormat::Commander: + return "commander"; + case DeckFormat::Legacy: + return "legacy"; + case DeckFormat::Vintage: + return "vintage"; + case DeckFormat::Pauper: + return "pauper"; + case DeckFormat::Brawl: + return "brawl"; + case DeckFormat::PennyDreadful: + return "penny"; + case DeckFormat::FutureStandard: + return "future"; + case DeckFormat::Commander1v1: + return "duel"; + case DeckFormat::DualCommander: + return "duel"; + case DeckFormat::Alchemy: + return "alchemy"; + case DeckFormat::Historic: + return "historic"; + case DeckFormat::Gladiator: + return "gladiator"; + case DeckFormat::Oathbreaker: + return "oathbreaker"; + case DeckFormat::OldSchool: + return "oldschool"; + case DeckFormat::PauperCommander: + return "paupercommander"; + case DeckFormat::Pioneer: + return "pioneer"; + case DeckFormat::PreDH: + return "predh"; + case DeckFormat::Premodern: + return "premodern"; + case DeckFormat::StandardBrawl: + return "standardbrawl"; + case DeckFormat::Timeless: + return "timeless"; + + default: + return {}; + } +} + +inline static DeckFormat cockatriceNameToFormat(const QString &apiName) +{ + static const QHash map = { + {"standard", DeckFormat::Standard}, {"modern", DeckFormat::Modern}, + {"commander", DeckFormat::Commander}, {"legacy", DeckFormat::Legacy}, + {"vintage", DeckFormat::Vintage}, {"pauper", DeckFormat::Pauper}, + {"brawl", DeckFormat::Brawl}, {"penny", DeckFormat::PennyDreadful}, + {"future", DeckFormat::FutureStandard}, {"duel", DeckFormat::Commander1v1}, + {"alchemy", DeckFormat::Alchemy}, {"historic", DeckFormat::Historic}, + {"gladiator", DeckFormat::Gladiator}, {"oathbreaker", DeckFormat::Oathbreaker}, + {"oldschool", DeckFormat::OldSchool}, {"paupercommander", DeckFormat::PauperCommander}, + {"pioneer", DeckFormat::Pioneer}, {"predh", DeckFormat::PreDH}, + {"premodern", DeckFormat::Premodern}, {"standardbrawl", DeckFormat::StandardBrawl}, + {"timeless", DeckFormat::Timeless}}; + + return map.value(apiName.toLower(), DeckFormat::Unknown); +} + +} // namespace ArchidektFormats + +#endif // COCKATRICE_ARCHIDEKT_FORMATS_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h index 8cb81cac3..fce437751 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.h @@ -39,6 +39,11 @@ public: return name; }; + int getDeckFormat() const + { + return deckFormat; + } + private: int id; QString name; 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 63d732c9a..8745ca175 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 @@ -6,6 +6,7 @@ #include "../../../../cards/card_size_widget.h" #include "../../../../cards/deck_card_zone_display_widget.h" #include "../../../../visual_deck_editor/visual_deck_display_options_widget.h" +#include "../api_response/archidekt_formats.h" #include "../api_response/deck/archidekt_api_response_deck.h" #include @@ -93,6 +94,8 @@ void ArchidektApiResponseDeckDisplayWidget::actOpenInDeckEditor() loader->getDeckList()->loadFromString_Native(model->getDeckList()->writeToString_Native()); loader->getDeckList()->setName(response.getDeckName()); + loader->getDeckList()->setGameFormat( + ArchidektFormats::formatToCockatriceName(ArchidektFormats::DeckFormat(response.getDeckFormat() - 1))); emit openInDeckEditor(loader); } From ebb02b27b27fe579449502615b24dd58faa2b49c Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 18 Dec 2025 09:01:06 +0100 Subject: [PATCH 071/325] [Card DB] Properly pass along set priority controller to parsers (#6430) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Card DB] Properly pass along set priority controller to parsers Took 16 minutes Took 35 seconds * More adjustments. Took 13 minutes --------- Co-authored-by: Lukas Brübach --- dbconverter/src/main.h | 4 +++- .../libcockatrice/card/database/card_database.cpp | 2 +- .../card/database/card_database_loader.cpp | 7 ++++--- .../card/database/card_database_loader.h | 4 +++- .../card/database/parser/card_database_parser.cpp | 6 +++++- .../card/database/parser/card_database_parser.h | 2 ++ .../card/database/parser/cockatrice_xml_3.cpp | 5 +++++ .../card/database/parser/cockatrice_xml_3.h | 2 +- .../card/database/parser/cockatrice_xml_4.cpp | 5 +++-- .../card/database/parser/cockatrice_xml_4.h | 3 ++- .../interface_card_set_priority_controller.h | 2 ++ .../interfaces/noop_card_set_priority_controller.h | 12 ++++++------ oracle/src/oracleimporter.cpp | 2 +- 13 files changed, 38 insertions(+), 18 deletions(-) diff --git a/dbconverter/src/main.h b/dbconverter/src/main.h index d6e734316..72dbe939d 100644 --- a/dbconverter/src/main.h +++ b/dbconverter/src/main.h @@ -1,6 +1,8 @@ #ifndef MAIN_H #define MAIN_H +#include "libcockatrice/interfaces/noop_card_set_priority_controller.h" + #include #include #include @@ -26,7 +28,7 @@ public: bool saveCardDatabase(const QString &fileName) { - CockatriceXml4Parser parser(new NoopCardPreferenceProvider()); + CockatriceXml4Parser parser(new NoopCardPreferenceProvider(), new NoopCardSetPriorityController()); return parser.saveToFile(createDefaultMagicFormats(), sets, cards, fileName); } diff --git a/libcockatrice_card/libcockatrice/card/database/card_database.cpp b/libcockatrice_card/libcockatrice/card/database/card_database.cpp index de84ad814..5c4b408b3 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database.cpp @@ -21,7 +21,7 @@ CardDatabase::CardDatabase(QObject *parent, qRegisterMetaType("CardSetPtr"); // create loader and wire it up - loader = new CardDatabaseLoader(this, this, pathProvider, prefs); + loader = new CardDatabaseLoader(this, this, pathProvider, prefs, setPriorityController); // re-emit loader signals (so other code doesn't need to know about internals) connect(loader, &CardDatabaseLoader::loadingFinished, this, &CardDatabase::cardDatabaseLoadingFinished); connect(loader, &CardDatabaseLoader::loadingFailed, this, &CardDatabase::cardDatabaseLoadingFailed); diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp b/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp index 91bb4c741..716477a59 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database_loader.cpp @@ -12,12 +12,13 @@ CardDatabaseLoader::CardDatabaseLoader(QObject *parent, CardDatabase *db, ICardDatabasePathProvider *_pathProvider, - ICardPreferenceProvider *_preferenceProvider) + ICardPreferenceProvider *_preferenceProvider, + ICardSetPriorityController *_priorityController) : QObject(parent), database(db), pathProvider(_pathProvider) { // instantiate available parsers here and connect them to the database - availableParsers << new CockatriceXml4Parser(_preferenceProvider); - availableParsers << new CockatriceXml3Parser; + availableParsers << new CockatriceXml4Parser(_preferenceProvider, _priorityController); + availableParsers << new CockatriceXml3Parser(_priorityController); for (auto *p : availableParsers) { // connect parser outputs to the database adders diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_loader.h b/libcockatrice_card/libcockatrice/card/database/card_database_loader.h index f4e690428..861cc95b0 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_loader.h +++ b/libcockatrice_card/libcockatrice/card/database/card_database_loader.h @@ -6,6 +6,7 @@ #include #include #include +#include inline Q_LOGGING_CATEGORY(CardDatabaseLoadingLog, "card_database.loading"); inline Q_LOGGING_CATEGORY(CardDatabaseLoadingSuccessOrFailureLog, "card_database.loading.success_or_failure"); @@ -52,7 +53,8 @@ public: explicit CardDatabaseLoader(QObject *parent, CardDatabase *db, ICardDatabasePathProvider *pathProvider, - ICardPreferenceProvider *preferenceProvider); + ICardPreferenceProvider *preferenceProvider, + ICardSetPriorityController *_priorityController); /** @brief Destructor cleans up allocated parsers. */ ~CardDatabaseLoader() override; diff --git a/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.cpp b/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.cpp index e9686f816..f7e6bbfcf 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.cpp +++ b/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.cpp @@ -4,6 +4,10 @@ SetNameMap ICardDatabaseParser::sets; +ICardDatabaseParser::ICardDatabaseParser(ICardSetPriorityController *_cardSetPriorityController) + : cardSetPriorityController(_cardSetPriorityController) +{ +} void ICardDatabaseParser::clearSetlist() { sets.clear(); @@ -19,7 +23,7 @@ CardSetPtr ICardDatabaseParser::internalAddSet(const QString &setName, return sets.value(setName); } - CardSetPtr newSet = CardSet::newInstance(new NoopCardSetPriorityController(), setName); + CardSetPtr newSet = CardSet::newInstance(cardSetPriorityController, setName); newSet->setLongName(longName); newSet->setSetType(setType); newSet->setReleaseDate(releaseDate); diff --git a/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.h b/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.h index 93d46a3cc..a8eceab5a 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.h +++ b/libcockatrice_card/libcockatrice/card/database/parser/card_database_parser.h @@ -20,6 +20,7 @@ class ICardDatabaseParser : public QObject { Q_OBJECT public: + ICardDatabaseParser(ICardSetPriorityController *cardSetPriorityController); ~ICardDatabaseParser() override = default; /** @@ -59,6 +60,7 @@ public: protected: /** @brief Cached global list of sets shared between all parsers. */ static SetNameMap sets; + ICardSetPriorityController *cardSetPriorityController; /** * @brief Internal helper to add a set to the global set cache. 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 9df396c74..ba27d63c4 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp @@ -14,6 +14,11 @@ #define COCKATRICE_XML3_SCHEMALOCATION \ "https://raw.githubusercontent.com/Cockatrice/Cockatrice/master/doc/carddatabase_v3/cards.xsd" +CockatriceXml3Parser::CockatriceXml3Parser(ICardSetPriorityController *_cardSetPriorityController) + : ICardDatabaseParser(_cardSetPriorityController) +{ +} + bool CockatriceXml3Parser::getCanParseFile(const QString &fileName, QIODevice &device) { qCInfo(CockatriceXml3Log) << "Trying to parse: " << fileName; diff --git a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.h b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.h index c3a261739..a01b705aa 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.h +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.h @@ -26,7 +26,7 @@ class CockatriceXml3Parser : public ICardDatabaseParser { Q_OBJECT public: - CockatriceXml3Parser() = default; + CockatriceXml3Parser(ICardSetPriorityController *cardSetPriorityController); ~CockatriceXml3Parser() override = default; /** 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 0d46aad0f..cc0220526 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp @@ -14,8 +14,9 @@ #define COCKATRICE_XML4_SCHEMALOCATION \ "https://raw.githubusercontent.com/Cockatrice/Cockatrice/master/doc/carddatabase_v4/cards.xsd" -CockatriceXml4Parser::CockatriceXml4Parser(ICardPreferenceProvider *_cardPreferenceProvider) - : cardPreferenceProvider(_cardPreferenceProvider) +CockatriceXml4Parser::CockatriceXml4Parser(ICardPreferenceProvider *_cardPreferenceProvider, + ICardSetPriorityController *_cardSetPriorityController) + : ICardDatabaseParser(_cardSetPriorityController), cardPreferenceProvider(_cardPreferenceProvider) { } diff --git a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.h b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.h index 5b47e9090..189e79535 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.h +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.h @@ -29,7 +29,8 @@ class CockatriceXml4Parser : public ICardDatabaseParser { Q_OBJECT public: - explicit CockatriceXml4Parser(ICardPreferenceProvider *cardPreferenceProvider); + explicit CockatriceXml4Parser(ICardPreferenceProvider *cardPreferenceProvider, + ICardSetPriorityController *cardSetPriorityController); ~CockatriceXml4Parser() override = default; /** diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h index a4f3184cc..46a5897f7 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h @@ -1,6 +1,8 @@ #ifndef COCKATRICE_INTERFACE_CARD_SET_PRIORITY_CONTROLLER_H #define COCKATRICE_INTERFACE_CARD_SET_PRIORITY_CONTROLLER_H +#include + class ICardSetPriorityController { public: diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h index b2410781c..949ab5a91 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h @@ -6,25 +6,25 @@ class NoopCardSetPriorityController : public ICardSetPriorityController { public: - void setSortKey(QString /* shortName */, unsigned int /* sortKey */) + void setSortKey(QString /* shortName */, unsigned int /* sortKey */) override { } - void setEnabled(QString /* shortName */, bool /* enabled */) + void setEnabled(QString /* shortName */, bool /* enabled */) override { } - void setIsKnown(QString /* shortName */, bool /* isknown */) + void setIsKnown(QString /* shortName */, bool /* isknown */) override { } - unsigned int getSortKey(QString /* shortName */) + unsigned int getSortKey(QString /* shortName */) override { return 0; } - bool isEnabled(QString /* shortName */) + bool isEnabled(QString /* shortName */) override { return true; } - bool isKnown(QString /* shortName */) + bool isKnown(QString /* shortName */) override { return true; } diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 813df49ae..4b089846f 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -565,7 +565,7 @@ int OracleImporter::startImport() bool OracleImporter::saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion) { - CockatriceXml4Parser parser(new NoopCardPreferenceProvider()); + CockatriceXml4Parser parser(new NoopCardPreferenceProvider(), new NoopCardSetPriorityController()); return parser.saveToFile(createDefaultMagicFormats(), sets, cards, fileName, sourceUrl, sourceVersion); } From ad06a81765397368dffceb4e7993b6cf5b2cb405 Mon Sep 17 00:00:00 2001 From: tooomm Date: Fri, 19 Dec 2025 23:51:56 +0100 Subject: [PATCH 072/325] Enable internal documentation in Doxyfile (#6432) --- Doxyfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doxyfile b/Doxyfile index d5a3a4b7a..a2a5fb522 100644 --- a/Doxyfile +++ b/Doxyfile @@ -636,7 +636,7 @@ HIDE_IN_BODY_DOCS = NO # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. -INTERNAL_DOCS = NO +INTERNAL_DOCS = YES # With the correct setting of option CASE_SENSE_NAMES Doxygen will better be # able to match the capabilities of the underlying filesystem. In case the From 715ee1d6fed8298b1dd19dd40817114ccf03ab2d Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Fri, 19 Dec 2025 23:55:19 +0100 Subject: [PATCH 073/325] Revert "Enable internal documentation in Doxyfile (#6432)" (#6433) This reverts commit ad06a81765397368dffceb4e7993b6cf5b2cb405. --- Doxyfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doxyfile b/Doxyfile index a2a5fb522..d5a3a4b7a 100644 --- a/Doxyfile +++ b/Doxyfile @@ -636,7 +636,7 @@ HIDE_IN_BODY_DOCS = NO # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. -INTERNAL_DOCS = YES +INTERNAL_DOCS = NO # With the correct setting of option CASE_SENSE_NAMES Doxygen will better be # able to match the capabilities of the underlying filesystem. In case the From 367507e0548d1a941368a9f619275d1e092d6e34 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 20 Dec 2025 04:25:30 -0800 Subject: [PATCH 074/325] [DeckListModel] Refactor: Don't access underlying decklist for iteration (#6427) * [DeckListModel] Refactor: Don't access underlying decklist for iteration * add docs * extract method --- .../deck_list_statistics_analyzer.cpp | 40 +++++++---------- .../dialogs/dlg_select_set_for_cards.cpp | 44 +++++++------------ .../printing_selector/card_amount_widget.cpp | 15 ++----- ...al_database_display_name_filter_widget.cpp | 10 ++--- .../visual_deck_editor_sample_hand_widget.cpp | 16 +------ .../models/deck_list/deck_list_model.cpp | 32 +++++++------- .../models/deck_list/deck_list_model.h | 16 +++++++ 7 files changed, 73 insertions(+), 100 deletions(-) diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp index a5ddf37a1..3ca18d21c 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp @@ -21,32 +21,26 @@ void DeckListStatisticsAnalyzer::update() manaCurveMap.clear(); manaDevotionMap.clear(); - auto nodes = model->getDeckList()->getCardNodes(); + QList cards = model->getCards(); - for (auto *node : nodes) { - CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName()); - if (!info) - continue; + for (const ExactCard &card : cards) { + // ---- Mana curve ---- + if (config.computeManaCurve) { + manaCurveMap[card.getInfo().getCmc().toInt()]++; + } - for (int i = 0; i < node->getNumber(); ++i) { - // ---- Mana curve ---- - if (config.computeManaCurve) { - manaCurveMap[info->getCmc().toInt()]++; - } + // ---- Mana base ---- + if (config.computeManaBase) { + auto mana = determineManaProduction(card.getInfo().getText()); + for (auto it = mana.begin(); it != mana.end(); ++it) + manaBaseMap[it.key()] += it.value(); + } - // ---- Mana base ---- - if (config.computeManaBase) { - auto mana = determineManaProduction(info->getText()); - for (auto it = mana.begin(); it != mana.end(); ++it) - manaBaseMap[it.key()] += it.value(); - } - - // ---- Devotion ---- - if (config.computeDevotion) { - auto devo = countManaSymbols(info->getManaCost()); - for (auto &d : devo) - manaDevotionMap[d.first] += d.second; - } + // ---- Devotion ---- + if (config.computeDevotion) { + auto devo = countManaSymbols(card.getInfo().getManaCost()); + for (auto &d : devo) + manaDevotionMap[d.first] += d.second; } } 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 2bfe650c3..52768e0e7 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 @@ -224,14 +224,10 @@ QMap DlgSelectSetForCards::getSetsForCards() if (!model) return setCounts; - DeckList *decklist = model->getDeckList(); - if (!decklist) - return setCounts; + QList cardNames = model->getCardNames(); - QList cardsInDeck = decklist->getCardNodes(); - - for (auto currentCard : cardsInDeck) { - CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(currentCard->getName()); + for (auto cardName : cardNames) { + CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName); if (!infoPtr) continue; @@ -267,19 +263,15 @@ void DlgSelectSetForCards::updateCardLists() } } - DeckList *decklist = model->getDeckList(); - if (!decklist) - return; + QList cardNames = model->getCardNames(); - QList cardsInDeck = decklist->getCardNodes(); - - for (auto currentCard : cardsInDeck) { + for (auto cardName : cardNames) { bool found = false; QString foundSetName; // Check across all sets if the card is present for (auto it = selectedCardsBySet.begin(); it != selectedCardsBySet.end(); ++it) { - if (it.value().contains(currentCard->getName())) { + if (it.value().contains(cardName)) { found = true; foundSetName = it.key(); // Store the set name where it was found break; // Stop at the first match @@ -288,16 +280,16 @@ void DlgSelectSetForCards::updateCardLists() if (!found) { // The card was not in any selected set - ExactCard card = CardDatabaseManager::query()->getCard({currentCard->getName()}); + ExactCard card = CardDatabaseManager::query()->getCard({cardName}); CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(uneditedCardsFlowWidget); picture_widget->setCard(card); uneditedCardsFlowWidget->addWidget(picture_widget); } else { - ExactCard card = CardDatabaseManager::query()->getCard( - {currentCard->getName(), CardDatabaseManager::getInstance() - ->query() - ->getSpecificPrinting(currentCard->getName(), foundSetName, "") - .getUuid()}); + ExactCard card = + CardDatabaseManager::query()->getCard({cardName, CardDatabaseManager::getInstance() + ->query() + ->getSpecificPrinting(cardName, foundSetName, "") + .getUuid()}); CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(modifiedCardsFlowWidget); picture_widget->setCard(card); modifiedCardsFlowWidget->addWidget(picture_widget); @@ -356,20 +348,16 @@ QMap DlgSelectSetForCards::getCardsForSets() if (!model) return setCards; - DeckList *decklist = model->getDeckList(); - if (!decklist) - return setCards; + QList cardNames = model->getCardNames(); - QList cardsInDeck = decklist->getCardNodes(); - - for (auto currentCard : cardsInDeck) { - CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(currentCard->getName()); + for (auto cardName : cardNames) { + CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName); if (!infoPtr) continue; SetToPrintingsMap setMap = infoPtr->getSets(); for (auto it = setMap.begin(); it != setMap.end(); ++it) { - setCards[it.key()].append(currentCard->getName()); + setCards[it.key()].append(cardName); } } diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index 0d0195d34..3d519e595 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -306,19 +306,12 @@ int CardAmountWidget::countCardsInZone(const QString &deckZone) return -1; } - DeckList *decklist = deckModel->getDeckList(); - if (!decklist) { - return -1; - } - - QList cardsInDeck = decklist->getCardNodes({deckZone}); + QList cards = deckModel->getCardsForZone(deckZone); int count = 0; - for (auto currentCard : cardsInDeck) { - for (int k = 0; k < currentCard->getNumber(); ++k) { - if (currentCard->getCardProviderId() == rootCard.getPrinting().getProperty("uuid")) { - count++; - } + for (auto currentCard : cards) { + if (currentCard.getPrinting().getUuid() == rootCard.getPrinting().getProperty("uuid")) { + count++; } } diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp index 9bc506056..b43b11f11 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp @@ -64,14 +64,10 @@ void VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck() if (!deckListModel) return; - DeckList *decklist = deckListModel->getDeckList(); - if (!decklist) - return; - QList cardsInDeck = decklist->getCardNodes(); - - for (auto currentCard : cardsInDeck) { - createNameFilter(currentCard->getName()); + QList cardNames = deckListModel->getCardNames(); + for (auto cardName : cardNames) { + createNameFilter(cardName); } updateFilterModel(); diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp index 778706f9a..0badb76ff 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp @@ -76,25 +76,11 @@ void VisualDeckEditorSampleHandWidget::updateDisplay() QList VisualDeckEditorSampleHandWidget::getRandomCards(int amountToGet) { - QList mainDeckCards; QList randomCards; if (!deckListModel) return randomCards; - DeckList *decklist = deckListModel->getDeckList(); - if (!decklist) - return randomCards; - QList cardsInDeck = decklist->getCardNodes({DECK_ZONE_MAIN}); - - // Collect all cards in the main deck, allowing duplicates based on their count - for (auto currentCard : cardsInDeck) { - for (int k = 0; k < currentCard->getNumber(); ++k) { - ExactCard card = CardDatabaseManager::query()->getCard(currentCard->toCardRef()); - if (card) { - mainDeckCards.append(card); - } - } - } + QList mainDeckCards = deckListModel->getCardsForZone(DECK_ZONE_MAIN); if (mainDeckCards.isEmpty()) return randomCards; 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 992c33961..8859a31fe 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -561,10 +561,8 @@ void DeckListModel::setDeckList(DeckList *_deck) rebuildTree(); } -QList DeckListModel::getCards() const +static QList cardNodesToExactCards(QList nodes) { - auto nodes = deckList->getCardNodes(); - QList cards; for (auto node : nodes) { ExactCard card = CardDatabaseManager::query()->getCard(node->toCardRef()); @@ -580,23 +578,26 @@ QList DeckListModel::getCards() const return cards; } +QList DeckListModel::getCards() const +{ + auto nodes = deckList->getCardNodes(); + return cardNodesToExactCards(nodes); +} + QList DeckListModel::getCardsForZone(const QString &zoneName) const { auto nodes = deckList->getCardNodes({zoneName}); + return cardNodesToExactCards(nodes); +} - QList cards; - for (auto node : nodes) { - ExactCard card = CardDatabaseManager::query()->getCard(node->toCardRef()); - if (card) { - for (int k = 0; k < node->getNumber(); ++k) { - cards.append(card); - } - } else { - qDebug() << "Card not found in database!"; - } - } +QList DeckListModel::getCardNames() const +{ + auto nodes = deckList->getCardNodes(); - return cards; + QList names; + std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(names), [](auto node) { return node->getName(); }); + + return names; } QList DeckListModel::getZones() const @@ -632,7 +633,6 @@ static int maxAllowedForLegality(const FormatRules &format, const QString &legal return -1; // unknown legality → treat as illegal } - bool DeckListModel::isCardQuantityLegalForCurrentFormat(const CardInfoPtr cardInfo, int quantity) { auto formatRules = CardDatabaseManager::query()->getFormat(deckList->getGameFormat()); 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 7d8265d7a..6e8882084 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -309,9 +309,25 @@ public: } void setDeckList(DeckList *_deck); + /** + * @brief Creates a list consisting of the entries of the model mapped into ExactCards (with each entry looked up + * in the card database). + * If a card node has number > 1, it will be added that many times to the list. + * If an entry's card is not found in the card database, that entry will be left out of the list. + * @return An ordered list of ExactCards + */ [[nodiscard]] QList getCards() const; [[nodiscard]] QList getCardsForZone(const QString &zoneName) const; + + /** + * @brief Gets a deduplicated list of all card names that appear in the model + */ + [[nodiscard]] QList getCardNames() const; + /** + * @brief Gets a list of all zone names that appear in the model + */ [[nodiscard]] QList getZones() const; + bool isCardLegalForCurrentFormat(CardInfoPtr cardInfo); bool isCardQuantityLegalForCurrentFormat(CardInfoPtr cardInfo, int quantity); void refreshCardFormatLegalities(); From d6db21419c5e8d08dafbbcd4e19aab38f58f13af Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 20 Dec 2025 04:39:00 -0800 Subject: [PATCH 075/325] [Refactor] Pass around LoadedDeck instead of DeckLoader (#6422) --- .../parsers/interface_json_deck_parser.h | 34 +++++------ cockatrice/src/filters/deck_filter_string.cpp | 6 +- .../src/game/deckview/deck_view_container.cpp | 23 ++++---- .../src/game/deckview/deck_view_container.h | 4 +- .../src/game/player/menu/utility_menu.cpp | 6 +- cockatrice/src/game/player/player.cpp | 7 +-- cockatrice/src/game/player/player.h | 10 ++-- cockatrice/src/game/player/player_actions.cpp | 2 +- .../src/interface/deck_loader/deck_loader.cpp | 46 +++++++-------- .../src/interface/deck_loader/deck_loader.h | 28 ++++----- .../src/interface/deck_loader/loaded_deck.h | 4 +- .../deck_preview_card_picture_widget.cpp | 1 - .../deck_editor_deck_dock_widget.cpp | 15 +++-- .../deck_editor_deck_dock_widget.h | 2 +- .../dialogs/dlg_load_deck_from_clipboard.cpp | 31 +++++----- .../dialogs/dlg_load_deck_from_clipboard.h | 23 ++++---- .../dialogs/dlg_load_deck_from_website.cpp | 8 +-- .../dialogs/dlg_load_deck_from_website.h | 4 +- .../interface/widgets/general/home_widget.cpp | 19 +++--- .../interface/widgets/general/home_widget.h | 4 +- .../widgets/tabs/abstract_tab_deck_editor.cpp | 58 +++++++++---------- .../widgets/tabs/abstract_tab_deck_editor.h | 8 +-- ...idekt_api_response_deck_display_widget.cpp | 12 ++-- ...chidekt_api_response_deck_display_widget.h | 8 +-- .../average_deck/edhrec_deck_api_response.cpp | 4 +- .../average_deck/edhrec_deck_api_response.h | 2 +- .../tabs/api/edhrec/tab_edhrec_main.cpp | 2 +- .../interface/widgets/tabs/tab_deck_editor.h | 1 - .../widgets/tabs/tab_deck_storage.cpp | 26 +++++---- .../interface/widgets/tabs/tab_deck_storage.h | 4 +- .../src/interface/widgets/tabs/tab_game.cpp | 9 ++- .../src/interface/widgets/tabs/tab_game.h | 3 +- .../interface/widgets/tabs/tab_supervisor.cpp | 20 +++---- .../interface/widgets/tabs/tab_supervisor.h | 6 +- .../tab_deck_storage_visual.cpp | 6 +- .../tab_deck_storage_visual.h | 4 +- ...al_database_display_name_filter_widget.cpp | 2 +- .../deck_preview_deck_tags_display_widget.cpp | 4 +- .../deck_preview/deck_preview_widget.cpp | 42 +++++++------- .../deck_preview/deck_preview_widget.h | 2 +- ...ual_deck_storage_folder_display_widget.cpp | 2 +- .../visual_deck_storage_sort_widget.cpp | 9 ++- .../visual_deck_storage_tag_filter_widget.cpp | 4 +- .../visual_deck_storage_widget.h | 2 +- 44 files changed, 253 insertions(+), 264 deletions(-) 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 e0fe4967a..e19e4f4e4 100644 --- a/cockatrice/src/client/network/parsers/interface_json_deck_parser.h +++ b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h @@ -18,21 +18,21 @@ class IJsonDeckParser public: virtual ~IJsonDeckParser() = default; - virtual DeckLoader *parse(const QJsonObject &obj) = 0; + virtual DeckList parse(const QJsonObject &obj) = 0; }; class ArchidektJsonParser : public IJsonDeckParser { public: - DeckLoader *parse(const QJsonObject &obj) override + DeckList parse(const QJsonObject &obj) override { - DeckLoader *loader = new DeckLoader(nullptr); + DeckList deckList; QString deckName = obj.value("name").toString(); QString deckDescription = obj.value("description").toString(); - loader->getDeckList()->setName(deckName); - loader->getDeckList()->setComments(deckDescription); + deckList.setName(deckName); + deckList.setComments(deckDescription); QString outputText; QTextStream outStream(&outputText); @@ -49,25 +49,25 @@ public: outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n'; } - loader->getDeckList()->loadFromStream_Plain(outStream, false); - loader->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); + deckList.loadFromStream_Plain(outStream, false); + deckList.forEachCard(CardNodeFunction::ResolveProviderId()); - return loader; + return deckList; } }; class MoxfieldJsonParser : public IJsonDeckParser { public: - DeckLoader *parse(const QJsonObject &obj) override + DeckList parse(const QJsonObject &obj) override { - DeckLoader *loader = new DeckLoader(nullptr); + DeckList deckList; QString deckName = obj.value("name").toString(); QString deckDescription = obj.value("description").toString(); - loader->getDeckList()->setName(deckName); - loader->getDeckList()->setComments(deckDescription); + deckList.setName(deckName); + deckList.setComments(deckDescription); QString outputText; QTextStream outStream(&outputText); @@ -96,8 +96,8 @@ public: outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n'; } - loader->getDeckList()->loadFromStream_Plain(outStream, false); - loader->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); + deckList.loadFromStream_Plain(outStream, false); + deckList.forEachCard(CardNodeFunction::ResolveProviderId()); QJsonObject commandersObj = obj.value("commanders").toObject(); if (!commandersObj.isEmpty()) { @@ -108,12 +108,12 @@ public: QString collectorNumber = cardData.value("cn").toString(); QString providerId = cardData.value("scryfall_id").toString(); - loader->getDeckList()->setBannerCard({commanderName, providerId}); - loader->getDeckList()->addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId); + deckList.setBannerCard({commanderName, providerId}); + deckList.addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId); } } - return loader; + return deckList; } }; diff --git a/cockatrice/src/filters/deck_filter_string.cpp b/cockatrice/src/filters/deck_filter_string.cpp index ed1a24322..5cce5d323 100644 --- a/cockatrice/src/filters/deck_filter_string.cpp +++ b/cockatrice/src/filters/deck_filter_string.cpp @@ -119,7 +119,7 @@ static void setupParserRules() return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) -> bool { int count = 0; - auto cardNodes = deck->deckLoader->getDeckList()->getCardNodes(); + auto cardNodes = deck->deckLoader->getDeck().deckList.getCardNodes(); for (auto node : cardNodes) { auto cardInfoPtr = CardDatabaseManager::query()->getCardInfo(node->getName()); if (!cardInfoPtr.isNull() && cardFilter.check(cardInfoPtr)) { @@ -139,7 +139,7 @@ static void setupParserRules() search["DeckNameQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { auto name = std::any_cast(sv[0]); return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) { - return deck->deckLoader->getDeckList()->getName().contains(name, Qt::CaseInsensitive); + return deck->deckLoader->getDeck().deckList.getName().contains(name, Qt::CaseInsensitive); }; }; @@ -161,7 +161,7 @@ static void setupParserRules() search["FormatQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { auto format = std::any_cast(sv[0]); return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) { - auto gameFormat = deck->deckLoader->getDeckList()->getGameFormat(); + auto gameFormat = deck->deckLoader->getDeck().deckList.getGameFormat(); return QString::compare(format, gameFormat, Qt::CaseInsensitive) == 0; }; }; diff --git a/cockatrice/src/game/deckview/deck_view_container.cpp b/cockatrice/src/game/deckview/deck_view_container.cpp index 5d4a07bdf..c7876a12f 100644 --- a/cockatrice/src/game/deckview/deck_view_container.cpp +++ b/cockatrice/src/game/deckview/deck_view_container.cpp @@ -269,12 +269,12 @@ void DeckViewContainer::loadDeckFromFile(const QString &filePath) return; } - loadDeckFromDeckLoader(&deck); + loadDeckFromDeckList(deck.getDeck().deckList); } -void DeckViewContainer::loadDeckFromDeckLoader(DeckLoader *deck) +void DeckViewContainer::loadDeckFromDeckList(const DeckList &deck) { - QString deckString = deck->getDeckList()->writeToString_Native(); + QString deckString = deck.writeToString_Native(); if (deckString.length() > MAX_FILE_LENGTH) { QMessageBox::critical(this, tr("Error"), tr("Deck is greater than maximum file size.")); @@ -308,8 +308,8 @@ void DeckViewContainer::loadFromClipboard() return; } - DeckLoader *deck = dlg.getDeckList(); - loadDeckFromDeckLoader(deck); + DeckList deck = dlg.getDeckList(); + loadDeckFromDeckList(deck); } void DeckViewContainer::loadFromWebsite() @@ -320,16 +320,15 @@ void DeckViewContainer::loadFromWebsite() return; } - DeckLoader *deck = dlg.getDeck(); - loadDeckFromDeckLoader(deck); + DeckList deck = dlg.getDeck(); + loadDeckFromDeckList(deck); } void DeckViewContainer::deckSelectFinished(const Response &r) { const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext); - DeckLoader newDeck(this, new DeckList(QString::fromStdString(resp.deck()))); - CardPictureLoader::cacheCardPixmaps( - CardDatabaseManager::query()->getCards(newDeck.getDeckList()->getCardRefList())); + DeckList newDeck = DeckList(QString::fromStdString(resp.deck())); + CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(newDeck.getCardRefList())); setDeck(newDeck); switchToDeckLoadedView(); } @@ -410,8 +409,8 @@ void DeckViewContainer::setSideboardLocked(bool locked) deckView->resetSideboardPlan(); } -void DeckViewContainer::setDeck(DeckLoader &deck) +void DeckViewContainer::setDeck(const DeckList &deck) { - deckView->setDeck(*deck.getDeckList()); + deckView->setDeck(deck); switchToDeckLoadedView(); } \ No newline at end of file diff --git a/cockatrice/src/game/deckview/deck_view_container.h b/cockatrice/src/game/deckview/deck_view_container.h index 620929789..6d685cd79 100644 --- a/cockatrice/src/game/deckview/deck_view_container.h +++ b/cockatrice/src/game/deckview/deck_view_container.h @@ -85,12 +85,12 @@ public: void setReadyStart(bool ready); void readyAndUpdate(); void setSideboardLocked(bool locked); - void setDeck(DeckLoader &deck); + void setDeck(const DeckList &deck); void setVisualDeckStorageExists(bool exists); public slots: void loadDeckFromFile(const QString &filePath); - void loadDeckFromDeckLoader(DeckLoader *deck); + void loadDeckFromDeckList(const DeckList &deck); }; #endif // DECK_VIEW_CONTAINER_H diff --git a/cockatrice/src/game/player/menu/utility_menu.cpp b/cockatrice/src/game/player/menu/utility_menu.cpp index ab14d629a..37bdcbbaf 100644 --- a/cockatrice/src/game/player/menu/utility_menu.cpp +++ b/cockatrice/src/game/player/menu/utility_menu.cpp @@ -60,13 +60,13 @@ void UtilityMenu::populatePredefinedTokensMenu() clear(); setEnabled(false); predefinedTokens.clear(); - DeckLoader *_deck = player->getDeck(); + const DeckList &deckList = player->getDeck(); - if (!_deck) { + if (deckList.isEmpty()) { return; } - auto tokenCardNodes = _deck->getDeckList()->getCardNodes({DECK_ZONE_TOKENS}); + auto tokenCardNodes = deckList.getCardNodes({DECK_ZONE_TOKENS}); if (!tokenCardNodes.isEmpty()) { setEnabled(true); diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index a61bb5c00..0723ae6bc 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -32,7 +32,7 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent) : QObject(_parent), game(_parent), playerInfo(new PlayerInfo(info, _id, _local, _judge)), playerEventHandler(new PlayerEventHandler(this)), playerActions(new PlayerActions(this)), active(false), - conceded(false), deck(nullptr), zoneId(0), dialogSemaphore(false) + conceded(false), zoneId(0), dialogSemaphore(false) { initializeZones(); @@ -263,10 +263,9 @@ void Player::deleteCard(CardItem *card) } } -//! \todo Does a player need a DeckLoader? -void Player::setDeck(DeckLoader &_deck) +void Player::setDeck(const DeckList &_deck) { - deck = new DeckLoader(this, _deck.getDeckList()); + deck = _deck; emit deckChanged(); } diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h index 0a4fc9e69..0a03b3abe 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player.h @@ -9,6 +9,7 @@ #include "../../game_graphics/board/abstract_graphics_item.h" #include "../../interface/widgets/menus/tearoff_menu.h" +#include "../interface/deck_loader/loaded_deck.h" #include "../zones/logic/hand_zone_logic.h" #include "../zones/logic/pile_zone_logic.h" #include "../zones/logic/stack_zone_logic.h" @@ -44,7 +45,6 @@ class ArrowTarget; class CardDatabase; class CardZone; class CommandContainer; -class DeckLoader; class GameCommand; class GameEvent; class PlayerInfo; @@ -66,7 +66,7 @@ class Player : public QObject Q_OBJECT signals: - void openDeckEditor(DeckLoader *deck); + void openDeckEditor(const LoadedDeck &deck); void deckChanged(); void newCardAdded(AbstractCardItem *card); void rearrangeCounters(); @@ -130,9 +130,9 @@ public: return playerMenu; } - void setDeck(DeckLoader &_deck); + void setDeck(const DeckList &_deck); - [[nodiscard]] DeckLoader *getDeck() const + [[nodiscard]] const DeckList &getDeck() const { return deck; } @@ -241,7 +241,7 @@ private: bool active; bool conceded; - DeckLoader *deck; + DeckList deck; int zoneId; QMap zones; diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index 058fdb510..444eecb0d 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -218,7 +218,7 @@ void PlayerActions::actAlwaysLookAtTopCard() void PlayerActions::actOpenDeckInDeckEditor() { - emit player->openDeckEditor(player->getDeck()); + emit player->openDeckEditor({.deckList = player->getDeck()}); } void PlayerActions::actViewGraveyard() diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index bd5fe3ef2..e567e45fd 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -25,11 +25,7 @@ const QStringList DeckLoader::ACCEPTED_FILE_EXTENSIONS = {"*.cod", "*.dec", "*.d const QStringList DeckLoader::FILE_NAME_FILTERS = { tr("Common deck formats (%1)").arg(ACCEPTED_FILE_EXTENSIONS.join(" ")), tr("All files (*.*)")}; -DeckLoader::DeckLoader(QObject *parent) : QObject(parent), deckList(new DeckList()) -{ -} - -DeckLoader::DeckLoader(QObject *parent, DeckList *_deckList) : QObject(parent), deckList(_deckList) +DeckLoader::DeckLoader(QObject *parent) : QObject(parent) { } @@ -41,17 +37,18 @@ bool DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fm } bool result = false; + DeckList deckList = DeckList(); switch (fmt) { case DeckFileFormat::PlainText: - result = deckList->loadFromFile_Plain(&file); + result = deckList.loadFromFile_Plain(&file); break; case DeckFileFormat::Cockatrice: { - result = deckList->loadFromFile_Native(&file); + result = deckList.loadFromFile_Native(&file); qCInfo(DeckLoaderLog) << "Loaded from" << fileName << "-" << result; if (!result) { qCInfo(DeckLoaderLog) << "Retrying as plain format"; file.seek(0); - result = deckList->loadFromFile_Plain(&file); + result = deckList.loadFromFile_Plain(&file); fmt = DeckFileFormat::PlainText; } break; @@ -62,7 +59,8 @@ bool DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fm } if (result) { - lastLoadInfo = { + loadedDeck.deckList = deckList; + loadedDeck.lastLoadInfo = { .fileName = fileName, .fileFormat = fmt, }; @@ -86,7 +84,7 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Form watcher->deleteLater(); if (result) { - lastLoadInfo = { + loadedDeck.lastLoadInfo = { .fileName = fileName, .fileFormat = fmt, }; @@ -107,13 +105,13 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Form switch (fmt) { case DeckFileFormat::PlainText: - return deckList->loadFromFile_Plain(&file); + return loadedDeck.deckList.loadFromFile_Plain(&file); case DeckFileFormat::Cockatrice: { bool result = false; - result = deckList->loadFromFile_Native(&file); + result = loadedDeck.deckList.loadFromFile_Native(&file); if (!result) { file.seek(0); - return deckList->loadFromFile_Plain(&file); + return loadedDeck.deckList.loadFromFile_Plain(&file); } return result; } @@ -129,9 +127,9 @@ bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Form bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId) { - bool result = deckList->loadFromString_Native(nativeString); + bool result = loadedDeck.deckList.loadFromString_Native(nativeString); if (result) { - lastLoadInfo = { + loadedDeck.lastLoadInfo = { .remoteDeckId = remoteDeckId, }; @@ -150,16 +148,16 @@ bool DeckLoader::saveToFile(const QString &fileName, DeckFileFormat::Format fmt) bool result = false; switch (fmt) { case DeckFileFormat::PlainText: - result = deckList->saveToFile_Plain(&file); + result = loadedDeck.deckList.saveToFile_Plain(&file); break; case DeckFileFormat::Cockatrice: - result = deckList->saveToFile_Native(&file); + result = loadedDeck.deckList.saveToFile_Native(&file); qCInfo(DeckLoaderLog) << "Saving to " << fileName << "-" << result; break; } if (result) { - lastLoadInfo = { + loadedDeck.lastLoadInfo = { .fileName = fileName, .fileFormat = fmt, }; @@ -194,18 +192,18 @@ bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, DeckFileForm // Perform file modifications switch (fmt) { case DeckFileFormat::PlainText: - result = deckList->saveToFile_Plain(&file); + result = loadedDeck.deckList.saveToFile_Plain(&file); break; case DeckFileFormat::Cockatrice: - deckList->setLastLoadedTimestamp(QDateTime::currentDateTime().toString()); - result = deckList->saveToFile_Native(&file); + loadedDeck.deckList.setLastLoadedTimestamp(QDateTime::currentDateTime().toString()); + result = loadedDeck.deckList.saveToFile_Native(&file); break; } file.close(); // Close the file to ensure changes are flushed if (result) { - lastLoadInfo = { + loadedDeck.lastLoadInfo = { .fileName = fileName, .fileFormat = fmt, }; @@ -455,7 +453,7 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName) switch (DeckFileFormat::getFormatFromName(fileName)) { case DeckFileFormat::PlainText: // Save in Cockatrice's native format - result = deckList->saveToFile_Native(&file); + result = loadedDeck.deckList.saveToFile_Native(&file); break; case DeckFileFormat::Cockatrice: qCInfo(DeckLoaderLog) << "File is already in Cockatrice format. No conversion needed."; @@ -476,7 +474,7 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName) } else { qCInfo(DeckLoaderLog) << "Original file deleted successfully:" << fileName; } - lastLoadInfo = { + loadedDeck.lastLoadInfo = { .fileName = newFileName, .fileFormat = DeckFileFormat::Cockatrice, }; diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h index 75eb38423..4ceb9006b 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.h +++ b/cockatrice/src/interface/deck_loader/deck_loader.h @@ -41,28 +41,16 @@ public: }; private: - DeckList *deckList; - LoadedDeck::LoadInfo lastLoadInfo; + LoadedDeck loadedDeck; public: DeckLoader(QObject *parent); - DeckLoader(QObject *parent, DeckList *_deckList); DeckLoader(const DeckLoader &) = delete; DeckLoader &operator=(const DeckLoader &) = delete; - const LoadedDeck::LoadInfo &getLastLoadInfo() const - { - return lastLoadInfo; - } - - void setLastLoadInfo(const LoadedDeck::LoadInfo &info) - { - lastLoadInfo = info; - } - [[nodiscard]] bool hasNotBeenLoaded() const { - return lastLoadInfo.isEmpty(); + return loadedDeck.lastLoadInfo.isEmpty(); } bool loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest = false); @@ -88,9 +76,17 @@ public: bool convertToCockatriceFormat(QString fileName); - DeckList *getDeckList() + LoadedDeck &getDeck() { - return deckList; + return loadedDeck; + } + const LoadedDeck &getDeck() const + { + return loadedDeck; + } + void setDeck(const LoadedDeck &deck) + { + loadedDeck = deck; } private: diff --git a/cockatrice/src/interface/deck_loader/loaded_deck.h b/cockatrice/src/interface/deck_loader/loaded_deck.h index 90f3853cf..e07cd0db6 100644 --- a/cockatrice/src/interface/deck_loader/loaded_deck.h +++ b/cockatrice/src/interface/deck_loader/loaded_deck.h @@ -30,8 +30,8 @@ struct LoadedDeck bool isEmpty() const; }; - DeckList deckList; ///< The decklist itself - LoadInfo lastLoadInfo; ///< info about where the deck was loaded from + DeckList deckList; ///< The decklist itself + LoadInfo lastLoadInfo = {}; ///< info about where the deck was loaded from bool isEmpty() const; }; diff --git a/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.cpp b/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.cpp index 486b3302d..b27fe274f 100644 --- a/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/deck_preview_card_picture_widget.cpp @@ -17,7 +17,6 @@ * @param outlineColor The color of the outline around the text. * @param fontSize The font size of the overlay text. * @param alignment The alignment of the text within the overlay. - * @param _deckLoader The Deck Loader holding the Deck associated with this preview. * * Sets the widget's size policy and default border style. */ 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 268a13d5f..a9e19195d 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 @@ -56,7 +56,7 @@ void DeckEditorDeckDockWidget::createDeckDock() deckModel->setObjectName("deckModel"); connect(deckModel, &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash); - deckLoader = new DeckLoader(this, deckModel->getDeckList()); + deckLoader = new DeckLoader(this); proxy = new DeckListStyleProxy(this); proxy->setSourceModel(deckModel); @@ -347,7 +347,7 @@ void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const void DeckEditorDeckDockWidget::updateName(const QString &name) { emit requestDeckHistorySave( - QString(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeckList()->getName())); + QString(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeck().deckList.getName())); deckModel->getDeckList()->setName(name); deckEditor->setModified(name.isEmpty()); emit nameChanged(); @@ -357,7 +357,7 @@ void DeckEditorDeckDockWidget::updateName(const QString &name) void DeckEditorDeckDockWidget::updateComments() { emit requestDeckHistorySave(tr("Updated comments (was %1 chars, now %2 chars)") - .arg(deckLoader->getDeckList()->getComments().size()) + .arg(deckLoader->getDeck().deckList.getComments().size()) .arg(commentsEdit->toPlainText().size())); deckModel->getDeckList()->setComments(commentsEdit->toPlainText()); @@ -474,13 +474,12 @@ void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck() /** * Sets the currently active deck for this tab - * @param _deck The deck. Takes ownership of the object + * @param _deck The deck. */ -void DeckEditorDeckDockWidget::setDeck(DeckLoader *_deck) +void DeckEditorDeckDockWidget::setDeck(const LoadedDeck &_deck) { - deckLoader = _deck; - deckLoader->setParent(this); - deckModel->setDeckList(deckLoader->getDeckList()); + deckLoader->setDeck(_deck); + deckModel->setDeckList(&deckLoader->getDeck().deckList); connect(deckLoader, &DeckLoader::deckLoaded, deckModel, &DeckListModel::rebuildTree); emit requestDeckHistoryClear(); diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index 08aa38e5e..da175d417 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -57,7 +57,7 @@ public: public slots: void cleanDeck(); void updateBannerCardComboBox(); - void setDeck(DeckLoader *_deck); + void setDeck(const LoadedDeck &_deck); void syncDisplayWidgetsToModel(); void sortDeckModelToDeckView(); DeckLoader *getDeckLoader(); 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 952364e37..9024b3bdc 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 @@ -66,26 +66,26 @@ void AbstractDlgDeckTextEdit::setText(const QString &text) } /** - * Tries to load the current contents of the contentsEdit into the DeckLoader + * Tries to load the current contents of the contentsEdit into the deckList * - * @param deckLoader The DeckLoader to load the deck into + * @param deckList The deckList to load the deck into * @return Whether the loading was successful */ -bool AbstractDlgDeckTextEdit::loadIntoDeck(DeckLoader *deckLoader) const +bool AbstractDlgDeckTextEdit::loadIntoDeck(DeckList &deckList) const { QString buffer = contentsEdit->toPlainText(); if (buffer.contains("")) { - return deckLoader->getDeckList()->loadFromString_Native(buffer); + return deckList.loadFromString_Native(buffer); } QTextStream stream(&buffer); - if (deckLoader->getDeckList()->loadFromStream_Plain(stream, true)) { + if (deckList.loadFromStream_Plain(stream, true)) { if (loadSetNameAndNumberCheckBox->isChecked()) { - deckLoader->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); + deckList.forEachCard(CardNodeFunction::ResolveProviderId()); } else { - deckLoader->getDeckList()->forEachCard(CardNodeFunction::ClearPrintingData()); + deckList.forEachCard(CardNodeFunction::ClearPrintingData()); } return true; } @@ -108,7 +108,7 @@ void AbstractDlgDeckTextEdit::keyPressEvent(QKeyEvent *event) * * @param parent The parent widget */ -DlgLoadDeckFromClipboard::DlgLoadDeckFromClipboard(QWidget *parent) : AbstractDlgDeckTextEdit(parent), deckList(nullptr) +DlgLoadDeckFromClipboard::DlgLoadDeckFromClipboard(QWidget *parent) : AbstractDlgDeckTextEdit(parent) { setWindowTitle(tr("Load deck from clipboard")); @@ -122,8 +122,6 @@ void DlgLoadDeckFromClipboard::actRefresh() void DlgLoadDeckFromClipboard::actOK() { - deckList = new DeckLoader(this); - if (loadIntoDeck(deckList)) { accept(); } else { @@ -134,18 +132,15 @@ void DlgLoadDeckFromClipboard::actOK() /** * Creates the dialog window for the "Edit deck in clipboard" action * - * @param _deckLoader The existing deck in the deck editor. Copies the instance + * @param _deckList The existing deck in the deck editor. * @param _annotated Whether to add annotations to the text that is loaded from the deck * @param parent The parent widget */ -DlgEditDeckInClipboard::DlgEditDeckInClipboard(DeckLoader *_deckLoader, bool _annotated, QWidget *parent) - : AbstractDlgDeckTextEdit(parent), annotated(_annotated) +DlgEditDeckInClipboard::DlgEditDeckInClipboard(const DeckList &_deckList, bool _annotated, QWidget *parent) + : AbstractDlgDeckTextEdit(parent), deckList(_deckList), annotated(_annotated) { setWindowTitle(tr("Edit deck in clipboard")); - deckLoader = new DeckLoader(this, _deckLoader->getDeckList()); - deckLoader->setParent(this); - DlgEditDeckInClipboard::actRefresh(); } @@ -165,12 +160,12 @@ static QString deckListToString(const DeckList *deckList, bool addComments) void DlgEditDeckInClipboard::actRefresh() { - setText(deckListToString(deckLoader->getDeckList(), annotated)); + setText(deckListToString(&deckList, annotated)); } void DlgEditDeckInClipboard::actOK() { - if (loadIntoDeck(deckLoader)) { + if (loadIntoDeck(deckList)) { accept(); } else { QMessageBox::critical(this, tr("Error"), tr("Invalid deck list.")); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h index 982840228..0e59653ea 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.h @@ -8,10 +8,11 @@ #ifndef DLG_LOAD_DECK_FROM_CLIPBOARD_H #define DLG_LOAD_DECK_FROM_CLIPBOARD_H +#include "../../deck_loader/loaded_deck.h" + #include #include -class DeckLoader; class QPlainTextEdit; class QPushButton; @@ -35,15 +36,13 @@ public: /** * Gets the loaded deck. Only call this method after this dialog window has been successfully exec'd. * - * The returned DeckLoader is parented to this object; make sure to take ownership of the DeckLoader if you intend - * to use it, since otherwise it will get destroyed once this dlg is destroyed - * @return The DeckLoader + * @return The loaded decklist */ - [[nodiscard]] virtual DeckLoader *getDeckList() const = 0; + [[nodiscard]] virtual const DeckList &getDeckList() = 0; protected: void setText(const QString &text); - bool loadIntoDeck(DeckLoader *deckLoader) const; + bool loadIntoDeck(DeckList &deckList) const; void keyPressEvent(QKeyEvent *event) override; protected slots: @@ -62,12 +61,12 @@ protected slots: void actRefresh() override; private: - DeckLoader *deckList; + DeckList deckList; public: explicit DlgLoadDeckFromClipboard(QWidget *parent = nullptr); - [[nodiscard]] DeckLoader *getDeckList() const override + [[nodiscard]] const DeckList &getDeckList() override { return deckList; } @@ -84,15 +83,15 @@ protected slots: void actRefresh() override; private: - DeckLoader *deckLoader; + DeckList deckList; bool annotated; public: - explicit DlgEditDeckInClipboard(DeckLoader *_deckLoader, bool _annotated, QWidget *parent = nullptr); + explicit DlgEditDeckInClipboard(const DeckList &_deckList, bool _annotated, QWidget *parent = nullptr); - [[nodiscard]] DeckLoader *getDeckList() const override + [[nodiscard]] const DeckList &getDeckList() override { - return deckLoader; + return deckList; } }; 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 2b63af1a4..da4135246 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 @@ -97,11 +97,11 @@ void DlgLoadDeckFromWebsite::accept() } // Parse the plain text deck here - DeckLoader *loader = new DeckLoader(this); + DeckList deckList; QTextStream stream(&deckText); - loader->getDeckList()->loadFromStream_Plain(stream, false); - loader->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); - deck = loader; + deckList.loadFromStream_Plain(stream, false); + deckList.forEachCard(CardNodeFunction::ResolveProviderId()); + deck = deckList; QDialog::accept(); return; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h index 84adb760f..fe6f0a7e9 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h @@ -26,9 +26,9 @@ public: explicit DlgLoadDeckFromWebsite(QWidget *parent); void retranslateUi(); bool testValidUrl(); - DeckLoader *deck; + DeckList deck; - DeckLoader *getDeck() + const DeckList &getDeck() const { return deck; } diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index 7aa6b40c7..f35f90256 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -20,10 +20,6 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor) layout = new QGridLayout(this); backgroundSourceCard = new CardInfoPictureArtCropWidget(this); - backgroundSourceDeck = new DeckLoader(this); - - backgroundSourceDeck->loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod", - DeckFileFormat::Cockatrice, false); gradientColors = extractDominantColors(background); @@ -72,13 +68,20 @@ void HomeWidget::initializeBackgroundFromSource() cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000); break; case BackgroundSources::DeckFileArt: - backgroundSourceDeck->loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod", - DeckFileFormat::Cockatrice, false); + loadBackgroundSourceDeck(); cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000); break; } } +void HomeWidget::loadBackgroundSourceDeck() +{ + DeckLoader deckLoader = DeckLoader(this); + deckLoader.loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod", DeckFileFormat::Cockatrice, + false); + backgroundSourceDeck = deckLoader.getDeck().deckList; +} + void HomeWidget::updateRandomCard() { auto backgroundSourceType = BackgroundSources::fromId(SettingsCache::instance().getHomeTabBackgroundSource()); @@ -95,7 +98,7 @@ void HomeWidget::updateRandomCard() newCard.getCardPtr()->getProperty("layout") != "normal"); break; case BackgroundSources::DeckFileArt: - QList cardRefs = backgroundSourceDeck->getDeckList()->getCardRefList(); + QList cardRefs = backgroundSourceDeck.getCardRefList(); ExactCard oldCard = backgroundSourceCard->getCard(); if (!cardRefs.empty()) { @@ -183,7 +186,7 @@ QGroupBox *HomeWidget::createButtons() auto visualDeckEditorButton = new HomeStyledButton(tr("Create New Deck"), gradientColors); connect(visualDeckEditorButton, &QPushButton::clicked, tabSupervisor, - [this] { tabSupervisor->openDeckInNewTab(nullptr); }); + [this] { tabSupervisor->openDeckInNewTab(LoadedDeck()); }); boxLayout->addWidget(visualDeckEditorButton); auto visualDeckStorageButton = new HomeStyledButton(tr("Browse Decks"), gradientColors); connect(visualDeckStorageButton, &QPushButton::clicked, tabSupervisor, diff --git a/cockatrice/src/interface/widgets/general/home_widget.h b/cockatrice/src/interface/widgets/general/home_widget.h index 1db33446a..233fda543 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.h +++ b/cockatrice/src/interface/widgets/general/home_widget.h @@ -40,10 +40,12 @@ private: TabSupervisor *tabSupervisor; QPixmap background; CardInfoPictureArtCropWidget *backgroundSourceCard = nullptr; - DeckLoader *backgroundSourceDeck; + DeckList backgroundSourceDeck; QPixmap overlay; QPair gradientColors; HomeStyledButton *connectButton; + + void loadBackgroundSourceDeck(); }; #endif // HOME_WIDGET_H 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 55fa0b95c..cdfa5b529 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -213,22 +213,22 @@ void AbstractTabDeckEditor::actSwapCard(const ExactCard &card, const QString &zo /** * @brief Opens a deck in this tab. - * @param deck DeckLoader object (takes ownership). + * @param deck The deck */ -void AbstractTabDeckEditor::openDeck(DeckLoader *deck) +void AbstractTabDeckEditor::openDeck(const LoadedDeck &deck) { setDeck(deck); - if (!deck->getLastLoadInfo().fileName.isEmpty()) { - SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(deck->getLastLoadInfo().fileName); + if (!deck.lastLoadInfo.fileName.isEmpty()) { + SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(deck.lastLoadInfo.fileName); } } /** * @brief Sets the currently active deck. - * @param _deck DeckLoader object. + * @param _deck The deck */ -void AbstractTabDeckEditor::setDeck(DeckLoader *_deck) +void AbstractTabDeckEditor::setDeck(const LoadedDeck &_deck) { deckDockWidget->setDeck(_deck); CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(getDeckList()->getCardRefList())); @@ -265,8 +265,8 @@ void AbstractTabDeckEditor::setModified(bool _modified) */ bool AbstractTabDeckEditor::isBlankNewDeck() const { - DeckLoader *deck = deckDockWidget->getDeckLoader(); - return !modified && deck->getDeckList()->isBlankDeck() && deck->hasNotBeenLoaded(); + const LoadedDeck &loadedDeck = deckDockWidget->getDeckLoader()->getDeck(); + return !modified && loadedDeck.isEmpty(); } /** @brief Creates a new deck. Handles opening in new tab if needed. */ @@ -277,7 +277,7 @@ void AbstractTabDeckEditor::actNewDeck() return; if (deckOpenLocation == NEW_TAB) { - emit openDeckEditor(nullptr); + emit openDeckEditor(LoadedDeck()); return; } @@ -382,17 +382,15 @@ void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLo { DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName); - auto *l = new DeckLoader(this); - if (l->loadFromFile(fileName, fmt, true)) { + auto l = DeckLoader(this); + if (l.loadFromFile(fileName, fmt, true)) { if (deckOpenLocation == NEW_TAB) { - emit openDeckEditor(l); - l->deleteLater(); + emit openDeckEditor(l.getDeck()); } else { deckMenu->setSaveStatus(false); - openDeck(l); + openDeck(l.getDeck()); } } else { - l->deleteLater(); QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(fileName)); } deckMenu->setSaveStatus(true); @@ -405,16 +403,16 @@ void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLo */ bool AbstractTabDeckEditor::actSaveDeck() { - DeckLoader *const deck = getDeckLoader(); - if (deck->getLastLoadInfo().remoteDeckId != LoadedDeck::LoadInfo::NON_REMOTE_ID) { - QString deckString = deck->getDeckList()->writeToString_Native(); + const LoadedDeck &loadedDeck = getDeckLoader()->getDeck(); + if (loadedDeck.lastLoadInfo.remoteDeckId != LoadedDeck::LoadInfo::NON_REMOTE_ID) { + QString deckString = loadedDeck.deckList.writeToString_Native(); if (deckString.length() > MAX_FILE_LENGTH) { QMessageBox::critical(this, tr("Error"), tr("Could not save remote deck")); return false; } Command_DeckUpload cmd; - cmd.set_deck_id(static_cast(deck->getLastLoadInfo().remoteDeckId)); + cmd.set_deck_id(static_cast(loadedDeck.lastLoadInfo.remoteDeckId)); cmd.set_deck_list(deckString.toStdString()); PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd); @@ -422,9 +420,11 @@ bool AbstractTabDeckEditor::actSaveDeck() tabSupervisor->getClient()->sendCommand(pend); return true; - } else if (deck->getLastLoadInfo().fileName.isEmpty()) + } + if (loadedDeck.lastLoadInfo.fileName.isEmpty()) return actSaveDeckAs(); - else if (deck->saveToFile(deck->getLastLoadInfo().fileName, deck->getLastLoadInfo().fileFormat)) { + + if (getDeckLoader()->saveToFile(loadedDeck.lastLoadInfo.fileName, loadedDeck.lastLoadInfo.fileFormat)) { setModified(false); return true; } @@ -493,9 +493,9 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard() return; if (deckOpenLocation == NEW_TAB) { - emit openDeckEditor(dlg.getDeckList()); + emit openDeckEditor({.deckList = dlg.getDeckList()}); } else { - setDeck(dlg.getDeckList()); + setDeck({.deckList = dlg.getDeckList()}); setModified(true); } @@ -508,11 +508,11 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard() */ void AbstractTabDeckEditor::editDeckInClipboard(bool annotated) { - DlgEditDeckInClipboard dlg(getDeckLoader(), annotated, this); + DlgEditDeckInClipboard dlg(getDeckLoader()->getDeck().deckList, annotated, this); if (!dlg.exec()) return; - setDeck(dlg.getDeckList()); + setDeck({dlg.getDeckList(), getDeckLoader()->getDeck().lastLoadInfo}); setModified(true); deckMenu->setSaveStatus(true); } @@ -576,9 +576,9 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite() return; if (deckOpenLocation == NEW_TAB) { - emit openDeckEditor(dlg.getDeck()); + emit openDeckEditor({.deckList = dlg.getDeck()}); } else { - setDeck(dlg.getDeck()); + setDeck({.deckList = dlg.getDeck()}); setModified(true); } @@ -591,8 +591,8 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite() */ void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website) { - if (DeckLoader *const deck = getDeckLoader()) { - QString decklistUrlString = deck->exportDeckToDecklist(getDeckList(), website); + if (DeckList *deckList = getDeckList()) { + QString decklistUrlString = DeckLoader::exportDeckToDecklist(deckList, 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/tabs/abstract_tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h index 8544e6c00..e1ffa45de 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h @@ -114,9 +114,9 @@ public: virtual void retranslateUi() override = 0; /** @brief Opens a deck in this tab. - * @param deck Pointer to a DeckLoader object. + * @param deck The deck to open */ - void openDeck(DeckLoader *deck); + void openDeck(const LoadedDeck &deck); /** @brief Returns the currently active deck loader. */ DeckLoader *getDeckLoader() const; @@ -198,7 +198,7 @@ public slots: signals: /** @brief Emitted when a deck should be opened in a new editor tab. */ - void openDeckEditor(DeckLoader *deckLoader); + void openDeckEditor(const LoadedDeck &deck); /** @brief Emitted before the tab is closed. */ void deckEditorClosing(AbstractTabDeckEditor *tab); @@ -286,7 +286,7 @@ private: /** @brief Sets the deck for this tab. * @param _deck The deck object. */ - virtual void setDeck(DeckLoader *_deck); + virtual void setDeck(const LoadedDeck &_deck); /** @brief Helper for editing decks from the clipboard. */ void editDeckInClipboard(bool annotated); 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 8745ca175..ef29a1732 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 @@ -90,14 +90,14 @@ void ArchidektApiResponseDeckDisplayWidget::onGroupCriteriaChange(const QString void ArchidektApiResponseDeckDisplayWidget::actOpenInDeckEditor() { - auto loader = new DeckLoader(this); - loader->getDeckList()->loadFromString_Native(model->getDeckList()->writeToString_Native()); - - loader->getDeckList()->setName(response.getDeckName()); - loader->getDeckList()->setGameFormat( + DeckList deckList(*model->getDeckList()); + deckList.setName(response.getDeckName()); + deckList.setGameFormat( ArchidektFormats::formatToCockatriceName(ArchidektFormats::DeckFormat(response.getDeckFormat() - 1))); - emit openInDeckEditor(loader); + LoadedDeck loadedDeck = {deckList, {}}; + + emit openInDeckEditor(loadedDeck); } void ArchidektApiResponseDeckDisplayWidget::clearAllDisplayWidgets() diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h index 3d624d293..58aca429e 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h @@ -31,7 +31,7 @@ * * ### Signals * - `requestNavigation(QString url)` — triggered when navigation to a deck URL is requested. - * - `openInDeckEditor(DeckLoader *loader)` — emitted when the user chooses to open the deck + * - `openInDeckEditor(const LoadedDeck &deck)` — emitted when the user chooses to open the deck * in the deck editor. * * ### Features @@ -52,9 +52,9 @@ signals: /** * @brief Emitted when the deck should be opened in the deck editor. - * @param loader Initialized DeckLoader containing the deck data. + * @param deck LoadedDeck containing the deck data. */ - void openInDeckEditor(DeckLoader *loader); + void openInDeckEditor(const LoadedDeck &deck); public: /** @@ -75,7 +75,7 @@ public: void retranslateUi(); /** - * @brief Opens the deck in the deck editor via DeckLoader. + * @brief Opens the deck in the deck editor. */ void actOpenInDeckEditor(); diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp index ceafb719b..a744a9b14 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp @@ -14,10 +14,8 @@ void EdhrecDeckApiResponse::fromJson(const QJsonArray &json) deckList += cardlistValue.toString() + "\n"; } - deckLoader = new DeckLoader(nullptr); - QTextStream stream(&deckList); - deckLoader->getDeckList()->loadFromStream_Plain(stream, true); + deck.loadFromStream_Plain(stream, true); } void EdhrecDeckApiResponse::debugPrint() const diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h index 96205e149..cd4f9d99c 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.h @@ -21,7 +21,7 @@ public: // Debug method for logging void debugPrint() const; - DeckLoader *deckLoader; + DeckList deck; }; #endif // EDHREC_DECK_API_RESPONSE_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp index 92163997c..fb26732f6 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp @@ -363,7 +363,7 @@ void TabEdhRecMain::processAverageDeckResponse(QJsonObject reply) { EdhrecAverageDeckApiResponse deckData; deckData.fromJson(reply); - tabSupervisor->openDeckInNewTab(deckData.deck.deckLoader); + tabSupervisor->openDeckInNewTab({deckData.deck.deck, {}}); } void TabEdhRecMain::prettyPrintJson(const QJsonValue &value, int indentLevel) diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h index 9e00099a0..b3ffb8d90 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h @@ -12,7 +12,6 @@ class CardDatabaseDisplayModel; class DeckListModel; class QLabel; -class DeckLoader; /** * @class TabDeckEditor diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp index a54c16258..73b3fc0ac 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp @@ -245,7 +245,7 @@ void TabDeckStorage::actOpenLocalDeck() if (!deckLoader->loadFromFile(filePath, DeckFileFormat::Cockatrice, true)) continue; - emit openDeckEditor(deckLoader); + emit openDeckEditor(deckLoader->getDeck()); } } @@ -307,13 +307,15 @@ void TabDeckStorage::uploadDeck(const QString &filePath, const QString &targetPa QFile deckFile(filePath); QFileInfo deckFileInfo(deckFile); - DeckLoader deck(this); - if (!deck.loadFromFile(filePath, DeckFileFormat::Cockatrice)) { + DeckLoader deckLoader(this); + if (!deckLoader.loadFromFile(filePath, DeckFileFormat::Cockatrice)) { QMessageBox::critical(this, tr("Error"), tr("Invalid deck file")); return; } - if (deck.getDeckList()->getName().isEmpty()) { + DeckList deck = deckLoader.getDeck().deckList; + + if (deck.getName().isEmpty()) { bool ok; QString deckName = getTextWithMax(this, tr("Enter deck name"), tr("This decklist does not have a name.\nPlease enter a name:"), @@ -322,12 +324,12 @@ void TabDeckStorage::uploadDeck(const QString &filePath, const QString &targetPa return; if (deckName.isEmpty()) deckName = tr("Unnamed deck"); - deck.getDeckList()->setName(deckName); + deck.setName(deckName); } else { - deck.getDeckList()->setName(deck.getDeckList()->getName().left(MAX_NAME_LENGTH)); + deck.setName(deck.getName().left(MAX_NAME_LENGTH)); } - QString deckString = deck.getDeckList()->writeToString_Native(); + QString deckString = deck.writeToString_Native(); if (deckString.length() > MAX_FILE_LENGTH) { QMessageBox::critical(this, tr("Error"), tr("Invalid deck file")); return; @@ -436,7 +438,7 @@ void TabDeckStorage::openRemoteDeckFinished(const Response &r, const CommandCont if (!loader.loadFromRemote(QString::fromStdString(resp.deck()), cmd.deck_id())) return; - emit openDeckEditor(&loader); + emit openDeckEditor(loader.getDeck()); } void TabDeckStorage::actDownload() @@ -492,8 +494,12 @@ void TabDeckStorage::downloadFinished(const Response &r, const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext); QString filePath = extraData.toString(); - DeckLoader deck(this, new DeckList(QString::fromStdString(resp.deck()))); - deck.saveToFile(filePath, DeckFileFormat::Cockatrice); + DeckList deckList = DeckList(QString::fromStdString(resp.deck())); + + DeckLoader deckLoader(this); + deckLoader.setDeck({deckList, {}}); + + deckLoader.saveToFile(filePath, DeckFileFormat::Cockatrice); } void TabDeckStorage::actNewFolder() diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.h b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.h index 0226785e3..f8c0497f7 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.h +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.h @@ -13,6 +13,7 @@ #include +struct LoadedDeck; class ServerInfo_User; class AbstractClient; class QTreeView; @@ -23,7 +24,6 @@ class QTreeWidgetItem; class QGroupBox; class CommandContainer; class Response; -class DeckLoader; class TabDeckStorage : public Tab { @@ -87,7 +87,7 @@ public: return tr("Deck Storage"); } signals: - void openDeckEditor(DeckLoader *deckLoader); + void openDeckEditor(const LoadedDeck &deck); }; #endif diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index 2f83ba2e6..5b6c8d52a 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -749,11 +749,10 @@ void TabGame::loadDeckForLocalPlayer(Player *localPlayer, int playerId, ServerIn { TabbedDeckViewContainer *deckViewContainer = deckViewContainers.value(playerId); if (playerInfo.has_deck_list()) { - DeckLoader newDeck(this, new DeckList(QString::fromStdString(playerInfo.deck_list()))); - CardPictureLoader::cacheCardPixmaps( - CardDatabaseManager::query()->getCards(newDeck.getDeckList()->getCardRefList())); - deckViewContainer->playerDeckView->setDeck(newDeck); - localPlayer->setDeck(newDeck); + DeckList deckList = DeckList(QString::fromStdString(playerInfo.deck_list())); + CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(deckList.getCardRefList())); + deckViewContainer->playerDeckView->setDeck(deckList); + localPlayer->setDeck(deckList); } } diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.h b/cockatrice/src/interface/widgets/tabs/tab_game.h index b6fe5b51c..27374ef2a 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.h +++ b/cockatrice/src/interface/widgets/tabs/tab_game.h @@ -45,7 +45,6 @@ class ReplayTimelineWidget; class CardZone; class AbstractCardItem; class CardItem; -class DeckLoader; class QVBoxLayout; class QHBoxLayout; class GameReplay; @@ -119,7 +118,7 @@ signals: void containerProcessingStarted(const GameEventContext &context); void containerProcessingDone(); void openMessageDialog(const QString &userName, bool focus); - void openDeckEditor(DeckLoader *deck); + void openDeckEditor(const LoadedDeck &deck); void notIdle(); void phaseChanged(int phase); diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp index df9387a6c..b10d615ff 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp @@ -133,10 +133,10 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget * // create tabs menu actions aTabDeckEditor = new QAction(this); - connect(aTabDeckEditor, &QAction::triggered, this, [this] { addDeckEditorTab(nullptr); }); + connect(aTabDeckEditor, &QAction::triggered, this, [this] { addDeckEditorTab(LoadedDeck()); }); aTabVisualDeckEditor = new QAction(this); - connect(aTabVisualDeckEditor, &QAction::triggered, this, [this] { addVisualDeckEditorTab(nullptr); }); + connect(aTabVisualDeckEditor, &QAction::triggered, this, [this] { addVisualDeckEditorTab(LoadedDeck()); }); aTabEdhRec = new QAction(this); connect(aTabEdhRec, &QAction::triggered, this, [this] { addEdhrecMainTab(); }); @@ -846,9 +846,9 @@ void TabSupervisor::talkLeft(TabMessage *tab) /** * Creates a new deck editor tab and loads the deck into it. * Creates either a classic or visual deck editor tab depending on settings - * @param deckToOpen The deck to open in the tab. Creates a copy of the DeckLoader instance. + * @param deckToOpen The deck to open in the tab. */ -void TabSupervisor::openDeckInNewTab(DeckLoader *deckToOpen) +void TabSupervisor::openDeckInNewTab(const LoadedDeck &deckToOpen) { int type = SettingsCache::instance().getDefaultDeckEditorType(); switch (type) { @@ -868,13 +868,12 @@ void TabSupervisor::openDeckInNewTab(DeckLoader *deckToOpen) /** * Creates a new deck editor tab - * @param deckToOpen The deck to open in the tab. Creates a copy of the DeckLoader instance. + * @param deckToOpen The deck to open in the tab. */ -TabDeckEditor *TabSupervisor::addDeckEditorTab(DeckLoader *deckToOpen) +TabDeckEditor *TabSupervisor::addDeckEditorTab(const LoadedDeck &deckToOpen) { auto *tab = new TabDeckEditor(this); - if (deckToOpen) - tab->openDeck(deckToOpen); + tab->openDeck(deckToOpen); connect(tab, &AbstractTabDeckEditor::deckEditorClosing, this, &TabSupervisor::deckEditorClosed); connect(tab, &AbstractTabDeckEditor::openDeckEditor, this, &TabSupervisor::addDeckEditorTab); myAddTab(tab); @@ -883,11 +882,10 @@ TabDeckEditor *TabSupervisor::addDeckEditorTab(DeckLoader *deckToOpen) return tab; } -TabDeckEditorVisual *TabSupervisor::addVisualDeckEditorTab(DeckLoader *deckToOpen) +TabDeckEditorVisual *TabSupervisor::addVisualDeckEditorTab(const LoadedDeck &deckToOpen) { auto *tab = new TabDeckEditorVisual(this); - if (deckToOpen) - tab->openDeck(deckToOpen); + tab->openDeck(deckToOpen); connect(tab, &AbstractTabDeckEditor::deckEditorClosing, this, &TabSupervisor::deckEditorClosed); connect(tab, &AbstractTabDeckEditor::openDeckEditor, this, &TabSupervisor::addVisualDeckEditorTab); myAddTab(tab); diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h index 948ac6d7e..0c4428f83 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h @@ -168,9 +168,9 @@ signals: void showWindowIfHidden(); public slots: - void openDeckInNewTab(DeckLoader *deckToOpen); - TabDeckEditor *addDeckEditorTab(DeckLoader *deckToOpen); - TabDeckEditorVisual *addVisualDeckEditorTab(DeckLoader *deckToOpen); + void openDeckInNewTab(const LoadedDeck &deckToOpen); + TabDeckEditor *addDeckEditorTab(const LoadedDeck &deckToOpen); + TabDeckEditorVisual *addVisualDeckEditorTab(const LoadedDeck &deckToOpen); TabVisualDatabaseDisplay *addVisualDatabaseDisplayTab(); TabEdhRecMain *addEdhrecMainTab(); TabArchidekt *addArchidektTab(); diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp index 69c80bb35..9570702ed 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp @@ -24,11 +24,11 @@ TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor) void TabDeckStorageVisual::actOpenLocalDeck(const QString &filePath) { - auto deckLoader = new DeckLoader(this); - if (!deckLoader->loadFromFile(filePath, DeckFileFormat::getFormatFromName(filePath), true)) { + auto deckLoader = DeckLoader(this); + if (!deckLoader.loadFromFile(filePath, DeckFileFormat::getFormatFromName(filePath), true)) { QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(filePath)); return; } - emit openDeckEditor(deckLoader); + emit openDeckEditor(deckLoader.getDeck()); } diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.h b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.h index a56ce4c24..ccf752e67 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.h +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.h @@ -9,9 +9,9 @@ #include "../tab.h" +struct LoadedDeck; class AbstractClient; class CommandContainer; -class DeckLoader; class DeckPreviewWidget; class QFileSystemModel; class QGroupBox; @@ -39,7 +39,7 @@ public slots: void actOpenLocalDeck(const QString &filePath); signals: - void openDeckEditor(DeckLoader *deckLoader); + void openDeckEditor(const LoadedDeck &deck); private: VisualDeckStorageWidget *visualDeckStorageWidget; diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp index b43b11f11..7551954f3 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp @@ -79,7 +79,7 @@ void VisualDatabaseDisplayNameFilterWidget::actLoadFromClipboard() if (!dlg.exec()) return; - QStringList cardsInClipboard = dlg.getDeckList()->getDeckList()->getCardList(); + QStringList cardsInClipboard = dlg.getDeckList().getCardList(); for (QString cardName : cardsInClipboard) { createNameFilter(cardName); } diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp index d3858a135..d57cda1c6 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp @@ -80,7 +80,7 @@ static QStringList findAllKnownTags() auto loader = DeckLoader(nullptr); for (const QString &file : allFiles) { loader.loadFromFile(file, DeckFileFormat::getFormatFromName(file), false); - QStringList tags = loader.getDeckList()->getTags(); + QStringList tags = loader.getDeck().deckList.getTags(); knownTags.append(tags); knownTags.removeDuplicates(); } @@ -125,7 +125,7 @@ static bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath) static void convertFileToCockatriceFormat(DeckPreviewWidget *deckPreviewWidget) { deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath); - deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastLoadInfo().fileName; + deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getDeck().lastLoadInfo.fileName; deckPreviewWidget->refreshBannerCardText(); } 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 68243100c..7177879a8 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 @@ -74,16 +74,16 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess) if (!deckLoadSuccess) { return; } - auto bannerCard = deckLoader->getDeckList()->getBannerCard().name.isEmpty() + auto bannerCard = deckLoader->getDeck().deckList.getBannerCard().name.isEmpty() ? ExactCard() - : CardDatabaseManager::query()->getCard(deckLoader->getDeckList()->getBannerCard()); + : CardDatabaseManager::query()->getCard(deckLoader->getDeck().deckList.getBannerCard()); bannerCardDisplayWidget->setCard(bannerCard); bannerCardDisplayWidget->setFontSize(24); - setFilePath(deckLoader->getLastLoadInfo().fileName); + setFilePath(deckLoader->getDeck().lastLoadInfo.fileName); colorIdentityWidget = new ColorIdentityWidget(this, getColorIdentity()); - deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeckList()->getTags()); + deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeck().deckList.getTags()); connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this, &DeckPreviewWidget::setTags); bannerCardLabel = new QLabel(this); @@ -91,7 +91,7 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess) bannerCardComboBox = new QComboBox(this); bannerCardComboBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); bannerCardComboBox->setObjectName("bannerCardComboBox"); - bannerCardComboBox->setCurrentText(deckLoader->getDeckList()->getBannerCard().name); + bannerCardComboBox->setCurrentText(deckLoader->getDeck().deckList.getBannerCard().name); bannerCardComboBox->installEventFilter(new NoScrollFilter()); connect(bannerCardComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &DeckPreviewWidget::setBannerCard); @@ -153,7 +153,7 @@ void DeckPreviewWidget::updateTagsVisibility(bool visible) QString DeckPreviewWidget::getColorIdentity() { - QStringList cardList = deckLoader->getDeckList()->getCardList(); + QStringList cardList = deckLoader->getDeck().deckList.getCardList(); if (cardList.isEmpty()) { return {}; } @@ -187,8 +187,8 @@ QString DeckPreviewWidget::getColorIdentity() */ QString DeckPreviewWidget::getDisplayName() const { - return deckLoader->getDeckList()->getName().isEmpty() ? QFileInfo(deckLoader->getLastLoadInfo().fileName).fileName() - : deckLoader->getDeckList()->getName(); + QString deckName = deckLoader->getDeck().deckList.getName(); + return !deckName.isEmpty() ? deckName : QFileInfo(deckLoader->getDeck().lastLoadInfo.fileName).fileName(); } void DeckPreviewWidget::setFilePath(const QString &_filePath) @@ -235,7 +235,7 @@ void DeckPreviewWidget::updateBannerCardComboBox() // Prepare the new items with deduplication QSet> bannerCardSet; - QList cardsInDeck = deckLoader->getDeckList()->getCardNodes(); + QList cardsInDeck = deckLoader->getDeck().deckList.getCardNodes(); for (auto currentCard : cardsInDeck) { for (int k = 0; k < currentCard->getNumber(); ++k) { @@ -269,7 +269,7 @@ void DeckPreviewWidget::updateBannerCardComboBox() bannerCardComboBox->setCurrentIndex(restoredIndex); } else { // Add a placeholder "-" and set it as the current selection - int bannerIndex = bannerCardComboBox->findText(deckLoader->getDeckList()->getBannerCard().name); + int bannerIndex = bannerCardComboBox->findText(deckLoader->getDeck().deckList.getBannerCard().name); if (bannerIndex != -1) { bannerCardComboBox->setCurrentIndex(bannerIndex); } else { @@ -287,7 +287,7 @@ void DeckPreviewWidget::setBannerCard(int /* changedIndex */) { auto [name, id] = bannerCardComboBox->currentData().value>(); CardRef cardRef = {name, id}; - deckLoader->getDeckList()->setBannerCard(cardRef); + deckLoader->getDeck().deckList.setBannerCard(cardRef); deckLoader->saveToFile(filePath, DeckFileFormat::getFormatFromName(filePath)); bannerCardDisplayWidget->setCard(CardDatabaseManager::query()->getCard(cardRef)); } @@ -310,7 +310,7 @@ void DeckPreviewWidget::imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewC void DeckPreviewWidget::setTags(const QStringList &tags) { - deckLoader->getDeckList()->setTags(tags); + deckLoader->getDeck().deckList.setTags(tags); deckLoader->saveToFile(filePath, DeckFileFormat::Cockatrice); } @@ -320,7 +320,7 @@ QMenu *DeckPreviewWidget::createRightClickMenu() menu->setAttribute(Qt::WA_DeleteOnClose); connect(menu->addAction(tr("Open in deck editor")), &QAction::triggered, this, - [this] { emit openDeckEditor(deckLoader); }); + [this] { emit openDeckEditor(deckLoader->getDeck()); }); connect(menu->addAction(tr("Edit Tags")), &QAction::triggered, deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::openTagEditDlg); @@ -334,13 +334,13 @@ QMenu *DeckPreviewWidget::createRightClickMenu() auto saveToClipboardMenu = menu->addMenu(tr("Save Deck to Clipboard")); connect(saveToClipboardMenu->addAction(tr("Annotated")), &QAction::triggered, this, - [this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), true, true); }); + [this] { DeckLoader::saveToClipboard(&deckLoader->getDeck().deckList, true, true); }); connect(saveToClipboardMenu->addAction(tr("Annotated (No set info)")), &QAction::triggered, this, - [this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), true, false); }); + [this] { DeckLoader::saveToClipboard(&deckLoader->getDeck().deckList, true, false); }); connect(saveToClipboardMenu->addAction(tr("Not Annotated")), &QAction::triggered, this, - [this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), false, true); }); + [this] { DeckLoader::saveToClipboard(&deckLoader->getDeck().deckList, false, true); }); connect(saveToClipboardMenu->addAction(tr("Not Annotated (No set info)")), &QAction::triggered, this, - [this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), false, false); }); + [this] { DeckLoader::saveToClipboard(&deckLoader->getDeck().deckList, false, false); }); menu->addSeparator(); @@ -376,7 +376,7 @@ void DeckPreviewWidget::addSetBannerCardMenu(QMenu *menu) void DeckPreviewWidget::actRenameDeck() { // read input - const QString oldName = deckLoader->getDeckList()->getName(); + const QString oldName = deckLoader->getDeck().deckList.getName(); bool ok; QString newName = QInputDialog::getText(this, "Rename deck", tr("New name:"), QLineEdit::Normal, oldName, &ok); @@ -385,7 +385,7 @@ void DeckPreviewWidget::actRenameDeck() } // write change - deckLoader->getDeckList()->setName(newName); + deckLoader->getDeck().deckList.setName(newName); deckLoader->saveToFile(filePath, DeckFileFormat::getFormatFromName(filePath)); // update VDS @@ -416,9 +416,7 @@ void DeckPreviewWidget::actRenameFile() return; } - LoadedDeck::LoadInfo lastLoadInfo = deckLoader->getLastLoadInfo(); - lastLoadInfo.fileName = newFilePath; - deckLoader->setLastLoadInfo(lastLoadInfo); + deckLoader->getDeck().lastLoadInfo.fileName = newFilePath; // update VDS setFilePath(newFilePath); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h index 6e6fdb9af..e5975db71 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h @@ -51,7 +51,7 @@ public: signals: void deckLoadRequested(const QString &filePath); - void openDeckEditor(DeckLoader *deck); + void openDeckEditor(const LoadedDeck &deck); public slots: void setFilePath(const QString &filePath); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp index 883682749..fdb44dff4 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp @@ -211,7 +211,7 @@ QStringList VisualDeckStorageFolderDisplayWidget::gatherAllTagsFromFlowWidget() // Iterate through all DeckPreviewWidgets for (DeckPreviewWidget *display : flowWidget->findChildren()) { // Get tags from each DeckPreviewWidget - QStringList tags = display->deckLoader->getDeckList()->getTags(); + QStringList tags = display->deckLoader->getDeck().deckList.getTags(); // Add tags to the list while avoiding duplicates allTags.append(tags); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp index cbc78ba50..0eb45cdcc 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp @@ -95,14 +95,17 @@ QList VisualDeckStorageSortWidget::filterFiles(QListdeckLoader->getDeckList()->getName() < widget2->deckLoader->getDeckList()->getName(); + return widget1->deckLoader->getDeck().deckList.getName() < + widget2->deckLoader->getDeck().deckList.getName(); case Alphabetical: return QString::localeAwareCompare(info1.fileName(), info2.fileName()) <= 0; case ByLastModified: return info1.lastModified() > info2.lastModified(); case ByLastLoaded: { - QDateTime time1 = QDateTime::fromString(widget1->deckLoader->getDeckList()->getLastLoadedTimestamp()); - QDateTime time2 = QDateTime::fromString(widget2->deckLoader->getDeckList()->getLastLoadedTimestamp()); + QDateTime time1 = + QDateTime::fromString(widget1->deckLoader->getDeck().deckList.getLastLoadedTimestamp()); + QDateTime time2 = + QDateTime::fromString(widget2->deckLoader->getDeck().deckList.getLastLoadedTimestamp()); return time1 > time2; } } diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp index 1686de054..28fd7a5ca 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp @@ -57,7 +57,7 @@ void VisualDeckStorageTagFilterWidget::filterDecksBySelectedTags(const QListdeckLoader->getDeckList()->getTags(); + QStringList deckTags = deckPreview->deckLoader->getDeck().deckList.getTags(); bool hasAllSelected = std::all_of(selectedTags.begin(), selectedTags.end(), [&deckTags](const QString &tag) { return deckTags.contains(tag); }); @@ -153,7 +153,7 @@ QSet VisualDeckStorageTagFilterWidget::gatherAllTags() const for (DeckPreviewWidget *widget : deckWidgets) { if (widget->checkVisibility()) { - for (const QString &tag : widget->deckLoader->getDeckList()->getTags()) { + for (const QString &tag : widget->deckLoader->getDeck().deckList.getTags()) { allTags.insert(tag); } } diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h index b205cb67e..b13c51700 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h @@ -53,7 +53,7 @@ public slots: signals: void bannerCardsRefreshed(); void deckLoadRequested(const QString &filePath); - void openDeckEditor(DeckLoader *deck); + void openDeckEditor(const LoadedDeck &deck); private: QVBoxLayout *layout; From 7f1d891e268fe344283f05e11964f3a0306c4cee Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sat, 20 Dec 2025 15:25:13 +0100 Subject: [PATCH 076/325] [Deprecation] Remove DBConverter from sources. (#6431) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 10 minutes Co-authored-by: Lukas Brübach --- .ci/compile.sh | 4 +- CMakeLists.txt | 7 - Dockerfile | 2 +- Doxyfile | 1 - cmake/CMakeDMGSetup.script | 1 - cmake/FindQtRuntime.cmake | 9 +- cmake/NSIS.template.in | 1 - cmake/SignMacApplications.cmake | 2 +- dbconverter/CMakeLists.txt | 133 ---------- dbconverter/src/main.cpp | 66 ----- dbconverter/src/main.h | 102 -------- dbconverter/src/mocks.cpp | 451 -------------------------------- dbconverter/src/mocks.h | 24 -- format.sh | 1 - 14 files changed, 6 insertions(+), 798 deletions(-) delete mode 100644 dbconverter/CMakeLists.txt delete mode 100644 dbconverter/src/main.cpp delete mode 100644 dbconverter/src/main.h delete mode 100644 dbconverter/src/mocks.cpp delete mode 100644 dbconverter/src/mocks.h diff --git a/.ci/compile.sh b/.ci/compile.sh index a7c349fb0..0af79868b 100755 --- a/.ci/compile.sh +++ b/.ci/compile.sh @@ -122,7 +122,7 @@ if [[ $MAKE_SERVER ]]; then flags+=("-DWITH_SERVER=1") fi if [[ $MAKE_NO_CLIENT ]]; then - flags+=("-DWITH_CLIENT=0" "-DWITH_ORACLE=0" "-DWITH_DBCONVERTER=0") + flags+=("-DWITH_CLIENT=0" "-DWITH_ORACLE=0") fi if [[ $MAKE_TEST ]]; then flags+=("-DTEST=1") @@ -246,7 +246,7 @@ fi if [[ $RUNNER_OS == macOS ]]; then echo "::group::Inspect Mach-O binaries" - for app in cockatrice oracle servatrice dbconverter; do + for app in cockatrice oracle servatrice; do binary="$GITHUB_WORKSPACE/build/$app/$app.app/Contents/MacOS/$app" echo "Inspecting $app..." vtool -show-build "$binary" diff --git a/CMakeLists.txt b/CMakeLists.txt index affc6472d..574ccecb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,8 +20,6 @@ option(WITH_SERVER "build servatrice" OFF) option(WITH_CLIENT "build cockatrice" ON) # Compile oracle option(WITH_ORACLE "build oracle" ON) -# Compile dbconverter -option(WITH_DBCONVERTER "build dbconverter" ON) # Compile tests option(TEST "build tests" OFF) # Use vcpkg regardless of OS @@ -356,11 +354,6 @@ if(WITH_ORACLE) set(CPACK_INSTALL_CMAKE_PROJECTS "Oracle;Oracle;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS}) endif() -if(WITH_DBCONVERTER) - add_subdirectory(dbconverter) - set(CPACK_INSTALL_CMAKE_PROJECTS "Dbconverter;Dbconverter;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS}) -endif() - if(TEST) include(CTest) add_subdirectory(tests) diff --git a/Dockerfile b/Dockerfile index 80f9f69f2..d185b746a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ WORKDIR /src COPY . . RUN mkdir build && cd build && \ - cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 -DWITH_DBCONVERTER=0 && \ + cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 && \ make -j$(nproc) && \ make install diff --git a/Doxyfile b/Doxyfile index d5a3a4b7a..ecacc6b8f 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1068,7 +1068,6 @@ RECURSIVE = YES EXCLUDE = build/ \ cmake/ \ - dbconverter/ \ vcpkg/ \ webclient/ diff --git a/cmake/CMakeDMGSetup.script b/cmake/CMakeDMGSetup.script index fe02eea13..e9a826eb8 100644 --- a/cmake/CMakeDMGSetup.script +++ b/cmake/CMakeDMGSetup.script @@ -42,7 +42,6 @@ tell disk image_name set position of item "Cockatrice.app" to { 139, 214 } set position of item "Oracle.app" to { 139, 414 } set position of item "Servatrice.app" to { 139, 614 } - set position of item "dbconverter.app" to { 1400, 1400 } set position of item "Applications" to { 861, 414 } end tell update without registering applications diff --git a/cmake/FindQtRuntime.cmake b/cmake/FindQtRuntime.cmake index 769f9b463..6be08a694 100644 --- a/cmake/FindQtRuntime.cmake +++ b/cmake/FindQtRuntime.cmake @@ -1,12 +1,11 @@ # Find a compatible Qt version -# Inputs: WITH_SERVER, WITH_CLIENT, WITH_ORACLE, WITH_DBCONVERTER, FORCE_USE_QT5 +# Inputs: WITH_SERVER, WITH_CLIENT, WITH_ORACLE, FORCE_USE_QT5 # Optional Input: QT6_DIR -- Hint as to where Qt6 lives on the system # Optional Input: QT5_DIR -- Hint as to where Qt5 lives on the system # Output: COCKATRICE_QT_VERSION_NAME -- Example values: Qt5, Qt6 # Output: SERVATRICE_QT_MODULES # Output: COCKATRICE_QT_MODULES # Output: ORACLE_QT_MODULES -# Output: DBCONVERTER_QT_MODULES # Output: TEST_QT_MODULES set(REQUIRED_QT_COMPONENTS Core) @@ -29,15 +28,12 @@ endif() if(WITH_ORACLE) set(_ORACLE_NEEDED Concurrent Network Svg Widgets) endif() -if(WITH_DBCONVERTER) - set(_DBCONVERTER_NEEDED Network Widgets) -endif() if(TEST) set(_TEST_NEEDED Widgets) endif() set(REQUIRED_QT_COMPONENTS ${REQUIRED_QT_COMPONENTS} ${_SERVATRICE_NEEDED} ${_COCKATRICE_NEEDED} ${_ORACLE_NEEDED} - ${_DBCONVERTER_NEEDED} ${_TEST_NEEDED} + ${_TEST_NEEDED} ) list(REMOVE_DUPLICATES REQUIRED_QT_COMPONENTS) @@ -112,7 +108,6 @@ message(DEBUG "QT_LIBRARY_DIR = ${QT_LIBRARY_DIR}") string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" SERVATRICE_QT_MODULES "${_SERVATRICE_NEEDED}") string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" COCKATRICE_QT_MODULES "${_COCKATRICE_NEEDED}") string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" ORACLE_QT_MODULES "${_ORACLE_NEEDED}") -string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" DB_CONVERTER_QT_MODULES "${_DBCONVERTER_NEEDED}") string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" TEST_QT_MODULES "${_TEST_NEEDED}") # Core-only export (useful for headless libs) diff --git a/cmake/NSIS.template.in b/cmake/NSIS.template.in index fbdde0b86..a0d51fa2b 100644 --- a/cmake/NSIS.template.in +++ b/cmake/NSIS.template.in @@ -213,7 +213,6 @@ ${AndIf} ${FileExists} "$INSTDIR\portable.dat" Delete "$INSTDIR\uninstall.exe" Delete "$INSTDIR\cockatrice.exe" Delete "$INSTDIR\oracle.exe" - Delete "$INSTDIR\dbconverter.exe" Delete "$INSTDIR\servatrice.exe" Delete "$INSTDIR\Qt*.dll" Delete "$INSTDIR\libmysql.dll" diff --git a/cmake/SignMacApplications.cmake b/cmake/SignMacApplications.cmake index b91e53725..c08c30c1e 100644 --- a/cmake/SignMacApplications.cmake +++ b/cmake/SignMacApplications.cmake @@ -3,7 +3,7 @@ string(LENGTH "$ENV{MACOS_CERTIFICATE_NAME}" MACOS_CERTIFICATE_NAME_LEN) if(APPLE AND MACOS_CERTIFICATE_NAME_LEN GREATER 0) - set(APPLICATIONS "cockatrice" "servatrice" "oracle" "dbconverter") + set(APPLICATIONS "cockatrice" "servatrice" "oracle") foreach(app_name IN LISTS APPLICATIONS) set(FULL_APP_PATH "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/${app_name}.app") diff --git a/dbconverter/CMakeLists.txt b/dbconverter/CMakeLists.txt deleted file mode 100644 index b2de9dcb8..000000000 --- a/dbconverter/CMakeLists.txt +++ /dev/null @@ -1,133 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(Dbconverter VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") - -# ------------------------ -# Sources -# ------------------------ -set(dbconverter_SOURCES src/main.cpp src/mocks.cpp ${VERSION_STRING_CPP}) - -# ------------------------ -# Qt configuration -# ------------------------ -set(QT_DONT_USE_QTGUI TRUE) -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - -# ------------------------ -# Build executable -# ------------------------ -add_executable(dbconverter MACOSX_BUNDLE ${dbconverter_SOURCES}) - -# ------------------------ -# Link libraries -# ------------------------ -target_link_libraries( - dbconverter - PRIVATE libcockatrice_card - PRIVATE libcockatrice_interfaces - PRIVATE libcockatrice_settings - PRIVATE ${DB_CONVERTER_QT_MODULES} -) - -# ------------------------ -# Install rules -# ------------------------ -if(UNIX) - if(APPLE) - set(MACOSX_BUNDLE_INFO_STRING "${PROJECT_NAME}") - set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.cockatrice.${PROJECT_NAME}") - set(MACOSX_BUNDLE_LONG_VERSION_STRING "${PROJECT_NAME}-${PROJECT_VERSION}") - set(MACOSX_BUNDLE_BUNDLE_NAME ${PROJECT_NAME}) - set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}) - set(MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}) - - install(TARGETS dbconverter BUNDLE DESTINATION ./) - else() - # Linux - install(TARGETS dbconverter RUNTIME DESTINATION bin/) - endif() -elseif(WIN32) - install(TARGETS dbconverter RUNTIME DESTINATION ./) -endif() - -# ------------------------ -# Qt plugin handling -# ------------------------ -if(APPLE) - set(plugin_dest_dir dbconverter.app/Contents/Plugins) - set(qtconf_dest_dir dbconverter.app/Contents/Resources) - - install( - DIRECTORY "${QT_PLUGINS_DIR}/" - DESTINATION ${plugin_dest_dir} - COMPONENT Runtime - FILES_MATCHING - PATTERN "*.dSYM" EXCLUDE - PATTERN "*_debug.dylib" EXCLUDE - PATTERN "platforms/*.dylib" - ) - - install( - CODE " - file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"[Paths] -Plugins = Plugins -Translations = Resources/translations\") - " - COMPONENT Runtime - ) - - install( - CODE " - file(GLOB_RECURSE QTPLUGINS - \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/*.dylib\") - set(BU_CHMOD_BUNDLE_ITEMS ON) - include(BundleUtilities) - fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/dbconverter.app\" \"\${QTPLUGINS}\" \"${QT_LIBRARY_DIR};${MYSQLCLIENT_LIBRARY_DIR}\") - " - COMPONENT Runtime - ) -endif() - -if(WIN32) - set(plugin_dest_dir Plugins) - set(qtconf_dest_dir .) - - install( - DIRECTORY "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/${CMAKE_BUILD_TYPE}/" - DESTINATION ./ - FILES_MATCHING - PATTERN "*.dll" - ) - - install( - DIRECTORY "${QT_PLUGINS_DIR}/" - DESTINATION ${plugin_dest_dir} - COMPONENT Runtime - FILES_MATCHING - PATTERN "platforms/qdirect2d.dll" - PATTERN "platforms/qminimal.dll" - PATTERN "platforms/qoffscreen.dll" - PATTERN "platforms/qwindows.dll" - ) - - install( - CODE " - file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"[Paths] -Plugins = Plugins -Translations = Resources/translations\") - " - COMPONENT Runtime - ) - - install( - CODE " - file(GLOB_RECURSE QTPLUGINS - \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/*.dll\") - set(BU_CHMOD_BUNDLE_ITEMS ON) - include(BundleUtilities) - fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/dbconverter.exe\" \"\${QTPLUGINS}\" \"${QT_LIBRARY_DIR}\") - " - COMPONENT Runtime - ) -endif() diff --git a/dbconverter/src/main.cpp b/dbconverter/src/main.cpp deleted file mode 100644 index 67748fa45..000000000 --- a/dbconverter/src/main.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2019 by Fabio Bas * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#include "main.h" - -#include "mocks.h" -#include "version_string.h" - -#include -#include -#include -#include -#include - -int main(int argc, char *argv[]) -{ - QCoreApplication app(argc, argv); - app.setOrganizationName("Cockatrice"); - app.setApplicationName("Dbconverter"); - app.setApplicationVersion(VERSION_STRING); - - QCommandLineParser parser; - parser.addPositionalArgument("olddb", "Read existing card database from "); - parser.addPositionalArgument("newdb", "Write new card database to "); - parser.addHelpOption(); - parser.addVersionOption(); - parser.process(app); - - QString oldDbPath; - QString newDbPath; - QStringList args = parser.positionalArguments(); - if (args.count() == 2) { - oldDbPath = QFileInfo(args.at(0)).absoluteFilePath(); - newDbPath = QFileInfo(args.at(1)).absoluteFilePath(); - } else { - qCritical() << "Usage: dbconverter "; - parser.showHelp(1); - exit(0); - } - - CardDatabaseConverter *db = new CardDatabaseConverter; - - qInfo() << "---------------------------------------------"; - qInfo() << "Loading cards from" << oldDbPath; - db->loadCardDatabase(oldDbPath); - qInfo() << "---------------------------------------------"; - qInfo() << "Saving cards to" << newDbPath; - db->saveCardDatabase(newDbPath); - qInfo() << "---------------------------------------------"; -} diff --git a/dbconverter/src/main.h b/dbconverter/src/main.h deleted file mode 100644 index 72dbe939d..000000000 --- a/dbconverter/src/main.h +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef MAIN_H -#define MAIN_H - -#include "libcockatrice/interfaces/noop_card_set_priority_controller.h" - -#include -#include -#include - -static const QList kConstructedCounts = {{4, "legal"}, {0, "banned"}}; - -static const QList kSingletonCounts = {{1, "legal"}, {0, "banned"}}; - -class CardDatabaseConverter : public CardDatabase -{ -public: - explicit CardDatabaseConverter() - { - // Replace querier with one that ignores SettingsCache - delete querier; - querier = new CardDatabaseQuerier(this, this, new NoopCardPreferenceProvider()); - }; - - LoadStatus loadCardDatabase(const QString &path) - { - return loader->loadCardDatabase(path); - } - - bool saveCardDatabase(const QString &fileName) - { - CockatriceXml4Parser parser(new NoopCardPreferenceProvider(), new NoopCardSetPriorityController()); - - return parser.saveToFile(createDefaultMagicFormats(), sets, cards, fileName); - } - - FormatRulesNameMap createDefaultMagicFormats() - { - // Predefined common exceptions - CardCondition superTypeIsBasic; - superTypeIsBasic.field = "type"; - superTypeIsBasic.matchType = "contains"; - superTypeIsBasic.value = "Basic Land"; - - ExceptionRule basicLands; - basicLands.conditions.append(superTypeIsBasic); - - CardCondition anyNumberAllowed; - anyNumberAllowed.field = "text"; - anyNumberAllowed.matchType = "contains"; - anyNumberAllowed.value = "A deck can have any number of"; - - ExceptionRule mayContainAnyNumber; - mayContainAnyNumber.conditions.append(anyNumberAllowed); - - // Map to store default rules - FormatRulesNameMap defaultFormatRulesNameMap; - - // ----------------- Helper lambda to create format ----------------- - auto makeFormat = [&](const QString &name, int minDeck = 60, int maxDeck = -1, int maxSideboardSize = 15, - const QList &allowedCounts = kConstructedCounts) -> FormatRulesPtr { - FormatRulesPtr f(new FormatRules); - f->formatName = name; - f->allowedCounts = allowedCounts; - f->minDeckSize = minDeck; - f->maxDeckSize = maxDeck; - f->maxSideboardSize = maxSideboardSize; - f->exceptions.append(basicLands); - f->exceptions.append(mayContainAnyNumber); - defaultFormatRulesNameMap.insert(name.toLower(), f); - return f; - }; - - // ----------------- Standard formats ----------------- - makeFormat("Standard"); - makeFormat("Modern"); - makeFormat("Legacy"); - makeFormat("Pioneer"); - makeFormat("Historic"); - makeFormat("Timeless"); - makeFormat("Future"); - makeFormat("OldSchool"); - makeFormat("Premodern"); - makeFormat("Pauper"); - makeFormat("Penny"); - - // ----------------- Singleton formats ----------------- - makeFormat("Commander", 100, 100, 15, kSingletonCounts); - makeFormat("Duel", 100, 100, 15, kSingletonCounts); - makeFormat("Brawl", 60, 60, 15, kSingletonCounts); - makeFormat("StandardBrawl", 60, 60, 15, kSingletonCounts); - makeFormat("Oathbreaker", 60, 60, 15, kSingletonCounts); - makeFormat("PauperCommander", 100, 100, 15, kSingletonCounts); - makeFormat("Predh", 100, 100, 15, kSingletonCounts); - - // ----------------- Restricted formats ----------------- - makeFormat("Vintage", 60, -1, 15, {{4, "legal"}, {1, "restricted"}, {0, "banned"}}); - - return defaultFormatRulesNameMap; - } -}; - -#endif diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp deleted file mode 100644 index b97bd09c7..000000000 --- a/dbconverter/src/mocks.cpp +++ /dev/null @@ -1,451 +0,0 @@ - -#include "mocks.h" - -CardDatabaseSettings::CardDatabaseSettings(const QString &settingPath, QObject *parent) - : SettingsManager(settingPath + "cardDatabase.ini", QString(), QString(), parent) -{ -} -void CardDatabaseSettings::setSortKey(QString /* shortName */, unsigned int /* sortKey */) -{ -} -void CardDatabaseSettings::setEnabled(QString /* shortName */, bool /* enabled */) -{ -} -void CardDatabaseSettings::setIsKnown(QString /* shortName */, bool /* isknown */) -{ -} -unsigned int CardDatabaseSettings::getSortKey(QString /* shortName */) -{ - return 0; -}; -bool CardDatabaseSettings::isEnabled(QString /* shortName */) -{ - return true; -}; -bool CardDatabaseSettings::isKnown(QString /* shortName */) -{ - return true; -}; - -QString SettingsCache::getDataPath() -{ - return ""; -} -QString SettingsCache::getSettingsPath() -{ - return ""; -} -void SettingsCache::translateLegacySettings() -{ -} -QString SettingsCache::getSafeConfigPath(QString /* configEntry */, QString defaultPath) const -{ - return defaultPath; -} -QString SettingsCache::getSafeConfigFilePath(QString /* configEntry */, QString defaultPath) const -{ - return defaultPath; -} - -void SettingsCache::setUseTearOffMenus(bool /* _useTearOffMenus */) -{ -} -void SettingsCache::setCardViewInitialRowsMax(int /* _cardViewInitialRowsMax */) -{ -} -void SettingsCache::setCardViewExpandedRowsMax(int /* value */) -{ -} -void SettingsCache::setCloseEmptyCardView(const QT_STATE_CHANGED_T /* value */) -{ -} -void SettingsCache::setFocusCardViewSearchBar(QT_STATE_CHANGED_T /* value */) -{ -} -void SettingsCache::setKnownMissingFeatures(const QString & /* _knownMissingFeatures */) -{ -} -void SettingsCache::setCardInfoViewMode(const int /* _viewMode */) -{ -} -void SettingsCache::setHighlightWords(const QString & /* _highlightWords */) -{ -} -void SettingsCache::setMasterVolume(int /* _masterVolume */) -{ -} -void SettingsCache::setLeftJustified(const QT_STATE_CHANGED_T /* _leftJustified */) -{ -} -void SettingsCache::setCardScaling(const QT_STATE_CHANGED_T /* _scaleCards */) -{ -} -void SettingsCache::setStackCardOverlapPercent(const int /* _verticalCardOverlapPercent */) -{ -} -void SettingsCache::setShowMessagePopups(const QT_STATE_CHANGED_T /* _showMessagePopups */) -{ -} -void SettingsCache::setShowMentionPopups(const QT_STATE_CHANGED_T /* _showMentionPopus */) -{ -} -void SettingsCache::setRoomHistory(const QT_STATE_CHANGED_T /* _roomHistory */) -{ -} -void SettingsCache::setLang(const QString & /* _lang */) -{ -} -void SettingsCache::setShowTipsOnStartup(bool /* _showTipsOnStartup */) -{ -} -void SettingsCache::setSeenTips(const QList & /* _seenTips */) -{ -} -void SettingsCache::setDeckPath(const QString & /* _deckPath */) -{ -} -void SettingsCache::setFiltersPath(const QString & /*_filtersPath */) -{ -} -void SettingsCache::setReplaysPath(const QString & /* _replaysPath */) -{ -} -void SettingsCache::setThemesPath(const QString & /* _themesPath */) -{ -} -void SettingsCache::setPicsPath(const QString & /* _picsPath */) -{ -} -void SettingsCache::setCardDatabasePath(const QString & /* _cardDatabasePath */) -{ -} -void SettingsCache::setCustomCardDatabasePath(const QString & /* _customCardDatabasePath */) -{ -} -void SettingsCache::setSpoilerDatabasePath(const QString & /* _spoilerDatabasePath */) -{ -} -void SettingsCache::setTokenDatabasePath(const QString & /* _tokenDatabasePath */) -{ -} -void SettingsCache::setThemeName(const QString & /* _themeName */) -{ -} -void SettingsCache::setHomeTabBackgroundSource(const QString & /* _backgroundSource */) -{ -} -void SettingsCache::setHomeTabBackgroundShuffleFrequency(int /* frequency */) -{ -} -void SettingsCache::setTabVisualDeckStorageOpen(bool /*value*/) -{ -} -void SettingsCache::setTabServerOpen(bool /*value*/) -{ -} -void SettingsCache::setTabAccountOpen(bool /*value*/) -{ -} -void SettingsCache::setTabDeckStorageOpen(bool /*value*/) -{ -} -void SettingsCache::setTabReplaysOpen(bool /*value*/) -{ -} -void SettingsCache::setTabAdminOpen(bool /*value*/) -{ -} -void SettingsCache::setTabLogOpen(bool /*value*/) -{ -} -void SettingsCache::setPicDownload(QT_STATE_CHANGED_T /* _picDownload */) -{ -} -void SettingsCache::setShowStatusBar(bool /* value */) -{ -} -void SettingsCache::setNotificationsEnabled(QT_STATE_CHANGED_T /* _notificationsEnabled */) -{ -} -void SettingsCache::setSpectatorNotificationsEnabled(QT_STATE_CHANGED_T /* _spectatorNotificationsEnabled */) -{ -} -void SettingsCache::setBuddyConnectNotificationsEnabled(QT_STATE_CHANGED_T /* _buddyConnectNotificationsEnabled */) -{ -} -void SettingsCache::setDoubleClickToPlay(QT_STATE_CHANGED_T /* _doubleClickToPlay */) -{ -} -void SettingsCache::setClickPlaysAllSelected(QT_STATE_CHANGED_T /* _clickPlaysAllSelected */) -{ -} -void SettingsCache::setPlayToStack(QT_STATE_CHANGED_T /* _playToStack */) -{ -} -void SettingsCache::setDoNotDeleteArrowsInSubPhases(QT_STATE_CHANGED_T /* _doNotDeleteArrowsInSubPhases */) -{ -} -void SettingsCache::setStartingHandSize(int /* _startingHandSize */) -{ -} -void SettingsCache::setAnnotateTokens(QT_STATE_CHANGED_T /* _annotateTokens */) -{ -} -void SettingsCache::setTabGameSplitterSizes(const QByteArray & /* _tabGameSplitterSizes */) -{ -} -void SettingsCache::setShowShortcuts(QT_STATE_CHANGED_T /* _showShortcuts */) -{ -} -void SettingsCache::setDisplayCardNames(QT_STATE_CHANGED_T /* _displayCardNames */) -{ -} -void SettingsCache::setOverrideAllCardArtWithPersonalPreference(QT_STATE_CHANGED_T /* _displayCardNames */) -{ -} -void SettingsCache::setBumpSetsWithCardsInDeckToTop(QT_STATE_CHANGED_T /* _bumpSetsWithCardsInDeckToTop */) -{ -} -void SettingsCache::setPrintingSelectorSortOrder(int /* _printingSelectorSortOrder */) -{ -} -void SettingsCache::setPrintingSelectorCardSize(int /* _printingSelectorCardSize */) -{ -} -void SettingsCache::setIncludeRebalancedCards(bool /* _includeRebalancedCards */) -{ -} -void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T /* _navigationButtonsVisible */) -{ -} -void SettingsCache::setDeckEditorBannerCardComboBoxVisible( - QT_STATE_CHANGED_T /* _deckEditorBannerCardComboBoxVisible */) -{ -} -void SettingsCache::setDeckEditorTagsWidgetVisible(QT_STATE_CHANGED_T /* _deckEditorTagsWidgetVisible */) -{ -} -void SettingsCache::setVisualDeckStorageSortingOrder(int /* _visualDeckStorageSortingOrder */) -{ -} -void SettingsCache::setVisualDeckStorageShowFolders(QT_STATE_CHANGED_T /* value */) -{ -} -void SettingsCache::setVisualDeckStorageShowTagFilter(QT_STATE_CHANGED_T /* _showTags */) -{ -} -void SettingsCache::setVisualDeckStorageDefaultTagsList(QStringList /* _defaultTagsList */) -{ -} -void SettingsCache::setVisualDeckStorageSearchFolderNames(QT_STATE_CHANGED_T /* value */) -{ -} -void SettingsCache::setVisualDeckStorageShowBannerCardComboBox(QT_STATE_CHANGED_T /* _showBannerCardComboBox */) -{ -} -void SettingsCache::setVisualDeckStorageShowTagsOnDeckPreviews(QT_STATE_CHANGED_T /* _showTags */) -{ -} -void SettingsCache::setVisualDeckStorageCardSize(int /* _visualDeckStorageCardSize */) -{ -} -void SettingsCache::setVisualDeckStorageDrawUnusedColorIdentities( - QT_STATE_CHANGED_T /* _visualDeckStorageDrawUnusedColorIdentities */) -{ -} -void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity( - int /* _visualDeckStorageUnusedColorIdentitiesOpacity */) -{ -} -void SettingsCache::setVisualDeckStorageTooltipType(int /* value */) -{ -} -void SettingsCache::setVisualDeckStoragePromptForConversion(bool /* _visualDeckStoragePromptForConversion */) -{ -} -void SettingsCache::setVisualDeckStorageAlwaysConvert(bool /* _visualDeckStorageAlwaysConvert */) -{ -} -void SettingsCache::setVisualDeckStorageInGame(QT_STATE_CHANGED_T /* value */) -{ -} -void SettingsCache::setVisualDeckStorageSelectionAnimation(QT_STATE_CHANGED_T /* value */) -{ -} -void SettingsCache::setDefaultDeckEditorType(int /* value */) -{ -} -void SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsEnabled(QT_STATE_CHANGED_T /* _enabled */) -{ -} -void SettingsCache::setVisualDatabaseDisplayFilterToMostRecentSetsAmount(int /* _amount */) -{ -} -void SettingsCache::setVisualDeckEditorSampleHandSize(int /* _amount */) -{ -} -void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T /* _horizontalHand */) -{ -} -void SettingsCache::setInvertVerticalCoordinate(QT_STATE_CHANGED_T /* _invertVerticalCoordinate */) -{ -} -void SettingsCache::setMinPlayersForMultiColumnLayout(int /* _minPlayersForMultiColumnLayout */) -{ -} -void SettingsCache::setTapAnimation(QT_STATE_CHANGED_T /* _tapAnimation */) -{ -} -void SettingsCache::setAutoRotateSidewaysLayoutCards(QT_STATE_CHANGED_T /* _autoRotateSidewaysLayoutCards */) -{ -} -void SettingsCache::setOpenDeckInNewTab(QT_STATE_CHANGED_T /* _openDeckInNewTab */) -{ -} -void SettingsCache::setRewindBufferingMs(int /* _rewindBufferingMs */) -{ -} -void SettingsCache::setChatMention(QT_STATE_CHANGED_T /* _chatMention */) -{ -} -void SettingsCache::setChatMentionCompleter(const QT_STATE_CHANGED_T /* _enableMentionCompleter */) -{ -} -void SettingsCache::setChatMentionForeground(QT_STATE_CHANGED_T /* _chatMentionForeground */) -{ -} -void SettingsCache::setChatHighlightForeground(QT_STATE_CHANGED_T /* _chatHighlightForeground */) -{ -} -void SettingsCache::setChatMentionColor(const QString & /* _chatMentionColor */) -{ -} -void SettingsCache::setChatHighlightColor(const QString & /* _chatHighlightColor */) -{ -} -void SettingsCache::setZoneViewGroupByIndex(int /* _zoneViewGroupByIndex */) -{ -} -void SettingsCache::setZoneViewSortByIndex(int /* _zoneViewSortByIndex */) -{ -} -void SettingsCache::setZoneViewPileView(QT_STATE_CHANGED_T /* _zoneViewPileView */) -{ -} -void SettingsCache::setSoundEnabled(QT_STATE_CHANGED_T /* _soundEnabled */) -{ -} -void SettingsCache::setSoundThemeName(const QString & /* _soundThemeName */) -{ -} -void SettingsCache::setIgnoreUnregisteredUsers(QT_STATE_CHANGED_T /* _ignoreUnregisteredUsers */) -{ -} -void SettingsCache::setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T /* _ignoreUnregisteredUserMessages */) -{ -} -void SettingsCache::setMainWindowGeometry(const QByteArray & /* _mainWindowGeometry */) -{ -} -void SettingsCache::setTokenDialogGeometry(const QByteArray & /* _tokenDialogGeometry */) -{ -} -void SettingsCache::setSetsDialogGeometry(const QByteArray & /* _setsDialogGeometry */) -{ -} -void SettingsCache::setPixmapCacheSize(const int /* _pixmapCacheSize */) -{ -} -void SettingsCache::setNetworkCacheSizeInMB(const int /* _networkCacheSize */) -{ -} -void SettingsCache::setNetworkRedirectCacheTtl(const int /* _redirectCacheTtl */) -{ -} -void SettingsCache::setClientID(const QString & /* _clientID */) -{ -} -void SettingsCache::setClientVersion(const QString & /* _clientVersion */) -{ -} -QStringList SettingsCache::getCountries() const -{ - static QStringList countries = QStringList() << "us"; - return countries; -} -void SettingsCache::setGameDescription(const QString /* _gameDescription */) -{ -} -void SettingsCache::setMaxPlayers(const int /* _maxPlayers */) -{ -} -void SettingsCache::setGameTypes(const QString /* _gameTypes */) -{ -} -void SettingsCache::setOnlyBuddies(const bool /* _onlyBuddies */) -{ -} -void SettingsCache::setOnlyRegistered(const bool /* _onlyRegistered */) -{ -} -void SettingsCache::setSpectatorsAllowed(const bool /* _spectatorsAllowed */) -{ -} -void SettingsCache::setSpectatorsNeedPassword(const bool /* _spectatorsNeedPassword */) -{ -} -void SettingsCache::setSpectatorsCanTalk(const bool /* _spectatorsCanTalk */) -{ -} -void SettingsCache::setSpectatorsCanSeeEverything(const bool /* _spectatorsCanSeeEverything */) -{ -} -void SettingsCache::setCreateGameAsSpectator(const bool /* _createGameAsSpectator */) -{ -} -void SettingsCache::setDefaultStartingLifeTotal(const int /* _startingLifeTotal */) -{ -} -void SettingsCache::setShareDecklistsOnLoad(const bool /* _shareDecklistsOnLoad */) -{ -} -void SettingsCache::setRememberGameSettings(const bool /* _rememberGameSettings */) -{ -} -void SettingsCache::setCheckUpdatesOnStartup(QT_STATE_CHANGED_T /* value */) -{ -} -void SettingsCache::setStartupCardUpdateCheckPromptForUpdate(bool /* value */) -{ -} -void SettingsCache::setStartupCardUpdateCheckAlwaysUpdate(bool /* value */) -{ -} -void SettingsCache::setCardUpdateCheckInterval(int /* value */) -{ -} -void SettingsCache::setLastCardUpdateCheck(QDate /* value */) -{ -} -void SettingsCache::setNotifyAboutUpdate(QT_STATE_CHANGED_T /* _notifyaboutupdate */) -{ -} -void SettingsCache::setNotifyAboutNewVersion(QT_STATE_CHANGED_T /* _notifyaboutnewversion */) -{ -} -void SettingsCache::setDownloadSpoilerStatus(bool /* _spoilerStatus */) -{ -} -void SettingsCache::setUpdateReleaseChannelIndex(int /* value */) -{ -} -void SettingsCache::setMaxFontSize(int /* _max */) -{ -} -void SettingsCache::setRoundCardCorners(bool /* _roundCardCorners */) -{ -} - -void CardPictureLoader::clearPixmapCache(CardInfoPtr /* card */) -{ -} diff --git a/dbconverter/src/mocks.h b/dbconverter/src/mocks.h deleted file mode 100644 index 929092bd7..000000000 --- a/dbconverter/src/mocks.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Beware of this preprocessor hack used to redefine the settingCache class - * instead of including it and all of its dependencies. - * Always set header guards of mocked objects before including any headers - * with mocked objects. - */ - -#include -#include - -#define PICTURELOADER_H - -#include "../../cockatrice/src/client/settings/cache_settings.h" - -#include -#include - -extern SettingsCache *settingsCache; - -class CardPictureLoader -{ -public: - static void clearPixmapCache(CardInfoPtr card); -}; diff --git a/format.sh b/format.sh index 51fccbdcf..f8c183dfc 100755 --- a/format.sh +++ b/format.sh @@ -14,7 +14,6 @@ cd "${BASH_SOURCE%/*}/" || exit 2 # could not find path, this could happen with # defaults include=("cockatrice/src" \ -"dbconverter/src" \ "libcockatrice_card" \ "libcockatrice_deck_list" \ "libcockatrice_network" \ From 73a90bdf38f47e9cb2bbf6a2ce3ff652a1b37476 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sat, 20 Dec 2025 17:46:13 +0100 Subject: [PATCH 077/325] Doxygen: Add bullet points to subpages lists & link webpage on welcome page (#6377) * add bullet points to subpages * Link to webpage from welcome page * Add bullet points to subpages * grouping * Add TODO note to card database documentation Added a TODO note for future updates. * Fix GH alerts commands to be doxygen compatible --- README.md | 8 ++++---- .../card_database_schema_and_parsing.md | 4 +++- doc/doxygen/extra-pages/index.md | 4 +++- .../deck_management/editing_decks.md | 7 +++---- .../extra-pages/user_documentation/index.md | 18 +++++++----------- 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 47fb3a159..3d01e9f3d 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,8 @@ Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other p - [Official Discord](https://discord.gg/3Z9yzmA) - [reddit r/Cockatrice](https://reddit.com/r/cockatrice) ->[!IMPORTANT] ->For support regarding specific servers, please contact that server's admin/mods and use their dedicated communication channels rather than contacting the team building the software. +> [!IMPORTANT] +> For support regarding specific servers, please contact that server's admin/mods and use their dedicated communication channels rather than contacting the team building the software. # Contribute @@ -131,8 +131,8 @@ You can then make package ``` ->[!NOTE] ->Detailed compiling instructions can be found in the Cockatrice wiki at [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice) +> [!NOTE] +> Detailed compiling instructions can be found in the Cockatrice wiki at [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)
diff --git a/doc/doxygen/extra-pages/developer_documentation/card_database_schema_and_parsing.md b/doc/doxygen/extra-pages/developer_documentation/card_database_schema_and_parsing.md index 6c5e3512b..1de6fda4f 100644 --- a/doc/doxygen/extra-pages/developer_documentation/card_database_schema_and_parsing.md +++ b/doc/doxygen/extra-pages/developer_documentation/card_database_schema_and_parsing.md @@ -1 +1,3 @@ -@page card_database_schema_and_parsing Card Database Schema and Parsing \ No newline at end of file +@page card_database_schema_and_parsing Card Database Schema and Parsing + +TODO diff --git a/doc/doxygen/extra-pages/index.md b/doc/doxygen/extra-pages/index.md index 66113117f..c370a6fc2 100644 --- a/doc/doxygen/extra-pages/index.md +++ b/doc/doxygen/extra-pages/index.md @@ -5,4 +5,6 @@ This is the **main landing page** of the Cockatrice documentation. - Go to the @subpage user_reference page -- Or check out the @subpage developer_reference \ No newline at end of file +- Review the @subpage developer_reference + +Or check out the [Cockatrice Webpage](https://cockatrice.github.io/). 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 030811306..37b613461 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 @@ -1,7 +1,6 @@ @page editing_decks Editing Decks -@subpage editing_decks_classic + - @subpage editing_decks_classic + - @subpage editing_decks_visual -@subpage editing_decks_visual - -@subpage editing_decks_printings \ No newline at end of file + - @subpage editing_decks_printings diff --git a/doc/doxygen/extra-pages/user_documentation/index.md b/doc/doxygen/extra-pages/user_documentation/index.md index 8a5c88c37..33104991a 100644 --- a/doc/doxygen/extra-pages/user_documentation/index.md +++ b/doc/doxygen/extra-pages/user_documentation/index.md @@ -1,13 +1,9 @@ @page user_reference User Reference -@subpage search_syntax_help - -@subpage deck_search_syntax_help - -@subpage creating_decks - -@subpage importing_decks - -@subpage editing_decks - -@subpage exporting_decks \ No newline at end of file +- @subpage search_syntax_help +- @subpage deck_search_syntax_help + +- @subpage creating_decks +- @subpage importing_decks +- @subpage editing_decks +- @subpage exporting_decks From a0f977e80c9e3513fb288899bb5c18b9cc02ea75 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 21 Dec 2025 16:19:33 -0800 Subject: [PATCH 078/325] [DeckList] refactor: pass DeckList by const ref (#6437) * [DeckList] refactor: pass DeckList by const ref * Change getDeckList to return a const ref --- .../interfaces/deck_stats_interface.cpp | 14 +++---- .../network/interfaces/deck_stats_interface.h | 6 +-- .../interfaces/tapped_out_interface.cpp | 14 +++---- .../network/interfaces/tapped_out_interface.h | 6 +-- .../src/interface/deck_loader/deck_loader.cpp | 32 +++++++-------- .../src/interface/deck_loader/deck_loader.h | 12 +++--- .../deck_editor_deck_dock_widget.cpp | 4 +- .../deck_editor_deck_dock_widget.h | 2 +- .../dialogs/dlg_load_deck_from_clipboard.cpp | 4 +- .../widgets/tabs/abstract_tab_deck_editor.cpp | 41 ++++++++----------- .../widgets/tabs/abstract_tab_deck_editor.h | 2 +- .../deck_preview/deck_preview_widget.cpp | 8 ++-- 12 files changed, 70 insertions(+), 75 deletions(-) diff --git a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp index 5e9b874d0..0298daa6b 100644 --- a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp +++ b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp @@ -43,23 +43,23 @@ void DeckStatsInterface::queryFinished(QNetworkReply *reply) deleteLater(); } -void DeckStatsInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data) +void DeckStatsInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray &data) { DeckList deckWithoutTokens; - copyDeckWithoutTokens(*deck, deckWithoutTokens); + copyDeckWithoutTokens(deck, deckWithoutTokens); QUrl params; QUrlQuery urlQuery; urlQuery.addQueryItem("deck", deckWithoutTokens.writeToString_Plain()); - urlQuery.addQueryItem("decktitle", deck->getName()); + urlQuery.addQueryItem("decktitle", deck.getName()); params.setQuery(urlQuery); - data->append(params.query(QUrl::EncodeReserved).toUtf8()); + data.append(params.query(QUrl::EncodeReserved).toUtf8()); } -void DeckStatsInterface::analyzeDeck(DeckList *deck) +void DeckStatsInterface::analyzeDeck(const DeckList &deck) { QByteArray data; - getAnalyzeRequestData(deck, &data); + getAnalyzeRequestData(deck, data); QNetworkRequest request(QUrl("https://deckstats.net/index.php")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); @@ -68,7 +68,7 @@ void DeckStatsInterface::analyzeDeck(DeckList *deck) manager->post(request, data); } -void DeckStatsInterface::copyDeckWithoutTokens(DeckList &source, DeckList &destination) +void DeckStatsInterface::copyDeckWithoutTokens(const DeckList &source, DeckList &destination) { auto copyIfNotAToken = [this, &destination](const auto node, const auto card) { CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName()); diff --git a/cockatrice/src/client/network/interfaces/deck_stats_interface.h b/cockatrice/src/client/network/interfaces/deck_stats_interface.h index fd1691a8b..7dc841027 100644 --- a/cockatrice/src/client/network/interfaces/deck_stats_interface.h +++ b/cockatrice/src/client/network/interfaces/deck_stats_interface.h @@ -28,15 +28,15 @@ private: * closest non-token card instead. So we construct a new deck which has no * tokens. */ - void copyDeckWithoutTokens(DeckList &source, DeckList &destination); + void copyDeckWithoutTokens(const DeckList &source, DeckList &destination); private slots: void queryFinished(QNetworkReply *reply); - void getAnalyzeRequestData(DeckList *deck, QByteArray *data); + void getAnalyzeRequestData(const DeckList &deck, QByteArray &data); public: explicit DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr); - void analyzeDeck(DeckList *deck); + void analyzeDeck(const DeckList &deck); }; #endif diff --git a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp index 137c7728c..a30a7f531 100644 --- a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp +++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp @@ -67,24 +67,24 @@ void TappedOutInterface::queryFinished(QNetworkReply *reply) deleteLater(); } -void TappedOutInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data) +void TappedOutInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray &data) { DeckList mainboard, sideboard; - copyDeckSplitMainAndSide(*deck, mainboard, sideboard); + copyDeckSplitMainAndSide(deck, mainboard, sideboard); QUrl params; QUrlQuery urlQuery; - urlQuery.addQueryItem("name", deck->getName()); + urlQuery.addQueryItem("name", deck.getName()); urlQuery.addQueryItem("mainboard", mainboard.writeToString_Plain(false, true)); urlQuery.addQueryItem("sideboard", sideboard.writeToString_Plain(false, true)); params.setQuery(urlQuery); - data->append(params.query(QUrl::EncodeReserved).toUtf8()); + data.append(params.query(QUrl::EncodeReserved).toUtf8()); } -void TappedOutInterface::analyzeDeck(DeckList *deck) +void TappedOutInterface::analyzeDeck(const DeckList &deck) { QByteArray data; - getAnalyzeRequestData(deck, &data); + getAnalyzeRequestData(deck, data); QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); @@ -93,7 +93,7 @@ void TappedOutInterface::analyzeDeck(DeckList *deck) manager->post(request, data); } -void TappedOutInterface::copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard) +void TappedOutInterface::copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard) { auto copyMainOrSide = [this, &mainboard, &sideboard](const auto node, const auto card) { CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName()); diff --git a/cockatrice/src/client/network/interfaces/tapped_out_interface.h b/cockatrice/src/client/network/interfaces/tapped_out_interface.h index 056d7fd9a..0ea9c8358 100644 --- a/cockatrice/src/client/network/interfaces/tapped_out_interface.h +++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.h @@ -30,14 +30,14 @@ private: QNetworkAccessManager *manager; CardDatabase &cardDatabase; - void copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard); + void copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard); private slots: void queryFinished(QNetworkReply *reply); - void getAnalyzeRequestData(DeckList *deck, QByteArray *data); + void getAnalyzeRequestData(const DeckList &deck, QByteArray &data); public: explicit TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr); - void analyzeDeck(DeckList *deck); + void analyzeDeck(const DeckList &deck); }; #endif diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index e567e45fd..45c3ec1ba 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -287,14 +287,14 @@ static QString toDecklistExportString(const QList &car * @param deckList The decklist to export * @param website The website we're sending the deck to */ -QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsite website) +QString DeckLoader::exportDeckToDecklist(const DeckList &deckList, DecklistWebsite website) { // Add the base url QString deckString = "https://" + getDomainForWebsite(website) + "/?"; // export all cards in zone - QString mainBoardCards = toDecklistExportString(deckList->getCardNodes({DECK_ZONE_MAIN})); - QString sideBoardCards = toDecklistExportString(deckList->getCardNodes({DECK_ZONE_SIDE})); + QString mainBoardCards = toDecklistExportString(deckList.getCardNodes({DECK_ZONE_MAIN})); + QString sideBoardCards = toDecklistExportString(deckList.getCardNodes({DECK_ZONE_SIDE})); // Remove the extra return at the end of the last cards mainBoardCards.chop(3); @@ -310,7 +310,7 @@ QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsi return deckString; } -void DeckLoader::saveToClipboard(const DeckList *deckList, bool addComments, bool addSetNameAndNumber) +void DeckLoader::saveToClipboard(const DeckList &deckList, bool addComments, bool addSetNameAndNumber) { QString buffer; QTextStream stream(&buffer); @@ -320,7 +320,7 @@ void DeckLoader::saveToClipboard(const DeckList *deckList, bool addComments, boo } bool DeckLoader::saveToStream_Plain(QTextStream &out, - const DeckList *deckList, + const DeckList &deckList, bool addComments, bool addSetNameAndNumber) { @@ -329,7 +329,7 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out, } // loop zones - for (auto zoneNode : deckList->getZoneNodes()) { + for (auto zoneNode : deckList.getZoneNodes()) { saveToStream_DeckZone(out, zoneNode, addComments, addSetNameAndNumber); // end of zone @@ -339,14 +339,14 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out, return true; } -void DeckLoader::saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList) +void DeckLoader::saveToStream_DeckHeader(QTextStream &out, const DeckList &deckList) { - if (!deckList->getName().isEmpty()) { - out << "// " << deckList->getName() << "\n\n"; + if (!deckList.getName().isEmpty()) { + out << "// " << deckList.getName() << "\n\n"; } - if (!deckList->getComments().isEmpty()) { - QStringList commentRows = deckList->getComments().split(QRegularExpression("\n|\r\n|\r")); + if (!deckList.getComments().isEmpty()) { + QStringList commentRows = deckList.getComments().split(QRegularExpression("\n|\r\n|\r")); for (const QString &row : commentRows) { out << "// " << row << "\n"; } @@ -434,7 +434,7 @@ void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out, } } -bool DeckLoader::convertToCockatriceFormat(QString fileName) +bool DeckLoader::convertToCockatriceFormat(const QString &fileName) { // Change the file extension to .cod QFileInfo fileInfo(fileName); @@ -543,7 +543,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode cursor->movePosition(QTextCursor::End); } -void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList) +void DeckLoader::printDeckList(QPrinter *printer, const DeckList &deckList) { QTextDocument doc; @@ -559,14 +559,14 @@ void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList) headerCharFormat.setFontWeight(QFont::Bold); cursor.insertBlock(headerBlockFormat, headerCharFormat); - cursor.insertText(deckList->getName()); + cursor.insertText(deckList.getName()); headerCharFormat.setFontPointSize(12); cursor.insertBlock(headerBlockFormat, headerCharFormat); - cursor.insertText(deckList->getComments()); + cursor.insertText(deckList.getComments()); cursor.insertBlock(headerBlockFormat, headerCharFormat); - for (auto zoneNode : deckList->getZoneNodes()) { + for (auto zoneNode : deckList.getZoneNodes()) { cursor.insertHtml("
"); cursor.insertBlock(headerBlockFormat, headerCharFormat); diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h index 4ceb9006b..ec5636995 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.h +++ b/cockatrice/src/interface/deck_loader/deck_loader.h @@ -59,11 +59,11 @@ public: bool saveToFile(const QString &fileName, DeckFileFormat::Format fmt); bool updateLastLoadedTimestamp(const QString &fileName, DeckFileFormat::Format fmt); - static QString exportDeckToDecklist(const DeckList *deckList, DecklistWebsite website); + static QString exportDeckToDecklist(const DeckList &deckList, DecklistWebsite website); - static void saveToClipboard(const DeckList *deckList, bool addComments = true, bool addSetNameAndNumber = true); + static void saveToClipboard(const DeckList &deckList, bool addComments = true, bool addSetNameAndNumber = true); static bool saveToStream_Plain(QTextStream &out, - const DeckList *deckList, + const DeckList &deckList, bool addComments = true, bool addSetNameAndNumber = true); @@ -72,9 +72,9 @@ public: * @param printer The printer to render the decklist to. * @param deckList */ - static void printDeckList(QPrinter *printer, const DeckList *deckList); + static void printDeckList(QPrinter *printer, const DeckList &deckList); - bool convertToCockatriceFormat(QString fileName); + bool convertToCockatriceFormat(const QString &fileName); LoadedDeck &getDeck() { @@ -91,7 +91,7 @@ public: private: static void printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node); - static void saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList); + static void saveToStream_DeckHeader(QTextStream &out, const DeckList &deckList); static void saveToStream_DeckZone(QTextStream &out, const InnerDecklistNode *zoneNode, 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 a9e19195d..facf0c98b 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 @@ -527,9 +527,9 @@ DeckLoader *DeckEditorDeckDockWidget::getDeckLoader() return deckLoader; } -DeckList *DeckEditorDeckDockWidget::getDeckList() +const DeckList &DeckEditorDeckDockWidget::getDeckList() const { - return deckModel->getDeckList(); + return *deckModel->getDeckList(); } /** diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index da175d417..60e1d36d2 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -61,7 +61,7 @@ public slots: void syncDisplayWidgetsToModel(); void sortDeckModelToDeckView(); DeckLoader *getDeckLoader(); - DeckList *getDeckList(); + const DeckList &getDeckList() const; void actIncrement(); bool swapCard(const QModelIndex &idx); void actDecrementCard(const ExactCard &card, QString zoneName); 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 9024b3bdc..b72130c81 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 @@ -150,7 +150,7 @@ DlgEditDeckInClipboard::DlgEditDeckInClipboard(const DeckList &_deckList, bool _ * @param addComments Whether to add annotations * @return A QString */ -static QString deckListToString(const DeckList *deckList, bool addComments) +static QString deckListToString(const DeckList &deckList, bool addComments) { QString buffer; QTextStream stream(&buffer); @@ -160,7 +160,7 @@ static QString deckListToString(const DeckList *deckList, bool addComments) void DlgEditDeckInClipboard::actRefresh() { - setText(deckListToString(&deckList, annotated)); + setText(deckListToString(deckList, annotated)); } void DlgEditDeckInClipboard::actOK() 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 cdfa5b529..398208993 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -124,7 +124,7 @@ void AbstractTabDeckEditor::onDeckModified() */ void AbstractTabDeckEditor::onDeckHistorySaveRequested(const QString &modificationReason) { - historyManager->save(deckDockWidget->getDeckList()->createMemento(modificationReason)); + historyManager->save(deckDockWidget->getDeckList().createMemento(modificationReason)); } /** @@ -231,7 +231,7 @@ void AbstractTabDeckEditor::openDeck(const LoadedDeck &deck) void AbstractTabDeckEditor::setDeck(const LoadedDeck &_deck) { deckDockWidget->setDeck(_deck); - CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(getDeckList()->getCardRefList())); + CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(getDeckList().getCardRefList())); setModified(false); aDeckDockVisible->setChecked(true); @@ -245,7 +245,7 @@ DeckLoader *AbstractTabDeckEditor::getDeckLoader() const } /** @brief Returns the currently loaded deck list. */ -DeckList *AbstractTabDeckEditor::getDeckList() const +const DeckList &AbstractTabDeckEditor::getDeckList() const { return deckDockWidget->getDeckList(); } @@ -446,7 +446,7 @@ bool AbstractTabDeckEditor::actSaveDeckAs() dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setDefaultSuffix("cod"); dialog.setNameFilters(DeckLoader::FILE_NAME_FILTERS); - dialog.selectFile(getDeckList()->getName().trimmed()); + dialog.selectFile(getDeckList().getName().trimmed()); if (!dialog.exec()) return false; @@ -591,26 +591,21 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite() */ void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website) { - if (DeckList *deckList = getDeckList()) { - QString decklistUrlString = DeckLoader::exportDeckToDecklist(deckList, website); - // Check to make sure the string isn't empty. - if (decklistUrlString.isEmpty()) { - // Show an error if the deck is empty, and return. - QMessageBox::critical(this, tr("Error"), tr("There are no cards in your deck to be exported")); - return; - } - - // Encode the string recieved from the model to make sure all characters are encoded. - // first we put it into a qurl object - QUrl decklistUrl = QUrl(decklistUrlString); - // we get the correctly encoded url. - decklistUrlString = decklistUrl.toEncoded(); - // We open the url in the user's default browser - QDesktopServices::openUrl(decklistUrlString); - } else { - // if there's no deck loader object, return an error - QMessageBox::critical(this, tr("Error"), tr("No deck was selected to be exported.")); + QString decklistUrlString = DeckLoader::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. + QMessageBox::critical(this, tr("Error"), tr("There are no cards in your deck to be exported")); + return; } + + // Encode the string recieved from the model to make sure all characters are encoded. + // first we put it into a qurl object + QUrl decklistUrl = QUrl(decklistUrlString); + // we get the correctly encoded url. + decklistUrlString = decklistUrl.toEncoded(); + // We open the url in the user's default browser + QDesktopServices::openUrl(decklistUrlString); } /** @brief Exports deck to www.decklist.org. */ diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h index e1ffa45de..10675dd39 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h @@ -122,7 +122,7 @@ public: DeckLoader *getDeckLoader() const; /** @brief Returns the currently active deck list. */ - DeckList *getDeckList() const; + const DeckList &getDeckList() const; /** @brief Sets the modified state of the tab. * @param _windowModified Whether the tab is modified. 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 7177879a8..4c782c989 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 @@ -334,13 +334,13 @@ QMenu *DeckPreviewWidget::createRightClickMenu() auto saveToClipboardMenu = menu->addMenu(tr("Save Deck to Clipboard")); connect(saveToClipboardMenu->addAction(tr("Annotated")), &QAction::triggered, this, - [this] { DeckLoader::saveToClipboard(&deckLoader->getDeck().deckList, true, true); }); + [this] { DeckLoader::saveToClipboard(deckLoader->getDeck().deckList, true, true); }); connect(saveToClipboardMenu->addAction(tr("Annotated (No set info)")), &QAction::triggered, this, - [this] { DeckLoader::saveToClipboard(&deckLoader->getDeck().deckList, true, false); }); + [this] { DeckLoader::saveToClipboard(deckLoader->getDeck().deckList, true, false); }); connect(saveToClipboardMenu->addAction(tr("Not Annotated")), &QAction::triggered, this, - [this] { DeckLoader::saveToClipboard(&deckLoader->getDeck().deckList, false, true); }); + [this] { DeckLoader::saveToClipboard(deckLoader->getDeck().deckList, false, true); }); connect(saveToClipboardMenu->addAction(tr("Not Annotated (No set info)")), &QAction::triggered, this, - [this] { DeckLoader::saveToClipboard(&deckLoader->getDeck().deckList, false, false); }); + [this] { DeckLoader::saveToClipboard(deckLoader->getDeck().deckList, false, false); }); menu->addSeparator(); From c12f4e9d2a0cf81ef457221ccc4d80f1fb06f059 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 21 Dec 2025 16:19:57 -0800 Subject: [PATCH 079/325] [DeckListModel] remove more access to underlying decklist for iteration (#6436) * [DeckListModel] remove more access to underlying decklist for iteration * remove one last direct iteration of decklist --- .../deck_editor/deck_editor_deck_dock_widget.cpp | 8 ++++---- .../widgets/dialogs/dlg_select_set_for_cards.cpp | 6 +++--- ...rchidekt_api_response_deck_display_widget.cpp | 4 +--- .../models/deck_list/deck_list_model.cpp | 16 ++++++++++++++++ .../models/deck_list/deck_list_model.h | 11 +++++++++++ 5 files changed, 35 insertions(+), 10 deletions(-) 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 facf0c98b..93286191f 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 @@ -386,15 +386,15 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() // Collect unique (name, providerId) pairs QSet> bannerCardSet; - QList cardsInDeck = deckModel->getDeckList()->getCardNodes(); + QList cardsInDeck = deckModel->getCardRefs(); - for (auto currentCard : cardsInDeck) { - if (!CardDatabaseManager::query()->getCard(currentCard->toCardRef())) { + for (auto cardRef : cardsInDeck) { + if (!CardDatabaseManager::query()->getCard(cardRef)) { continue; } // Insert one entry per distinct card, ignore copies - bannerCardSet.insert({currentCard->getName(), currentCard->getCardProviderId()}); + bannerCardSet.insert({cardRef.name, cardRef.providerId}); } // Convert to sorted list 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 52768e0e7..656abbfdc 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 @@ -178,7 +178,7 @@ void DlgSelectSetForCards::actOK() void DlgSelectSetForCards::actClear() { emit deckAboutToBeModified(tr("Cleared all printing information.")); - model->getDeckList()->forEachCard(CardNodeFunction::ClearPrintingData()); + model->forEachCard(CardNodeFunction::ClearPrintingData()); emit deckModified(); accept(); } @@ -186,8 +186,8 @@ void DlgSelectSetForCards::actClear() void DlgSelectSetForCards::actSetAllToPreferred() { emit deckAboutToBeModified(tr("Set all printings to preferred.")); - model->getDeckList()->forEachCard(CardNodeFunction::ClearPrintingData()); - model->getDeckList()->forEachCard(CardNodeFunction::SetProviderIdToPreferred()); + model->forEachCard(CardNodeFunction::ClearPrintingData()); + model->forEachCard(CardNodeFunction::SetProviderIdToPreferred()); emit deckModified(); accept(); } 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 ef29a1732..288cdc425 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 @@ -70,9 +70,7 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset); model->getDeckList()->loadFromStream_Plain(deckStream, false); - model->getDeckList()->forEachCard(CardNodeFunction::ResolveProviderId()); - - model->rebuildTree(); + model->forEachCard(CardNodeFunction::ResolveProviderId()); retranslateUi(); } 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 8859a31fe..5fd4d71e8 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -561,6 +561,11 @@ void DeckListModel::setDeckList(DeckList *_deck) rebuildTree(); } +void DeckListModel::forEachCard(const std::function &func) +{ + deckList->forEachCard(func); +} + static QList cardNodesToExactCards(QList nodes) { QList cards; @@ -600,6 +605,17 @@ QList DeckListModel::getCardNames() const return names; } +QList DeckListModel::getCardRefs() const +{ + auto nodes = deckList->getCardNodes(); + + QList cardRefs; + std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(cardRefs), + [](auto node) { return node->toCardRef(); }); + + return cardRefs; +} + QList DeckListModel::getZones() const { auto zoneNodes = deckList->getZoneNodes(); 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 6e8882084..80a519297 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -309,6 +309,13 @@ public: } void setDeckList(DeckList *_deck); + /** + * @brief Apply a function to every card in the deck tree. + * + * @param func Function taking (zone node, card node). + */ + void forEachCard(const std::function &func); + /** * @brief Creates a list consisting of the entries of the model mapped into ExactCards (with each entry looked up * in the card database). @@ -323,6 +330,10 @@ public: * @brief Gets a deduplicated list of all card names that appear in the model */ [[nodiscard]] QList getCardNames() const; + /** + * @brief Gets a deduplicated list of all CardRefs that appear in the model + */ + [[nodiscard]] QList getCardRefs() const; /** * @brief Gets a list of all zone names that appear in the model */ From e80f13b78e25ca90cdb88e14ababeb2c22e6aa52 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Mon, 22 Dec 2025 05:48:55 -0800 Subject: [PATCH 080/325] [DeckDockWidget] Refactor to move down some methods in AbstractTabDeckEditor (#6444) * move actSwapCard down * rename method * move actAddCard down --- .../deck_editor_deck_dock_widget.cpp | 56 ++++++++++++++++--- .../deck_editor_deck_dock_widget.h | 10 ++-- .../widgets/tabs/abstract_tab_deck_editor.cpp | 36 +----------- .../widgets/tabs/abstract_tab_deck_editor.h | 6 +- .../tab_deck_editor_visual.cpp | 2 +- 5 files changed, 57 insertions(+), 53 deletions(-) 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 93286191f..3ee7f647a 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 @@ -76,14 +76,14 @@ void DeckEditorDeckDockWidget::createDeckDock() deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); connect(deckView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &DeckEditorDeckDockWidget::updateCard); - connect(deckView, &QTreeView::doubleClicked, this, &DeckEditorDeckDockWidget::actSwapCard); + connect(deckView, &QTreeView::doubleClicked, this, &DeckEditorDeckDockWidget::actSwapSelection); deckView->setContextMenuPolicy(Qt::CustomContextMenu); connect(deckView, &QTreeView::customContextMenuRequested, this, &DeckEditorDeckDockWidget::decklistCustomMenu); - connect(&deckViewKeySignals, &KeySignals::onShiftS, this, &DeckEditorDeckDockWidget::actSwapCard); - connect(&deckViewKeySignals, &KeySignals::onEnter, this, &DeckEditorDeckDockWidget::actIncrement); - connect(&deckViewKeySignals, &KeySignals::onCtrlAltEqual, this, &DeckEditorDeckDockWidget::actIncrement); + connect(&deckViewKeySignals, &KeySignals::onShiftS, this, &DeckEditorDeckDockWidget::actSwapSelection); + connect(&deckViewKeySignals, &KeySignals::onEnter, this, &DeckEditorDeckDockWidget::actIncrementSelection); + connect(&deckViewKeySignals, &KeySignals::onCtrlAltEqual, this, &DeckEditorDeckDockWidget::actIncrementSelection); connect(&deckViewKeySignals, &KeySignals::onCtrlAltMinus, this, &DeckEditorDeckDockWidget::actDecrementSelection); - connect(&deckViewKeySignals, &KeySignals::onShiftRight, this, &DeckEditorDeckDockWidget::actIncrement); + connect(&deckViewKeySignals, &KeySignals::onShiftRight, this, &DeckEditorDeckDockWidget::actIncrementSelection); connect(&deckViewKeySignals, &KeySignals::onShiftLeft, this, &DeckEditorDeckDockWidget::actDecrementSelection); connect(&deckViewKeySignals, &KeySignals::onDelete, this, &DeckEditorDeckDockWidget::actRemoveCard); @@ -181,7 +181,7 @@ void DeckEditorDeckDockWidget::createDeckDock() aIncrement = new QAction(QString(), this); aIncrement->setIcon(QPixmap("theme:icons/increment")); - connect(aIncrement, &QAction::triggered, this, &DeckEditorDeckDockWidget::actIncrement); + connect(aIncrement, &QAction::triggered, this, &DeckEditorDeckDockWidget::actIncrementSelection); auto *tbIncrement = new QToolButton(this); tbIncrement->setDefaultAction(aIncrement); @@ -199,7 +199,7 @@ void DeckEditorDeckDockWidget::createDeckDock() aSwapCard = new QAction(QString(), this); aSwapCard->setIcon(QPixmap("theme:icons/swap")); - connect(aSwapCard, &QAction::triggered, this, &DeckEditorDeckDockWidget::actSwapCard); + connect(aSwapCard, &QAction::triggered, this, &DeckEditorDeckDockWidget::actSwapSelection); auto *tbSwapCard = new QToolButton(this); tbSwapCard->setDefaultAction(aSwapCard); @@ -582,7 +582,32 @@ QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodes() const return selectedRows; } -void DeckEditorDeckDockWidget::actIncrement() +void DeckEditorDeckDockWidget::actAddCard(const ExactCard &card, const QString &_zoneName) +{ + if (!card) { + return; + } + + QString zoneName = card.getInfo().getIsToken() ? DECK_ZONE_TOKENS : _zoneName; + + emit requestDeckHistorySave(tr("Added (%1): %2 (%3) %4") + .arg(zoneName, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(), + card.getPrinting().getProperty("num"))); + + QModelIndex newCardIndex = deckModel->addCard(card, zoneName); + + if (!newCardIndex.isValid()) { + return; + } + + expandAll(); + deckView->clearSelection(); + deckView->setCurrentIndex(newCardIndex); + + emit deckModified(); +} + +void DeckEditorDeckDockWidget::actIncrementSelection() { auto selectedRows = getSelectedCardNodes(); @@ -591,7 +616,20 @@ void DeckEditorDeckDockWidget::actIncrement() } } -void DeckEditorDeckDockWidget::actSwapCard() +void DeckEditorDeckDockWidget::actSwapCard(const ExactCard &card, const QString &zoneName) +{ + QString providerId = card.getPrinting().getUuid(); + QString collectorNumber = card.getPrinting().getProperty("num"); + + QModelIndex foundCard = deckModel->findCard(card.getName(), zoneName, providerId, collectorNumber); + if (!foundCard.isValid()) { + foundCard = deckModel->findCard(card.getName(), zoneName); + } + + swapCard(foundCard); +} + +void DeckEditorDeckDockWidget::actSwapSelection() { auto selectedRows = getSelectedCardNodes(); diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index 60e1d36d2..6c88cafff 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -62,13 +62,13 @@ public slots: void sortDeckModelToDeckView(); DeckLoader *getDeckLoader(); const DeckList &getDeckList() const; - void actIncrement(); - bool swapCard(const QModelIndex &idx); + void actAddCard(const ExactCard &card, const QString &zoneName); + void actIncrementSelection(); void actDecrementCard(const ExactCard &card, QString zoneName); void actDecrementSelection(); - void actSwapCard(); + void actSwapCard(const ExactCard &card, const QString &zoneName); + void actSwapSelection(); void actRemoveCard(); - void offsetCountAtIndex(const QModelIndex &idx, int offset); void initializeFormats(); void expandAll(); @@ -108,9 +108,11 @@ private: void recursiveExpand(const QModelIndex &index); [[nodiscard]] QModelIndexList getSelectedCardNodes() const; + void offsetCountAtIndex(const QModelIndex &idx, int offset); private slots: void decklistCustomMenu(QPoint point); + bool swapCard(const QModelIndex ¤tIndex); void updateCard(QModelIndex, const QModelIndex ¤t); void updateName(const QString &name); void updateComments(); 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 398208993..8eb1477f1 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -140,23 +140,9 @@ void AbstractTabDeckEditor::onDeckHistoryClearRequested() * @param card Card to add. * @param zoneName Zone to add the card to. */ -void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, QString zoneName) +void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, const QString &zoneName) { - if (!card) - return; - - if (card.getInfo().getIsToken()) - zoneName = DECK_ZONE_TOKENS; - - onDeckHistorySaveRequested(QString(tr("Added (%1): %2 (%3) %4")) - .arg(zoneName, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(), - card.getPrinting().getProperty("num"))); - - QModelIndex newCardIndex = deckDockWidget->deckModel->addCard(card, zoneName); - deckDockWidget->expandAll(); - deckDockWidget->deckView->clearSelection(); - deckDockWidget->deckView->setCurrentIndex(newCardIndex); - setModified(true); + deckDockWidget->actAddCard(card, zoneName); databaseDisplayDockWidget->searchEdit->setSelection(0, databaseDisplayDockWidget->searchEdit->text().length()); } @@ -193,24 +179,6 @@ void AbstractTabDeckEditor::actDecrementCardFromSideboard(const ExactCard &card) emit decrementCard(card, DECK_ZONE_SIDE); } -/** - * @brief Swaps a card in a deck zone. - * @param card Card to swap. - * @param zoneName Zone to swap in. - */ -void AbstractTabDeckEditor::actSwapCard(const ExactCard &card, const QString &zoneName) -{ - QString providerId = card.getPrinting().getUuid(); - QString collectorNumber = card.getPrinting().getProperty("num"); - - QModelIndex foundCard = deckDockWidget->deckModel->findCard(card.getName(), zoneName, providerId, collectorNumber); - if (!foundCard.isValid()) { - foundCard = deckDockWidget->deckModel->findCard(card.getName(), zoneName); - } - - deckDockWidget->swapCard(foundCard); -} - /** * @brief Opens a deck in this tab. * @param deck The deck diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h index 10675dd39..bfbda778c 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h @@ -77,7 +77,6 @@ class QAction; * * - actAddCard(const ExactCard &card) — Adds a card to the deck. * - actDecrementCard(const ExactCard &card) — Removes a single instance of a card from the deck. - * - actSwapCard(const ExactCard &card, const QString &zone) — Swaps a card between zones. * - actRemoveCard() — Removes the currently selected card from the deck. * - actSaveDeckAs() — Performs a "Save As" action for the deck. * - updateCard(const ExactCard &card) — Updates the currently displayed card info in the dock. @@ -320,10 +319,7 @@ protected: bool isBlankNewDeck() const; /** @brief Helper function to add a card to a specific deck zone. */ - void addCardHelper(const ExactCard &card, QString zoneName); - - /** @brief Swaps a card in the deck view. */ - void actSwapCard(const ExactCard &card, const QString &zoneName); + void addCardHelper(const ExactCard &card, const QString &zoneName); /** @brief Opens a deck from a file. */ virtual void openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation); diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 6dd55d545..57615df94 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -191,7 +191,7 @@ void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event, // Double click = swap if (event->type() == QEvent::MouseButtonDblClick && event->button() == Qt::LeftButton) { - actSwapCard(card, zoneName); + deckDockWidget->actSwapCard(card, zoneName); idx = deckDockWidget->deckModel->findCard(card.getName(), zoneName); sel->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect); return; From e557ae0f2a9177961b86299701e4f3dcbde0b286 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:09:50 +0100 Subject: [PATCH 081/325] Bump actions/upload-artifact from 5 to 6 (#6445) --- .github/workflows/desktop-build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 06549702d..b89190c8d 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -213,7 +213,7 @@ jobs: - name: Upload artifact id: upload_artifact if: matrix.package != 'skip' - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: ${{matrix.distro}}${{matrix.version}}-package path: ${{steps.build.outputs.path}} @@ -450,7 +450,7 @@ jobs: - name: Upload artifact id: upload_artifact if: matrix.make_package - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: ${{matrix.artifact_name}} path: ${{steps.build.outputs.path}} @@ -458,7 +458,7 @@ jobs: - name: Upload pdb database if: matrix.os == 'Windows' - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: Windows${{matrix.target}}-debug-pdbs path: | From be17ee190222c763588bd88d27b413f330ae251b Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 23 Dec 2025 06:07:39 -0800 Subject: [PATCH 082/325] [DeckListModel] Refactor to use column num constants (#6441) --- .../card_group_display_widget.cpp | 7 ++--- .../cards/deck_card_zone_display_widget.cpp | 5 ++-- .../deck_editor_database_display_widget.cpp | 8 +++--- .../deck_editor_deck_dock_widget.cpp | 27 ++++++++++--------- .../printing_selector/card_amount_widget.cpp | 14 +++++----- ...idekt_api_response_deck_display_widget.cpp | 12 +++++---- .../visual_deck_editor_widget.cpp | 7 ++--- .../deck_list_sort_filter_proxy_model.cpp | 4 +-- 8 files changed, 46 insertions(+), 38 deletions(-) diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp index cb83768f4..fdac9d0f0 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp @@ -91,8 +91,9 @@ QWidget *CardGroupDisplayWidget::constructWidgetForIndex(QPersistentModelIndex i if (indexToWidgetMap.contains(index)) { return indexToWidgetMap[index]; } - auto cardName = deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString(); - auto cardProviderId = deckListModel->data(index.sibling(index.row(), 4), Qt::EditRole).toString(); + auto cardName = index.sibling(index.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); + auto cardProviderId = + index.sibling(index.row(), DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::EditRole).toString(); auto widget = new CardInfoPictureWithTextOverlayWidget(getLayoutParent(), true); widget->setScaleFactor(cardSizeWidget->getSlider()->value()); @@ -114,7 +115,7 @@ void CardGroupDisplayWidget::updateCardDisplays() // This doesn't really matter since overwrite the whole lessThan function to just compare dynamically anyway. proxy.setSortRole(Qt::EditRole); - proxy.sort(1, Qt::AscendingOrder); + proxy.sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder); // 1. trackedIndex is a source index → map it to proxy space QModelIndex proxyParent = proxy.mapFromSource(trackedIndex); diff --git a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp index 132964f13..4742467b1 100644 --- a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp @@ -82,10 +82,11 @@ void DeckCardZoneDisplayWidget::cleanupInvalidCardGroup(CardGroupDisplayWidget * void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex index) { - auto categoryName = deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString(); if (indexToWidgetMap.contains(index)) { return; } + + auto categoryName = index.sibling(index.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); if (displayType == DisplayType::Overlap) { auto *displayWidget = new OverlappedCardGroupDisplayWidget( cardGroupContainer, deckListModel, selectionModel, index, zoneName, categoryName, activeGroupCriteria, @@ -120,7 +121,7 @@ void DeckCardZoneDisplayWidget::displayCards() QSortFilterProxyModel proxy; proxy.setSourceModel(deckListModel); proxy.setSortRole(Qt::EditRole); - proxy.sort(1, Qt::AscendingOrder); + proxy.sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder); // 1. trackedIndex is a source index → map it to proxy space QModelIndex proxyParent = proxy.mapFromSource(trackedIndex); 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 b4b27e48a..f5549a98c 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 @@ -134,13 +134,13 @@ void DeckEditorDatabaseDisplayWidget::clearAllDatabaseFilters() void DeckEditorDatabaseDisplayWidget::updateCard(const QModelIndex ¤t, const QModelIndex & /*previous*/) { - const QString cardName = current.sibling(current.row(), 0).data().toString(); - if (!current.isValid()) { return; } - if (!current.model()->hasChildren(current.sibling(current.row(), 0))) { + const QString cardName = current.siblingAtColumn(CardDatabaseModel::NameColumn).data().toString(); + + if (!current.model()->hasChildren(current.siblingAtColumn(CardDatabaseModel::NameColumn))) { emit cardChanged(CardDatabaseManager::query()->getPreferredCard(cardName)); } } @@ -172,7 +172,7 @@ ExactCard DeckEditorDatabaseDisplayWidget::currentCard() const return {}; } - const QString cardName = currentIndex.sibling(currentIndex.row(), 0).data().toString(); + const QString cardName = currentIndex.siblingAtColumn(CardDatabaseModel::NameColumn).data().toString(); return CardDatabaseManager::query()->getPreferredCard(cardName); } 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 3ee7f647a..1e7319aa3 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 @@ -319,17 +319,17 @@ ExactCard DeckEditorDeckDockWidget::getCurrentCard() QModelIndex current = deckView->selectionModel()->currentIndex(); if (!current.isValid()) return {}; - const QString cardName = current.sibling(current.row(), 1).data().toString(); - const QString cardProviderID = current.sibling(current.row(), 4).data().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(); if (!gparent.isValid()) { return {}; } - const QString zoneName = gparent.sibling(gparent.row(), 1).data(Qt::EditRole).toString(); + const QString zoneName = gparent.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); - if (!current.model()->hasChildren(current.sibling(current.row(), 0))) { + if (!current.model()->hasChildren(current.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT))) { if (ExactCard selectedCard = CardDatabaseManager::query()->getCard({cardName, cardProviderID})) { return selectedCard; } @@ -665,14 +665,15 @@ bool DeckEditorDeckDockWidget::swapCard(const QModelIndex ¤tIndex) { if (!currentIndex.isValid()) return false; - const QString cardName = currentIndex.sibling(currentIndex.row(), 1).data().toString(); - const QString cardProviderID = currentIndex.sibling(currentIndex.row(), 4).data().toString(); + const QString cardName = currentIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); + const QString cardProviderID = + currentIndex.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString(); const QModelIndex gparent = currentIndex.parent().parent(); if (!gparent.isValid()) return false; - const QString zoneName = gparent.sibling(gparent.row(), 1).data(Qt::EditRole).toString(); + const QString zoneName = gparent.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); offsetCountAtIndex(currentIndex, -1); const QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN; @@ -738,7 +739,7 @@ void DeckEditorDeckDockWidget::actRemoveCard() continue; } QModelIndex sourceIndex = proxy->mapToSource(index); - QString cardName = sourceIndex.sibling(sourceIndex.row(), 1).data().toString(); + QString cardName = sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); emit requestDeckHistorySave(QString(tr("Removed \"%1\" (all copies)")).arg(cardName)); @@ -761,11 +762,11 @@ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, int of QModelIndex sourceIndex = proxy->mapToSource(idx); - const QModelIndex numberIndex = sourceIndex.sibling(sourceIndex.row(), 0); - const QModelIndex nameIndex = sourceIndex.sibling(sourceIndex.row(), 1); + const QModelIndex numberIndex = sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT); + const QModelIndex nameIndex = sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME); - const QString cardName = deckModel->data(nameIndex, Qt::EditRole).toString(); - const int count = deckModel->data(numberIndex, Qt::EditRole).toInt(); + const QString cardName = nameIndex.data(Qt::EditRole).toString(); + const int count = numberIndex.data(Qt::EditRole).toInt(); const int new_count = count + offset; const auto reason = @@ -773,7 +774,7 @@ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, int of .arg(offset > 0 ? tr("Added") : tr("Removed")) .arg(qAbs(offset)) .arg(cardName) - .arg(deckModel->data(sourceIndex.sibling(sourceIndex.row(), 4), Qt::DisplayRole).toString()); + .arg(sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString()); emit requestDeckHistorySave(reason); diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index 3d519e595..e979637f8 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -151,9 +151,10 @@ void CardAmountWidget::addPrinting(const QString &zone) bool replacingProviderless = false; if (existing.isValid()) { - QString providerId = deckModel->data(existing.sibling(existing.row(), 4), Qt::DisplayRole).toString(); + QString providerId = + existing.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString(); if (providerId.isEmpty()) { - int amount = deckModel->data(existing, Qt::DisplayRole).toInt(); + int amount = existing.data(Qt::DisplayRole).toInt(); extraCopies = amount - 1; // One less because we *always* add one replacingProviderless = true; } @@ -177,9 +178,10 @@ void CardAmountWidget::addPrinting(const QString &zone) recursiveExpand(newCardIndex); // Check if a card without a providerId already exists in the deckModel and replace it, if so. - QString foundProviderId = deckModel->data(existing.sibling(existing.row(), 4), Qt::DisplayRole).toString(); + QString foundProviderId = + existing.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString(); if (existing.isValid() && existing != newCardIndex && foundProviderId == "") { - auto amount = deckModel->data(existing, Qt::DisplayRole); + auto amount = existing.data(Qt::DisplayRole); for (int i = 0; i < amount.toInt() - 1; i++) { deckModel->addCard(rootCard, zone); } @@ -252,8 +254,8 @@ void CardAmountWidget::offsetCountAtIndex(const QModelIndex &idx, int offset) return; } - const QModelIndex numberIndex = idx.sibling(idx.row(), 0); - const int count = deckModel->data(numberIndex, Qt::EditRole).toInt(); + const QModelIndex numberIndex = idx.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT); + const int count = numberIndex.data(Qt::EditRole).toInt(); const int new_count = count + offset; deckView->setCurrentIndex(numberIndex); 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 288cdc425..1547cca74 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 @@ -83,7 +83,7 @@ void ArchidektApiResponseDeckDisplayWidget::retranslateUi() void ArchidektApiResponseDeckDisplayWidget::onGroupCriteriaChange(const QString &activeGroupCriteria) { model->setActiveGroupCriteria(DeckListModelGroupCriteria::fromString(activeGroupCriteria)); - model->sort(1, Qt::AscendingOrder); + model->sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder); } void ArchidektApiResponseDeckDisplayWidget::actOpenInDeckEditor() @@ -120,7 +120,7 @@ void ArchidektApiResponseDeckDisplayWidget::constructZoneWidgetsFromDeckListMode QSortFilterProxyModel proxy; proxy.setSourceModel(model); proxy.setSortRole(Qt::EditRole); - proxy.sort(1, Qt::AscendingOrder); + proxy.sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder); for (int i = 0; i < proxy.rowCount(); ++i) { QModelIndex proxyIndex = proxy.index(i, 0); @@ -133,10 +133,12 @@ void ArchidektApiResponseDeckDisplayWidget::constructZoneWidgetsFromDeckListMode continue; } + QString zoneName = + persistent.sibling(persistent.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); + DeckCardZoneDisplayWidget *zoneDisplayWidget = - new DeckCardZoneDisplayWidget(zoneContainer, model, nullptr, persistent, - model->data(persistent.sibling(persistent.row(), 1), Qt::EditRole).toString(), - "maintype", {"name"}, DisplayType::Overlap, 20, 10, cardSizeSlider); + new DeckCardZoneDisplayWidget(zoneContainer, model, nullptr, persistent, zoneName, "maintype", {"name"}, + DisplayType::Overlap, 20, 10, cardSizeSlider); connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::sortCriteriaChanged, zoneDisplayWidget, &DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged); 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 76cfe8c8e..7b70e2b6d 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 @@ -264,9 +264,10 @@ void VisualDeckEditorWidget::onCardRemoval(const QModelIndex &parent, int first, void VisualDeckEditorWidget::constructZoneWidgetForIndex(QPersistentModelIndex persistent) { + QString zoneName = + persistent.sibling(persistent.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); DeckCardZoneDisplayWidget *zoneDisplayWidget = new DeckCardZoneDisplayWidget( - zoneContainer, deckListModel, selectionModel, persistent, - deckListModel->data(persistent.sibling(persistent.row(), 1), Qt::EditRole).toString(), + zoneContainer, deckListModel, selectionModel, persistent, zoneName, displayOptionsWidget->getActiveGroupCriteria(), displayOptionsWidget->getActiveSortCriteria(), displayOptionsWidget->getDisplayType(), 20, 10, cardSizeWidget); connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover); @@ -290,7 +291,7 @@ void VisualDeckEditorWidget::constructZoneWidgetsFromDeckListModel() QSortFilterProxyModel proxy; proxy.setSourceModel(deckListModel); proxy.setSortRole(Qt::EditRole); - proxy.sort(1, Qt::AscendingOrder); + proxy.sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder); for (int i = 0; i < proxy.rowCount(); ++i) { QModelIndex proxyIndex = proxy.index(i, 0); diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.cpp b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.cpp index 35fd4d9f8..0ec159737 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.cpp @@ -11,8 +11,8 @@ bool DeckListSortFilterProxyModel::lessThan(const QModelIndex &left, const QMode bool rightIsCard = src->data(right, Qt::UserRole + 1).toBool(); if (!leftIsCard || !rightIsCard) { - QString lName = src->data(left.siblingAtColumn(1), Qt::EditRole).toString(); - QString rName = src->data(right.siblingAtColumn(1), Qt::EditRole).toString(); + QString lName = src->data(left.siblingAtColumn(DeckListModelColumns::CARD_NAME), Qt::EditRole).toString(); + QString rName = src->data(right.siblingAtColumn(DeckListModelColumns::CARD_NAME), Qt::EditRole).toString(); return lName.localeAwareCompare(rName) < 0; } From 01e8e4d5895408a5eee9a0300988467c5c506db6 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 23 Dec 2025 06:45:27 -0800 Subject: [PATCH 083/325] [DeckDockWidget] Fix swap not auto-expanding tree (#6443) --- .../widgets/deck_editor/deck_editor_deck_dock_widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1e7319aa3..625af4dfa 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 @@ -681,7 +681,7 @@ bool DeckEditorDeckDockWidget::swapCard(const QModelIndex ¤tIndex) QModelIndex newCardIndex = card ? deckModel->addCard(card, otherZoneName) // Third argument (true) says create the card no matter what, even if not in DB : deckModel->addPreferredPrintingCard(cardName, otherZoneName, true); - recursiveExpand(proxy->mapToSource(newCardIndex)); + recursiveExpand(proxy->mapFromSource(newCardIndex)); return true; } From e7af1bbec9425146de1d3f92ba942f093d302379 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Tue, 23 Dec 2025 16:00:07 +0100 Subject: [PATCH 084/325] [EDHRec] New layout for commander details (#6405) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Stuff Took 22 minutes * New layout for commander details. Took 1 hour 18 minutes * Update plate to encompass everything, update font sizes. Took 10 minutes * Include map. Took 2 minutes * Include QSet Took 5 minutes --------- Co-authored-by: Lukas Brübach --- cockatrice/CMakeLists.txt | 4 + ...i_response_card_details_display_widget.cpp | 6 +- ...ponse_commander_details_display_widget.cpp | 33 ++++- ...esponse_commander_details_display_widget.h | 6 +- ...api_response_bracket_navigation_widget.cpp | 99 ++++++++++++++ ...r_api_response_bracket_navigation_widget.h | 37 ++++++ ..._api_response_budget_navigation_widget.cpp | 101 ++++++++++++++ ...er_api_response_budget_navigation_widget.h | 37 ++++++ ...mmander_api_response_navigation_widget.cpp | 123 +++--------------- ...commander_api_response_navigation_widget.h | 17 +-- 10 files changed, 336 insertions(+), 127 deletions(-) create mode 100644 cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp create mode 100644 cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h create mode 100644 cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp create mode 100644 cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 4c3679011..b2d387391 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -287,6 +287,10 @@ set(cockatrice_SOURCES src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp src/interface/key_signals.cpp src/interface/logger.cpp + src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp + src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h + src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp + src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h ) add_subdirectory(sounds) diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp index 26ade96dd..c01c7fa43 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp @@ -19,17 +19,17 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi nameLabel = new QLabel(this); nameLabel->setText(toDisplay.name); nameLabel->setAlignment(Qt::AlignHCenter); + nameLabel->setStyleSheet("font-size: 20px; font-weight: bold"); inclusionDisplayWidget = new EdhrecApiResponseCardInclusionDisplayWidget(this, toDisplay); synergyDisplayWidget = new EdhrecApiResponseCardSynergyDisplayWidget(this, toDisplay); - layout->addWidget(nameLabel); - layout->addWidget(cardPictureWidget); - backgroundPlateWidget = new BackgroundPlateWidget(this); auto plateLayout = new QVBoxLayout(backgroundPlateWidget); + plateLayout->addWidget(nameLabel); + plateLayout->addWidget(cardPictureWidget); plateLayout->addWidget(inclusionDisplayWidget); plateLayout->addWidget(synergyDisplayWidget); diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp index b15d559f5..515475f3e 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp @@ -3,6 +3,7 @@ #include "../../../../../cards/card_info_picture_widget.h" #include "../../tab_edhrec_main.h" #include "../card_prices/edhrec_api_response_card_prices_display_widget.h" +#include "edhrec_commander_api_response_bracket_navigation_widget.h" #include @@ -12,9 +13,14 @@ EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCom QString baseUrl) : QWidget(parent), commanderDetails(_commanderDetails) { - layout = new QVBoxLayout(this); + layout = new QHBoxLayout(this); setLayout(layout); + commanderLayout = new QHBoxLayout(); + commanderDetailsLayout = new QVBoxLayout(); + commanderDetailsLayout->setAlignment(Qt::AlignCenter); + navigationAndPricesLayout = new QVBoxLayout(); + commanderPicture = new CardInfoPictureWidget(this); commanderPicture->setCard(CardDatabaseManager::query()->getCard({commanderDetails.getName()})); @@ -36,20 +42,35 @@ EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCom commanderDetails.debugPrint(); + commanderName = new QLabel(this); + commanderName->setText(commanderDetails.getName()); + commanderName->setAlignment(Qt::AlignCenter); + commanderName->setStyleSheet("font-size: 28px; font-weight: bold"); + label = new QLabel(this); label->setAlignment(Qt::AlignCenter); + label->setStyleSheet("font-size: 16px"); + salt = new QLabel(this); salt->setAlignment(Qt::AlignCenter); + salt->setStyleSheet("font-size: 16px"); cardPricesDisplayWidget = new EdhrecApiResponseCardPricesDisplayWidget(this, commanderDetails.getPrices()); navigationWidget = new EdhrecCommanderApiResponseNavigationWidget(this, commanderDetails, baseUrl); - layout->addWidget(commanderPicture); - layout->addWidget(label); - layout->addWidget(salt); - layout->addWidget(cardPricesDisplayWidget); - layout->addWidget(navigationWidget); + commanderLayout->addWidget(commanderPicture); + commanderDetailsLayout->addWidget(commanderName); + commanderDetailsLayout->addSpacing(1); + commanderDetailsLayout->addWidget(label); + commanderDetailsLayout->addWidget(salt); + commanderDetailsLayout->addWidget(cardPricesDisplayWidget); + commanderLayout->addLayout(commanderDetailsLayout); + navigationAndPricesLayout->addWidget(navigationWidget); + // navigationAndPricesLayout->addWidget(cardPricesDisplayWidget); + + layout->addLayout(commanderLayout); + layout->addLayout(navigationAndPricesLayout); retranslateUi(); } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h index 295e4228b..8e74588e2 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h @@ -29,8 +29,12 @@ public: private: EdhrecCommanderApiResponseCommanderDetails commanderDetails; - QVBoxLayout *layout; + QHBoxLayout *layout; + QHBoxLayout *commanderLayout; + QVBoxLayout *commanderDetailsLayout; + QVBoxLayout *navigationAndPricesLayout; CardInfoPictureWidget *commanderPicture; + QLabel *commanderName; QLabel *label; QLabel *salt; EdhrecApiResponseCardPricesDisplayWidget *cardPricesDisplayWidget; diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp new file mode 100644 index 000000000..2bc727bd2 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp @@ -0,0 +1,99 @@ +#include "edhrec_commander_api_response_bracket_navigation_widget.h" + +#include + +EdhrecCommanderApiResponseBracketNavigationWidget::EdhrecCommanderApiResponseBracketNavigationWidget( + QWidget *parent, + const QString &baseUrl) + : QWidget(parent) +{ + layout = new QGridLayout(this); + setLayout(layout); + + gameChangerLabel = new QLabel(this); + + layout->addWidget(gameChangerLabel, 1, 0, 1, 2); + + for (int i = 0; i < gameChangerOptions.length(); i++) { + QString option = gameChangerOptions.at(i); + QString label = option.isEmpty() ? "All" : option.at(0).toUpper() + option.mid(1); + QPushButton *optionButton = new QPushButton(label, this); + optionButton->setMinimumHeight(84); + optionButton->setStyleSheet("font-size: 24px"); + gameChangerButtons[option] = optionButton; + layout->addWidget(optionButton, 2, i); + connect(optionButton, &QPushButton::clicked, this, [=, this]() { + selectedGameChanger = option; + updateOptionButtonSelection(gameChangerButtons, option); + emit requestNavigation(); + }); + } + + updateOptionButtonSelection(gameChangerButtons, ""); + + retranslateUi(); + applyOptionsFromUrl(baseUrl); +} + +void EdhrecCommanderApiResponseBracketNavigationWidget::retranslateUi() +{ + gameChangerLabel->setText(tr("Game Changers")); +} + +void EdhrecCommanderApiResponseBracketNavigationWidget::applyOptionsFromUrl(const QString &url) +{ + QString cleanedUrl = url; + + // Remove base and file extension + if (cleanedUrl.startsWith("https://json.edhrec.com/pages/")) { + cleanedUrl = cleanedUrl.mid(QString("https://json.edhrec.com/pages/").length()); + } + if (cleanedUrl.endsWith(".json")) { + cleanedUrl.chop(5); + } + + // Expecting something like: "commanders/the-ur-dragon/core/expensive" +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + QStringList parts = cleanedUrl.split('/', Qt::SkipEmptyParts); +#else + QStringList parts = cleanedUrl.split('/', QString::SkipEmptyParts); +#endif + + if (parts.size() < 2) { + return; + } + + QString commanderName = parts[1]; + QString gameChangerOpt; + + // Define valid sets + QSet validGameChangers = {"exhibition", "core", "upgraded", "optimized", "cedh"}; + + // Check remaining parts after commander + for (int i = 2; i < parts.size(); ++i) { + QString part = parts[i].toLower(); + if (validGameChangers.contains(part)) { + gameChangerOpt = part; + } + } + + // Validate and apply + if (!gameChangerButtons.contains(gameChangerOpt)) { + gameChangerOpt.clear(); + } + + selectedGameChanger = gameChangerOpt; + + updateOptionButtonSelection(gameChangerButtons, selectedGameChanger); +} + +void EdhrecCommanderApiResponseBracketNavigationWidget::updateOptionButtonSelection( + QMap &buttons, + const QString &selectedKey) +{ + for (auto it = buttons.begin(); it != buttons.end(); ++it) { + it.value()->setStyleSheet(it.key() == selectedKey + ? "background-color: lightblue; font-weight: bold; font-size: 24px;" + : "font-size: 24px"); + } +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h new file mode 100644 index 000000000..713ef2791 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h @@ -0,0 +1,37 @@ +#ifndef COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BRACKET_NAVIGATION_WIDGET_H +#define COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BRACKET_NAVIGATION_WIDGET_H + +#include +#include +#include +#include +#include + +class EdhrecCommanderApiResponseBracketNavigationWidget : public QWidget +{ + Q_OBJECT +public: + explicit EdhrecCommanderApiResponseBracketNavigationWidget(QWidget *parent, const QString &baseUrl); + void retranslateUi(); + void applyOptionsFromUrl(const QString &url); + QString getSelectedGameChanger() const + { + return selectedGameChanger; + } + +signals: + void requestNavigation(); + +private: + QGridLayout *layout; + QLabel *gameChangerLabel; + + QStringList gameChangerOptions = {"", "exhibition", "core", "upgraded", "optimized", "cedh"}; + QString selectedGameChanger; + + QMap gameChangerButtons; + + void updateOptionButtonSelection(QMap &buttons, const QString &selectedKey); +}; + +#endif // COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BRACKET_NAVIGATION_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp new file mode 100644 index 000000000..8470e6b3f --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp @@ -0,0 +1,101 @@ +#include "edhrec_commander_api_response_budget_navigation_widget.h" + +#include + +EdhrecCommanderApiResponseBudgetNavigationWidget::EdhrecCommanderApiResponseBudgetNavigationWidget( + QWidget *parent, + const QString &baseUrl) + : QWidget(parent) +{ + layout = new QGridLayout(this); + setLayout(layout); + + budgetLabel = new QLabel(this); + + layout->addWidget(budgetLabel, 3, 0, 1, 2); + + for (int i = 0; i < budgetOptions.length(); i++) { + QString option = budgetOptions.at(i); + QString label = option.isEmpty() ? "Any" : option.at(0).toUpper() + option.mid(1); + QPushButton *btn = new QPushButton(label, this); + btn->setMinimumHeight(84); + btn->setStyleSheet("font-size: 24px"); + budgetButtons[option] = btn; + layout->addWidget(btn, 4, i); + connect(btn, &QPushButton::clicked, this, [=, this]() { + selectedBudget = option; + updateOptionButtonSelection(budgetButtons, option); + emit requestNavigation(); + }); + } + + updateOptionButtonSelection(budgetButtons, ""); + + retranslateUi(); + applyOptionsFromUrl(baseUrl); +} + +void EdhrecCommanderApiResponseBudgetNavigationWidget::retranslateUi() +{ + budgetLabel->setText(tr("Budget")); +} + +void EdhrecCommanderApiResponseBudgetNavigationWidget::applyOptionsFromUrl(const QString &url) +{ + QString cleanedUrl = url; + + // Remove base and file extension + if (cleanedUrl.startsWith("https://json.edhrec.com/pages/")) { + cleanedUrl = cleanedUrl.mid(QString("https://json.edhrec.com/pages/").length()); + } + if (cleanedUrl.endsWith(".json")) { + cleanedUrl.chop(5); + } + + // Expecting something like: "commanders/the-ur-dragon/core/expensive" +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + QStringList parts = cleanedUrl.split('/', Qt::SkipEmptyParts); +#else + QStringList parts = cleanedUrl.split('/', QString::SkipEmptyParts); +#endif + + if (parts.size() < 2) { + return; + } + + QString commanderName = parts[1]; + QString gameChangerOpt, budgetOpt; + + // Define valid sets + QSet validGameChangers = {"exhibition", "core", "upgraded", "optimized", "cedh"}; + QSet validBudgets = {"budget", "expensive"}; + + // Check remaining parts after commander + for (int i = 2; i < parts.size(); ++i) { + QString part = parts[i].toLower(); + if (validGameChangers.contains(part)) { + gameChangerOpt = part; + } else if (validBudgets.contains(part)) { + budgetOpt = part; + } + } + + if (!budgetButtons.contains(budgetOpt)) { + budgetOpt.clear(); + } + + selectedBudget = budgetOpt; + + updateOptionButtonSelection(budgetButtons, selectedBudget); +} + +void EdhrecCommanderApiResponseBudgetNavigationWidget::updateOptionButtonSelection( + QMap &buttons, + const QString &selectedKey) +{ + for (auto it = buttons.begin(); it != buttons.end(); ++it) { + it.value()->setStyleSheet(it.key() == selectedKey + ? "background-color: lightblue; font-weight: bold; font-size: 24px;" + : "font-size: 24px"); + } +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h new file mode 100644 index 000000000..666edba16 --- /dev/null +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h @@ -0,0 +1,37 @@ +#ifndef COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BUDGET_NAVIGATION_WIDGET_H +#define COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BUDGET_NAVIGATION_WIDGET_H + +#include +#include +#include +#include +#include + +class EdhrecCommanderApiResponseBudgetNavigationWidget : public QWidget +{ + Q_OBJECT +public: + explicit EdhrecCommanderApiResponseBudgetNavigationWidget(QWidget *parent, const QString &baseUrl); + void retranslateUi(); + void applyOptionsFromUrl(const QString &url); + QString getSelectedBudget() const + { + return selectedBudget; + } + +signals: + void requestNavigation(); + +private: + QGridLayout *layout; + QLabel *budgetLabel; + + QStringList budgetOptions = {"", "budget", "expensive"}; + QString selectedBudget; + + QMap budgetButtons; + + void updateOptionButtonSelection(QMap &buttons, const QString &selectedKey); +}; + +#endif // COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BUDGET_NAVIGATION_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp index 3f3b9f1ba..c42d2ed90 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp @@ -11,47 +11,28 @@ EdhrecCommanderApiResponseNavigationWidget::EdhrecCommanderApiResponseNavigation layout = new QGridLayout(this); setLayout(layout); - gameChangerLabel = new QLabel(this); - budgetLabel = new QLabel(this); + bracketNavigationWidget = new EdhrecCommanderApiResponseBracketNavigationWidget(this, baseUrl); + + connect(bracketNavigationWidget, &EdhrecCommanderApiResponseBracketNavigationWidget::requestNavigation, this, + &EdhrecCommanderApiResponseNavigationWidget::actRequestCommanderNavigation); + + budgetNavigationWidget = new EdhrecCommanderApiResponseBudgetNavigationWidget(this, baseUrl); + + connect(budgetNavigationWidget, &EdhrecCommanderApiResponseBudgetNavigationWidget::requestNavigation, this, + &EdhrecCommanderApiResponseNavigationWidget::actRequestCommanderNavigation); comboPushButton = new QPushButton(this); + comboPushButton->setMinimumHeight(84); + comboPushButton->setStyleSheet("font-size: 24px"); averageDeckPushButton = new QPushButton(this); + averageDeckPushButton->setMinimumHeight(84); + averageDeckPushButton->setStyleSheet("font-size: 24px"); layout->addWidget(comboPushButton, 0, 0, 1, 1); layout->addWidget(averageDeckPushButton, 0, 1, 1, 1); - layout->addWidget(gameChangerLabel, 1, 0, 1, 2); - - for (int i = 0; i < gameChangerOptions.length(); i++) { - QString option = gameChangerOptions.at(i); - QString label = option.isEmpty() ? "All" : option.at(0).toUpper() + option.mid(1); - QPushButton *optionButton = new QPushButton(label, this); - gameChangerButtons[option] = optionButton; - layout->addWidget(optionButton, 2, i); - connect(optionButton, &QPushButton::clicked, this, [=, this]() { - selectedGameChanger = option; - updateOptionButtonSelection(gameChangerButtons, option); - actRequestCommanderNavigation(); - }); - } - - layout->addWidget(budgetLabel, 3, 0, 1, 2); - - for (int i = 0; i < budgetOptions.length(); i++) { - QString option = budgetOptions.at(i); - QString label = option.isEmpty() ? "Any" : option.at(0).toUpper() + option.mid(1); - QPushButton *btn = new QPushButton(label, this); - budgetButtons[option] = btn; - layout->addWidget(btn, 4, i); - connect(btn, &QPushButton::clicked, this, [=, this]() { - selectedBudget = option; - updateOptionButtonSelection(budgetButtons, option); - actRequestCommanderNavigation(); - }); - } - - updateOptionButtonSelection(gameChangerButtons, ""); - updateOptionButtonSelection(budgetButtons, ""); + layout->addWidget(bracketNavigationWidget, 1, 0, 1, 2); + layout->addWidget(budgetNavigationWidget, 2, 0, 1, 2); QWidget *currentParent = parentWidget(); TabEdhRecMain *parentTab = nullptr; @@ -73,87 +54,21 @@ EdhrecCommanderApiResponseNavigationWidget::EdhrecCommanderApiResponseNavigation } retranslateUi(); - applyOptionsFromUrl(baseUrl); } void EdhrecCommanderApiResponseNavigationWidget::retranslateUi() { comboPushButton->setText(tr("Combos")); averageDeckPushButton->setText(tr("Average Deck")); - gameChangerLabel->setText(tr("Game Changers")); - budgetLabel->setText(tr("Budget")); -} - -void EdhrecCommanderApiResponseNavigationWidget::applyOptionsFromUrl(const QString &url) -{ - QString cleanedUrl = url; - - // Remove base and file extension - if (cleanedUrl.startsWith("https://json.edhrec.com/pages/")) { - cleanedUrl = cleanedUrl.mid(QString("https://json.edhrec.com/pages/").length()); - } - if (cleanedUrl.endsWith(".json")) { - cleanedUrl.chop(5); - } - - // Expecting something like: "commanders/the-ur-dragon/core/expensive" -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) - QStringList parts = cleanedUrl.split('/', Qt::SkipEmptyParts); -#else - QStringList parts = cleanedUrl.split('/', QString::SkipEmptyParts); -#endif - - if (parts.size() < 2) { - return; - } - - QString commanderName = parts[1]; - QString gameChangerOpt, budgetOpt; - - // Define valid sets - QSet validGameChangers = {"core", "upgraded", "optimized"}; - QSet validBudgets = {"budget", "expensive"}; - - // Check remaining parts after commander - for (int i = 2; i < parts.size(); ++i) { - QString part = parts[i].toLower(); - if (validGameChangers.contains(part)) { - gameChangerOpt = part; - } else if (validBudgets.contains(part)) { - budgetOpt = part; - } - } - - // Validate and apply - if (!gameChangerButtons.contains(gameChangerOpt)) { - gameChangerOpt.clear(); - } - if (!budgetButtons.contains(budgetOpt)) { - budgetOpt.clear(); - } - - selectedGameChanger = gameChangerOpt; - selectedBudget = budgetOpt; - - updateOptionButtonSelection(gameChangerButtons, selectedGameChanger); - updateOptionButtonSelection(budgetButtons, selectedBudget); -} - -void EdhrecCommanderApiResponseNavigationWidget::updateOptionButtonSelection(QMap &buttons, - const QString &selectedKey) -{ - for (auto it = buttons.begin(); it != buttons.end(); ++it) { - it.value()->setStyleSheet(it.key() == selectedKey ? "background-color: lightblue; font-weight: bold;" : ""); - } } QString EdhrecCommanderApiResponseNavigationWidget::addNavigationOptionsToUrl(QString baseUrl) { - if (!selectedGameChanger.isEmpty()) { - baseUrl += "/" + selectedGameChanger; + if (!bracketNavigationWidget->getSelectedGameChanger().isEmpty()) { + baseUrl += "/" + bracketNavigationWidget->getSelectedGameChanger(); } - if (!selectedBudget.isEmpty()) { - baseUrl += "/" + selectedBudget; + if (!budgetNavigationWidget->getSelectedBudget().isEmpty()) { + baseUrl += "/" + budgetNavigationWidget->getSelectedBudget(); } return baseUrl; } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h index b97e095f7..10dfa8223 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h @@ -8,6 +8,8 @@ #define EDHREC_COMMANDER_API_RESPONSE_NAVIGATION_WIDGET_H #include "edhrec_api_response_commander_details_display_widget.h" +#include "edhrec_commander_api_response_bracket_navigation_widget.h" +#include "edhrec_commander_api_response_budget_navigation_widget.h" #include #include @@ -23,7 +25,6 @@ public: const EdhrecCommanderApiResponseCommanderDetails &_commanderDetails, QString baseUrl); void retranslateUi(); - void applyOptionsFromUrl(const QString &url); public slots: void actRequestCommanderNavigation(); @@ -35,24 +36,14 @@ signals: private: QGridLayout *layout; - QLabel *gameChangerLabel; - QLabel *budgetLabel; - - QStringList gameChangerOptions = {"", "core", "upgraded", "optimized"}; - QStringList budgetOptions = {"", "budget", "expensive"}; - - QString selectedGameChanger; - QString selectedBudget; - - QMap gameChangerButtons; - QMap budgetButtons; + EdhrecCommanderApiResponseBracketNavigationWidget *bracketNavigationWidget; + EdhrecCommanderApiResponseBudgetNavigationWidget *budgetNavigationWidget; QPushButton *comboPushButton; QPushButton *averageDeckPushButton; EdhrecCommanderApiResponseCommanderDetails commanderDetails; - void updateOptionButtonSelection(QMap &buttons, const QString &selectedKey); QString addNavigationOptionsToUrl(QString baseUrl); QString buildComboUrl() const; }; From 421d6b334abf57951ab6c6e8ad28a5010936458e Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 23 Dec 2025 07:21:47 -0800 Subject: [PATCH 085/325] [DeckDockWidget] Correctly handle auto-expanding tree (#6446) * move method * remove expandAll calls * update recursiveExpand * Refactor DeckModel access * [DeckDockWidget] Correctly handle auto-expand --- .../deck_editor_deck_dock_widget.cpp | 82 ++++++++++--------- .../deck_editor_deck_dock_widget.h | 6 +- .../printing_selector/card_amount_widget.cpp | 43 +--------- .../printing_selector/card_amount_widget.h | 2 - .../models/deck_list/deck_list_model.cpp | 47 ++++++++++- .../models/deck_list/deck_list_model.h | 37 ++++++++- 6 files changed, 131 insertions(+), 86 deletions(-) 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 625af4dfa..57d34be00 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 @@ -156,6 +156,9 @@ void DeckEditorDeckDockWidget::createDeckDock() // Delay the update to avoid race conditions QTimer::singleShot(100, this, &DeckEditorDeckDockWidget::updateBannerCardComboBox); }); + connect(deckModel, &DeckListModel::cardAddedAt, this, &DeckEditorDeckDockWidget::recursiveExpand); + connect(deckModel, &DeckListModel::deckReplaced, this, &DeckEditorDeckDockWidget::expandAll); + connect(bannerCardComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &DeckEditorDeckDockWidget::setBannerCard); bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); @@ -175,8 +178,6 @@ void DeckEditorDeckDockWidget::createDeckDock() deckModel->setActiveGroupCriteria(static_cast( activeGroupCriteriaComboBox->currentData(Qt::UserRole).toInt())); deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); - deckView->expandAll(); - deckView->expandAll(); }); aIncrement = new QAction(QString(), this); @@ -506,7 +507,6 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel() bannerCardComboBox->blockSignals(false); updateHash(); sortDeckModelToDeckView(); - expandAll(); deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags()); } @@ -516,8 +516,6 @@ void DeckEditorDeckDockWidget::sortDeckModelToDeckView() deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat()); formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat())); - deckView->expandAll(); - deckView->expandAll(); emit deckChanged(); } @@ -550,17 +548,26 @@ void DeckEditorDeckDockWidget::cleanDeck() deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags()); } -void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index) +/** + * @brief Expands all parents of the given index. + * @param sourceIndex The index to expand (model source index) + */ +void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &sourceIndex) { - if (index.parent().isValid()) - recursiveExpand(index.parent()); - deckView->expand(index); + auto index = proxy->mapFromSource(sourceIndex); + + while (index.parent().isValid()) { + index = index.parent(); + deckView->expand(index); + } } +/** + * @brief Fully expands all levels of the deck view + */ void DeckEditorDeckDockWidget::expandAll() { - deckView->expandAll(); - deckView->expandAll(); + deckView->expandRecursively(deckView->rootIndex()); } /** @@ -600,7 +607,6 @@ void DeckEditorDeckDockWidget::actAddCard(const ExactCard &card, const QString & return; } - expandAll(); deckView->clearSelection(); deckView->setCurrentIndex(newCardIndex); @@ -612,7 +618,7 @@ void DeckEditorDeckDockWidget::actIncrementSelection() auto selectedRows = getSelectedCardNodes(); for (const auto &index : selectedRows) { - offsetCountAtIndex(index, 1); + offsetCountAtIndex(index, true); } } @@ -674,14 +680,15 @@ bool DeckEditorDeckDockWidget::swapCard(const QModelIndex ¤tIndex) return false; const QString zoneName = gparent.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); - offsetCountAtIndex(currentIndex, -1); + offsetCountAtIndex(currentIndex, false); const QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN; - ExactCard card = CardDatabaseManager::query()->getCard({cardName, cardProviderID}); - QModelIndex newCardIndex = card ? deckModel->addCard(card, otherZoneName) - // Third argument (true) says create the card no matter what, even if not in DB - : deckModel->addPreferredPrintingCard(cardName, otherZoneName, true); - recursiveExpand(proxy->mapFromSource(newCardIndex)); + if (ExactCard card = CardDatabaseManager::query()->getCard({cardName, cardProviderID})) { + deckModel->addCard(card, otherZoneName); + } else { + // Third argument (true) says create the card no matter what, even if not in DB + deckModel->addPreferredPrintingCard(cardName, otherZoneName, true); + } return true; } @@ -703,7 +710,7 @@ void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString z deckView->clearSelection(); deckView->setCurrentIndex(proxy->mapToSource(idx)); - offsetCountAtIndex(idx, -1); + offsetCountAtIndex(idx, false); } void DeckEditorDeckDockWidget::actDecrementSelection() @@ -717,7 +724,7 @@ void DeckEditorDeckDockWidget::actDecrementSelection() } for (const auto &index : selectedRows) { - offsetCountAtIndex(index, -1); + offsetCountAtIndex(index, false); } deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); @@ -754,7 +761,12 @@ void DeckEditorDeckDockWidget::actRemoveCard() } } -void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, int offset) +/** + * @brief Increments or decrements the amount of the card node at the index by 1. + * @param idx The proxy index + * @param isIncrement If true, increments the count. If false, decrements the count + */ +void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, bool isIncrement) { if (!idx.isValid() || deckModel->hasChildren(idx)) { return; @@ -762,26 +774,22 @@ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, int of QModelIndex sourceIndex = proxy->mapToSource(idx); - const QModelIndex numberIndex = sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT); - const QModelIndex nameIndex = sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME); + QString cardName = sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); + QString providerId = + sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString(); - const QString cardName = nameIndex.data(Qt::EditRole).toString(); - const int count = numberIndex.data(Qt::EditRole).toInt(); - const int new_count = count + offset; - - const auto reason = - QString(tr("%1 %2 × \"%3\" (%4)")) - .arg(offset > 0 ? tr("Added") : tr("Removed")) - .arg(qAbs(offset)) - .arg(cardName) - .arg(sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString()); + const auto reason = QString(tr("%1 %2 × \"%3\" (%4)")) + .arg(isIncrement ? tr("Added") : tr("Removed")) + .arg(1) + .arg(cardName) + .arg(providerId); emit requestDeckHistorySave(reason); - if (new_count <= 0) { - deckModel->removeRow(sourceIndex.row(), sourceIndex.parent()); + if (isIncrement) { + deckModel->incrementAmountAtIndex(sourceIndex); } else { - deckModel->setData(numberIndex, new_count, Qt::EditRole); + deckModel->decrementAmountAtIndex(sourceIndex); } emit deckModified(); diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index 6c88cafff..30f752203 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -70,7 +70,6 @@ public slots: void actSwapSelection(); void actRemoveCard(); void initializeFormats(); - void expandAll(); signals: void nameChanged(); @@ -106,9 +105,8 @@ private: QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard; - void recursiveExpand(const QModelIndex &index); [[nodiscard]] QModelIndexList getSelectedCardNodes() const; - void offsetCountAtIndex(const QModelIndex &idx, int offset); + void offsetCountAtIndex(const QModelIndex &idx, bool isIncrement); private slots: void decklistCustomMenu(QPoint point); @@ -124,6 +122,8 @@ private slots: void updateShowBannerCardComboBox(bool visible); void updateShowTagsWidget(bool visible); void syncBannerCardComboBoxSelectionWithDeck(); + void recursiveExpand(const QModelIndex &parent); + void expandAll(); }; #endif // DECK_EDITOR_DECK_DOCK_WIDGET_H diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index e979637f8..7c5804c37 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -175,7 +175,6 @@ void CardAmountWidget::addPrinting(const QString &zone) // Add the card and expand the list UI auto newCardIndex = deckModel->addCard(rootCard, zone); - recursiveExpand(newCardIndex); // Check if a card without a providerId already exists in the deckModel and replace it, if so. QString foundProviderId = @@ -229,46 +228,6 @@ void CardAmountWidget::removePrintingSideboard() decrementCardHelper(DECK_ZONE_SIDE); } -/** - * @brief Recursively expands the card in the deck view starting from the given index. - * - * @param index The model index of the card to expand. - */ -void CardAmountWidget::recursiveExpand(const QModelIndex &index) -{ - if (index.parent().isValid()) { - recursiveExpand(index.parent()); - } - deckView->expand(index); -} - -/** - * @brief Offsets the card count at the specified index by the given amount. - * - * @param idx The model index of the card. - * @param offset The amount to add or subtract from the card count. - */ -void CardAmountWidget::offsetCountAtIndex(const QModelIndex &idx, int offset) -{ - if (!idx.isValid() || offset == 0) { - return; - } - - const QModelIndex numberIndex = idx.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT); - const int count = numberIndex.data(Qt::EditRole).toInt(); - const int new_count = count + offset; - - deckView->setCurrentIndex(numberIndex); - - if (new_count <= 0) { - deckModel->removeRow(idx.row(), idx.parent()); - } else { - deckModel->setData(numberIndex, new_count, Qt::EditRole); - } - - deckEditor->setModified(true); -} - /** * @brief Helper function to decrement the card count for a given zone. * @@ -288,7 +247,7 @@ void CardAmountWidget::decrementCardHelper(const QString &zone) QModelIndex idx = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(), rootCard.getPrinting().getProperty("num")); - offsetCountAtIndex(idx, -1); + deckModel->decrementAmountAtIndex(idx); deckEditor->setModified(true); } diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h index 6d059bc04..b4704cede 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h @@ -57,9 +57,7 @@ private: bool hovered; - void offsetCountAtIndex(const QModelIndex &idx, int offset); void decrementCardHelper(const QString &zoneName); - void recursiveExpand(const QModelIndex &index); private slots: void addPrintingMainboard(); 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 5fd4d71e8..53d08cd9d 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -436,7 +436,51 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam } sort(lastKnownColumn, lastKnownOrder); emitRecursiveUpdates(parentIndex); - return nodeToIndex(cardNode); + auto index = nodeToIndex(cardNode); + + emit cardAddedAt(index); + + return index; +} + +bool DeckListModel::incrementAmountAtIndex(const QModelIndex &idx) +{ + return offsetAmountAtIndex(idx, 1); +} + +bool DeckListModel::decrementAmountAtIndex(const QModelIndex &idx) +{ + return offsetAmountAtIndex(idx, -1); +} + +bool DeckListModel::offsetAmountAtIndex(const QModelIndex &idx, int offset) +{ + if (!idx.isValid()) { + return false; + } + + auto *node = static_cast(idx.internalPointer()); + auto *card = dynamic_cast(node); + + if (!card) { + return false; + } + + const QModelIndex numberIndex = idx.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT); + const int count = numberIndex.data(Qt::EditRole).toInt(); + const int newCount = count + offset; + + if (newCount <= 0) { + removeRow(idx.row(), idx.parent()); + } else { + setData(numberIndex, newCount, Qt::EditRole); + } + + if (offset > 0) { + emit cardAddedAt(idx); + } + + return true; } int DeckListModel::findSortedInsertRow(InnerDecklistNode *parent, CardInfoPtr cardInfo) const @@ -559,6 +603,7 @@ void DeckListModel::setDeckList(DeckList *_deck) deckList = _deck; } rebuildTree(); + emit deckReplaced(); } void DeckListModel::forEachCard(const std::function &func) 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 80a519297..a85542d97 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -226,6 +226,18 @@ signals: */ void deckHashChanged(); + /** + * @brief Emitted whenever a card is added to the deck, regardless of whether it's an entirely new card or an + * existing card that got incremented. + * @param index The index of the card that got added. + */ + void cardAddedAt(const QModelIndex &index); + + /** + * @brief Emitted whenever the deck in the model has been replaced with a new one + */ + void deckReplaced(); + public: explicit DeckListModel(QObject *parent = nullptr); ~DeckListModel() override; @@ -250,7 +262,6 @@ public: [[nodiscard]] int rowCount(const QModelIndex &parent) const override; [[nodiscard]] int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; - void emitBackgroundUpdates(const QModelIndex &parent); [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role) const override; [[nodiscard]] QModelIndex index(int row, int column, const QModelIndex &parent) const override; [[nodiscard]] QModelIndex parent(const QModelIndex &index) const override; @@ -258,6 +269,12 @@ public: bool setData(const QModelIndex &index, const QVariant &value, int role) override; bool removeRows(int row, int count, const QModelIndex &parent) override; + /** + * Recursively emits the dataChanged signal for all child nodes. + * @param parent The parent node + */ + void emitBackgroundUpdates(const QModelIndex &parent); + /** * @brief Finds a card by name, zone, and optional identifiers. * @param cardName The card's name. @@ -289,6 +306,21 @@ public: */ QModelIndex addCard(const ExactCard &card, const QString &zoneName); + /** + * @brief Increments the `amount` field of the card node at the index by 1. + * @param idx The index of a card node. No-ops if the index is invalid or not a card node + * @return Whether the operation was successful + */ + bool incrementAmountAtIndex(const QModelIndex &idx); + + /** + * @brief Decrements the `amount` field of the card node at the index by 1. + * Removes the node if it causes the amount to fall to 0. + * @param idx The index of a card node. No-ops if the index is invalid or not a card node + * @return Whether the operation was successful + */ + bool decrementAmountAtIndex(const QModelIndex &idx); + /** * @brief Determines the sorted insertion row for a card. * @param parent The parent node where the card will be inserted. @@ -362,6 +394,9 @@ private: const QString &zoneName, const QString &providerId = "", const QString &cardNumber = "") const; + + bool offsetAmountAtIndex(const QModelIndex &idx, int offset); + void emitRecursiveUpdates(const QModelIndex &index); void sortHelper(InnerDecklistNode *node, Qt::SortOrder order); From 521046fb0941bb130e2b122d15a2282d8994c7cf Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Tue, 23 Dec 2025 17:48:10 +0100 Subject: [PATCH 086/325] Hashing tests (#5026) * add deck hashing tests * format * fix header * fix cmakelists * fix test * add 5 second timeout to test let the optimising begin * expand tests * remove debug message * manually format * I installed cmake format from the aur * use decklist library * format --- tests/CMakeLists.txt | 11 ++- tests/deck_hash_performance_test.cpp | 81 +++++++++++++++++++ .../clipboard_testing.cpp | 22 +++-- .../clipboard_testing.h | 2 +- .../loading_from_clipboard_test.cpp | 16 ++++ 5 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 tests/deck_hash_performance_test.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6a5eacf54..d80ccce4f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,16 +1,20 @@ enable_testing() + add_test(NAME dummy_test COMMAND dummy_test) add_test(NAME expression_test COMMAND expression_test) - add_test(NAME test_age_formatting COMMAND test_age_formatting) add_test(NAME password_hash_test COMMAND password_hash_test) +add_test(NAME deck_hash_performance_test COMMAND deck_hash_performance_test) +set_tests_properties(deck_hash_performance_test PROPERTIES TIMEOUT 5) + # Find GTest add_executable(dummy_test dummy_test.cpp) add_executable(expression_test expression_test.cpp) add_executable(test_age_formatting test_age_formatting.cpp) add_executable(password_hash_test password_hash_test.cpp) +add_executable(deck_hash_performance_test deck_hash_performance_test.cpp) find_package(GTest) @@ -41,6 +45,7 @@ if(NOT GTEST_FOUND) add_dependencies(expression_test gtest) add_dependencies(test_age_formatting gtest) add_dependencies(password_hash_test gtest) + add_dependencies(deck_hash_performance_test gtest) endif() include_directories(${GTEST_INCLUDE_DIRS}) @@ -50,6 +55,10 @@ target_link_libraries(test_age_formatting Threads::Threads ${GTEST_BOTH_LIBRARIE target_link_libraries( password_hash_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} ) +target_link_libraries( + deck_hash_performance_test libcockatrice_deck_list libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} + ${TEST_QT_MODULES} +) add_subdirectory(carddatabase) add_subdirectory(loading_from_clipboard) diff --git a/tests/deck_hash_performance_test.cpp b/tests/deck_hash_performance_test.cpp new file mode 100644 index 000000000..154283e8e --- /dev/null +++ b/tests/deck_hash_performance_test.cpp @@ -0,0 +1,81 @@ +#include "gtest/gtest.h" +#include +#include + +static constexpr int amount = 1e5; +QString repeatDeck; +QString numberDeck; +QString uniquesDeck; +QString uniquesXorDeck; +QString duplicatesDeck; + +TEST(DeckHashTest, RepeatTest) +{ + DeckList decklist(repeatDeck); + for (int i = 0; i < amount; ++i) { + decklist.getDeckHash(); + decklist.refreshDeckHash(); + } + auto hash = decklist.getDeckHash().toStdString(); + ASSERT_EQ(hash, "5cac19qm") << "The hash does not match!"; +} + +TEST(DeckHashTest, NumberTest) +{ + DeckList decklist(numberDeck); + auto hash = decklist.getDeckHash().toStdString(); + ASSERT_EQ(hash, "e0m38p19") << "The hash does not match!"; +} + +TEST(DeckHashTest, UniquesTest) +{ + DeckList decklist(uniquesDeck); + auto hash = decklist.getDeckHash().toStdString(); + ASSERT_EQ(hash, "88prk025") << "The hash does not match!"; +} + +TEST(DeckHashTest, UniquesTestXor) +{ + DeckList decklist(uniquesXorDeck); + auto hash = decklist.getDeckHash().toStdString(); + ASSERT_EQ(hash, "hkn6q4pf") << "The hash does not match!"; +} + +TEST(DeckHashTest, DuplicatesTest) +{ + DeckList decklist(duplicatesDeck); + auto hash = decklist.getDeckHash().toStdString(); + ASSERT_EQ(hash, "ekt6tg1h") << "The hash does not match!"; +} + +int main(int argc, char **argv) +{ + const QString deckStart = + R"()"; + const QString deckEnd = R"()"; + + repeatDeck = + deckStart + + R"()" + + deckEnd; + numberDeck = deckStart + QString(R"()").arg(amount) + deckEnd; + + QStringList deckString{deckStart}; + QStringList deckStringXor = deckString; + int len = QString::number(amount).length(); + for (int i = 0; i < amount; ++i) { + // creates already sorted list + deckString << R"()"; + // xor in order to mess with sorting + deckStringXor << R"()"; + } + deckString << deckEnd; + deckStringXor << deckEnd; + uniquesDeck = deckString.join(""); + uniquesXorDeck = deckStringXor.join(""); + + duplicatesDeck = deckStart + QString(R"()").repeated(amount) + deckEnd; + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/loading_from_clipboard/clipboard_testing.cpp b/tests/loading_from_clipboard/clipboard_testing.cpp index 2342bc088..29535c1dc 100644 --- a/tests/loading_from_clipboard/clipboard_testing.cpp +++ b/tests/loading_from_clipboard/clipboard_testing.cpp @@ -3,22 +3,32 @@ #include #include -void testEmpty(const QString &clipboard) +DeckList getDeckList(const QString &clipboard) { - QString cp(clipboard); DeckList deckList; + QString cp(clipboard); QTextStream stream(&cp); // text stream requires local copy deckList.loadFromStream_Plain(stream, false); + return deckList; +} + +void testEmpty(const QString &clipboard) +{ + DeckList deckList = getDeckList(clipboard); ASSERT_TRUE(deckList.getCardList().isEmpty()); } +void testHash(const QString &clipboard, const std::string &hash) +{ + DeckList deckList = getDeckList(clipboard); + + ASSERT_EQ(deckList.getDeckHash().toStdString(), hash); +} + void testDeck(const QString &clipboard, const Result &result) { - QString cp(clipboard); - DeckList deckList; - QTextStream stream(&cp); // text stream requires local copy - deckList.loadFromStream_Plain(stream, false); + DeckList deckList = getDeckList(clipboard); ASSERT_EQ(result.name, deckList.getName().toStdString()); ASSERT_EQ(result.comments, deckList.getComments().toStdString()); diff --git a/tests/loading_from_clipboard/clipboard_testing.h b/tests/loading_from_clipboard/clipboard_testing.h index 5e9cff915..41d8ea794 100644 --- a/tests/loading_from_clipboard/clipboard_testing.h +++ b/tests/loading_from_clipboard/clipboard_testing.h @@ -21,7 +21,7 @@ struct Result }; void testEmpty(const QString &clipboard); - +void testHash(const QString &clipboard, const std::string &hash); void testDeck(const QString &clipboard, const Result &result); #endif // CLIPBOARD_TESTING_H diff --git a/tests/loading_from_clipboard/loading_from_clipboard_test.cpp b/tests/loading_from_clipboard/loading_from_clipboard_test.cpp index 6f9762be9..fcfbb22db 100644 --- a/tests/loading_from_clipboard/loading_from_clipboard_test.cpp +++ b/tests/loading_from_clipboard/loading_from_clipboard_test.cpp @@ -203,6 +203,22 @@ TEST(LoadingFromClipboardTest, emptyMainBoard) testEmpty(clipboard); } +TEST(LoadingFromClipboardTest, emptyHash) +{ + QString clipboard(""); + + testHash(clipboard, "r8sq7riu"); +} + +TEST(LoadingFromClipboardTest, deckHash) +{ + QString clipboard("1 Mountain\n" + "2 Island\n" + "SB: 3 Forest\n"); + + testHash(clipboard, "5cac19qm"); +} + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); From 70f9982c29d0951eb911cab43a3b13c6b4de8cf9 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:58:23 -0800 Subject: [PATCH 087/325] Bump minimum Qt version from 5.8 to 5.15 (#6442) * Bump minimum Qt version from 5.8 to 5.15 * remove version check * remove version checks --- cmake/FindQtRuntime.cmake | 2 +- .../src/client/settings/shortcut_treeview.cpp | 7 +------ cockatrice/src/game/dialogs/dlg_create_token.cpp | 8 -------- .../src/game/player/player_event_handler.cpp | 7 ------- .../board/abstract_graphics_item.cpp | 7 +------ .../card_picture_loader_worker.cpp | 2 -- cockatrice/src/interface/logger.cpp | 15 --------------- .../widgets/server/chat_view/chat_view.cpp | 4 ---- .../widgets/server/chat_view/chat_view.h | 3 --- .../src/interface/widgets/server/games_model.cpp | 4 +--- ...api_response_deck_listings_display_widget.cpp | 3 --- .../widgets/tabs/api/archidekt/tab_archidekt.cpp | 3 --- ...er_api_response_bracket_navigation_widget.cpp | 4 ---- ...der_api_response_budget_navigation_widget.cpp | 4 ---- .../widgets/tabs/api/edhrec/tab_edhrec.cpp | 3 --- .../widgets/tabs/api/edhrec/tab_edhrec_main.cpp | 3 --- cockatrice/src/interface/window_main.cpp | 4 ---- cockatrice/src/main.cpp | 2 -- .../card/database/card_database_querier.cpp | 4 ---- .../models/database/card_set/card_sets_model.cpp | 4 ---- .../network/client/remote/remote_client.cpp | 4 ---- .../server/remote/game/server_cardzone.cpp | 4 ---- .../network/server/remote/game/server_game.cpp | 4 ---- .../network/server/remote/game/server_game.h | 4 ---- servatrice/src/isl_interface.cpp | 8 -------- servatrice/src/servatrice.cpp | 9 --------- servatrice/src/servatrice_database_interface.cpp | 4 ---- servatrice/src/server_logger.cpp | 4 ---- servatrice/src/serversocketinterface.cpp | 16 ---------------- servatrice/src/settingscache.cpp | 7 +------ servatrice/src/smtp/qxtsmtp.cpp | 4 ---- 31 files changed, 5 insertions(+), 156 deletions(-) diff --git a/cmake/FindQtRuntime.cmake b/cmake/FindQtRuntime.cmake index 6be08a694..c205ebdcf 100644 --- a/cmake/FindQtRuntime.cmake +++ b/cmake/FindQtRuntime.cmake @@ -59,7 +59,7 @@ if(Qt6_FOUND) endif() else() find_package( - Qt5 5.8.0 + Qt5 5.15.2 COMPONENTS ${REQUIRED_QT_COMPONENTS} QUIET HINTS ${Qt5_DIR} ) diff --git a/cockatrice/src/client/settings/shortcut_treeview.cpp b/cockatrice/src/client/settings/shortcut_treeview.cpp index 6b329b23d..b909d47ac 100644 --- a/cockatrice/src/client/settings/shortcut_treeview.cpp +++ b/cockatrice/src/client/settings/shortcut_treeview.cpp @@ -150,12 +150,7 @@ void ShortcutTreeView::currentChanged(const QModelIndex ¤t, const QModelIn */ void ShortcutTreeView::updateSearchString(const QString &searchString) { -#if QT_VERSION > QT_VERSION_CHECK(5, 14, 0) - const auto skipEmptyParts = Qt::SkipEmptyParts; -#else - const auto skipEmptyParts = QString::SkipEmptyParts; -#endif - QStringList searchWords = searchString.split(" ", skipEmptyParts); + QStringList searchWords = searchString.split(" ", Qt::SkipEmptyParts); auto escapeRegex = [](const QString &s) { return QRegularExpression::escape(s); }; std::transform(searchWords.begin(), searchWords.end(), searchWords.begin(), escapeRegex); diff --git a/cockatrice/src/game/dialogs/dlg_create_token.cpp b/cockatrice/src/game/dialogs/dlg_create_token.cpp index 1f6f9b08c..836c0c16a 100644 --- a/cockatrice/src/game/dialogs/dlg_create_token.cpp +++ b/cockatrice/src/game/dialogs/dlg_create_token.cpp @@ -117,11 +117,7 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa chooseTokenFromDeckRadioButton->setDisabled(true); // No tokens in deck = no need for option } else { chooseTokenFromDeckRadioButton->setChecked(true); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) cardDatabaseDisplayModel->setCardNameSet(QSet(predefinedTokens.begin(), predefinedTokens.end())); -#else - cardDatabaseDisplayModel->setCardNameSet(QSet::fromList(predefinedTokens)); -#endif } auto *tokenChooseLayout = new QVBoxLayout; @@ -223,11 +219,7 @@ void DlgCreateToken::actChooseTokenFromAll(bool checked) void DlgCreateToken::actChooseTokenFromDeck(bool checked) { if (checked) { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) cardDatabaseDisplayModel->setCardNameSet(QSet(predefinedTokens.begin(), predefinedTokens.end())); -#else - cardDatabaseDisplayModel->setCardNameSet(QSet::fromList(predefinedTokens)); -#endif } } diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp index 4cf814d86..331605918 100644 --- a/cockatrice/src/game/player/player_event_handler.cpp +++ b/cockatrice/src/game/player/player_event_handler.cpp @@ -78,14 +78,7 @@ void PlayerEventHandler::eventShuffle(const Event_Shuffle &event) void PlayerEventHandler::eventRollDie(const Event_RollDie &event) { if (!event.values().empty()) { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QList rolls(event.values().begin(), event.values().end()); -#else - QList rolls; - for (const auto &value : event.values()) { - rolls.append(value); - } -#endif std::sort(rolls.begin(), rolls.end()); emit logRollDie(player, static_cast(event.sides()), rolls); } else if (event.value()) { diff --git a/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp b/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp index 8ced8d5bc..05f4a41ab 100644 --- a/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp +++ b/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp @@ -17,12 +17,7 @@ void AbstractGraphicsItem::paintNumberEllipse(int number, font.setWeight(QFont::Bold); QFontMetrics fm(font); - double w = 1.3 * -#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) - fm.horizontalAdvance(numStr); -#else - fm.width(numStr); -#endif + double w = 1.3 * fm.horizontalAdvance(numStr); double h = fm.height() * 1.3; if (w < h) w = h; 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 77fddc9de..128b03c95 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 @@ -21,9 +21,7 @@ CardPictureLoaderWorker::CardPictureLoaderWorker() // We need a timeout to ensure requests don't hang indefinitely in case of // cache corruption, see related Qt bug: https://bugreports.qt.io/browse/QTBUG-111397 // Use Qt's default timeout (30s, as of 2023-02-22) -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) networkManager->setTransferTimeout(); -#endif cache = new QNetworkDiskCache(this); cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath()); cache->setMaximumCacheSize(1024L * 1024L * diff --git a/cockatrice/src/interface/logger.cpp b/cockatrice/src/interface/logger.cpp index 37e07acec..d6df065e8 100644 --- a/cockatrice/src/interface/logger.cpp +++ b/cockatrice/src/interface/logger.cpp @@ -57,17 +57,10 @@ void Logger::openLogfileSession() return; } fileStream.setDevice(&fileHandle); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) fileStream << "Log session started at " << QDateTime::currentDateTime().toString() << Qt::endl; fileStream << getClientVersion() << Qt::endl; fileStream << getSystemArchitecture() << Qt::endl; fileStream << getClientInstallInfo() << Qt::endl; -#else - fileStream << "Log session started at " << QDateTime::currentDateTime().toString() << endl; - fileStream << getClientVersion() << endl; - fileStream << getSystemArchitecture() << endl; - fileStream << getClientInstallInfo() << endl; -#endif logToFileEnabled = true; } @@ -77,11 +70,7 @@ void Logger::closeLogfileSession() return; logToFileEnabled = false; -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << Qt::endl; -#else - fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << endl; -#endif fileHandle.close(); } @@ -103,11 +92,7 @@ void Logger::internalLog(const QString &message) std::cerr << message.toStdString() << std::endl; // Print to stdout if (logToFileEnabled) { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) fileStream << message << Qt::endl; // Print to fileStream -#else - fileStream << message << endl; // Print to fileStream -#endif } } diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp index 694085771..50375c936 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp @@ -236,11 +236,7 @@ void ChatView::appendMessage(QString message, cursor.setCharFormat(defaultFormat); bool mentionEnabled = SettingsCache::instance().getChatMention(); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) highlightedWords = SettingsCache::instance().getHighlightWords().split(' ', Qt::SkipEmptyParts); -#else - highlightedWords = SettingsCache::instance().getHighlightWords().split(' ', QString::SkipEmptyParts); -#endif // parse the message while (message.size()) { diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.h b/cockatrice/src/interface/widgets/server/chat_view/chat_view.h index 00fcc4f5e..6cf8370ed 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.h +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.h @@ -28,9 +28,6 @@ class UserListProxy; class UserMessagePosition { public: -#if (QT_VERSION < QT_VERSION_CHECK(5, 13, 0)) - UserMessagePosition() = default; // older qt versions require a default constructor to use in containers -#endif UserMessagePosition(QTextCursor &cursor); int relativePosition; QTextBlock block; diff --git a/cockatrice/src/interface/widgets/server/games_model.cpp b/cockatrice/src/interface/widgets/server/games_model.cpp index cdac71b28..05d363fee 100644 --- a/cockatrice/src/interface/widgets/server/games_model.cpp +++ b/cockatrice/src/interface/widgets/server/games_model.cpp @@ -421,10 +421,8 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const { #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) static const QDate epochDate = QDateTime::fromSecsSinceEpoch(0, QTimeZone::UTC).date(); -#elif (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) - static const QDate epochDate = QDateTime::fromSecsSinceEpoch(0, Qt::UTC).date(); #else - static const QDate epochDate = QDateTime::fromTime_t(0, Qt::UTC).date(); + static const QDate epochDate = QDateTime::fromSecsSinceEpoch(0, Qt::UTC).date(); #endif auto *model = qobject_cast(sourceModel()); if (!model) diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp index 5747ce90d..8746093c7 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp @@ -16,10 +16,7 @@ ArchidektApiResponseDeckListingsDisplayWidget::ArchidektApiResponseDeckListingsD flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); imageNetworkManager = new QNetworkAccessManager(this); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) imageNetworkManager->setTransferTimeout(); // Use Qt's default timeout -#endif - imageNetworkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); // Add widgets for deck listings 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 e6615fa7b..3769cd9a2 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp @@ -27,10 +27,7 @@ TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) { networkManager = new QNetworkAccessManager(this); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) networkManager->setTransferTimeout(); // Use Qt's default timeout -#endif - networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(processApiJson(QNetworkReply *))); diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp index 2bc727bd2..c3ab23e41 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp @@ -53,11 +53,7 @@ void EdhrecCommanderApiResponseBracketNavigationWidget::applyOptionsFromUrl(cons } // Expecting something like: "commanders/the-ur-dragon/core/expensive" -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QStringList parts = cleanedUrl.split('/', Qt::SkipEmptyParts); -#else - QStringList parts = cleanedUrl.split('/', QString::SkipEmptyParts); -#endif if (parts.size() < 2) { return; diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp index 8470e6b3f..1845c020d 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp @@ -53,11 +53,7 @@ void EdhrecCommanderApiResponseBudgetNavigationWidget::applyOptionsFromUrl(const } // Expecting something like: "commanders/the-ur-dragon/core/expensive" -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QStringList parts = cleanedUrl.split('/', Qt::SkipEmptyParts); -#else - QStringList parts = cleanedUrl.split('/', QString::SkipEmptyParts); -#endif if (parts.size() < 2) { return; diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp index 93dbd6bc5..10389f4eb 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp @@ -15,10 +15,7 @@ TabEdhRec::TabEdhRec(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) { networkManager = new QNetworkAccessManager(this); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) networkManager->setTransferTimeout(); // Use Qt's default timeout -#endif - networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); connect(networkManager, &QNetworkAccessManager::finished, this, &TabEdhRec::processApiJson); } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp index fb26732f6..f861d8afd 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp @@ -37,10 +37,7 @@ static bool canBeCommander(const CardInfoPtr &cardInfo) TabEdhRecMain::TabEdhRecMain(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) { networkManager = new QNetworkAccessManager(this); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) networkManager->setTransferTimeout(); // Use Qt's default timeout -#endif - networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(processApiJson(QNetworkReply *))); diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 4c9922c80..41113185c 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -499,11 +499,7 @@ QString MainWindow::extractInvalidUsernameMessage(QString &in) if (words.startsWith("\n")) { out += tr("no unacceptable language as specified by these server rules:", "note that the following lines will not be translated"); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) for (QString &line : words.split("\n", Qt::SkipEmptyParts)) { -#else - for (QString &line : words.split("\n", QString::SkipEmptyParts)) { -#endif out += "
  • " + line + "
  • "; } } else { diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index 29dacee74..7092a3fd7 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -260,10 +260,8 @@ int main(int argc, char *argv[]) qCInfo(MainLog) << "MainWindow constructor finished"; ui.setWindowIcon(QPixmap("theme:cockatrice")); -#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) // set name of the app desktop file; used by wayland to load the window icon QGuiApplication::setDesktopFileName("cockatrice"); -#endif SettingsCache::instance().setClientID(generateClientID()); diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp b/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp index b2a675b99..26e515a2d 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp @@ -328,11 +328,7 @@ QMap CardDatabaseQuerier::getAllSubCardTypesWithCount() const QStringList parts = type.split(" — "); if (parts.size() > 1) { // Ensure there are subtypes -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QStringList subtypes = parts[1].split(" ", Qt::SkipEmptyParts); -#else - QStringList subtypes = parts[1].split(" ", QString::SkipEmptyParts); -#endif for (const QString &subtype : subtypes) { typeCounts[subtype]++; diff --git a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp index 2af815246..b678e8276 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp +++ b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp @@ -283,11 +283,7 @@ bool SetsDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex &source auto nameIndex = sourceModel()->index(sourceRow, SetsModel::LongNameCol, sourceParent); auto shortNameIndex = sourceModel()->index(sourceRow, SetsModel::ShortNameCol, sourceParent); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) const auto filter = filterRegularExpression(); -#else - const auto filter = filterRegExp(); -#endif return (sourceModel()->data(typeIndex).toString().contains(filter) || sourceModel()->data(nameIndex).toString().contains(filter) || diff --git a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp index c0167a875..4b5d3f1b8 100644 --- a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp +++ b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp @@ -42,11 +42,7 @@ RemoteClient::RemoteClient(QObject *parent, INetworkSettingsProvider *_networkSe connect(socket, &QTcpSocket::connected, this, &RemoteClient::slotConnected); connect(socket, &QTcpSocket::readyRead, this, &RemoteClient::readData); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) connect(socket, &QTcpSocket::errorOccurred, this, &RemoteClient::slotSocketError); -#else - connect(socket, qOverload(&QTcpSocket::error), this, &RemoteClient::slotSocketError); -#endif websocket = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this); connect(websocket, &QWebSocket::binaryMessageReceived, this, &RemoteClient::websocketMessageReceived); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.cpp index 68dcadd35..f2a35e548 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_cardzone.cpp @@ -63,11 +63,7 @@ void Server_CardZone::shuffle(int start, int end) for (int i = end; i > start; i--) { int j = rng->rand(start, i); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) cards.swapItemsAt(j, i); -#else - cards.swap(j, i); -#endif } playersWithWritePermission.clear(); } 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 e53838695..5cd6c8bf8 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -73,11 +73,7 @@ Server_Game::Server_Game(const ServerInfo_User &_creatorInfo, spectatorsSeeEverything(_spectatorsSeeEverything), startingLifeTotal(_startingLifeTotal), shareDecklistsOnLoad(_shareDecklistsOnLoad), inactivityCounter(0), startTimeOfThisGame(0), secondsElapsed(0), firstGameStarted(false), turnOrderReversed(false), startTime(QDateTime::currentDateTime()), pingClock(nullptr), -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) gameMutex() -#else - gameMutex(QMutex::Recursive) -#endif { currentReplay = new GameReplay; currentReplay->set_replay_id(room->getServer()->getDatabaseInterface()->getNextReplayId()); 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 033542fad..64374019c 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h @@ -90,11 +90,7 @@ private slots: void doStartGameIfReady(bool forceStartGame = false); public: -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) mutable QRecursiveMutex gameMutex; -#else - mutable QMutex gameMutex; -#endif Server_Game(const ServerInfo_User &_creatorInfo, int _gameId, const QString &_description, diff --git a/servatrice/src/isl_interface.cpp b/servatrice/src/isl_interface.cpp index 2269ff314..bcf71a98a 100644 --- a/servatrice/src/isl_interface.cpp +++ b/servatrice/src/isl_interface.cpp @@ -112,11 +112,7 @@ void IslInterface::initServer() socket->startServerEncryption(); if (!socket->waitForEncrypted(5000)) { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) QList sslErrors(socket->sslHandshakeErrors()); -#else - QList sslErrors(socket->sslErrors()); -#endif if (sslErrors.isEmpty()) qDebug() << "[ISL] SSL handshake timeout, terminating connection"; else @@ -193,11 +189,7 @@ void IslInterface::initClient() return; } if (!socket->waitForEncrypted(5000)) { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) QList sslErrors(socket->sslHandshakeErrors()); -#else - QList sslErrors(socket->sslErrors()); -#endif if (sslErrors.isEmpty()) qDebug() << "[ISL] SSL handshake timeout, terminating connection"; else diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index ea3d783bd..410bf4ed9 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -254,13 +254,8 @@ bool Servatrice::initServer() qDebug() << "Accept registered users only:" << getRegOnlyServerEnabled(); qDebug() << "Registration enabled:" << getRegistrationEnabled(); if (getRegistrationEnabled()) { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QStringList emailBlackListFilters = getEmailBlackList().split(",", Qt::SkipEmptyParts); QStringList emailWhiteListFilters = getEmailWhiteList().split(",", Qt::SkipEmptyParts); -#else - QStringList emailBlackListFilters = getEmailBlackList().split(",", QString::SkipEmptyParts); - QStringList emailWhiteListFilters = getEmailWhiteList().split(",", QString::SkipEmptyParts); -#endif qDebug() << "Email blacklist:" << emailBlackListFilters; qDebug() << "Email whitelist:" << emailWhiteListFilters; qDebug() << "Require email address to register:" << getRequireEmailForRegistrationEnabled(); @@ -564,11 +559,7 @@ void Servatrice::setRequiredFeatures(const QString &featureList) FeatureSet features; serverRequiredFeatureList.clear(); features.initalizeFeatureList(serverRequiredFeatureList); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QStringList listReqFeatures = featureList.split(",", Qt::SkipEmptyParts); -#else - QStringList listReqFeatures = featureList.split(",", QString::SkipEmptyParts); -#endif if (!listReqFeatures.isEmpty()) for (const QString &reqFeature : listReqFeatures) { features.enableRequiredFeature(serverRequiredFeatureList, reqFeature); diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index bad16bec3..fa9b14f31 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -163,11 +163,7 @@ bool Servatrice_DatabaseInterface::usernameIsValid(const QString &user, QString bool allowPunctuationPrefix = settingsCache->value("users/allowpunctuationprefix", false).toBool(); QString allowedPunctuation = settingsCache->value("users/allowedpunctuation", "_").toString(); QString disallowedWordsStr = settingsCache->value("users/disallowedwords", "").toString(); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QStringList disallowedWords = disallowedWordsStr.split(",", Qt::SkipEmptyParts); -#else - QStringList disallowedWords = disallowedWordsStr.split(",", QString::SkipEmptyParts); -#endif disallowedWords.removeDuplicates(); QVariant displayDisallowedWords = settingsCache->value("users/displaydisallowedwords"); QString disallowedRegExpStr; diff --git a/servatrice/src/server_logger.cpp b/servatrice/src/server_logger.cpp index 9e40ac7b4..79d8cdfe0 100644 --- a/servatrice/src/server_logger.cpp +++ b/servatrice/src/server_logger.cpp @@ -57,11 +57,7 @@ void ServerLogger::logMessage(const QString &message, void *caller) // filter out all log entries based on values in configuration file bool shouldWeWriteLog = settingsCache->value("server/writelog", 1).toBool(); QString logFilters = settingsCache->value("server/logfilters").toString(); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QStringList listlogFilters = logFilters.split(",", Qt::SkipEmptyParts); -#else - QStringList listlogFilters = logFilters.split(",", QString::SkipEmptyParts); -#endif bool shouldWeSkipLine = false; if (!shouldWeWriteLog) diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index bc686ad28..619d36d3a 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -972,11 +972,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetWarnList(const Comma Response_WarnList *re = new Response_WarnList; QString officialWarnings = settingsCache->value("server/officialwarnings").toString(); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QStringList warningsList = officialWarnings.split(",", Qt::SkipEmptyParts); -#else - QStringList warningsList = officialWarnings.split(",", QString::SkipEmptyParts); -#endif for (const QString &warning : warningsList) { re->add_warning(warning.toStdString()); } @@ -1172,13 +1168,8 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C const auto parsedEmailParts = EmailParser::parseEmailAddress(nameFromStdString(cmd.email())); const auto emailUser = parsedEmailParts.first; const auto emailDomain = parsedEmailParts.second; -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) const QStringList emailBlackListFilters = emailBlackList.split(",", Qt::SkipEmptyParts); const QStringList emailWhiteListFilters = emailWhiteList.split(",", Qt::SkipEmptyParts); -#else - const QStringList emailBlackListFilters = emailBlackList.split(",", QString::SkipEmptyParts); - const QStringList emailWhiteListFilters = emailWhiteList.split(",", QString::SkipEmptyParts); -#endif bool requireEmailForRegistration = settingsCache->value("registration/requireemail", true).toBool(); if (requireEmailForRegistration && emailUser.isEmpty()) { @@ -1930,13 +1921,8 @@ TcpServerSocketInterface::TcpServerSocketInterface(Servatrice *_server, socket->setSocketOption(QAbstractSocket::LowDelayOption, 1); connect(socket, SIGNAL(readyRead()), this, SLOT(readClient())); connect(socket, SIGNAL(disconnected()), this, SLOT(catchSocketDisconnected())); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) connect(socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(catchSocketError(QAbstractSocket::SocketError))); -#else - connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, - SLOT(catchSocketError(QAbstractSocket::SocketError))); -#endif } TcpServerSocketInterface::~TcpServerSocketInterface() @@ -2109,10 +2095,8 @@ void WebsocketServerSocketInterface::initConnection(void *_socket) } socket = (QWebSocket *)_socket; socket->setParent(this); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) // https://bugreports.qt.io/browse/QTBUG-70693 socket->setMaxAllowedIncomingMessageSize(1500000); // 1.5MB -#endif address = socket->peerAddress(); diff --git a/servatrice/src/settingscache.cpp b/servatrice/src/settingscache.cpp index 14bfaebfb..f6dcd5fc8 100644 --- a/servatrice/src/settingscache.cpp +++ b/servatrice/src/settingscache.cpp @@ -11,12 +11,7 @@ SettingsCache::SettingsCache(const QString &fileName, QSettings::Format format, // first, figure out if we are running in portable mode isPortableBuild = QFile::exists(qApp->applicationDirPath() + "/portable.dat"); - QStringList disallowedRegExpStr = -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) - value("users/disallowedregexp", "").toString().split(",", Qt::SkipEmptyParts); -#else - value("users/disallowedregexp", "").toString().split(",", QString::SkipEmptyParts); -#endif + QStringList disallowedRegExpStr = value("users/disallowedregexp", "").toString().split(",", Qt::SkipEmptyParts); disallowedRegExpStr.removeDuplicates(); for (const QString ®ExpStr : disallowedRegExpStr) { disallowedRegExp.append(QRegularExpression(QString("\\A%1\\z").arg(regExpStr))); diff --git a/servatrice/src/smtp/qxtsmtp.cpp b/servatrice/src/smtp/qxtsmtp.cpp index 951492d7a..6326b101d 100644 --- a/servatrice/src/smtp/qxtsmtp.cpp +++ b/servatrice/src/smtp/qxtsmtp.cpp @@ -334,11 +334,7 @@ void QxtSmtpPrivate::authenticate() state = Authenticated; emit qxt_p().authenticated(); } else { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) QStringList auth = extensions["AUTH"].toUpper().split(' ', Qt::SkipEmptyParts); -#else - QStringList auth = extensions["AUTH"].toUpper().split(' ', QString::SkipEmptyParts); -#endif if (auth.contains("CRAM-MD5")) { authCramMD5(); } else if (auth.contains("PLAIN")) { From ca3f6bba0234ba098d49cd7cd91c0659bcd813dd Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 26 Dec 2025 04:29:35 -0800 Subject: [PATCH 088/325] [Refactor] Move prev/next card logic out of PrintingSelector (#6450) --- .../deck_editor_deck_dock_widget.cpp | 46 ++++++++++++++++ .../deck_editor_deck_dock_widget.h | 3 ++ ...k_editor_printing_selector_dock_widget.cpp | 4 ++ .../printing_selector/printing_selector.cpp | 52 ------------------- .../printing_selector/printing_selector.h | 13 +++-- ...rinting_selector_card_selection_widget.cpp | 4 +- 6 files changed, 65 insertions(+), 57 deletions(-) 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 57d34be00..8ded2c50a 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 @@ -548,6 +548,52 @@ void DeckEditorDeckDockWidget::cleanDeck() deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags()); } +void DeckEditorDeckDockWidget::selectPrevCard() +{ + changeSelectedCard(-1); +} + +void DeckEditorDeckDockWidget::selectNextCard() +{ + changeSelectedCard(1); +} + +/** + * @brief Selects a card based on the change direction. + * + * @param changeBy The direction to change, -1 for previous, 1 for next. + */ +void DeckEditorDeckDockWidget::changeSelectedCard(int changeBy) +{ + if (changeBy == 0) { + return; + } + + // Get the current index of the selected item + auto deckViewCurrentIndex = deckView->currentIndex(); + + auto nextIndex = deckViewCurrentIndex.siblingAtRow(deckViewCurrentIndex.row() + changeBy); + if (!nextIndex.isValid()) { + nextIndex = deckViewCurrentIndex; + + // Increment to the next valid index, skipping header rows + AbstractDecklistNode *node; + do { + if (changeBy > 0) { + nextIndex = deckView->indexBelow(nextIndex); + } else { + nextIndex = deckView->indexAbove(nextIndex); + } + node = static_cast(nextIndex.internalPointer()); + } while (node && node->isDeckHeader()); + } + + if (nextIndex.isValid()) { + deckView->setCurrentIndex(nextIndex); + deckView->setFocus(Qt::FocusReason::MouseFocusReason); + } +} + /** * @brief Expands all parents of the given index. * @param sourceIndex The index to expand (model source index) diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index 30f752203..a50434f0b 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -56,6 +56,8 @@ public: public slots: void cleanDeck(); + void selectPrevCard(); + void selectNextCard(); void updateBannerCardComboBox(); void setDeck(const LoadedDeck &_deck); void syncDisplayWidgetsToModel(); @@ -122,6 +124,7 @@ private slots: void updateShowBannerCardComboBox(bool visible); void updateShowTagsWidget(bool visible); void syncBannerCardComboBoxSelectionWithDeck(); + void changeSelectedCard(int changeBy); void recursiveExpand(const QModelIndex &parent); void expandAll(); }; diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp index 0c068e043..03760c22d 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp @@ -33,6 +33,10 @@ void DeckEditorPrintingSelectorDockWidget::createPrintingSelectorDock() installEventFilter(deckEditor); connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged); + connect(printingSelector, &PrintingSelector::prevCardRequested, deckEditor->getDeckDockWidget(), + &DeckEditorDeckDockWidget::selectPrevCard); + connect(printingSelector, &PrintingSelector::nextCardRequested, deckEditor->getDeckDockWidget(), + &DeckEditorDeckDockWidget::selectNextCard); } void DeckEditorPrintingSelectorDockWidget::retranslateUi() diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp index f27101b93..f6851a9c7 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp @@ -138,58 +138,6 @@ void PrintingSelector::setCard(const CardInfoPtr &newCard, const QString &_curre flowWidget->repaint(); } -/** - * @brief Selects the previous card in the list. - */ -void PrintingSelector::selectPreviousCard() -{ - selectCard(-1); -} - -/** - * @brief Selects the next card in the list. - */ -void PrintingSelector::selectNextCard() -{ - selectCard(1); -} - -/** - * @brief Selects a card based on the change direction. - * - * @param changeBy The direction to change, -1 for previous, 1 for next. - */ -void PrintingSelector::selectCard(const int changeBy) -{ - if (changeBy == 0) { - return; - } - - // Get the current index of the selected item - auto deckViewCurrentIndex = deckView->currentIndex(); - - auto nextIndex = deckViewCurrentIndex.siblingAtRow(deckViewCurrentIndex.row() + changeBy); - if (!nextIndex.isValid()) { - nextIndex = deckViewCurrentIndex; - - // Increment to the next valid index, skipping header rows - AbstractDecklistNode *node; - do { - if (changeBy > 0) { - nextIndex = deckView->indexBelow(nextIndex); - } else { - nextIndex = deckView->indexAbove(nextIndex); - } - node = static_cast(nextIndex.internalPointer()); - } while (node && node->isDeckHeader()); - } - - if (nextIndex.isValid()) { - deckView->setCurrentIndex(nextIndex); - deckView->setFocus(Qt::FocusReason::MouseFocusReason); - } -} - /** * @brief Loads and displays all sets for the current selected card. */ diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h index fbe3b5b06..0de67619f 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h @@ -48,13 +48,21 @@ public: public slots: void retranslateUi(); void updateDisplay(); - void selectPreviousCard(); - void selectNextCard(); void toggleVisibilityNavigationButtons(bool _state); private slots: void printingsInDeckChanged(); +signals: + /** + * Requests the previous card in the list + */ + void prevCardRequested(); + /** + * Requests the next card in the list + */ + void nextCardRequested(); + private: QVBoxLayout *layout; SettingsButtonWidget *displayOptionsWidget; @@ -73,7 +81,6 @@ private: QString currentZone; QTimer *widgetLoadingBufferTimer; int currentIndex = 0; - void selectCard(int changeBy); }; #endif // PRINTING_SELECTOR_H diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp index fc17cecd0..317ce83b6 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp @@ -42,8 +42,8 @@ PrintingSelectorCardSelectionWidget::PrintingSelectorCardSelectionWidget(Printin */ void PrintingSelectorCardSelectionWidget::connectSignals() { - connect(previousCardButton, &QPushButton::clicked, parent, &PrintingSelector::selectPreviousCard); - connect(nextCardButton, &QPushButton::clicked, parent, &PrintingSelector::selectNextCard); + connect(previousCardButton, &QPushButton::clicked, parent, &PrintingSelector::prevCardRequested); + connect(nextCardButton, &QPushButton::clicked, parent, &PrintingSelector::nextCardRequested); } void PrintingSelectorCardSelectionWidget::selectSetForCards() From 96c82a03771b1e74b686a7e8e8a896a08f86ecff Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Mon, 29 Dec 2025 03:03:44 -0800 Subject: [PATCH 089/325] [Refactor] Clean up some PrintingSelector widgets (#6451) * remove currentZone from PrintingSelector * don't store constructor args in fields if they're just passed * simplify some methods * refactor * clean up initializeFormats * more refactoring in CardAmountWidget --- .../deck_editor_deck_dock_widget.cpp | 7 +- .../dialogs/dlg_select_set_for_cards.cpp | 30 +++++--- .../all_zones_card_amount_widget.cpp | 3 +- .../all_zones_card_amount_widget.h | 4 - .../printing_selector/card_amount_widget.cpp | 76 ++++++++++--------- .../printing_selector/printing_selector.cpp | 8 +- .../printing_selector/printing_selector.h | 3 +- .../printing_selector_card_display_widget.cpp | 25 +++--- .../printing_selector_card_display_widget.h | 17 ++--- .../printing_selector_card_overlay_widget.cpp | 15 ++-- .../printing_selector_card_overlay_widget.h | 3 - ...e_and_collectors_number_display_widget.cpp | 3 +- .../widgets/tabs/abstract_tab_deck_editor.cpp | 2 +- .../widgets/tabs/tab_deck_editor.cpp | 3 +- .../tab_deck_editor_visual.cpp | 3 +- 15 files changed, 92 insertions(+), 110 deletions(-) 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 8ded2c50a..409d43e64 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 @@ -284,16 +284,15 @@ void DeckEditorDeckDockWidget::createDeckDock() void DeckEditorDeckDockWidget::initializeFormats() { - QMap allFormats = CardDatabaseManager::query()->getAllFormatsWithCount(); + QStringList allFormats = CardDatabaseManager::query()->getAllFormatsWithCount().keys(); formatComboBox->clear(); // Remove "Loading Database..." formatComboBox->setEnabled(true); // Populate with formats formatComboBox->addItem("", ""); - for (auto it = allFormats.constBegin(); it != allFormats.constEnd(); ++it) { - QString displayText = QString("%1").arg(it.key()); - formatComboBox->addItem(displayText, it.key()); // store the raw key in itemData + for (auto formatName : allFormats) { + formatComboBox->addItem(formatName, formatName); // store the raw key in itemData } if (!deckModel->getDeckList()->getGameFormat().isEmpty()) { 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 656abbfdc..587407065 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 @@ -143,6 +143,22 @@ void DlgSelectSetForCards::retranslateUi() setAllToPreferredButton->setText(tr("Set all to preferred")); } +static bool swapPrinting(DeckListModel *model, const QString &modifiedSet, const QString &cardName) +{ + QModelIndex idx = model->findCard(cardName, DECK_ZONE_MAIN); + if (!idx.isValid()) { + return false; + } + int amount = model->data(idx.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT), Qt::DisplayRole).toInt(); + model->removeRow(idx.row(), idx.parent()); + CardInfoPtr cardInfo = CardDatabaseManager::query()->getCardInfo(cardName); + PrintingInfo printing = CardDatabaseManager::query()->getSpecificPrinting(cardName, modifiedSet, ""); + for (int i = 0; i < amount; i++) { + model->addCard(ExactCard(cardInfo, printing), DECK_ZONE_MAIN); + } + return true; +} + void DlgSelectSetForCards::actOK() { QMap modifiedSetsAndCardsMap = getModifiedCards(); @@ -155,20 +171,10 @@ void DlgSelectSetForCards::actOK() for (QString modifiedSet : modifiedSetsAndCardsMap.keys()) { for (QString card : modifiedSetsAndCardsMap.value(modifiedSet)) { - QModelIndex find_card = model->findCard(card, DECK_ZONE_MAIN); - if (!find_card.isValid()) { - continue; - } - int amount = - model->data(find_card.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT), Qt::DisplayRole).toInt(); - model->removeRow(find_card.row(), find_card.parent()); - CardInfoPtr cardInfo = CardDatabaseManager::query()->getCardInfo(card); - PrintingInfo printing = CardDatabaseManager::query()->getSpecificPrinting(card, modifiedSet, ""); - for (int i = 0; i < amount; i++) { - model->addCard(ExactCard(cardInfo, printing), DECK_ZONE_MAIN); - } + swapPrinting(model, modifiedSet, card); } } + if (!modifiedSetsAndCardsMap.isEmpty()) { emit deckModified(); } diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp index 5b67ae727..d8bd88b37 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp @@ -23,8 +23,7 @@ AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent, QTreeView *deckView, QSlider *cardSizeSlider, const ExactCard &rootCard) - : QWidget(parent), deckEditor(deckEditor), deckModel(deckModel), deckView(deckView), cardSizeSlider(cardSizeSlider), - rootCard(rootCard) + : QWidget(parent), cardSizeSlider(cardSizeSlider) { layout = new QVBoxLayout(this); layout->setAlignment(Qt::AlignHCenter); diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h index 1a8ec8bfd..6ce10cf2e 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h @@ -36,11 +36,7 @@ public slots: private: QVBoxLayout *layout; - AbstractTabDeckEditor *deckEditor; - DeckListModel *deckModel; - QTreeView *deckView; QSlider *cardSizeSlider; - ExactCard rootCard; QLabel *zoneLabelMainboard; CardAmountWidget *buttonBoxMainboard; QLabel *zoneLabelSideboard; diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index 7c5804c37..7371976eb 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -137,6 +137,31 @@ void CardAmountWidget::updateCardCount() layout->activate(); } +static QModelIndex addAndReplacePrintings(DeckListModel *model, + const QModelIndex &existing, + const ExactCard &rootCard, + const QString &zone, + int extraCopies, + bool replaceProviderless) +{ + auto newCardIndex = model->addCard(rootCard, zone); + if (!newCardIndex.isValid()) { + return {}; + } + + // Check if a card without a providerId already exists in the deckModel and replace it, if so. + if (existing.isValid() && existing != newCardIndex && replaceProviderless) { + for (int i = 0; i < extraCopies; i++) { + model->addCard(rootCard, zone); + } + model->removeRow(existing.row(), existing.parent()); + } + + // Set Index and Focus as if the user had just clicked the new card and modify the deckEditor saveState + return model->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(), + rootCard.getPrinting().getProperty("num")); +} + /** * @brief Adds a printing of the card to the specified zone (Mainboard or Sideboard). * @@ -144,26 +169,24 @@ void CardAmountWidget::updateCardCount() */ void CardAmountWidget::addPrinting(const QString &zone) { - int addedCount = 1; // Check if we will need to add extra copies due to replacing copies without providerIds QModelIndex existing = deckModel->findCard(rootCard.getName(), zone); + int extraCopies = 0; bool replacingProviderless = false; if (existing.isValid()) { - QString providerId = + QString foundProviderId = existing.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString(); - if (providerId.isEmpty()) { + if (foundProviderId.isEmpty()) { int amount = existing.data(Qt::DisplayRole).toInt(); extraCopies = amount - 1; // One less because we *always* add one replacingProviderless = true; } } - addedCount += extraCopies; - QString reason = QString("Added %1 copies of '%2 (%3) %4' to %5 [ProviderID: %6]%7") - .arg(addedCount) + .arg(1 + extraCopies) .arg(rootCard.getName()) .arg(rootCard.getPrinting().getSet()->getShortName()) .arg(rootCard.getPrinting().getProperty("num")) @@ -174,26 +197,13 @@ void CardAmountWidget::addPrinting(const QString &zone) emit deckModified(reason); // Add the card and expand the list UI - auto newCardIndex = deckModel->addCard(rootCard, zone); + auto newCardIndex = addAndReplacePrintings(deckModel, existing, rootCard, zone, extraCopies, replacingProviderless); - // Check if a card without a providerId already exists in the deckModel and replace it, if so. - QString foundProviderId = - existing.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString(); - if (existing.isValid() && existing != newCardIndex && foundProviderId == "") { - auto amount = existing.data(Qt::DisplayRole); - for (int i = 0; i < amount.toInt() - 1; i++) { - deckModel->addCard(rootCard, zone); - } - deckModel->removeRow(existing.row(), existing.parent()); + if (newCardIndex.isValid()) { + deckView->setCurrentIndex(newCardIndex); + deckView->setFocus(Qt::FocusReason::MouseFocusReason); + deckEditor->setModified(true); } - - // Set Index and Focus as if the user had just clicked the new card and modify the deckEditor saveState - newCardIndex = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(), - rootCard.getPrinting().getProperty("num")); - - deckView->setCurrentIndex(newCardIndex); - deckView->setFocus(Qt::FocusReason::MouseFocusReason); - deckEditor->setModified(true); } /** @@ -259,22 +269,14 @@ void CardAmountWidget::decrementCardHelper(const QString &zone) */ int CardAmountWidget::countCardsInZone(const QString &deckZone) { - if (rootCard.getPrinting().getUuid().isEmpty()) { - return 0; // Cards without uuids/providerIds CANNOT match another card, they are undefined for us. - } + QString uuid = rootCard.getPrinting().getUuid(); - if (!deckModel) { - return -1; + if (uuid.isEmpty()) { + return 0; // Cards without uuids/providerIds CANNOT match another card, they are undefined for us. } QList cards = deckModel->getCardsForZone(deckZone); - int count = 0; - for (auto currentCard : cards) { - if (currentCard.getPrinting().getUuid() == rootCard.getPrinting().getProperty("uuid")) { - count++; - } - } - - return count; + return std::count_if(cards.cbegin(), cards.cend(), + [&uuid](const ExactCard &card) { return card.getPrinting().getUuid() == uuid; }); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp index f6851a9c7..e64d6a009 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp @@ -115,9 +115,8 @@ void PrintingSelector::updateDisplay() * @brief Sets the current card for the selector and updates the display. * * @param newCard The new card to set. - * @param _currentZone The current zone the card is in. */ -void PrintingSelector::setCard(const CardInfoPtr &newCard, const QString &_currentZone) +void PrintingSelector::setCard(const CardInfoPtr &newCard) { if (newCard.isNull()) { return; @@ -129,7 +128,6 @@ void PrintingSelector::setCard(const CardInfoPtr &newCard, const QString &_curre } selectedCard = newCard; - currentZone = _currentZone; if (isVisible()) { updateDisplay(); } @@ -166,8 +164,8 @@ void PrintingSelector::getAllSetsForCurrentCard() connect(widgetLoadingBufferTimer, &QTimer::timeout, this, [=, this]() mutable { for (int i = 0; i < BATCH_SIZE && currentIndex < printingsToUse.size(); ++i, ++currentIndex) { auto card = ExactCard(selectedCard, printingsToUse[currentIndex]); - auto *cardDisplayWidget = new PrintingSelectorCardDisplayWidget( - this, deckEditor, deckModel, deckView, cardSizeWidget->getSlider(), card, currentZone); + auto *cardDisplayWidget = new PrintingSelectorCardDisplayWidget(this, deckEditor, deckModel, deckView, + cardSizeWidget->getSlider(), card); flowWidget->addWidget(cardDisplayWidget); cardDisplayWidget->clampSetNameToPicture(); connect(cardDisplayWidget, &PrintingSelectorCardDisplayWidget::cardPreferenceChanged, this, diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h index 0de67619f..e34ce3fe6 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h @@ -33,7 +33,7 @@ class PrintingSelector : public QWidget public: PrintingSelector(QWidget *parent, AbstractTabDeckEditor *deckEditor); - void setCard(const CardInfoPtr &newCard, const QString &_currentZone); + void setCard(const CardInfoPtr &newCard); void getAllSetsForCurrentCard(); [[nodiscard]] DeckListModel *getDeckModel() const { @@ -78,7 +78,6 @@ private: DeckListModel *deckModel; QTreeView *deckView; CardInfoPtr selectedCard; - QString currentZone; QTimer *widgetLoadingBufferTimer; int currentIndex = 0; }; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp index 24c45a7e3..86d6659a8 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp @@ -17,22 +17,19 @@ * display. * * @param parent The parent widget for this display. - * @param _deckEditor The TabDeckEditor instance for deck management. - * @param _deckModel The DeckListModel instance providing deck data. - * @param _deckView The QTreeView instance displaying the deck. - * @param _cardSizeSlider The slider controlling the size of the displayed card. - * @param _rootCard The root card object, representing the card to be displayed. - * @param _currentZone The current zone in which the card is located. + * @param deckEditor The TabDeckEditor instance for deck management. + * @param deckModel The DeckListModel instance providing deck data. + * @param deckView The QTreeView instance displaying the deck. + * @param cardSizeSlider The slider controlling the size of the displayed card. + * @param rootCard The root card object, representing the card to be displayed. */ PrintingSelectorCardDisplayWidget::PrintingSelectorCardDisplayWidget(QWidget *parent, - AbstractTabDeckEditor *_deckEditor, - DeckListModel *_deckModel, - QTreeView *_deckView, - QSlider *_cardSizeSlider, - const ExactCard &_rootCard, - QString &_currentZone) - : QWidget(parent), deckEditor(_deckEditor), deckModel(_deckModel), deckView(_deckView), - cardSizeSlider(_cardSizeSlider), rootCard(_rootCard), currentZone(_currentZone) + AbstractTabDeckEditor *deckEditor, + DeckListModel *deckModel, + QTreeView *deckView, + QSlider *cardSizeSlider, + const ExactCard &rootCard) + : QWidget(parent) { layout = new QVBoxLayout(this); setLayout(layout); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h index 2f6bd6920..608c2df5c 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h @@ -20,12 +20,11 @@ class PrintingSelectorCardDisplayWidget : public QWidget public: PrintingSelectorCardDisplayWidget(QWidget *parent, - AbstractTabDeckEditor *_deckEditor, - DeckListModel *_deckModel, - QTreeView *_deckView, - QSlider *_cardSizeSlider, - const ExactCard &_rootCard, - QString &_currentZone); + AbstractTabDeckEditor *deckEditor, + DeckListModel *deckModel, + QTreeView *deckView, + QSlider *cardSizeSlider, + const ExactCard &rootCard); public slots: void clampSetNameToPicture(); @@ -36,12 +35,6 @@ signals: private: QVBoxLayout *layout; SetNameAndCollectorsNumberDisplayWidget *setNameAndCollectorsNumberDisplayWidget; - AbstractTabDeckEditor *deckEditor; - DeckListModel *deckModel; - QTreeView *deckView; - QSlider *cardSizeSlider; - ExactCard rootCard; - QString currentZone; PrintingSelectorCardOverlayWidget *overlayWidget; }; 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 5f3a44b30..04ab07a59 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 @@ -22,19 +22,18 @@ * * @param parent The parent widget for this overlay. * @param _deckEditor The TabDeckEditor instance for deck management. - * @param _deckModel The DeckListModel instance providing deck data. - * @param _deckView The QTreeView instance displaying the deck. - * @param _cardSizeSlider The slider controlling the size of the card. + * @param deckModel The DeckListModel instance providing deck data. + * @param deckView The QTreeView instance displaying the deck. + * @param cardSizeSlider The slider controlling the size of the card. * @param _rootCard The root card object that contains information about the card. */ PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *parent, AbstractTabDeckEditor *_deckEditor, - DeckListModel *_deckModel, - QTreeView *_deckView, - QSlider *_cardSizeSlider, + DeckListModel *deckModel, + QTreeView *deckView, + QSlider *cardSizeSlider, const ExactCard &_rootCard) - : QWidget(parent), deckEditor(_deckEditor), deckModel(_deckModel), deckView(_deckView), - cardSizeSlider(_cardSizeSlider), rootCard(_rootCard) + : QWidget(parent), deckEditor(_deckEditor), rootCard(_rootCard) { // Set up the main layout auto *mainLayout = new QVBoxLayout(this); 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 10df9d53e..3bd5ce247 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 @@ -48,9 +48,6 @@ private: AllZonesCardAmountWidget *allZonesCardAmountWidget; QLabel *pinBadge = nullptr; AbstractTabDeckEditor *deckEditor; - DeckListModel *deckModel; - QTreeView *deckView; - QSlider *cardSizeSlider; ExactCard rootCard; }; diff --git a/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp index b1a6a61c1..5962680cd 100644 --- a/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp @@ -13,7 +13,7 @@ SetNameAndCollectorsNumberDisplayWidget::SetNameAndCollectorsNumberDisplayWidget const QString &_setName, const QString &_collectorsNumber, QSlider *_cardSizeSlider) - : QWidget(parent) + : QWidget(parent), cardSizeSlider(_cardSizeSlider) { // Set up the layout for the widget layout = new QVBoxLayout(this); @@ -35,7 +35,6 @@ SetNameAndCollectorsNumberDisplayWidget::SetNameAndCollectorsNumberDisplayWidget collectorsNumber->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); // Store the card size slider and connect its signal to the font size adjustment slot - cardSizeSlider = _cardSizeSlider; connect(cardSizeSlider, &QSlider::valueChanged, this, &SetNameAndCollectorsNumberDisplayWidget::adjustFontSize); // Add labels to the layout 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 8eb1477f1..cb2002199 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -101,7 +101,7 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta void AbstractTabDeckEditor::updateCard(const ExactCard &card) { cardInfoDockWidget->updateCard(card); - printingSelectorDockWidget->printingSelector->setCard(card.getCardPtr(), DECK_ZONE_MAIN); + printingSelectorDockWidget->printingSelector->setCard(card.getCardPtr()); } /** @brief Placeholder: called when the deck changes. */ diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index 4fa12259c..4e3d0c57d 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -158,8 +158,7 @@ void TabDeckEditor::refreshShortcuts() */ void TabDeckEditor::showPrintingSelector() { - printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr(), - DECK_ZONE_MAIN); + printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr()); printingSelectorDockWidget->printingSelector->updateDisplay(); aPrintingSelectorDockVisible->setChecked(true); printingSelectorDockWidget->setVisible(true); diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 57615df94..a124eaa01 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -266,8 +266,7 @@ bool TabDeckEditorVisual::actSaveDeckAs() /** @brief Shows the printing selector dock and updates it with the current card. */ void TabDeckEditorVisual::showPrintingSelector() { - printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr(), - DECK_ZONE_MAIN); + printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr()); printingSelectorDockWidget->printingSelector->updateDisplay(); aPrintingSelectorDockVisible->setChecked(true); printingSelectorDockWidget->setVisible(true); From 296866a67525cc3e524c04420afea00729be8799 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Mon, 29 Dec 2025 08:19:03 -0800 Subject: [PATCH 090/325] [DeckListModel] Refactor api for offset count (#6454) --- .../deck_editor_deck_dock_widget.cpp | 7 ++----- .../printing_selector/card_amount_widget.cpp | 2 +- .../models/deck_list/deck_list_model.cpp | 12 +----------- .../models/deck_list/deck_list_model.h | 18 +++++------------- 4 files changed, 9 insertions(+), 30 deletions(-) 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 409d43e64..543f1a1c0 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 @@ -831,11 +831,8 @@ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, bool i emit requestDeckHistorySave(reason); - if (isIncrement) { - deckModel->incrementAmountAtIndex(sourceIndex); - } else { - deckModel->decrementAmountAtIndex(sourceIndex); - } + int offset = isIncrement ? 1 : -1; + deckModel->offsetCountAtIndex(sourceIndex, offset); emit deckModified(); } diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index 7371976eb..623b797a7 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -257,7 +257,7 @@ void CardAmountWidget::decrementCardHelper(const QString &zone) QModelIndex idx = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(), rootCard.getPrinting().getProperty("num")); - deckModel->decrementAmountAtIndex(idx); + deckModel->offsetCountAtIndex(idx, -1); deckEditor->setModified(true); } 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 53d08cd9d..130f05c8a 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -443,17 +443,7 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam return index; } -bool DeckListModel::incrementAmountAtIndex(const QModelIndex &idx) -{ - return offsetAmountAtIndex(idx, 1); -} - -bool DeckListModel::decrementAmountAtIndex(const QModelIndex &idx) -{ - return offsetAmountAtIndex(idx, -1); -} - -bool DeckListModel::offsetAmountAtIndex(const QModelIndex &idx, int offset) +bool DeckListModel::offsetCountAtIndex(const QModelIndex &idx, int offset) { if (!idx.isValid()) { return false; 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 a85542d97..7bb504e60 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -307,19 +307,13 @@ public: QModelIndex addCard(const ExactCard &card, const QString &zoneName); /** - * @brief Increments the `amount` field of the card node at the index by 1. - * @param idx The index of a card node. No-ops if the index is invalid or not a card node + * @brief Changes the `amount` field in the card node at the index by the amount. + * Removes the node if it causes the amount to fall to 0 or below. + * @param idx The index of a card node. No-ops if the index is invalid or not a card node. + * @param offset The amount to change the amount field by. * @return Whether the operation was successful */ - bool incrementAmountAtIndex(const QModelIndex &idx); - - /** - * @brief Decrements the `amount` field of the card node at the index by 1. - * Removes the node if it causes the amount to fall to 0. - * @param idx The index of a card node. No-ops if the index is invalid or not a card node - * @return Whether the operation was successful - */ - bool decrementAmountAtIndex(const QModelIndex &idx); + bool offsetCountAtIndex(const QModelIndex &idx, int offset); /** * @brief Determines the sorted insertion row for a card. @@ -395,8 +389,6 @@ private: const QString &providerId = "", const QString &cardNumber = "") const; - bool offsetAmountAtIndex(const QModelIndex &idx, int offset); - void emitRecursiveUpdates(const QModelIndex &index); void sortHelper(InnerDecklistNode *node, Qt::SortOrder order); From 9d0bb0d51a0cbfb89a3d350561d9bfbae28a1b13 Mon Sep 17 00:00:00 2001 From: Bruno Alexandre Rosa <1791393+brunoalr@users.noreply.github.com> Date: Mon, 29 Dec 2025 13:21:59 -0300 Subject: [PATCH 091/325] fix: manage ccache caches manually on macos (#6449) * fix: manage ccache caches manually on macos * install ccache * fix issues shown by bugbot * readd cache size limit --- .github/workflows/desktop-build.yml | 32 ++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index b89190c8d..a26e82ee5 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -342,6 +342,11 @@ jobs: name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} needs: configure runs-on: ${{matrix.runner}} + env: + CCACHE_DIR: ${{github.workspace}}/.cache/ + # Cache size over the entire repo is 10Gi: + # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy + CCACHE_SIZE: 500M steps: - name: Checkout @@ -356,16 +361,20 @@ jobs: with: msbuild-architecture: x64 - # Using jianmingyong/ccache-action to setup ccache without using brew - # It tries to download a binary of ccache from GitHub Release and falls back to building from source if it fails - name: Setup ccache + if: matrix.use_ccache == 1 && matrix.os == 'macOS' + run: brew install ccache + + - name: Restore compiler cache (ccache) if: matrix.use_ccache == 1 - uses: jianmingyong/ccache-action@v1 + id: ccache_restore + uses: actions/cache/restore@v5 + env: + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} with: - install-type: "binary" - ccache-key-prefix: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}} - max-size: 500M - gh-token: ${{ secrets.GITHUB_TOKEN }} + path: ${{env.CCACHE_DIR}} + key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}} + restore-keys: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}- - name: Install Qt ${{matrix.qt_version}} uses: jurplel/install-qt-action@v4 @@ -403,6 +412,15 @@ jobs: TARGET_MACOS_VERSION: ${{ matrix.override_target }} run: .ci/compile.sh --server --test --vcpkg + - name: Save compiler cache (ccache) + if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 + uses: actions/cache/save@v5 + env: + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + with: + path: ${{env.CCACHE_DIR}} + key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}} + - name: Sign app bundle if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null) env: From ce4a3bf11825ae01ce7225e6100d0533c0b8ff1f Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Tue, 30 Dec 2025 06:16:02 +0100 Subject: [PATCH 092/325] compile in debug mode on ubuntu 22.04 (#6418) * compile in debug mode on ubuntu 22.04 * Update card_info_display_widget.cpp Use c++ instead of c-style cast --------- Co-authored-by: BruebachL <44814898+BruebachL@users.noreply.github.com> --- .github/workflows/desktop-build.yml | 1 - .../src/interface/widgets/cards/card_info_display_widget.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index a26e82ee5..9f4ee4984 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -142,7 +142,6 @@ jobs: - distro: Ubuntu version: 22.04 package: DEB - test: skip # Running tests on all distros is superfluous - distro: Ubuntu version: 24.04 diff --git a/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp index 819bc57d0..f8b581e33 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp @@ -27,7 +27,7 @@ CardInfoDisplayWidget::CardInfoDisplayWidget(const CardRef &cardRef, QWidget *pa layout->addWidget(text, 0, Qt::AlignCenter); setLayout(layout); - setFrameStyle(QFrame::Panel | QFrame::Raised); + setFrameStyle(static_cast(QFrame::Panel) | QFrame::Raised); int pixmapHeight = QGuiApplication::primaryScreen()->geometry().height() / 3; int pixmapWidth = static_cast(pixmapHeight / aspectRatio); From cb2cf31cecf3588159bd8ef31d9f608579ad6632 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Mon, 29 Dec 2025 22:13:34 -0800 Subject: [PATCH 093/325] [DeckListModel] Clean up recursive updates (#6457) --- .../models/deck_list/deck_list_model.cpp | 33 +++++++++---------- .../models/deck_list/deck_list_model.h | 18 ++++++---- 2 files changed, 28 insertions(+), 23 deletions(-) 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 130f05c8a..2ccb95690 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -178,22 +178,6 @@ QVariant DeckListModel::data(const QModelIndex &index, int role) const } } -void DeckListModel::emitBackgroundUpdates(const QModelIndex &parent) -{ - int rows = rowCount(parent); - if (rows == 0) - return; - - QModelIndex topLeft = index(0, 0, parent); - QModelIndex bottomRight = index(rows - 1, columnCount() - 1, parent); - emit dataChanged(topLeft, bottomRight, {Qt::BackgroundRole}); - - for (int r = 0; r < rows; ++r) { - QModelIndex child = index(r, 0, parent); - emitBackgroundUpdates(child); - } -} - QVariant DeckListModel::headerData(const int section, const Qt::Orientation orientation, const int role) const { if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal)) { @@ -252,6 +236,22 @@ Qt::ItemFlags DeckListModel::flags(const QModelIndex &index) const return result; } +void DeckListModel::emitBackgroundUpdates(const QModelIndex &parent) +{ + int rows = rowCount(parent); + if (rows == 0) + return; + + QModelIndex topLeft = index(0, 0, parent); + QModelIndex bottomRight = index(rows - 1, columnCount() - 1, parent); + emit dataChanged(topLeft, bottomRight, {Qt::BackgroundRole}); + + for (int r = 0; r < rows; ++r) { + QModelIndex child = index(r, 0, parent); + emitBackgroundUpdates(child); + } +} + void DeckListModel::emitRecursiveUpdates(const QModelIndex &index) { if (!index.isValid()) { @@ -294,7 +294,6 @@ bool DeckListModel::setData(const QModelIndex &index, const QVariant &value, con deckList->refreshDeckHash(); emit deckHashChanged(); - emit dataChanged(index, index); return true; } 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 7bb504e60..724a4d9d8 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -269,12 +269,6 @@ public: bool setData(const QModelIndex &index, const QVariant &value, int role) override; bool removeRows(int row, int count, const QModelIndex &parent) override; - /** - * Recursively emits the dataChanged signal for all child nodes. - * @param parent The parent node - */ - void emitBackgroundUpdates(const QModelIndex &parent); - /** * @brief Finds a card by name, zone, and optional identifiers. * @param cardName The card's name. @@ -389,7 +383,19 @@ private: const QString &providerId = "", const QString &cardNumber = "") const; + /** + * @brief Recursively emits the dataChanged signal with role as Qt::BackgroundRole for all indices that are children + * of the given node. This is used to update the background color when changing formats. + * @param parent The parent node + */ + void emitBackgroundUpdates(const QModelIndex &parent); + + /** + * @brief Recursively emits the dataChanged signal for the given node and all parent nodes. + * @param index The parent node + */ void emitRecursiveUpdates(const QModelIndex &index); + void sortHelper(InnerDecklistNode *node, Qt::SortOrder order); template T getNode(const QModelIndex &index) const From daa7db7ce310e4a66b57221e636d9ab78c7c5cbd Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Mon, 29 Dec 2025 22:43:34 -0800 Subject: [PATCH 094/325] [DeckDockWidget] Fix tree unexpanding when changing group by (#6458) --- .../widgets/deck_editor/deck_editor_deck_dock_widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 543f1a1c0..3ca171a15 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 @@ -157,7 +157,7 @@ void DeckEditorDeckDockWidget::createDeckDock() QTimer::singleShot(100, this, &DeckEditorDeckDockWidget::updateBannerCardComboBox); }); connect(deckModel, &DeckListModel::cardAddedAt, this, &DeckEditorDeckDockWidget::recursiveExpand); - connect(deckModel, &DeckListModel::deckReplaced, this, &DeckEditorDeckDockWidget::expandAll); + connect(deckModel, &DeckListModel::modelReset, this, &DeckEditorDeckDockWidget::expandAll); connect(bannerCardComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &DeckEditorDeckDockWidget::setBannerCard); From 968be8a06f6d2eb50f140992f7214428f16d2f9b Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 31 Dec 2025 03:00:23 -0800 Subject: [PATCH 095/325] Fix bug with next/prev buttons in PrintingSelector (#6453) * Hacky fix and debug messages * remove debug * add todo --- .../widgets/deck_editor/deck_editor_deck_dock_widget.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) 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 3ca171a15..b8ebc1419 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 @@ -571,6 +571,15 @@ void DeckEditorDeckDockWidget::changeSelectedCard(int changeBy) // Get the current index of the selected item auto deckViewCurrentIndex = deckView->currentIndex(); + // For some reason, if the deckModel is modified but the view is not manually reselected, + // currentIndex will return an index for the underlying deckModel instead of the proxy. + // That index will return an invalid index when indexBelow/indexAbove crosses a header node, + // causing the selection to fail to move down. + /// \todo Figure out why it's happening so we can do a proper fix instead of a hacky workaround + if (deckViewCurrentIndex.model() == proxy->sourceModel()) { + deckViewCurrentIndex = proxy->mapFromSource(deckViewCurrentIndex); + } + auto nextIndex = deckViewCurrentIndex.siblingAtRow(deckViewCurrentIndex.row() + changeBy); if (!nextIndex.isValid()) { nextIndex = deckViewCurrentIndex; From d722b2569c157735632a9df9425b9a067839b630 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 31 Dec 2025 03:01:49 -0800 Subject: [PATCH 096/325] [DeckListModel] Refactor: general code cleanup (#6460) * change one usage * move method * move format check code * make group criteria method static * move method * make method private * more comments --- .../printing_selector/card_amount_widget.cpp | 4 +- .../libcockatrice/card/card_info.cpp | 10 ++++ .../libcockatrice/card/card_info.h | 9 ++++ .../models/deck_list/deck_list_model.cpp | 53 +++++++++---------- .../models/deck_list/deck_list_model.h | 49 +++++++---------- 5 files changed, 66 insertions(+), 59 deletions(-) diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index 623b797a7..d01725cc4 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -151,9 +151,7 @@ static QModelIndex addAndReplacePrintings(DeckListModel *model, // Check if a card without a providerId already exists in the deckModel and replace it, if so. if (existing.isValid() && existing != newCardIndex && replaceProviderless) { - for (int i = 0; i < extraCopies; i++) { - model->addCard(rootCard, zone); - } + model->offsetCountAtIndex(newCardIndex, extraCopies); model->removeRow(existing.row(), existing.parent()); } diff --git a/libcockatrice_card/libcockatrice/card/card_info.cpp b/libcockatrice_card/libcockatrice/card/card_info.cpp index 3054a10cf..acfaea8c8 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.cpp +++ b/libcockatrice_card/libcockatrice/card/card_info.cpp @@ -75,6 +75,16 @@ QString CardInfo::getCorrectedName() const return result.remove(rmrx).replace(spacerx, space); } +bool CardInfo::isLegalInFormat(const QString &format) const +{ + if (format.isEmpty()) { + return true; + } + + QString formatLegality = getProperty("format-" + format); + return formatLegality == "legal" || formatLegality == "restricted"; +} + void CardInfo::addToSet(const CardSetPtr &_set, const PrintingInfo _info) { if (!_set->contains(smartThis)) { diff --git a/libcockatrice_card/libcockatrice/card/card_info.h b/libcockatrice_card/libcockatrice/card/card_info.h index 00e8fec37..13b2b8a49 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.h +++ b/libcockatrice_card/libcockatrice/card/card_info.h @@ -291,6 +291,15 @@ public: */ [[nodiscard]] QString getCorrectedName() const; + /** + * @brief Checks if the card is legal in the given format. + * A card is considered legal in a format if its properties map contains an entry for "format-", with value + * "legal" or "restricted". + * @param format The format's name. If empty, will always return true. + * @return Whether the card is legal in the given format. + */ + [[nodiscard]] bool isLegalInFormat(const QString &format) const; + /** * @brief Adds a printing to a specific set. * 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 2ccb95690..6f479616e 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -18,13 +18,19 @@ DeckListModel::~DeckListModel() delete root; } -QString DeckListModel::getGroupCriteriaForCard(CardInfoPtr info) const +/** + * @brief Extract the value from the card that is used for the group criteria. + * @param info Pointer to card information. + * @param criteria The group criteria + * @return String representing the value of the criteria. + */ +static QString extractGroupCriteriaValue(const CardInfoPtr &info, DeckListModelGroupCriteria::Type criteria) { if (!info) { return "unknown"; } - switch (activeGroupCriteria) { + switch (criteria) { case DeckListModelGroupCriteria::MAIN_TYPE: return info->getMainCardType(); case DeckListModelGroupCriteria::MANA_COST: @@ -56,7 +62,7 @@ void DeckListModel::rebuildTree() } CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(currentCard->getName()); - QString groupCriteria = getGroupCriteriaForCard(info); + QString groupCriteria = extractGroupCriteriaValue(info, activeGroupCriteria); auto *groupNode = dynamic_cast(node->findChild(groupCriteria)); @@ -353,7 +359,7 @@ DecklistModelCardNode *DeckListModel::findCardNode(const QString &cardName, return nullptr; } - QString groupCriteria = getGroupCriteriaForCard(info); + QString groupCriteria = extractGroupCriteriaValue(info, activeGroupCriteria); InnerDecklistNode *groupNode = dynamic_cast(zoneNode->findChild(groupCriteria)); if (!groupNode) { return nullptr; @@ -406,7 +412,7 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam CardInfoPtr cardInfo = card.getCardPtr(); PrintingInfo printingInfo = card.getPrinting(); - QString groupCriteria = getGroupCriteriaForCard(cardInfo); + QString groupCriteria = extractGroupCriteriaValue(cardInfo, activeGroupCriteria); InnerDecklistNode *groupNode = createNodeIfNeeded(groupCriteria, zoneNode); const QModelIndex parentIndex = nodeToIndex(groupNode); @@ -420,7 +426,7 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam auto *decklistCard = deckList->addCard(cardInfo->getName(), zoneName, insertRow, cardSetName, printingInfo.getProperty("num"), - printingInfo.getProperty("uuid"), isCardLegalForCurrentFormat(cardInfo)); + printingInfo.getProperty("uuid"), cardInfo->isLegalInFormat(deckList->getGameFormat())); beginInsertRows(parentIndex, insertRow, insertRow); cardNode = new DecklistModelCardNode(decklistCard, groupNode, insertRow); @@ -472,7 +478,7 @@ bool DeckListModel::offsetCountAtIndex(const QModelIndex &idx, int offset) return true; } -int DeckListModel::findSortedInsertRow(InnerDecklistNode *parent, CardInfoPtr cardInfo) const +int DeckListModel::findSortedInsertRow(const InnerDecklistNode *parent, const CardInfoPtr &cardInfo) const { if (!cardInfo) { return parent->size(); // fallback: append at end @@ -661,18 +667,6 @@ QList DeckListModel::getZones() const return zones; } -bool DeckListModel::isCardLegalForCurrentFormat(const CardInfoPtr cardInfo) -{ - if (!deckList->getGameFormat().isEmpty()) { - if (cardInfo->getProperties().contains("format-" + deckList->getGameFormat())) { - QString formatLegality = cardInfo->getProperty("format-" + deckList->getGameFormat()); - return formatLegality == "legal" || formatLegality == "restricted"; - } - return false; - } - return true; -} - static int maxAllowedForLegality(const FormatRules &format, const QString &legality) { for (const AllowedCount &c : format.allowedCounts) { @@ -683,25 +677,29 @@ static int maxAllowedForLegality(const FormatRules &format, const QString &legal return -1; // unknown legality → treat as illegal } -bool DeckListModel::isCardQuantityLegalForCurrentFormat(const CardInfoPtr cardInfo, int quantity) +static bool isCardQuantityLegalForFormat(const QString &format, const CardInfo &cardInfo, int quantity) { - auto formatRules = CardDatabaseManager::query()->getFormat(deckList->getGameFormat()); + if (format.isEmpty()) { + return true; + } + + auto formatRules = CardDatabaseManager::query()->getFormat(format); if (!formatRules) { return true; } // Exceptions always win - if (cardHasAnyException(*cardInfo, *formatRules)) { + if (cardHasAnyException(cardInfo, *formatRules)) { return true; } - const QString legalityProp = "format-" + deckList->getGameFormat(); - if (!cardInfo->getProperties().contains(legalityProp)) { + const QString legalityProp = "format-" + format; + if (!cardInfo.getProperties().contains(legalityProp)) { return false; } - const QString legality = cardInfo->getProperty(legalityProp); + const QString legality = cardInfo.getProperty(legalityProp); int maxAllowed = maxAllowedForLegality(*formatRules, legality); @@ -735,10 +733,11 @@ void DeckListModel::refreshCardFormatLegalities() continue; } - bool legal = isCardLegalForCurrentFormat(exactCard.getCardPtr()); + QString format = deckList->getGameFormat(); + bool legal = exactCard.getInfo().isLegalInFormat(format); if (legal) { - legal = isCardQuantityLegalForCurrentFormat(exactCard.getCardPtr(), currentCard->getNumber()); + legal = isCardQuantityLegalForFormat(format, exactCard.getInfo(), currentCard->getNumber()); } currentCard->setFormatLegality(legal); 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 724a4d9d8..b6292d689 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -217,7 +217,12 @@ public slots: */ void rebuildTree(); -public slots: + /** + * @brief Sets the criteria used to group cards in the model. + * @param newCriteria The new grouping criteria. + */ + void setActiveGroupCriteria(DeckListModelGroupCriteria::Type newCriteria); + void setActiveFormat(const QString &_format); signals: @@ -251,14 +256,8 @@ public: return nodeToIndex(root); } - /** - * @brief Returns the value of the grouping category for a card based on the current criteria. - * @param info Pointer to card information. - * @return String representing the value of the current grouping criteria for the card. - */ - [[nodiscard]] QString getGroupCriteriaForCard(CardInfoPtr info) const; - - // Qt model overrides + /// @name Qt model overrides + ///@{ [[nodiscard]] int rowCount(const QModelIndex &parent) const override; [[nodiscard]] int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; @@ -268,6 +267,8 @@ public: [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; bool removeRows(int row, int count, const QModelIndex &parent) override; + void sort(int column, Qt::SortOrder order) override; + ///@} /** * @brief Finds a card by name, zone, and optional identifiers. @@ -309,16 +310,6 @@ public: */ bool offsetCountAtIndex(const QModelIndex &idx, int offset); - /** - * @brief Determines the sorted insertion row for a card. - * @param parent The parent node where the card will be inserted. - * @param cardInfo The card info to insert. - * @return Row index where the card should be inserted to maintain sort order. - */ - int findSortedInsertRow(InnerDecklistNode *parent, CardInfoPtr cardInfo) const; - - void sort(int column, Qt::SortOrder order) override; - /** * @brief Removes all cards and resets the model. */ @@ -359,16 +350,6 @@ public: */ [[nodiscard]] QList getZones() const; - bool isCardLegalForCurrentFormat(CardInfoPtr cardInfo); - bool isCardQuantityLegalForCurrentFormat(CardInfoPtr cardInfo, int quantity); - void refreshCardFormatLegalities(); - - /** - * @brief Sets the criteria used to group cards in the model. - * @param newCriteria The new grouping criteria. - */ - void setActiveGroupCriteria(DeckListModelGroupCriteria::Type newCriteria); - private: DeckList *deckList; /**< Pointer to the deck loader providing the underlying data. */ InnerDecklistNode *root; /**< Root node of the model tree. */ @@ -383,6 +364,14 @@ private: const QString &providerId = "", const QString &cardNumber = "") const; + /** + * @brief Determines the sorted insertion row for a card. + * @param parent The parent node where the card will be inserted. + * @param cardInfo The card info to insert. + * @return Row index where the card should be inserted to maintain sort order. + */ + int findSortedInsertRow(const InnerDecklistNode *parent, const CardInfoPtr &cardInfo) const; + /** * @brief Recursively emits the dataChanged signal with role as Qt::BackgroundRole for all indices that are children * of the given node. This is used to update the background color when changing formats. @@ -404,6 +393,8 @@ private: return dynamic_cast(root); return dynamic_cast(static_cast(index.internalPointer())); } + + void refreshCardFormatLegalities(); }; #endif From db3bdb586b63bc1ef79f6f6d41b54b31fac14031 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 31 Dec 2025 04:13:32 -0800 Subject: [PATCH 097/325] [CardInfo] clean up signatures (#6462) --- .../libcockatrice/card/card_info.cpp | 18 +++++++++--------- .../libcockatrice/card/card_info.h | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/libcockatrice_card/libcockatrice/card/card_info.cpp b/libcockatrice_card/libcockatrice/card/card_info.cpp index acfaea8c8..ae73ef2db 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.cpp +++ b/libcockatrice_card/libcockatrice/card/card_info.cpp @@ -85,7 +85,7 @@ bool CardInfo::isLegalInFormat(const QString &format) const return formatLegality == "legal" || formatLegality == "restricted"; } -void CardInfo::addToSet(const CardSetPtr &_set, const PrintingInfo _info) +void CardInfo::addToSet(const CardSetPtr &_set, const PrintingInfo &_info) { if (!_set->contains(smartThis)) { _set->append(smartThis); @@ -166,7 +166,7 @@ QString CardInfo::simplifyName(const QString &name) return simpleName; } -const QChar CardInfo::getColorChar() const +QChar CardInfo::getColorChar() const { QString colors = getColors(); switch (colors.size()) { @@ -188,7 +188,7 @@ void CardInfo::resetReverseRelatedCards2Me() } // Back-compatibility methods. Remove ASAP -const QString CardInfo::getCardType() const +QString CardInfo::getCardType() const { return getProperty(Mtg::CardType); } @@ -196,11 +196,11 @@ void CardInfo::setCardType(const QString &value) { setProperty(Mtg::CardType, value); } -const QString CardInfo::getCmc() const +QString CardInfo::getCmc() const { return getProperty(Mtg::ConvertedManaCost); } -const QString CardInfo::getColors() const +QString CardInfo::getColors() const { return getProperty(Mtg::Colors); } @@ -208,19 +208,19 @@ void CardInfo::setColors(const QString &value) { setProperty(Mtg::Colors, value); } -const QString CardInfo::getLoyalty() const +QString CardInfo::getLoyalty() const { return getProperty(Mtg::Loyalty); } -const QString CardInfo::getMainCardType() const +QString CardInfo::getMainCardType() const { return getProperty(Mtg::MainCardType); } -const QString CardInfo::getManaCost() const +QString CardInfo::getManaCost() const { return getProperty(Mtg::ManaCost); } -const QString CardInfo::getPowTough() const +QString CardInfo::getPowTough() const { return getProperty(Mtg::PowTough); } diff --git a/libcockatrice_card/libcockatrice/card/card_info.h b/libcockatrice_card/libcockatrice/card/card_info.h index 13b2b8a49..b81cf51dc 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.h +++ b/libcockatrice_card/libcockatrice/card/card_info.h @@ -267,18 +267,18 @@ public: } //@} - [[nodiscard]] const QChar getColorChar() const; + [[nodiscard]] QChar getColorChar() const; /** @name Legacy/Convenience Property Accessors */ //@{ - [[nodiscard]] const QString getCardType() const; + [[nodiscard]] QString getCardType() const; void setCardType(const QString &value); - [[nodiscard]] const QString getCmc() const; - [[nodiscard]] const QString getColors() const; + [[nodiscard]] QString getCmc() const; + [[nodiscard]] QString getColors() const; void setColors(const QString &value); - [[nodiscard]] const QString getLoyalty() const; - [[nodiscard]] const QString getMainCardType() const; - [[nodiscard]] const QString getManaCost() const; - [[nodiscard]] const QString getPowTough() const; + [[nodiscard]] QString getLoyalty() const; + [[nodiscard]] QString getMainCardType() const; + [[nodiscard]] QString getManaCost() const; + [[nodiscard]] QString getPowTough() const; void setPowTough(const QString &value); //@} @@ -308,7 +308,7 @@ public: * @param _set The set to which the card should be added. * @param _info Optional printing information. */ - void addToSet(const CardSetPtr &_set, PrintingInfo _info = PrintingInfo()); + void addToSet(const CardSetPtr &_set, const PrintingInfo &_info = PrintingInfo()); /** * @brief Combines legality properties from a provided map. From 0085015ebe59d3b0942ebf11f6dd32b64529ab28 Mon Sep 17 00:00:00 2001 From: Alex Okonechnikov <36140593+okonech@users.noreply.github.com> Date: Wed, 31 Dec 2025 11:48:30 -0500 Subject: [PATCH 098/325] manual drag override (#6461) * manual drag override * fix styles * pr comments * better close button rect calc --- .../src/game/zones/view_zone_widget.cpp | 194 ++++++++++++++++-- cockatrice/src/game/zones/view_zone_widget.h | 36 +++- 2 files changed, 210 insertions(+), 20 deletions(-) diff --git a/cockatrice/src/game/zones/view_zone_widget.cpp b/cockatrice/src/game/zones/view_zone_widget.cpp index 91abb4663..e15a466c3 100644 --- a/cockatrice/src/game/zones/view_zone_widget.cpp +++ b/cockatrice/src/game/zones/view_zone_widget.cpp @@ -13,12 +13,20 @@ #include #include #include +#include #include #include #include +#include #include #include +namespace +{ +constexpr qreal kTitleBarHeight = 24.0; +constexpr qreal kMinVisibleWidth = 100.0; +} // namespace + /** * @param _player the player the cards were revealed to. * @param _origZone the zone the cards were revealed from. @@ -241,33 +249,182 @@ void ZoneViewWidget::retranslateUi() pileViewCheckBox.setText(tr("pile view")); } -void ZoneViewWidget::moveEvent(QGraphicsSceneMoveEvent * /* event */) +void ZoneViewWidget::stopWindowDrag() { - if (!scene()) + if (!draggingWindow) return; - int titleBarHeight = 24; + draggingWindow = false; + ungrabMouse(); +} - QPointF scenePos = pos(); +void ZoneViewWidget::startWindowDrag(QGraphicsSceneMouseEvent *event) +{ + draggingWindow = true; + dragStartItemPos = pos(); + dragStartScreenPos = event->screenPos(); + dragView = findDragView(event->widget()); - if (scenePos.x() < 0) { - scenePos.setX(0); - } else { - qreal maxw = scene()->sceneRect().width() - 100; - if (scenePos.x() > maxw) - scenePos.setX(maxw); + // need to grab mouse to receive events and not miss initial movement + grabMouse(); +} + +QRectF ZoneViewWidget::closeButtonRect(QWidget *styleWidget) const +{ + const QRectF frameRectF = windowFrameRect(); + const QRect titleBarRect(frameRectF.toRect().x(), frameRectF.toRect().y(), frameRectF.toRect().width(), + static_cast(kTitleBarHeight)); + + // query the style for the close button position (handles macOS top-left placement) + if (styleWidget) { + QStyleOptionTitleBar opt; + opt.initFrom(styleWidget); + opt.rect = titleBarRect; + opt.text = windowTitle(); + opt.icon = styleWidget->windowIcon(); + opt.titleBarFlags = Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint; + opt.subControls = QStyle::SC_TitleBarCloseButton; + opt.activeSubControls = QStyle::SC_TitleBarCloseButton; + opt.titleBarState = styleWidget->isActiveWindow() ? Qt::WindowActive : Qt::WindowNoState; + if (styleWidget->isActiveWindow()) + opt.state |= QStyle::State_Active; + const QRect r = styleWidget->style()->subControlRect(QStyle::CC_TitleBar, &opt, QStyle::SC_TitleBarCloseButton, + styleWidget); + if (r.isValid() && !r.isEmpty()) { + return QRectF(r); + } } - if (scenePos.y() < titleBarHeight) { - scenePos.setY(titleBarHeight); - } else { - qreal maxh = scene()->sceneRect().height() - titleBarHeight; - if (scenePos.y() > maxh) - scenePos.setY(maxh); + // fallback: square at right end of titlebar (Windows/Linux style) + return QRectF(frameRectF.right() - kTitleBarHeight, frameRectF.top(), kTitleBarHeight, kTitleBarHeight); +} + +QGraphicsView *ZoneViewWidget::findDragView(QWidget *eventWidget) const +{ + QWidget *current = eventWidget; + while (current) { + if (auto *view = qobject_cast(current)) + return view; + current = current->parentWidget(); } - if (scenePos != pos()) - setPos(scenePos); + if (scene() && !scene()->views().isEmpty()) + return scene()->views().constFirst(); + + return nullptr; +} + +QPointF ZoneViewWidget::calcDraggedWindowPos(const QPoint &screenPos, + const QPointF &scenePos, + const QPointF &buttonDownScenePos) const +{ + if (dragView && dragView->viewport()) { + const QPoint vpStart = dragView->viewport()->mapFromGlobal(dragStartScreenPos); + const QPoint vpNow = dragView->viewport()->mapFromGlobal(screenPos); + const QPointF sceneStart = dragView->mapToScene(vpStart); + const QPointF sceneNow = dragView->mapToScene(vpNow); + return dragStartItemPos + (sceneNow - sceneStart); + } + + return dragStartItemPos + (scenePos - buttonDownScenePos); +} + +bool ZoneViewWidget::windowFrameEvent(QEvent *event) +{ + if (event->type() == QEvent::UngrabMouse) { + stopWindowDrag(); + return QGraphicsWidget::windowFrameEvent(event); + } + + auto *me = dynamic_cast(event); + if (!me) + return QGraphicsWidget::windowFrameEvent(event); + + switch (event->type()) { + case QEvent::GraphicsSceneMousePress: + if (me->button() == Qt::LeftButton && windowFrameSectionAt(me->pos()) == Qt::TitleBarArea) { + // avoid drag on close button + if (closeButtonRect(me->widget()).contains(me->pos())) { + me->accept(); + close(); + return true; + } + startWindowDrag(me); + me->accept(); + return true; + } + break; + + case QEvent::GraphicsSceneMouseMove: + if (draggingWindow) { + if (!(me->buttons() & Qt::LeftButton)) { + stopWindowDrag(); + } else { + setPos( + calcDraggedWindowPos(me->screenPos(), me->scenePos(), me->buttonDownScenePos(Qt::LeftButton))); + } + me->accept(); + return true; + } + break; + + case QEvent::GraphicsSceneMouseRelease: + if (draggingWindow && me->button() == Qt::LeftButton) { + stopWindowDrag(); + me->accept(); + return true; + } + break; + + default: + break; + } + + return QGraphicsWidget::windowFrameEvent(event); +} + +void ZoneViewWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + // move if the scene routes moves while dragging + if (draggingWindow && (event->buttons() & Qt::LeftButton)) { + setPos(calcDraggedWindowPos(event->screenPos(), event->scenePos(), event->buttonDownScenePos(Qt::LeftButton))); + event->accept(); + return; + } + + QGraphicsWidget::mouseMoveEvent(event); +} + +void ZoneViewWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if (draggingWindow && event->button() == Qt::LeftButton) { + stopWindowDrag(); + event->accept(); + return; + } + + QGraphicsWidget::mouseReleaseEvent(event); +} + +QVariant ZoneViewWidget::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == QGraphicsItem::ItemPositionChange && scene()) { + // Keep grab area in main view + const QRectF sceneRect = scene()->sceneRect(); + const QPointF requestedPos = value.toPointF(); + QPointF desiredPos = requestedPos; + + const qreal minX = sceneRect.left(); + const qreal maxX = qMax(minX, sceneRect.right() - kMinVisibleWidth); + const qreal minY = sceneRect.top() + kTitleBarHeight; + const qreal maxY = qMax(minY, sceneRect.bottom() - kTitleBarHeight); + + desiredPos.setX(qBound(minX, desiredPos.x(), maxX)); + desiredPos.setY(qBound(minY, desiredPos.y(), maxY)); + return desiredPos; + } + + return QGraphicsWidget::itemChange(change, value); } void ZoneViewWidget::resizeEvent(QGraphicsSceneResizeEvent *event) @@ -350,6 +507,7 @@ void ZoneViewWidget::handleScrollBarChange(int value) void ZoneViewWidget::closeEvent(QCloseEvent *event) { + stopWindowDrag(); disconnect(zone, &ZoneViewZone::closed, this, 0); // manually call zone->close in order to remove it from the origZones views zone->close(); diff --git a/cockatrice/src/game/zones/view_zone_widget.h b/cockatrice/src/game/zones/view_zone_widget.h index 272ff5560..4ed8f74d8 100644 --- a/cockatrice/src/game/zones/view_zone_widget.h +++ b/cockatrice/src/game/zones/view_zone_widget.h @@ -3,7 +3,6 @@ * @ingroup GameGraphicsZones * @brief TODO: Document this. */ - #ifndef ZONEVIEWWIDGET_H #define ZONEVIEWWIDGET_H @@ -14,6 +13,7 @@ #include #include #include +#include #include class QLabel; @@ -28,6 +28,8 @@ class ServerInfo_Card; class QGraphicsSceneMouseEvent; class QGraphicsSceneWheelEvent; class QStyleOption; +class QGraphicsView; +class QWidget; class ScrollableGraphicsProxyWidget : public QGraphicsProxyWidget { @@ -66,6 +68,33 @@ private: int extraHeight; Player *player; + bool draggingWindow = false; + QPoint dragStartScreenPos; + QPointF dragStartItemPos; + QPointer dragView; + + void stopWindowDrag(); + void startWindowDrag(QGraphicsSceneMouseEvent *event); + QRectF closeButtonRect(QWidget *styleWidget) const; + /** + * @brief Resolves the QGraphicsView to use for drag coordinate mapping + * + * @param eventWidget QWidget that originated the mouse event + * @return The resolved QGraphicsView + */ + QGraphicsView *findDragView(QWidget *eventWidget) const; + /** + * @brief Calculates the desired widget position while dragging + * + * @param screenPos Global screen coordinates of the current mouse position + * @param scenePos Scene coordinates of the current mouse position + * @param buttonDownScenePos Scene coordinates of the initial mouse press position + * + * @return The new widget position in scene coordinates + */ + QPointF + calcDraggedWindowPos(const QPoint &screenPos, const QPointF &scenePos, const QPointF &buttonDownScenePos) const; + void resizeScrollbar(qreal newZoneHeight); signals: void closePressed(ZoneViewWidget *zv); @@ -76,7 +105,6 @@ private slots: void resizeToZoneContents(bool forceInitialHeight = false); void handleScrollBarChange(int value); void zoneDeleted(); - void moveEvent(QGraphicsSceneMoveEvent * /* event */) override; void resizeEvent(QGraphicsSceneResizeEvent * /* event */) override; void expandWindow(); @@ -101,6 +129,10 @@ public: protected: void closeEvent(QCloseEvent *event) override; void initStyleOption(QStyleOption *option) const override; + bool windowFrameEvent(QEvent *event) override; + QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; }; From b2dd8eed3fbec6539dfbc8a61ce2d2e70664f229 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 31 Dec 2025 08:54:47 -0800 Subject: [PATCH 099/325] [TabDeckEditor] Create class to centralize deck state (#6459) * create new file * use QSharedPointer in DeckListModel * [TabDeckEditor] Create class to centralize deck state * delete method * update docs --- cockatrice/CMakeLists.txt | 1 + .../deck_editor_deck_dock_widget.cpp | 298 ++++----------- .../deck_editor_deck_dock_widget.h | 38 +- .../deck_list_history_manager_widget.cpp | 77 ++-- .../deck_list_history_manager_widget.h | 11 +- .../deck_editor/deck_state_manager.cpp | 361 ++++++++++++++++++ .../widgets/deck_editor/deck_state_manager.h | 297 ++++++++++++++ .../dialogs/dlg_select_set_for_cards.cpp | 49 +-- .../dialogs/dlg_select_set_for_cards.h | 6 +- .../all_zones_card_amount_widget.cpp | 14 +- .../all_zones_card_amount_widget.h | 4 +- .../printing_selector/card_amount_widget.cpp | 44 +-- .../printing_selector/card_amount_widget.h | 8 +- .../printing_selector/printing_selector.cpp | 16 +- .../printing_selector/printing_selector.h | 13 +- .../printing_selector_card_display_widget.cpp | 9 +- .../printing_selector_card_display_widget.h | 3 +- .../printing_selector_card_overlay_widget.cpp | 9 +- .../printing_selector_card_overlay_widget.h | 3 +- ...rinting_selector_card_selection_widget.cpp | 14 +- .../printing_selector_card_selection_widget.h | 3 +- .../printing_selector_card_sorting_widget.cpp | 2 +- .../printing_selector_card_sorting_widget.h | 2 +- .../widgets/tabs/abstract_tab_deck_editor.cpp | 135 +++---- .../widgets/tabs/abstract_tab_deck_editor.h | 36 +- ...idekt_api_response_deck_display_widget.cpp | 5 +- .../widgets/tabs/tab_deck_editor.cpp | 5 +- .../tab_deck_editor_visual.cpp | 19 +- ...al_database_display_name_filter_widget.cpp | 3 +- .../models/deck_list/deck_list_model.cpp | 15 +- .../models/deck_list/deck_list_model.h | 10 +- 31 files changed, 933 insertions(+), 577 deletions(-) create mode 100644 cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp create mode 100644 cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index b2d387391..c6a29969f 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -156,6 +156,7 @@ set(cockatrice_SOURCES src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp src/interface/widgets/deck_editor/deck_list_style_proxy.cpp + src/interface/widgets/deck_editor/deck_state_manager.cpp src/interface/widgets/general/background_sources.cpp src/interface/widgets/general/display/background_plate_widget.cpp src/interface/widgets/general/display/banner_widget.cpp 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 b8ebc1419..62195974b 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 @@ -1,8 +1,8 @@ #include "deck_editor_deck_dock_widget.h" #include "../../../client/settings/cache_settings.h" -#include "../../deck_loader/deck_loader.h" #include "deck_list_style_proxy.h" +#include "deck_state_manager.h" #include #include @@ -38,7 +38,7 @@ static int findRestoreIndex(const CardRef &wanted, const QComboBox *combo) } DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent) - : QDockWidget(parent), deckEditor(parent) + : QDockWidget(parent), deckEditor(parent), deckStateManager(parent->deckStateManager) { setObjectName("deckDock"); @@ -52,19 +52,19 @@ DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent void DeckEditorDeckDockWidget::createDeckDock() { - deckModel = new DeckListModel(this); - deckModel->setObjectName("deckModel"); - connect(deckModel, &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash); - - deckLoader = new DeckLoader(this); + connect(getModel(), &DeckListModel::deckHashChanged, this, &DeckEditorDeckDockWidget::updateHash); proxy = new DeckListStyleProxy(this); - proxy->setSourceModel(deckModel); + proxy->setSourceModel(getModel()); - historyManagerWidget = new DeckListHistoryManagerWidget(deckModel, proxy, deckEditor->getHistoryManager(), this); + historyManagerWidget = new DeckListHistoryManagerWidget(deckStateManager, proxy, this); connect(historyManagerWidget, &DeckListHistoryManagerWidget::requestDisplayWidgetSync, this, &DeckEditorDeckDockWidget::syncDisplayWidgetsToModel); + connect(deckStateManager, &DeckStateManager::focusIndexChanged, this, &DeckEditorDeckDockWidget::setSelectedIndex); + connect(deckStateManager, &DeckStateManager::deckReplaced, this, + &DeckEditorDeckDockWidget::syncDisplayWidgetsToModel); + deckView = new QTreeView(); deckView->setObjectName("deckView"); deckView->setModel(proxy); @@ -97,7 +97,7 @@ void DeckEditorDeckDockWidget::createDeckDock() nameDebounceTimer = new QTimer(this); nameDebounceTimer->setSingleShot(true); nameDebounceTimer->setInterval(300); // debounce duration in ms - connect(nameDebounceTimer, &QTimer::timeout, this, [this]() { updateName(nameEdit->text()); }); + connect(nameDebounceTimer, &QTimer::timeout, this, &DeckEditorDeckDockWidget::writeName); connect(nameEdit, &LineEditUnfocusable::textChanged, this, [this]() { nameDebounceTimer->start(); // restart debounce timer @@ -141,7 +141,7 @@ void DeckEditorDeckDockWidget::createDeckDock() commentsDebounceTimer = new QTimer(this); commentsDebounceTimer->setSingleShot(true); commentsDebounceTimer->setInterval(400); // longer debounce for multi-line - connect(commentsDebounceTimer, &QTimer::timeout, this, [this]() { updateComments(); }); + connect(commentsDebounceTimer, &QTimer::timeout, this, &DeckEditorDeckDockWidget::writeComments); connect(commentsEdit, &QTextEdit::textChanged, this, [this]() { commentsDebounceTimer->start(); // restart debounce timer @@ -152,21 +152,21 @@ void DeckEditorDeckDockWidget::createDeckDock() bannerCardLabel->setText(tr("Banner Card")); bannerCardLabel->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); bannerCardComboBox = new QComboBox(this); - connect(deckModel, &DeckListModel::dataChanged, this, [this]() { + connect(getModel(), &DeckListModel::dataChanged, this, [this]() { // Delay the update to avoid race conditions QTimer::singleShot(100, this, &DeckEditorDeckDockWidget::updateBannerCardComboBox); }); - connect(deckModel, &DeckListModel::cardAddedAt, this, &DeckEditorDeckDockWidget::recursiveExpand); - connect(deckModel, &DeckListModel::modelReset, this, &DeckEditorDeckDockWidget::expandAll); + connect(getModel(), &DeckListModel::cardAddedAt, this, &DeckEditorDeckDockWidget::recursiveExpand); + connect(getModel(), &DeckListModel::modelReset, this, &DeckEditorDeckDockWidget::expandAll); connect(bannerCardComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, - &DeckEditorDeckDockWidget::setBannerCard); + &DeckEditorDeckDockWidget::writeBannerCard); bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); - deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList()->getTags()); + deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, {}); deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible()); - connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this, - &DeckEditorDeckDockWidget::setTags); + connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, deckStateManager, + &DeckStateManager::setTags); activeGroupCriteriaLabel = new QLabel(this); @@ -175,9 +175,9 @@ void DeckEditorDeckDockWidget::createDeckDock() activeGroupCriteriaComboBox->addItem(tr("Mana Cost"), DeckListModelGroupCriteria::MANA_COST); activeGroupCriteriaComboBox->addItem(tr("Colors"), DeckListModelGroupCriteria::COLOR); connect(activeGroupCriteriaComboBox, QOverload::of(&QComboBox::currentIndexChanged), [this]() { - deckModel->setActiveGroupCriteria(static_cast( + getModel()->setActiveGroupCriteria(static_cast( activeGroupCriteriaComboBox->currentData(Qt::UserRole).toInt())); - deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); + getModel()->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); }); aIncrement = new QAction(QString(), this); @@ -295,9 +295,10 @@ void DeckEditorDeckDockWidget::initializeFormats() formatComboBox->addItem(formatName, formatName); // store the raw key in itemData } - if (!deckModel->getDeckList()->getGameFormat().isEmpty()) { - deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat()); - formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat())); + QString format = deckStateManager->getMetadata().gameFormat; + if (!format.isEmpty()) { + getModel()->setActiveFormat(format); + formatComboBox->setCurrentIndex(formatComboBox->findData(format)); } else { // Ensure no selection is visible initially formatComboBox->setCurrentIndex(-1); @@ -306,11 +307,10 @@ void DeckEditorDeckDockWidget::initializeFormats() connect(formatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { if (index >= 0) { QString formatKey = formatComboBox->itemData(index).toString(); - deckModel->setActiveFormat(formatKey); + deckStateManager->setFormat(formatKey); } else { - deckModel->setActiveFormat(QString()); // clear format if deselected + deckStateManager->setFormat(""); // clear format if deselected } - emit deckModified(); }); } @@ -340,43 +340,37 @@ ExactCard DeckEditorDeckDockWidget::getCurrentCard() void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const QModelIndex & /*previous*/) { if (ExactCard card = getCurrentCard()) { - emit cardChanged(card); + emit selectedCardChanged(card); } } -void DeckEditorDeckDockWidget::updateName(const QString &name) +/** + * @brief Writes the contents of the name textBox to the DeckStateManager + */ +void DeckEditorDeckDockWidget::writeName() { - emit requestDeckHistorySave( - QString(tr("Rename deck to \"%1\" from \"%2\"")).arg(name).arg(deckLoader->getDeck().deckList.getName())); - deckModel->getDeckList()->setName(name); - deckEditor->setModified(name.isEmpty()); - emit nameChanged(); - emit deckModified(); + QString name = nameEdit->text(); + deckStateManager->setName(name); } -void DeckEditorDeckDockWidget::updateComments() +/** + * @brief Writes the contents of the comments textBox to the DeckStateManager + */ +void DeckEditorDeckDockWidget::writeComments() { - emit requestDeckHistorySave(tr("Updated comments (was %1 chars, now %2 chars)") - .arg(deckLoader->getDeck().deckList.getComments().size()) - .arg(commentsEdit->toPlainText().size())); - - deckModel->getDeckList()->setComments(commentsEdit->toPlainText()); - deckEditor->setModified(commentsEdit->toPlainText().isEmpty()); - emit commentsChanged(); - emit deckModified(); + QString comments = commentsEdit->toPlainText(); + deckStateManager->setComments(comments); } void DeckEditorDeckDockWidget::updateHash() { - hashLabel->setText(deckModel->getDeckList()->getDeckHash()); - emit hashChanged(); - emit deckModified(); + hashLabel->setText(deckStateManager->getDeckHash()); } void DeckEditorDeckDockWidget::updateBannerCardComboBox() { // Store current banner card identity - CardRef wanted = deckModel->getDeckList()->getBannerCard(); + CardRef wanted = deckStateManager->getMetadata().bannerCard; // Block signals temporarily bool wasBlocked = bannerCardComboBox->blockSignals(true); @@ -386,7 +380,7 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() // Collect unique (name, providerId) pairs QSet> bannerCardSet; - QList cardsInDeck = deckModel->getCardRefs(); + QList cardsInDeck = getModel()->getCardRefs(); for (auto cardRef : cardsInDeck) { if (!CardDatabaseManager::query()->getCard(cardRef)) { @@ -415,7 +409,6 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() // Handle results if (restoreIndex != -1) { bannerCardComboBox->setCurrentIndex(restoreIndex); - syncDeckListBannerCardWithComboBox(); } else { // Add a placeholder "-" and set it as the current selection bannerCardComboBox->insertItem(0, "-"); @@ -426,25 +419,14 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox() bannerCardComboBox->blockSignals(wasBlocked); } -void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */) +/** + * @brief Writes the selected bannerCard to the DeckStateManager + */ +void DeckEditorDeckDockWidget::writeBannerCard(int index) { - emit requestDeckHistorySave(tr("Banner card changed")); - syncDeckListBannerCardWithComboBox(); - deckEditor->setModified(true); - emit deckModified(); -} - -void DeckEditorDeckDockWidget::setTags(const QStringList &tags) -{ - deckModel->getDeckList()->setTags(tags); - deckEditor->setModified(true); - emit deckModified(); -} - -void DeckEditorDeckDockWidget::syncDeckListBannerCardWithComboBox() -{ - auto [name, id] = bannerCardComboBox->currentData().value>(); - deckModel->getDeckList()->setBannerCard({name, id}); + auto [name, id] = bannerCardComboBox->itemData(index).value>(); + CardRef bannerCard = {name, id}; + deckStateManager->setBannerCard(bannerCard); } void DeckEditorDeckDockWidget::updateShowBannerCardComboBox(const bool visible) @@ -460,7 +442,7 @@ void DeckEditorDeckDockWidget::updateShowTagsWidget(const bool visible) void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck() { - if (deckModel->getDeckList()->getBannerCard().name == "") { + if (deckStateManager->getMetadata().bannerCard.name == "") { if (bannerCardComboBox->findText("-") != -1) { bannerCardComboBox->setCurrentIndex(bannerCardComboBox->findText("-")); } else { @@ -468,36 +450,26 @@ void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck() bannerCardComboBox->setCurrentIndex(0); } } else { - bannerCardComboBox->setCurrentText(deckModel->getDeckList()->getBannerCard().name); + bannerCardComboBox->setCurrentText(deckStateManager->getMetadata().bannerCard.name); } } -/** - * Sets the currently active deck for this tab - * @param _deck The deck. - */ -void DeckEditorDeckDockWidget::setDeck(const LoadedDeck &_deck) +void DeckEditorDeckDockWidget::setSelectedIndex(const QModelIndex &newCardIndex) { - deckLoader->setDeck(_deck); - deckModel->setDeckList(&deckLoader->getDeck().deckList); - connect(deckLoader, &DeckLoader::deckLoaded, deckModel, &DeckListModel::rebuildTree); - - emit requestDeckHistoryClear(); - historyManagerWidget->setDeckListModel(deckModel); - - syncDisplayWidgetsToModel(); - - emit deckChanged(); + deckView->clearSelection(); + deckView->setCurrentIndex(newCardIndex); + recursiveExpand(newCardIndex); + deckView->setFocus(Qt::FocusReason::MouseFocusReason); } void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel() { nameEdit->blockSignals(true); - nameEdit->setText(deckModel->getDeckList()->getName()); + nameEdit->setText(deckStateManager->getMetadata().name); nameEdit->blockSignals(false); commentsEdit->blockSignals(true); - commentsEdit->setText(deckModel->getDeckList()->getComments()); + commentsEdit->setText(deckStateManager->getMetadata().comments); commentsEdit->blockSignals(false); bannerCardComboBox->blockSignals(true); @@ -507,44 +479,22 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel() updateHash(); sortDeckModelToDeckView(); - deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags()); + deckTagsDisplayWidget->setTags(deckStateManager->getMetadata().tags); } void DeckEditorDeckDockWidget::sortDeckModelToDeckView() { - deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); - deckModel->setActiveFormat(deckModel->getDeckList()->getGameFormat()); - formatComboBox->setCurrentIndex(formatComboBox->findData(deckModel->getDeckList()->getGameFormat())); - - emit deckChanged(); -} - -DeckLoader *DeckEditorDeckDockWidget::getDeckLoader() -{ - return deckLoader; -} - -const DeckList &DeckEditorDeckDockWidget::getDeckList() const -{ - return *deckModel->getDeckList(); + getModel()->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); + getModel()->setActiveFormat(deckStateManager->getMetadata().gameFormat); + formatComboBox->setCurrentIndex(formatComboBox->findData(deckStateManager->getMetadata().gameFormat)); } /** - * Resets the tab to the state for a blank new tab. + * @brief Convenience method to get the underlying model instance from the DeckStateManager */ -void DeckEditorDeckDockWidget::cleanDeck() +DeckListModel *DeckEditorDeckDockWidget::getModel() const { - deckModel->cleanList(); - nameEdit->setText(QString()); - emit nameChanged(); - commentsEdit->setText(QString()); - emit commentsChanged(); - hashLabel->setText(QString()); - emit hashChanged(); - emit deckModified(); - emit deckChanged(); - updateBannerCardComboBox(); - deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags()); + return deckStateManager->getModel(); } void DeckEditorDeckDockWidget::selectPrevCard() @@ -635,7 +585,7 @@ QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodes() const auto selectedRows = deckView->selectionModel()->selectedRows(); const auto notLeafNode = [this](const QModelIndex &index) { - return deckModel->hasChildren(proxy->mapToSource(index)); + return getModel()->hasChildren(proxy->mapToSource(index)); }; selectedRows.erase(std::remove_if(selectedRows.begin(), selectedRows.end(), notLeafNode), selectedRows.end()); @@ -650,21 +600,7 @@ void DeckEditorDeckDockWidget::actAddCard(const ExactCard &card, const QString & } QString zoneName = card.getInfo().getIsToken() ? DECK_ZONE_TOKENS : _zoneName; - - emit requestDeckHistorySave(tr("Added (%1): %2 (%3) %4") - .arg(zoneName, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(), - card.getPrinting().getProperty("num"))); - - QModelIndex newCardIndex = deckModel->addCard(card, zoneName); - - if (!newCardIndex.isValid()) { - return; - } - - deckView->clearSelection(); - deckView->setCurrentIndex(newCardIndex); - - emit deckModified(); + deckStateManager->addCard(card, zoneName); } void DeckEditorDeckDockWidget::actIncrementSelection() @@ -681,12 +617,12 @@ void DeckEditorDeckDockWidget::actSwapCard(const ExactCard &card, const QString QString providerId = card.getPrinting().getUuid(); QString collectorNumber = card.getPrinting().getProperty("num"); - QModelIndex foundCard = deckModel->findCard(card.getName(), zoneName, providerId, collectorNumber); + QModelIndex foundCard = getModel()->findCard(card.getName(), zoneName, providerId, collectorNumber); if (!foundCard.isValid()) { - foundCard = deckModel->findCard(card.getName(), zoneName); + foundCard = getModel()->findCard(card.getName(), zoneName); } - swapCard(foundCard); + deckStateManager->swapCardAtIndex(foundCard); } void DeckEditorDeckDockWidget::actSwapSelection() @@ -699,54 +635,15 @@ void DeckEditorDeckDockWidget::actSwapSelection() deckView->setSelectionMode(QAbstractItemView::SingleSelection); } - bool isModified = false; for (const auto ¤tIndex : selectedRows) { - if (swapCard(currentIndex)) { - isModified = true; - } + deckStateManager->swapCardAtIndex(currentIndex); } deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); - if (isModified) { - emit deckModified(); - } - update(); } -/** - * Swaps the card at the index between the maindeck and sideboard - * - * @param currentIndex The index to swap. - * @return True if the swap was successful - */ -bool DeckEditorDeckDockWidget::swapCard(const QModelIndex ¤tIndex) -{ - if (!currentIndex.isValid()) - return false; - const QString cardName = currentIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); - const QString cardProviderID = - currentIndex.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString(); - const QModelIndex gparent = currentIndex.parent().parent(); - - if (!gparent.isValid()) - return false; - - const QString zoneName = gparent.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); - offsetCountAtIndex(currentIndex, false); - const QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN; - - if (ExactCard card = CardDatabaseManager::query()->getCard({cardName, cardProviderID})) { - deckModel->addCard(card, otherZoneName); - } else { - // Third argument (true) says create the card no matter what, even if not in DB - deckModel->addPreferredPrintingCard(cardName, otherZoneName, true); - } - - return true; -} - void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString zoneName) { if (!card) @@ -754,17 +651,7 @@ void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString z if (card.getInfo().getIsToken()) zoneName = DECK_ZONE_TOKENS; - QString providerId = card.getPrinting().getUuid(); - QString collectorNumber = card.getPrinting().getProperty("num"); - - QModelIndex idx = deckModel->findCard(card.getName(), zoneName, providerId, collectorNumber); - if (!idx.isValid()) { - return; - } - - deckView->clearSelection(); - deckView->setCurrentIndex(proxy->mapToSource(idx)); - offsetCountAtIndex(idx, false); + deckStateManager->decrementCard(card, zoneName); } void DeckEditorDeckDockWidget::actDecrementSelection() @@ -794,25 +681,11 @@ void DeckEditorDeckDockWidget::actRemoveCard() deckView->setSelectionMode(QAbstractItemView::SingleSelection); } - bool isModified = false; - for (const auto &index : selectedRows) { - if (!index.isValid() || deckModel->hasChildren(index)) { - continue; - } - QModelIndex sourceIndex = proxy->mapToSource(index); - QString cardName = sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); - - emit requestDeckHistorySave(QString(tr("Removed \"%1\" (all copies)")).arg(cardName)); - - deckModel->removeRow(sourceIndex.row(), sourceIndex.parent()); - isModified = true; + for (const auto &row : selectedRows) { + deckStateManager->removeCardAtIndex(row); } deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); - - if (isModified) { - emit deckModified(); - } } /** @@ -822,28 +695,17 @@ void DeckEditorDeckDockWidget::actRemoveCard() */ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, bool isIncrement) { - if (!idx.isValid() || deckModel->hasChildren(idx)) { + if (!idx.isValid() || getModel()->hasChildren(idx)) { return; } QModelIndex sourceIndex = proxy->mapToSource(idx); - QString cardName = sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); - QString providerId = - sourceIndex.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString(); - - const auto reason = QString(tr("%1 %2 × \"%3\" (%4)")) - .arg(isIncrement ? tr("Added") : tr("Removed")) - .arg(1) - .arg(cardName) - .arg(providerId); - - emit requestDeckHistorySave(reason); - - int offset = isIncrement ? 1 : -1; - deckModel->offsetCountAtIndex(sourceIndex, offset); - - emit deckModified(); + if (isIncrement) { + deckStateManager->incrementCountAtIndex(sourceIndex); + } else { + deckStateManager->decrementCountAtIndex(sourceIndex); + } } void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point) diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index a50434f0b..1a82b00d1 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -28,22 +28,14 @@ class DeckEditorDeckDockWidget : public QDockWidget Q_OBJECT public: explicit DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent); - DeckLoader *deckLoader; + DeckListStyleProxy *proxy; - DeckListModel *deckModel; QTreeView *deckView; QComboBox *bannerCardComboBox; void createDeckDock(); ExactCard getCurrentCard(); void retranslateUi(); - QString getDeckName() - { - return nameEdit->text(); - } - QString getSimpleDeckName() - { - return nameEdit->text().simplified(); - } + QComboBox *getGroupByComboBox() { return activeGroupCriteriaComboBox; @@ -55,15 +47,11 @@ public: } public slots: - void cleanDeck(); void selectPrevCard(); void selectNextCard(); void updateBannerCardComboBox(); - void setDeck(const LoadedDeck &_deck); void syncDisplayWidgetsToModel(); void sortDeckModelToDeckView(); - DeckLoader *getDeckLoader(); - const DeckList &getDeckList() const; void actAddCard(const ExactCard &card, const QString &zoneName); void actIncrementSelection(); void actDecrementCard(const ExactCard &card, QString zoneName); @@ -74,17 +62,12 @@ public slots: void initializeFormats(); signals: - void nameChanged(); - void commentsChanged(); - void hashChanged(); - void deckChanged(); - void deckModified(); - void requestDeckHistorySave(const QString &modificationReason); - void requestDeckHistoryClear(); - void cardChanged(const ExactCard &_card); + void selectedCardChanged(const ExactCard &card); private: AbstractTabDeckEditor *deckEditor; + DeckStateManager *deckStateManager; + DeckListHistoryManagerWidget *historyManagerWidget; KeySignals deckViewKeySignals; QLabel *nameLabel; @@ -107,18 +90,17 @@ private: QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard; + DeckListModel *getModel() const; [[nodiscard]] QModelIndexList getSelectedCardNodes() const; void offsetCountAtIndex(const QModelIndex &idx, bool isIncrement); private slots: void decklistCustomMenu(QPoint point); - bool swapCard(const QModelIndex ¤tIndex); void updateCard(QModelIndex, const QModelIndex ¤t); - void updateName(const QString &name); - void updateComments(); - void setBannerCard(int); - void setTags(const QStringList &tags); - void syncDeckListBannerCardWithComboBox(); + void writeName(); + void writeComments(); + void writeBannerCard(int); + void setSelectedIndex(const QModelIndex &newCardIndex); void updateHash(); void refreshShortcuts(); void updateShowBannerCardComboBox(bool visible); 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 c68934ea6..d2d748753 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,10 +1,11 @@ #include "deck_list_history_manager_widget.h" -DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckListModel *_deckListModel, +#include "deck_state_manager.h" + +DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckStateManager *_deckStateManager, DeckListStyleProxy *_styleProxy, - DeckListHistoryManager *manager, QWidget *parent) - : QWidget(parent), deckListModel(_deckListModel), styleProxy(_styleProxy), historyManager(manager) + : QWidget(parent), deckStateManager(_deckStateManager), styleProxy(_styleProxy) { layout = new QHBoxLayout(this); @@ -43,8 +44,7 @@ DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckListModel *_deckL connect(historyList, &QListWidget::itemClicked, this, &DeckListHistoryManagerWidget::onListClicked); - connect(historyManager, &DeckListHistoryManager::undoRedoStateChanged, this, - &DeckListHistoryManagerWidget::refreshList); + connect(deckStateManager, &DeckStateManager::historyChanged, this, &DeckListHistoryManagerWidget::refreshList); refreshList(); retranslateUi(); @@ -58,15 +58,12 @@ void DeckListHistoryManagerWidget::retranslateUi() historyLabel->setText(tr("Click on an entry to revert to that point in the history.")); } -void DeckListHistoryManagerWidget::setDeckListModel(DeckListModel *_deckListModel) -{ - deckListModel = _deckListModel; -} - void DeckListHistoryManagerWidget::refreshList() { historyList->clear(); + DeckListHistoryManager *historyManager = deckStateManager->getHistoryManager(); + // Fill redo section first (oldest redo at top, newest redo closest to divider) const auto redoStack = historyManager->getRedoStack(); for (int i = 0; i < redoStack.size(); ++i) { // iterate forward @@ -98,36 +95,7 @@ void DeckListHistoryManagerWidget::refreshList() redoButton->setEnabled(historyManager->canRedo()); } -void DeckListHistoryManagerWidget::doUndo() -{ - if (!historyManager->canUndo()) { - return; - } - - historyManager->undo(deckListModel->getDeckList()); - deckListModel->rebuildTree(); - emit deckListModel->layoutChanged(); - emit requestDisplayWidgetSync(); - - refreshList(); -} - -void DeckListHistoryManagerWidget::doRedo() -{ - if (!historyManager->canRedo()) { - return; - } - - historyManager->redo(deckListModel->getDeckList()); - deckListModel->rebuildTree(); - - emit deckListModel->layoutChanged(); - emit requestDisplayWidgetSync(); - - refreshList(); -} - -void DeckListHistoryManagerWidget::onListClicked(QListWidgetItem *item) +void DeckListHistoryManagerWidget::onListClicked(const QListWidgetItem *item) { // Ignore non-selectable items (like divider) if (!(item->flags() & Qt::ItemIsSelectable)) { @@ -138,23 +106,24 @@ void DeckListHistoryManagerWidget::onListClicked(QListWidgetItem *item) int index = item->data(Qt::UserRole + 1).toInt(); if (mode == "redo") { - const auto redoStack = historyManager->getRedoStack(); + const auto redoStack = deckStateManager->getHistoryManager()->getRedoStack(); int steps = redoStack.size() - index; - for (int i = 0; i < steps; ++i) { - historyManager->redo(deckListModel->getDeckList()); - } + deckStateManager->redo(steps); } else if (mode == "undo") { - const auto undoStack = historyManager->getUndoStack(); - int steps = undoStack.size() - 1 - index; - for (int i = 0; i < steps + 1; ++i) { - historyManager->undo(deckListModel->getDeckList()); - } + const auto undoStack = deckStateManager->getHistoryManager()->getUndoStack(); + int steps = undoStack.size() - index; + deckStateManager->undo(steps); } - deckListModel->rebuildTree(); - - emit deckListModel->layoutChanged(); - emit requestDisplayWidgetSync(); - refreshList(); +} + +void DeckListHistoryManagerWidget::doUndo() +{ + deckStateManager->undo(); +} + +void DeckListHistoryManagerWidget::doRedo() +{ + deckStateManager->redo(); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h index 0e208ad2b..ab53912e2 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_history_manager_widget.h @@ -14,6 +14,8 @@ #include #include +class DeckStateManager; + class DeckListHistoryManagerWidget : public QWidget { Q_OBJECT @@ -25,22 +27,19 @@ public slots: void retranslateUi(); public: - explicit DeckListHistoryManagerWidget(DeckListModel *deckListModel, + explicit DeckListHistoryManagerWidget(DeckStateManager *deckStateManager, DeckListStyleProxy *styleProxy, - DeckListHistoryManager *manager, QWidget *parent = nullptr); - void setDeckListModel(DeckListModel *_deckListModel); private slots: void refreshList(); - void onListClicked(QListWidgetItem *item); + void onListClicked(const QListWidgetItem *item); void doUndo(); void doRedo(); private: - DeckListModel *deckListModel; + DeckStateManager *deckStateManager; DeckListStyleProxy *styleProxy; - DeckListHistoryManager *historyManager; QHBoxLayout *layout; QAction *aUndo; diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp new file mode 100644 index 000000000..628d33d51 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp @@ -0,0 +1,361 @@ +#include "deck_state_manager.h" + +#include +#include + +DeckStateManager::DeckStateManager(QObject *parent) + : QObject(parent), deckList(QSharedPointer(new DeckList)), + deckListModel(new DeckListModel(this, deckList)), historyManager(new DeckListHistoryManager(this)) +{ + connect(historyManager, &DeckListHistoryManager::undoRedoStateChanged, this, [this] { + setModified(true); + emit historyChanged(); + }); + connect(deckListModel, &DeckListModel::rowsInserted, this, &DeckStateManager::uniqueCardsChanged); + connect(deckListModel, &DeckListModel::rowsRemoved, this, &DeckStateManager::uniqueCardsChanged); +} + +const DeckList &DeckStateManager::getDeckList() const +{ + return *deckList.get(); +} + +LoadedDeck DeckStateManager::toLoadedDeck() const +{ + return {getDeckList(), lastLoadInfo}; +} + +DeckList::Metadata const &DeckStateManager::getMetadata() const +{ + return deckList->getMetadata(); +} + +QString DeckStateManager::getSimpleDeckName() const +{ + return deckList->getMetadata().name.simplified(); +} + +QString DeckStateManager::getDeckHash() const +{ + return deckList->getDeckHash(); +} + +bool DeckStateManager::isModified() const +{ + return modified; +} + +void DeckStateManager::setModified(bool state) +{ + if (state == modified) { + return; + } + + modified = state; + emit isModifiedChanged(modified); +} + +bool DeckStateManager::isBlankNewDeck() const +{ + return !isModified() && deckList->isBlankDeck(); +} + +void DeckStateManager::replaceDeck(const LoadedDeck &deck) +{ + lastLoadInfo = deck.lastLoadInfo; + deckList = QSharedPointer(new DeckList(deck.deckList)); + deckListModel->setDeckList(deckList); + + historyManager->clear(); + + setModified(false); + emit deckReplaced(); +} + +void DeckStateManager::clearDeck() +{ + replaceDeck(LoadedDeck()); +} + +bool DeckStateManager::modifyDeck(const QString &reason, const std::function &operation) +{ + DeckListMemento memento = deckList->createMemento(reason); + bool success = operation(deckListModel); + + if (success) { + historyManager->save(memento); + doCardModified(); + } + + return success; +} + +QModelIndex DeckStateManager::modifyDeck(const QString &reason, + const std::function &operation) +{ + DeckListMemento memento = deckList->createMemento(reason); + QModelIndex idx = operation(deckListModel); + + if (idx.isValid()) { + historyManager->save(memento); + doCardModified(); + } + + return idx; +} + +void DeckStateManager::setName(const QString &name) +{ + QString previous = deckList->getName(); + if (previous == name) { + return; + } + + requestHistorySave(tr("Rename deck to \"%1\" from \"%2\"").arg(name).arg(previous)); + deckList->setName(name); + + doMetadataModified(); +} + +void DeckStateManager::setComments(const QString &comments) +{ + QString previous = deckList->getComments(); + if (previous == comments) { + return; + } + + requestHistorySave(tr("Updated comments (was %1 chars, now %2 chars)").arg(previous.size()).arg(comments.size())); + deckList->setComments(comments); + + doMetadataModified(); +} + +void DeckStateManager::setBannerCard(const CardRef &bannerCard) +{ + CardRef previous = deckList->getBannerCard(); + if (previous == bannerCard) { + return; + } + + requestHistorySave(tr("Set banner card to %1 (%2)").arg(bannerCard.name).arg(bannerCard.providerId)); + deckList->setBannerCard(bannerCard); + + doMetadataModified(); +} + +void DeckStateManager::setTags(const QStringList &tags) +{ + QStringList previous = deckList->getTags(); + if (previous == tags) { + return; + } + + requestHistorySave(tr("Tags changed")); + deckList->setTags(tags); + + doMetadataModified(); +} + +void DeckStateManager::setFormat(const QString &format) +{ + if (deckList->getMetadata().gameFormat == format) { + return; + } + + requestHistorySave(tr("Set format to %1").arg(format)); + deckListModel->setActiveFormat(format); + + doMetadataModified(); +} + +QModelIndex DeckStateManager::addCard(const ExactCard &card, const QString &zoneName) +{ + if (!card) { + return {}; + } + + QString reason = tr("Added (%1): %2 (%3) %4") + .arg(zoneName, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(), + card.getPrinting().getProperty("num")); + + QModelIndex idx = modifyDeck(reason, [&card, &zoneName](auto model) { return model->addCard(card, zoneName); }); + + if (idx.isValid()) { + emit focusIndexChanged(idx); + } + + return idx; +} + +QModelIndex DeckStateManager::decrementCard(const ExactCard &card, const QString &zoneName) +{ + if (!card) + return {}; + + QString providerId = card.getPrinting().getUuid(); + QString collectorNumber = card.getPrinting().getProperty("num"); + + QModelIndex idx = deckListModel->findCard(card.getName(), zoneName, providerId, collectorNumber); + if (!idx.isValid()) { + return {}; + } + + bool success = offsetCountAtIndex(idx, false); + + if (!success) { + return {}; + } + + if (idx.isValid()) { + emit focusIndexChanged(idx); + } + + return idx; +} + +static bool doSwapCard(DeckListModel *model, + const QModelIndex &idx, + const QString &cardName, + const QString &providerId, + const QString &otherZone) +{ + bool success = model->offsetCountAtIndex(idx, -1); + if (!success) { + return false; + } + + if (ExactCard card = CardDatabaseManager::query()->getCard({cardName, providerId})) { + model->addCard(card, otherZone); + } else { + // Third argument (true) says create the card no matter what, even if not in DB + model->addPreferredPrintingCard(cardName, otherZone, true); + } + + return true; +} + +bool DeckStateManager::swapCardAtIndex(const QModelIndex &idx) +{ + if (!idx.isValid()) + return false; + + QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data().toString(); + QString providerId = idx.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data().toString(); + QModelIndex gparent = idx.parent().parent(); + + if (!gparent.isValid()) + return false; + + QString zoneName = gparent.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); + QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN; + + QString reason = tr("Moved to %1 1 × \"%2\" (%3)") // + .arg(otherZoneName) + .arg(cardName) + .arg(providerId); + + return modifyDeck(reason, [&idx, &cardName, &providerId, &otherZoneName](auto model) { + return doSwapCard(model, idx, cardName, providerId, otherZoneName); + }); +} + +bool DeckStateManager::removeCardAtIndex(const QModelIndex &idx) +{ + if (!idx.isValid() || deckListModel->hasChildren(idx)) { + return false; + } + + 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()); }); +} + +bool DeckStateManager::incrementCountAtIndex(const QModelIndex &idx) +{ + return offsetCountAtIndex(idx, 1); +} + +bool DeckStateManager::decrementCountAtIndex(const QModelIndex &idx) +{ + return offsetCountAtIndex(idx, -1); +} + +bool DeckStateManager::offsetCountAtIndex(const QModelIndex &idx, int offset) +{ + if (!idx.isValid()) { + return false; + } + + QString cardName = idx.siblingAtColumn(DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); + QString providerId = idx.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString(); + + QString reason = tr("%1 1 × \"%2\" (%3)") // + .arg(offset > 0 ? tr("Added") : tr("Removed")) + .arg(cardName) + .arg(providerId); + + return modifyDeck(reason, [&idx, &offset](auto model) { return model->offsetCountAtIndex(idx, offset); }); +} + +void DeckStateManager::undo(int steps) +{ + if (!historyManager->canUndo()) { + return; + } + + for (int i = 0; i < steps; i++) { + if (!historyManager->canUndo()) { + continue; + } + historyManager->undo(deckList.get()); + } + + deckListModel->rebuildTree(); + + emit deckListModel->layoutChanged(); +} + +void DeckStateManager::redo(int steps) +{ + if (!historyManager->canRedo()) { + return; + } + + for (int i = 0; i < steps; i++) { + if (!historyManager->canRedo()) { + continue; + } + historyManager->redo(deckList.get()); + } + + deckListModel->rebuildTree(); + + emit deckListModel->layoutChanged(); +} + +void DeckStateManager::requestHistorySave(const QString &reason) +{ + historyManager->save(deckList->createMemento(reason)); +} + +/** + * @brief Handles updating state and emitting signals whenever the cards are modified + */ +void DeckStateManager::doCardModified() +{ + setModified(true); + emit cardModified(); + emit deckModified(); +} + +/** + * @brief Handles updating state and emitting signals whenever the metadata is modified + */ +void DeckStateManager::doMetadataModified() +{ + setModified(true); + emit metadataModified(); + emit deckModified(); +} diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h new file mode 100644 index 000000000..0f3ba3255 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h @@ -0,0 +1,297 @@ +#ifndef COCKATRICE_DECK_STATE_MANAGER_H +#define COCKATRICE_DECK_STATE_MANAGER_H + +#include "../../deck_loader/loaded_deck.h" +#include "deck_list_model.h" + +#include +#include + +class DeckListHistoryManager; + +/** + * @brief This class centralizes the management of the state of the deck in the deck editor tab. + * It is responsible for owning and managing the DeckListModel, underlying DeckList, load info, and edit history. + * + * Although this class provides getters for the underlying DeckListModel, you should generally refrain from directly + * modifying the returned model. Outside modifications to the deck state should be done through @link + * DeckStateManager::modifyDeck and the metadata setters. + * Those methods ensure that the history is recorded and correct signals are emitted. + */ +class DeckStateManager : public QObject +{ + Q_OBJECT + + LoadedDeck::LoadInfo lastLoadInfo; + QSharedPointer deckList; + DeckListModel *deckListModel; + DeckListHistoryManager *historyManager; + + bool modified = false; + +public: + explicit DeckStateManager(QObject *parent = nullptr); + + /** + * Gets the underlying HistoryManager. + * @return The DeckListHistoryManager instance + */ + DeckListHistoryManager *getHistoryManager() const + { + return historyManager; + } + + /** + * @brief Gets the underlying DeckListModel. + * You should generally refrain modifying the returned model directly. + * However, it's fine (and intended) to perform queries on the returned model. + * @return The DeckListModel instance + */ + DeckListModel *getModel() const + { + return deckListModel; + } + + /** + * @brief Gets a view of the current deck. + */ + const DeckList &getDeckList() const; + + /** + * @brief Creates a LoadedDeck containing the contents of the current deck and the current LoadInfo. + * + * @return A new LoadedDeck instance. + */ + LoadedDeck toLoadedDeck() const; + + /** + * @brief Gets a view of the metadata in the DeckList + */ + DeckList::Metadata const &getMetadata() const; + + /** + * @brief Gets the deck's simplified name. + */ + QString getSimpleDeckName() const; + + /** + * @brief Gets the deck hash. + */ + QString getDeckHash() const; + + /** + * @brief Checks if the deck has been modified since it was last saved + */ + bool isModified() const; + + /** + * @brief Sets the new isModified state, emitting a signal if the state changed. + * This class will automatically update its isModified state, but you may need to set it manually to handle, for + * example, saving. + * @param state The state + */ + void setModified(bool state); + + /** + * @brief Checks if the deck state is as if it was a new deck + */ + bool isBlankNewDeck() const; + + /** + * @brief Overwrites the current deck with a new deck, resetting all history + * @param deck The new deck. + */ + void replaceDeck(const LoadedDeck &deck); + + /** + * @brief Resets the deck to a blank new deck, resetting all history. + */ + void clearDeck(); + + /** + * @brief Sets the lastLoadInfo. + * @param loadInfo The lastLoadInfo + */ + void setLastLoadInfo(const LoadedDeck::LoadInfo &loadInfo) + { + lastLoadInfo = loadInfo; + } + + /** + * @brief Modifies the cards in the deck, in a wrapped operation that is saved to the history. + * + * The operation is a function that accepts a DeckListModel that it operates upon, and returns a bool. + * + * This method will pass the underlying DeckListModel into the operation function. The function can call methods on + * the model to modify the deck. + * The function should return a bool to indicate success/failure. + * + * If the operation returns true, the state of the deck before the operation is ran is saved to the history, and the + * isModified state is updated. + * If the operation returns false, the history and isModified state is not updated. + * + * Note that even if the operation fails, any modifications to the model will already have been made. + * It's recommended for the operation to always return true if any modification has already been made to the model, + * as not doing that may cause the state to become desynced. + * + * @param reason The reason to display in the history + * @param operation The modification operation. + * @return The bool returned from the operation + */ + bool modifyDeck(const QString &reason, const std::function &operation); + + /** + * @brief Modifies the cards in the deck, in a wrapped operation that is saved to the history. + * + * The operation is a function that accepts a DeckListModel that it operates upon, and returns a QModelIndex. + * If the index is invalid, then the operation is considered to be a failure. + * + * See the other @link DeckStateManager::modifyDeck for more info about the behavior of this method. + * + * @param reason The reason to display in the history + * @param operation The modification operation. + * @return The QModelIndex returned from the operation + */ + QModelIndex modifyDeck(const QString &reason, const std::function &operation); + + /// @name Metadata setters + /// @brief These methods set the metadata. Will no-op if the new value is the same as the current value. + /// Saves the operation to history if successful. + ///@{ + void setName(const QString &name); + void setComments(const QString &comments); + void setBannerCard(const CardRef &bannerCard); + void setTags(const QStringList &tags); + void setFormat(const QString &format); + ///@} + + /** + * @brief Adds the given card to the given zone. + * Saves the operation to history if successful. + * + * @param card The card to add + * @param zoneName The zone to add the card to + * @return The index of the added card + */ + QModelIndex addCard(const ExactCard &card, const QString &zoneName); + + /** + * @brief Removes 1 copy of the given card from the given zone. + * Saves the operation to history if successful. + * + * @param card The card to remove + * @param zoneName The zone to remove the card from + * @return The index of the removed card. Will be invalid if the last copy was removed. + */ + QModelIndex decrementCard(const ExactCard &card, const QString &zoneName); + + /** + * @brief Swaps one copy of the card at the given index between the maindeck and sideboard. + * No-ops if index is invalid or not a card node. + * Saves the operation to history if successful. + * + * @param idx The model index + * @return Whether the operation was successfully performed + */ + bool swapCardAtIndex(const QModelIndex &idx); + + /** + * @brief Removes all copies of the card at the given index. + * No-ops if index is invalid or not a card node. + * Saves the operation to history if successful. + * + * @param idx The model index + * @return Whether the operation was successfully performed + */ + bool removeCardAtIndex(const QModelIndex &idx); + + /** + * @brief Increments the number of copies of the card at the given index by 1. + * No-ops if index is invalid or not a card node. + * Saves the operation to history if successful. + * + * @param idx The model index + * @return Whether the operation was successfully performed + */ + bool incrementCountAtIndex(const QModelIndex &idx); + + /** + * @brief Decrements the number of copies of the card at the given index by 1. + * No-ops if index is invalid or not a card node. + * Saves the operation to history if successful. + * + * @param idx The model index + * @return Whether the operation was successfully performed + */ + bool decrementCountAtIndex(const QModelIndex &idx); + + /** + * Undoes n steps of the history, setting the decklist state and updating the current step in the historyManager. + * @param steps Number of steps to undo. + */ + void undo(int steps = 1); + + /** + * Redoes n steps of the history, setting the decklist state and updating the current step in the historyManager. + * @param steps Number of steps to redo. + */ + void redo(int steps = 1); + +public slots: + /** + * Saves the current decklist state to history. + * @param reason The reason that is shown in the history. + */ + void requestHistorySave(const QString &reason); + +private: + bool offsetCountAtIndex(const QModelIndex &idx, int offset); + void doCardModified(); + void doMetadataModified(); + +signals: + /** + * A modification has been made to the cards in the deck + */ + void cardModified(); + + /** + * A card that wasn't previously in the deck was added to the deck, or the last copy of a card was removed from the + * deck. + */ + void uniqueCardsChanged(); + + /** + * A modification has been made to the metadata in the deck + */ + void metadataModified(); + + /** + * A modification has been made to the cards or metadata in the deck + */ + void deckModified(); + + /** + * The history has been greatly changed and needs to be reloaded. + */ + void historyChanged(); + + /** + * The deck has been completely changed. + */ + void deckReplaced(); + + /** + * The isModified state of the deck has changed + * @param isModified the new state + */ + void isModifiedChanged(bool isModified); + + /** + * The selected card on any views connected to this deck should be changed to this index. + * @param index The model index + */ + void focusIndexChanged(QModelIndex index); +}; + +#endif // COCKATRICE_DECK_STATE_MANAGER_H \ No newline at end of file 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 587407065..6ed6b67a4 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 @@ -2,6 +2,7 @@ #include "../../deck_loader/card_node_function.h" #include "../../deck_loader/deck_loader.h" +#include "../deck_editor/deck_state_manager.h" #include "../interface/widgets/cards/card_info_picture_widget.h" #include "../interface/widgets/general/layout_containers/flow_widget.h" @@ -21,7 +22,8 @@ #include #include -DlgSelectSetForCards::DlgSelectSetForCards(QWidget *parent, DeckListModel *_model) : QDialog(parent), model(_model) +DlgSelectSetForCards::DlgSelectSetForCards(QWidget *parent, DeckStateManager *deckStateManger) + : QDialog(parent), deckStateManager(deckStateManger) { setMinimumSize(500, 500); setAcceptDrops(true); @@ -165,36 +167,39 @@ void DlgSelectSetForCards::actOK() if (modifiedSetsAndCardsMap.isEmpty()) { accept(); // Nothing to do - } else { - emit deckAboutToBeModified(tr("Bulk modified printings.")); + return; } - for (QString modifiedSet : modifiedSetsAndCardsMap.keys()) { - for (QString card : modifiedSetsAndCardsMap.value(modifiedSet)) { - swapPrinting(model, modifiedSet, card); + auto bulkModify = [&modifiedSetsAndCardsMap](DeckListModel *model) { + for (QString modifiedSet : modifiedSetsAndCardsMap.keys()) { + for (QString card : modifiedSetsAndCardsMap.value(modifiedSet)) { + swapPrinting(model, modifiedSet, card); + } } - } + return true; + }; + + deckStateManager->modifyDeck(tr("Bulk modified printings."), bulkModify); - if (!modifiedSetsAndCardsMap.isEmpty()) { - emit deckModified(); - } accept(); } void DlgSelectSetForCards::actClear() { - emit deckAboutToBeModified(tr("Cleared all printing information.")); - model->forEachCard(CardNodeFunction::ClearPrintingData()); - emit deckModified(); + deckStateManager->modifyDeck(tr("Cleared all printing information."), [](auto model) { + model->forEachCard(CardNodeFunction::ClearPrintingData()); + return true; + }); accept(); } void DlgSelectSetForCards::actSetAllToPreferred() { - emit deckAboutToBeModified(tr("Set all printings to preferred.")); - model->forEachCard(CardNodeFunction::ClearPrintingData()); - model->forEachCard(CardNodeFunction::SetProviderIdToPreferred()); - emit deckModified(); + deckStateManager->modifyDeck(tr("Set all printings to preferred."), [](auto model) { + model->forEachCard(CardNodeFunction::ClearPrintingData()); + model->forEachCard(CardNodeFunction::SetProviderIdToPreferred()); + return true; + }); accept(); } @@ -227,10 +232,8 @@ void DlgSelectSetForCards::sortSetsByCount() QMap DlgSelectSetForCards::getSetsForCards() { QMap setCounts; - if (!model) - return setCounts; - QList cardNames = model->getCardNames(); + QList cardNames = deckStateManager->getModel()->getCardNames(); for (auto cardName : cardNames) { CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName); @@ -269,7 +272,7 @@ void DlgSelectSetForCards::updateCardLists() } } - QList cardNames = model->getCardNames(); + QList cardNames = deckStateManager->getModel()->getCardNames(); for (auto cardName : cardNames) { bool found = false; @@ -351,10 +354,8 @@ void DlgSelectSetForCards::dropEvent(QDropEvent *event) QMap DlgSelectSetForCards::getCardsForSets() { QMap setCards; - if (!model) - return setCards; - QList cardNames = model->getCardNames(); + QList cardNames = deckStateManager->getModel()->getCardNames(); for (auto cardName : cardNames) { CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(cardName); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h index 5cdef5a30..795366b57 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.h @@ -18,6 +18,7 @@ #include #include +class DeckStateManager; class SetEntryWidget; // Forward declaration class DlgSelectSetForCards : public QDialog @@ -25,7 +26,7 @@ class DlgSelectSetForCards : public QDialog Q_OBJECT public: - explicit DlgSelectSetForCards(QWidget *parent, DeckListModel *_model); + explicit DlgSelectSetForCards(QWidget *parent, DeckStateManager *deckStateManager); void retranslateUi(); void sortSetsByCount(); QMap getCardsForSets(); @@ -37,7 +38,6 @@ public: signals: void widgetOrderChanged(); void orderChanged(); - void deckAboutToBeModified(const QString &reason); void deckModified(); public slots: @@ -61,7 +61,7 @@ private: QLabel *modifiedCardsLabel; QWidget *listContainer; QListWidget *listWidget; - DeckListModel *model; + DeckStateManager *deckStateManager; QMap setEntries; QPushButton *clearButton; QPushButton *setAllToPreferredButton; diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp index d8bd88b37..b1346e4fd 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp @@ -11,16 +11,12 @@ * UI elements for managing card counts in both the mainboard and sideboard zones. * * @param parent The parent widget. - * @param deckEditor Pointer to the TabDeckEditor. - * @param deckModel Pointer to the DeckListModel. - * @param deckView Pointer to the QTreeView for the deck display. + * @param deckStateManager Pointer to the DeckStateManager * @param cardSizeSlider Pointer to the QSlider used for dynamic font resizing. * @param rootCard The root card for the widget. */ AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent, - AbstractTabDeckEditor *deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &rootCard) : QWidget(parent), cardSizeSlider(cardSizeSlider) @@ -32,11 +28,9 @@ AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent, setContentsMargins(5, 5, 5, 5); // Padding around the text zoneLabelMainboard = new ShadowBackgroundLabel(this, tr("Mainboard")); - buttonBoxMainboard = - new CardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard, DECK_ZONE_MAIN); + buttonBoxMainboard = new CardAmountWidget(this, deckStateManager, cardSizeSlider, rootCard, DECK_ZONE_MAIN); zoneLabelSideboard = new ShadowBackgroundLabel(this, tr("Sideboard")); - buttonBoxSideboard = - new CardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard, DECK_ZONE_SIDE); + buttonBoxSideboard = new CardAmountWidget(this, deckStateManager, cardSizeSlider, rootCard, DECK_ZONE_SIDE); layout->addWidget(zoneLabelMainboard, 0, Qt::AlignHCenter | Qt::AlignBottom); layout->addWidget(buttonBoxMainboard, 0, Qt::AlignHCenter | Qt::AlignTop); diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h index 6ce10cf2e..5a03c5f4a 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h @@ -18,9 +18,7 @@ class AllZonesCardAmountWidget : public QWidget Q_OBJECT public: explicit AllZonesCardAmountWidget(QWidget *parent, - AbstractTabDeckEditor *deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &rootCard); int getMainboardAmount(); diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index d01725cc4..62c8e2d60 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -1,5 +1,7 @@ #include "card_amount_widget.h" +#include "../deck_editor/deck_state_manager.h" + #include #include @@ -7,22 +9,17 @@ * @brief Constructs a widget for displaying and controlling the card count in a specific zone. * * @param parent The parent widget. - * @param deckEditor Pointer to the TabDeckEditor instance. - * @param deckModel Pointer to the DeckListModel instance. - * @param deckView Pointer to the QTreeView displaying the deck. * @param cardSizeSlider Pointer to the QSlider for adjusting font size. * @param rootCard The root card to manage within the widget. * @param zoneName The zone name (e.g., DECK_ZONE_MAIN or DECK_ZONE_SIDE). */ CardAmountWidget::CardAmountWidget(QWidget *parent, - AbstractTabDeckEditor *deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &rootCard, const QString &zoneName) - : QWidget(parent), deckEditor(deckEditor), deckModel(deckModel), deckView(deckView), cardSizeSlider(cardSizeSlider), - rootCard(rootCard), zoneName(zoneName), hovered(false) + : QWidget(parent), deckStateManager(deckStateManager), cardSizeSlider(cardSizeSlider), rootCard(rootCard), + zoneName(zoneName), hovered(false) { layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); @@ -56,15 +53,10 @@ CardAmountWidget::CardAmountWidget(QWidget *parent, layout->addWidget(incrementButton); // React to model changes - connect(deckModel, &DeckListModel::dataChanged, this, &CardAmountWidget::updateCardCount); - connect(deckModel, &QAbstractItemModel::rowsRemoved, this, &CardAmountWidget::updateCardCount); + connect(deckStateManager, &DeckStateManager::cardModified, this, &CardAmountWidget::updateCardCount); // Connect slider for dynamic font size adjustment connect(cardSizeSlider, &QSlider::valueChanged, this, &CardAmountWidget::adjustFontSize); - - if (deckEditor) { - connect(this, &CardAmountWidget::deckModified, deckEditor, &AbstractTabDeckEditor::onDeckHistorySaveRequested); - } } /** @@ -168,7 +160,7 @@ static QModelIndex addAndReplacePrintings(DeckListModel *model, void CardAmountWidget::addPrinting(const QString &zone) { // Check if we will need to add extra copies due to replacing copies without providerIds - QModelIndex existing = deckModel->findCard(rootCard.getName(), zone); + QModelIndex existing = deckStateManager->getModel()->findCard(rootCard.getName(), zone); int extraCopies = 0; bool replacingProviderless = false; @@ -192,15 +184,13 @@ void CardAmountWidget::addPrinting(const QString &zone) .arg(rootCard.getPrinting().getUuid()) .arg(replacingProviderless ? " (replaced providerless printings)" : ""); - emit deckModified(reason); - // Add the card and expand the list UI - auto newCardIndex = addAndReplacePrintings(deckModel, existing, rootCard, zone, extraCopies, replacingProviderless); + QModelIndex newCardIndex = deckStateManager->modifyDeck(reason, [&](auto model) { + return addAndReplacePrintings(model, existing, rootCard, zone, extraCopies, replacingProviderless); + }); if (newCardIndex.isValid()) { - deckView->setCurrentIndex(newCardIndex); - deckView->setFocus(Qt::FocusReason::MouseFocusReason); - deckEditor->setModified(true); + emit deckStateManager->focusIndexChanged(newCardIndex); } } @@ -250,13 +240,11 @@ void CardAmountWidget::decrementCardHelper(const QString &zone) .arg(zone == DECK_ZONE_MAIN ? "mainboard" : "sideboard") .arg(rootCard.getPrinting().getUuid()); - emit deckModified(reason); - - QModelIndex idx = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(), + deckStateManager->modifyDeck(reason, [this, &zone](auto model) { + QModelIndex idx = model->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(), rootCard.getPrinting().getProperty("num")); - - deckModel->offsetCountAtIndex(idx, -1); - deckEditor->setModified(true); + return model->offsetCountAtIndex(idx, -1); + }); } /** @@ -273,7 +261,7 @@ int CardAmountWidget::countCardsInZone(const QString &deckZone) return 0; // Cards without uuids/providerIds CANNOT match another card, they are undefined for us. } - QList cards = deckModel->getCardsForZone(deckZone); + QList cards = deckStateManager->getModel()->getCardsForZone(deckZone); return std::count_if(cards.cbegin(), cards.cend(), [&uuid](const ExactCard &card) { return card.getPrinting().getUuid() == uuid; }); diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h index b4704cede..983416782 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h @@ -27,9 +27,7 @@ signals: public: explicit CardAmountWidget(QWidget *parent, - AbstractTabDeckEditor *deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &rootCard, const QString &zoneName); @@ -44,9 +42,7 @@ protected: void showEvent(QShowEvent *event) override; private: - AbstractTabDeckEditor *deckEditor; - DeckListModel *deckModel; - QTreeView *deckView; + DeckStateManager *deckStateManager; QSlider *cardSizeSlider; ExactCard rootCard; QString zoneName; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp index e64d6a009..95d6b2cdf 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp @@ -3,6 +3,7 @@ #include "../../../client/settings/cache_settings.h" #include "../../../interface/card_picture_loader/card_picture_loader.h" #include "../../../interface/widgets/dialogs/dlg_select_set_for_cards.h" +#include "../deck_editor/deck_state_manager.h" #include "printing_selector_card_display_widget.h" #include "printing_selector_card_search_widget.h" #include "printing_selector_card_selection_widget.h" @@ -21,12 +22,9 @@ * * @param parent The parent widget for the PrintingSelector. * @param deckEditor The TabDeckEditor instance used for managing the deck. - * @param deckModel The DeckListModel instance that provides data for the deck's contents. - * @param deckView The QTreeView instance used to display the deck and its contents. */ PrintingSelector::PrintingSelector(QWidget *parent, AbstractTabDeckEditor *_deckEditor) - : QWidget(parent), deckEditor(_deckEditor), deckModel(deckEditor->deckDockWidget->deckModel), - deckView(deckEditor->deckDockWidget->deckView) + : QWidget(parent), deckEditor(_deckEditor), deckStateManager(_deckEditor->deckStateManager) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); layout = new QVBoxLayout(this); @@ -74,13 +72,12 @@ PrintingSelector::PrintingSelector(QWidget *parent, AbstractTabDeckEditor *_deck layout->addWidget(flowWidget); - cardSelectionBar = new PrintingSelectorCardSelectionWidget(this); + cardSelectionBar = new PrintingSelectorCardSelectionWidget(this, deckStateManager); cardSelectionBar->setVisible(SettingsCache::instance().getPrintingSelectorNavigationButtonsVisible()); layout->addWidget(cardSelectionBar); // Connect deck model data change signal to update display - connect(deckModel, &DeckListModel::rowsInserted, this, &PrintingSelector::printingsInDeckChanged); - connect(deckModel, &DeckListModel::rowsRemoved, this, &PrintingSelector::printingsInDeckChanged); + connect(deckStateManager, &DeckStateManager::uniqueCardsChanged, this, &PrintingSelector::printingsInDeckChanged); retranslateUi(); } @@ -152,7 +149,8 @@ void PrintingSelector::getAllSetsForCurrentCard() QList printingsToUse; if (SettingsCache::instance().getBumpSetsWithCardsInDeckToTop()) { - printingsToUse = sortToolBar->prependPrintingsInDeck(filteredPrintings, selectedCard, deckModel); + printingsToUse = + sortToolBar->prependPrintingsInDeck(filteredPrintings, selectedCard, deckStateManager->getModel()); } else { printingsToUse = filteredPrintings; } @@ -164,7 +162,7 @@ void PrintingSelector::getAllSetsForCurrentCard() connect(widgetLoadingBufferTimer, &QTimer::timeout, this, [=, this]() mutable { for (int i = 0; i < BATCH_SIZE && currentIndex < printingsToUse.size(); ++i, ++currentIndex) { auto card = ExactCard(selectedCard, printingsToUse[currentIndex]); - auto *cardDisplayWidget = new PrintingSelectorCardDisplayWidget(this, deckEditor, deckModel, deckView, + auto *cardDisplayWidget = new PrintingSelectorCardDisplayWidget(this, deckEditor, deckStateManager, cardSizeWidget->getSlider(), card); flowWidget->addWidget(cardDisplayWidget); cardDisplayWidget->clampSetNameToPicture(); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h index e34ce3fe6..e1c07addf 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h @@ -21,6 +21,7 @@ #define BATCH_SIZE 10 +class DeckStateManager; class PrintingSelectorCardSearchWidget; class PrintingSelectorCardSelectionWidget; class PrintingSelectorCardSortingWidget; @@ -35,15 +36,6 @@ public: void setCard(const CardInfoPtr &newCard); void getAllSetsForCurrentCard(); - [[nodiscard]] DeckListModel *getDeckModel() const - { - return deckModel; - } - - [[nodiscard]] AbstractTabDeckEditor *getDeckEditor() const - { - return deckEditor; - } public slots: void retranslateUi(); @@ -75,8 +67,7 @@ private: CardSizeWidget *cardSizeWidget; PrintingSelectorCardSelectionWidget *cardSelectionBar; AbstractTabDeckEditor *deckEditor; - DeckListModel *deckModel; - QTreeView *deckView; + DeckStateManager *deckStateManager; CardInfoPtr selectedCard; QTimer *widgetLoadingBufferTimer; int currentIndex = 0; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp index 86d6659a8..92cf2437c 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp @@ -18,15 +18,13 @@ * * @param parent The parent widget for this display. * @param deckEditor The TabDeckEditor instance for deck management. - * @param deckModel The DeckListModel instance providing deck data. - * @param deckView The QTreeView instance displaying the deck. + * @param deckStateManager The DeckStateManager instance providing deck data. * @param cardSizeSlider The slider controlling the size of the displayed card. * @param rootCard The root card object, representing the card to be displayed. */ PrintingSelectorCardDisplayWidget::PrintingSelectorCardDisplayWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &rootCard) : QWidget(parent) @@ -36,8 +34,7 @@ PrintingSelectorCardDisplayWidget::PrintingSelectorCardDisplayWidget(QWidget *pa setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // Create the overlay widget for the card display - overlayWidget = - new PrintingSelectorCardOverlayWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard); + overlayWidget = new PrintingSelectorCardOverlayWidget(this, deckEditor, deckStateManager, cardSizeSlider, rootCard); connect(overlayWidget, &PrintingSelectorCardOverlayWidget::cardPreferenceChanged, this, [this]() { emit cardPreferenceChanged(); }); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h index 608c2df5c..2637b0e57 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h @@ -21,8 +21,7 @@ class PrintingSelectorCardDisplayWidget : public QWidget public: PrintingSelectorCardDisplayWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &rootCard); 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 04ab07a59..ac36f2cf4 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 @@ -22,15 +22,13 @@ * * @param parent The parent widget for this overlay. * @param _deckEditor The TabDeckEditor instance for deck management. - * @param deckModel The DeckListModel instance providing deck data. - * @param deckView The QTreeView instance displaying the deck. + * @param deckStateManager The DeckStateManager instance providing deck data. * @param cardSizeSlider The slider controlling the size of the card. * @param _rootCard The root card object that contains information about the card. */ PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *parent, AbstractTabDeckEditor *_deckEditor, - DeckListModel *deckModel, - QTreeView *deckView, + DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &_rootCard) : QWidget(parent), deckEditor(_deckEditor), rootCard(_rootCard) @@ -58,8 +56,7 @@ PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *pa updatePinBadgeVisibility(); // Add AllZonesCardAmountWidget - allZonesCardAmountWidget = - new AllZonesCardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, _rootCard); + allZonesCardAmountWidget = new AllZonesCardAmountWidget(this, deckStateManager, cardSizeSlider, _rootCard); allZonesCardAmountWidget->raise(); // Ensure it's on top of the picture // Set initial visibility based on amounts 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 3bd5ce247..8550612bd 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 @@ -20,8 +20,7 @@ class PrintingSelectorCardOverlayWidget : public QWidget public: explicit PrintingSelectorCardOverlayWidget(QWidget *parent, AbstractTabDeckEditor *_deckEditor, - DeckListModel *_deckModel, - QTreeView *_deckView, + DeckStateManager *_deckStateManager, QSlider *_cardSizeSlider, const ExactCard &_rootCard); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp index 317ce83b6..2eb2ef245 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp @@ -11,7 +11,9 @@ * * @param parent The parent PrintingSelector widget responsible for managing card selection. */ -PrintingSelectorCardSelectionWidget::PrintingSelectorCardSelectionWidget(PrintingSelector *parent) : parent(parent) +PrintingSelectorCardSelectionWidget::PrintingSelectorCardSelectionWidget(PrintingSelector *parent, + DeckStateManager *deckStateManager) + : parent(parent), deckStateManager(deckStateManager) { cardSelectionBarLayout = new QHBoxLayout(this); cardSelectionBarLayout->setContentsMargins(9, 0, 9, 0); @@ -48,12 +50,6 @@ void PrintingSelectorCardSelectionWidget::connectSignals() void PrintingSelectorCardSelectionWidget::selectSetForCards() { - auto *setSelectionDialog = new DlgSelectSetForCards(nullptr, parent->getDeckModel()); - connect(setSelectionDialog, &DlgSelectSetForCards::deckAboutToBeModified, parent->getDeckEditor(), - &AbstractTabDeckEditor::onDeckHistorySaveRequested); - connect(setSelectionDialog, &DlgSelectSetForCards::deckModified, parent->getDeckEditor(), - &AbstractTabDeckEditor::onDeckModified); - if (!setSelectionDialog->exec()) { - return; - } + auto *setSelectionDialog = new DlgSelectSetForCards(nullptr, deckStateManager); + setSelectionDialog->exec(); } diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.h index a1176a76c..ecd5c83e3 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.h @@ -18,7 +18,7 @@ class PrintingSelectorCardSelectionWidget : public QWidget Q_OBJECT public: - explicit PrintingSelectorCardSelectionWidget(PrintingSelector *parent); + explicit PrintingSelectorCardSelectionWidget(PrintingSelector *parent, DeckStateManager *deckStateManager); void connectSignals(); @@ -27,6 +27,7 @@ public slots: private: PrintingSelector *parent; + DeckStateManager *deckStateManager; QHBoxLayout *cardSelectionBarLayout; QPushButton *previousCardButton; QPushButton *selectSetForCardsButton; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp index af7cedbeb..725e5df90 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp @@ -183,7 +183,7 @@ QList PrintingSelectorCardSortingWidget::prependPinnedPrintings(co */ QList PrintingSelectorCardSortingWidget::prependPrintingsInDeck(const QList &printings, const CardInfoPtr &selectedCard, - DeckListModel *deckModel) + const DeckListModel *deckModel) { if (!selectedCard) { return {}; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.h index d0faea4ac..b5a00b81e 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.h @@ -23,7 +23,7 @@ public: QList prependPinnedPrintings(const QList &printings, const QString &cardName); QList prependPrintingsInDeck(const QList &printings, const CardInfoPtr &selectedCard, - DeckListModel *deckModel); + const DeckListModel *deckModel); public slots: void updateSortOrder(); 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 cb2002199..29bcc1ab1 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -12,6 +12,7 @@ #include "../../../client/settings/cache_settings.h" #include "../client/network/interfaces/deck_stats_interface.h" #include "../client/network/interfaces/tapped_out_interface.h" +#include "../deck_editor/deck_state_manager.h" #include "../interface/card_picture_loader/card_picture_loader.h" #include "../interface/pixel_map_generator.h" #include "../interface/widgets/dialogs/dlg_load_deck.h" @@ -52,7 +53,7 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta { setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks); - historyManager = new DeckListHistoryManager(this); + deckStateManager = new DeckStateManager(this); databaseDisplayDockWidget = new DeckEditorDatabaseDisplayWidget(this); deckDockWidget = new DeckEditorDeckDockWidget(this); @@ -64,14 +65,8 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta }); // Connect deck signals to this tab - connect(deckDockWidget, &DeckEditorDeckDockWidget::deckChanged, this, &AbstractTabDeckEditor::onDeckChanged); - connect(deckDockWidget, &DeckEditorDeckDockWidget::deckModified, this, &AbstractTabDeckEditor::onDeckModified); - connect(deckDockWidget, &DeckEditorDeckDockWidget::requestDeckHistorySave, this, - &AbstractTabDeckEditor::onDeckHistorySaveRequested); - connect(deckDockWidget, &DeckEditorDeckDockWidget::requestDeckHistoryClear, this, - &AbstractTabDeckEditor::onDeckHistoryClearRequested); - connect(deckDockWidget, &DeckEditorDeckDockWidget::cardChanged, this, &AbstractTabDeckEditor::updateCard); - connect(this, &AbstractTabDeckEditor::decrementCard, deckDockWidget, &DeckEditorDeckDockWidget::actDecrementCard); + connect(deckStateManager, &DeckStateManager::isModifiedChanged, this, &AbstractTabDeckEditor::onDeckModified); + connect(deckDockWidget, &DeckEditorDeckDockWidget::selectedCardChanged, this, &AbstractTabDeckEditor::updateCard); // Connect database display signals to this tab connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::cardChanged, this, @@ -107,7 +102,6 @@ void AbstractTabDeckEditor::updateCard(const ExactCard &card) /** @brief Placeholder: called when the deck changes. */ void AbstractTabDeckEditor::onDeckChanged() { - historyManager->clear(); } /** @@ -115,24 +109,8 @@ void AbstractTabDeckEditor::onDeckChanged() */ void AbstractTabDeckEditor::onDeckModified() { - setModified(!isBlankNewDeck()); - deckMenu->setSaveStatus(!isBlankNewDeck()); -} - -/** - * @brief Marks the tab as modified and updates the save menu status. - */ -void AbstractTabDeckEditor::onDeckHistorySaveRequested(const QString &modificationReason) -{ - historyManager->save(deckDockWidget->getDeckList().createMemento(modificationReason)); -} - -/** - * @brief Marks the tab as modified and updates the save menu status. - */ -void AbstractTabDeckEditor::onDeckHistoryClearRequested() -{ - historyManager->clear(); + deckMenu->setSaveStatus(!deckStateManager->isBlankNewDeck()); + emit tabTextChanged(this, getTabText()); } /** @@ -142,7 +120,7 @@ void AbstractTabDeckEditor::onDeckHistoryClearRequested() */ void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, const QString &zoneName) { - deckDockWidget->actAddCard(card, zoneName); + deckStateManager->addCard(card, zoneName); databaseDisplayDockWidget->searchEdit->setSelection(0, databaseDisplayDockWidget->searchEdit->text().length()); } @@ -170,13 +148,13 @@ void AbstractTabDeckEditor::actAddCardToSideboard(const ExactCard &card) /** @brief Decrements a card from the main deck. */ void AbstractTabDeckEditor::actDecrementCard(const ExactCard &card) { - emit decrementCard(card, DECK_ZONE_MAIN); + deckStateManager->decrementCard(card, DECK_ZONE_MAIN); } /** @brief Decrements a card from the sideboard. */ void AbstractTabDeckEditor::actDecrementCardFromSideboard(const ExactCard &card) { - emit decrementCard(card, DECK_ZONE_SIDE); + deckStateManager->decrementCard(card, DECK_ZONE_SIDE); } /** @@ -198,45 +176,13 @@ void AbstractTabDeckEditor::openDeck(const LoadedDeck &deck) */ void AbstractTabDeckEditor::setDeck(const LoadedDeck &_deck) { - deckDockWidget->setDeck(_deck); - CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(getDeckList().getCardRefList())); - setModified(false); + deckStateManager->replaceDeck(_deck); + CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(_deck.deckList.getCardRefList())); aDeckDockVisible->setChecked(true); deckDockWidget->setVisible(aDeckDockVisible->isChecked()); } -/** @brief Returns the currently loaded deck. */ -DeckLoader *AbstractTabDeckEditor::getDeckLoader() const -{ - return deckDockWidget->getDeckLoader(); -} - -/** @brief Returns the currently loaded deck list. */ -const DeckList &AbstractTabDeckEditor::getDeckList() const -{ - return deckDockWidget->getDeckList(); -} - -/** - * @brief Sets the modified state of the tab. - * @param _modified True if tab is modified, false otherwise. - */ -void AbstractTabDeckEditor::setModified(bool _modified) -{ - modified = _modified; - emit tabTextChanged(this, getTabText()); -} - -/** - * @brief Returns true if the tab is a blank newly created deck. - */ -bool AbstractTabDeckEditor::isBlankNewDeck() const -{ - const LoadedDeck &loadedDeck = deckDockWidget->getDeckLoader()->getDeck(); - return !modified && loadedDeck.isEmpty(); -} - /** @brief Creates a new deck. Handles opening in new tab if needed. */ void AbstractTabDeckEditor::actNewDeck() { @@ -255,9 +201,8 @@ void AbstractTabDeckEditor::actNewDeck() /** @brief Clears the current deck and resets modified flag. */ void AbstractTabDeckEditor::cleanDeckAndResetModified() { + deckStateManager->clearDeck(); deckMenu->setSaveStatus(false); - deckDockWidget->cleanDeck(); - setModified(false); } /** @@ -268,13 +213,13 @@ void AbstractTabDeckEditor::cleanDeckAndResetModified() AbstractTabDeckEditor::DeckOpenLocation AbstractTabDeckEditor::confirmOpen(const bool openInSameTabIfBlank) { if (SettingsCache::instance().getOpenDeckInNewTab()) { - if (openInSameTabIfBlank && isBlankNewDeck()) + if (openInSameTabIfBlank && deckStateManager->isBlankNewDeck()) return SAME_TAB; else return NEW_TAB; } - if (!modified) + if (!deckStateManager->isModified()) return SAME_TAB; tabSupervisor->setCurrentWidget(this); @@ -325,7 +270,6 @@ void AbstractTabDeckEditor::actLoadDeck() QString fileName = dialog.selectedFiles().at(0); openDeckFromFile(fileName, deckOpenLocation); - deckDockWidget->updateBannerCardComboBox(); } /** @@ -371,7 +315,7 @@ void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLo */ bool AbstractTabDeckEditor::actSaveDeck() { - const LoadedDeck &loadedDeck = getDeckLoader()->getDeck(); + const auto loadedDeck = deckStateManager->toLoadedDeck(); if (loadedDeck.lastLoadInfo.remoteDeckId != LoadedDeck::LoadInfo::NON_REMOTE_ID) { QString deckString = loadedDeck.deckList.writeToString_Native(); if (deckString.length() > MAX_FILE_LENGTH) { @@ -392,8 +336,10 @@ bool AbstractTabDeckEditor::actSaveDeck() if (loadedDeck.lastLoadInfo.fileName.isEmpty()) return actSaveDeckAs(); - if (getDeckLoader()->saveToFile(loadedDeck.lastLoadInfo.fileName, loadedDeck.lastLoadInfo.fileFormat)) { - setModified(false); + auto deckLoader = DeckLoader(this); + deckLoader.setDeck(loadedDeck); + if (deckLoader.saveToFile(loadedDeck.lastLoadInfo.fileName, loadedDeck.lastLoadInfo.fileFormat)) { + deckStateManager->setModified(false); return true; } @@ -409,12 +355,14 @@ bool AbstractTabDeckEditor::actSaveDeck() */ bool AbstractTabDeckEditor::actSaveDeckAs() { + LoadedDeck loadedDeck = deckStateManager->toLoadedDeck(); + QFileDialog dialog(this, tr("Save deck")); dialog.setDirectory(SettingsCache::instance().getDeckPath()); dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setDefaultSuffix("cod"); dialog.setNameFilters(DeckLoader::FILE_NAME_FILTERS); - dialog.selectFile(getDeckList().getName().trimmed()); + dialog.selectFile(loadedDeck.deckList.getName().trimmed()); if (!dialog.exec()) return false; @@ -422,14 +370,18 @@ bool AbstractTabDeckEditor::actSaveDeckAs() QString fileName = dialog.selectedFiles().at(0); DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName); - if (!getDeckLoader()->saveToFile(fileName, fmt)) { + DeckLoader deckLoader = DeckLoader(this); + deckLoader.setDeck(loadedDeck); + if (!deckLoader.saveToFile(fileName, fmt)) { QMessageBox::critical( this, tr("Error"), tr("The deck could not be saved.\nPlease check that the directory is writable and try again.")); return false; } - setModified(false); + deckStateManager->setLastLoadInfo({.fileName = fileName, .fileFormat = fmt}); + + deckStateManager->setModified(false); SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(fileName); return true; } @@ -443,7 +395,7 @@ void AbstractTabDeckEditor::saveDeckRemoteFinished(const Response &response) if (response.response_code() != Response::RespOk) QMessageBox::critical(this, tr("Error"), tr("The deck could not be saved.")); else - setModified(false); + deckStateManager->setModified(false); } /** @@ -464,7 +416,7 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard() emit openDeckEditor({.deckList = dlg.getDeckList()}); } else { setDeck({.deckList = dlg.getDeckList()}); - setModified(true); + deckStateManager->setModified(true); } deckMenu->setSaveStatus(true); @@ -476,12 +428,13 @@ void AbstractTabDeckEditor::actLoadDeckFromClipboard() */ void AbstractTabDeckEditor::editDeckInClipboard(bool annotated) { - DlgEditDeckInClipboard dlg(getDeckLoader()->getDeck().deckList, annotated, this); + LoadedDeck loadedDeck = deckStateManager->toLoadedDeck(); + DlgEditDeckInClipboard dlg(loadedDeck.deckList, annotated, this); if (!dlg.exec()) return; - setDeck({dlg.getDeckList(), getDeckLoader()->getDeck().lastLoadInfo}); - setModified(true); + setDeck({dlg.getDeckList(), loadedDeck.lastLoadInfo}); + deckStateManager->setModified(true); deckMenu->setSaveStatus(true); } @@ -500,25 +453,25 @@ void AbstractTabDeckEditor::actEditDeckInClipboardRaw() /** @brief Saves deck to clipboard with set info and annotation. */ void AbstractTabDeckEditor::actSaveDeckToClipboard() { - DeckLoader::saveToClipboard(getDeckList(), true, true); + DeckLoader::saveToClipboard(deckStateManager->getDeckList(), true, true); } /** @brief Saves deck to clipboard with annotation, without set info. */ void AbstractTabDeckEditor::actSaveDeckToClipboardNoSetInfo() { - DeckLoader::saveToClipboard(getDeckList(), true, false); + DeckLoader::saveToClipboard(deckStateManager->getDeckList(), true, false); } /** @brief Saves deck to clipboard without annotations, with set info. */ void AbstractTabDeckEditor::actSaveDeckToClipboardRaw() { - DeckLoader::saveToClipboard(getDeckList(), false, true); + DeckLoader::saveToClipboard(deckStateManager->getDeckList(), false, true); } /** @brief Saves deck to clipboard without annotations or set info. */ void AbstractTabDeckEditor::actSaveDeckToClipboardRawNoSetInfo() { - DeckLoader::saveToClipboard(getDeckList(), false, false); + DeckLoader::saveToClipboard(deckStateManager->getDeckList(), false, false); } /** @brief Prints the deck using a QPrintPreviewDialog. */ @@ -526,7 +479,7 @@ void AbstractTabDeckEditor::actPrintDeck() { auto *dlg = new QPrintPreviewDialog(this); connect(dlg, &QPrintPreviewDialog::paintRequested, this, - [this](QPrinter *printer) { DeckLoader::printDeckList(printer, getDeckList()); }); + [this](QPrinter *printer) { DeckLoader::printDeckList(printer, deckStateManager->getDeckList()); }); dlg->exec(); } @@ -547,7 +500,7 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite() emit openDeckEditor({.deckList = dlg.getDeck()}); } else { setDeck({.deckList = dlg.getDeck()}); - setModified(true); + deckStateManager->setModified(true); } deckMenu->setSaveStatus(true); @@ -559,7 +512,7 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite() */ void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website) { - QString decklistUrlString = DeckLoader::exportDeckToDecklist(getDeckList(), website); + QString decklistUrlString = DeckLoader::exportDeckToDecklist(deckStateManager->getDeckList(), website); // Check to make sure the string isn't empty. if (decklistUrlString.isEmpty()) { // Show an error if the deck is empty, and return. @@ -592,14 +545,14 @@ void AbstractTabDeckEditor::actExportDeckDecklistXyz() void AbstractTabDeckEditor::actAnalyzeDeckDeckstats() { auto *interface = new DeckStatsInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(), this); - interface->analyzeDeck(getDeckList()); + interface->analyzeDeck(deckStateManager->getDeckList()); } /** @brief Analyzes the deck using TappedOut. */ void AbstractTabDeckEditor::actAnalyzeDeckTappedout() { auto *interface = new TappedOutInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(), this); - interface->analyzeDeck(getDeckList()); + interface->analyzeDeck(deckStateManager->getDeckList()); } /** @brief Applies a new filter tree to the database display. */ @@ -658,7 +611,7 @@ bool AbstractTabDeckEditor::eventFilter(QObject *o, QEvent *e) /** @brief Shows a confirmation dialog before closing. */ bool AbstractTabDeckEditor::confirmClose() { - if (modified) { + if (deckStateManager->isModified()) { tabSupervisor->setCurrentWidget(this); int ret = createSaveConfirmationWindow()->exec(); if (ret == QMessageBox::Save) diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h index bfbda778c..69b2b4a1c 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h @@ -19,6 +19,7 @@ #include +class DeckStateManager; class CardDatabaseModel; class CardDatabaseDisplayModel; @@ -117,30 +118,13 @@ public: */ void openDeck(const LoadedDeck &deck); - /** @brief Returns the currently active deck loader. */ - DeckLoader *getDeckLoader() const; - - /** @brief Returns the currently active deck list. */ - const DeckList &getDeckList() const; - - /** @brief Sets the modified state of the tab. - * @param _windowModified Whether the tab is modified. - */ - void setModified(bool _windowModified); - DeckEditorDeckDockWidget *getDeckDockWidget() const { return deckDockWidget; } - DeckListHistoryManager *getHistoryManager() const - { - return historyManager; - } - - DeckListHistoryManager *historyManager; - // UI Elements + DeckStateManager *deckStateManager; DeckEditorMenu *deckMenu; ///< Menu for deck operations DeckEditorDatabaseDisplayWidget *databaseDisplayDockWidget; ///< Database dock DeckEditorCardInfoDockWidget *cardInfoDockWidget; ///< Card info dock @@ -155,14 +139,6 @@ public slots: /** @brief Called when the deck is modified. */ virtual void onDeckModified(); - /** @brief Called when a widget is about to modify the state of the DeckList. - * @param modificationReason The reason for the state modification - */ - virtual void onDeckHistorySaveRequested(const QString &modificationReason); - - /** @brief Called when a widget would like to clear the history. */ - virtual void onDeckHistoryClearRequested(); - /** @brief Updates the card info panel. * @param card The card to display. */ @@ -202,9 +178,6 @@ signals: /** @brief Emitted before the tab is closed. */ void deckEditorClosing(AbstractTabDeckEditor *tab); - /** @brief Emitted when a card should be decremented. */ - void decrementCard(const ExactCard &card, QString zoneName); - protected slots: /** @brief Starts a new deck in this tab. */ virtual void actNewDeck(); @@ -315,9 +288,6 @@ protected: */ QMessageBox *createSaveConfirmationWindow(); - /** @brief Returns true if the tab is a blank newly created deck. */ - bool isBlankNewDeck() const; - /** @brief Helper function to add a card to a specific deck zone. */ void addCardHelper(const ExactCard &card, const QString &zoneName); @@ -330,8 +300,6 @@ protected: QAction *aResetLayout; QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aDeckDockVisible, *aDeckDockFloating; QAction *aFilterDockVisible, *aFilterDockFloating, *aPrintingSelectorDockVisible, *aPrintingSelectorDockFloating; - - bool modified = false; ///< Whether the deck/tab has unsaved changes }; #endif // TAB_GENERIC_DECK_EDITOR_H 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 1547cca74..3a2468368 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 @@ -68,7 +68,10 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi model = new DeckListModel(this); connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset); - model->getDeckList()->loadFromStream_Plain(deckStream, false); + + auto decklist = QSharedPointer(new DeckList); + decklist->loadFromStream_Plain(deckStream, false); + model->setDeckList(decklist); model->forEachCard(CardNodeFunction::ResolveProviderId()); diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index 4e3d0c57d..286252e13 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -1,6 +1,7 @@ #include "tab_deck_editor.h" #include "../../../client/settings/cache_settings.h" +#include "../deck_editor/deck_state_manager.h" #include "../filters/filter_builder.h" #include "../interface/pixel_map_generator.h" #include "../interface/widgets/cards/card_info_frame_widget.h" @@ -114,8 +115,8 @@ void TabDeckEditor::createMenus() */ QString TabDeckEditor::getTabText() const { - QString result = tr("Deck: %1").arg(deckDockWidget->getSimpleDeckName()); - if (modified) + QString result = tr("Deck: %1").arg(deckStateManager->getSimpleDeckName()); + if (deckStateManager->isModified()) result.prepend("* "); return result; } diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index a124eaa01..f3d573d27 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -1,6 +1,7 @@ #include "tab_deck_editor_visual.h" #include "../../../../client/settings/cache_settings.h" +#include "../../deck_editor/deck_state_manager.h" #include "../../filters/filter_builder.h" #include "../../interface/pixel_map_generator.h" #include "../../interface/widgets/cards/card_info_frame_widget.h" @@ -61,7 +62,7 @@ void TabDeckEditorVisual::createCentralFrame() centralFrame = new QVBoxLayout; centralWidget->setLayout(centralFrame); - tabContainer = new TabDeckEditorVisualTabWidget(centralWidget, this, deckDockWidget->deckModel, + tabContainer = new TabDeckEditorVisualTabWidget(centralWidget, this, deckStateManager->getModel(), databaseDisplayDockWidget->databaseModel, databaseDisplayDockWidget->databaseDisplayModel); @@ -85,7 +86,7 @@ void TabDeckEditorVisual::onDeckChanged() AbstractTabDeckEditor::onDeckModified(); tabContainer->visualDeckView->constructZoneWidgetsFromDeckListModel(); tabContainer->deckAnalytics->refreshDisplays(); - tabContainer->sampleHandWidget->setDeckModel(deckDockWidget->deckModel); + tabContainer->sampleHandWidget->setDeckModel(deckStateManager->getModel()); } /** @brief Creates menus for deck editing and view options, including dock actions. */ @@ -149,8 +150,8 @@ void TabDeckEditorVisual::createMenus() /** @brief Returns the tab text, prepending a mark if the deck has unsaved changes. */ QString TabDeckEditorVisual::getTabText() const { - QString result = tr("Visual Deck: %1").arg(deckDockWidget->getSimpleDeckName()); - if (modified) + QString result = tr("Visual Deck: %1").arg(deckStateManager->getSimpleDeckName()); + if (deckStateManager->isModified()) result.prepend("* "); return result; } @@ -166,9 +167,9 @@ void TabDeckEditorVisual::changeModelIndexAndCardInfo(const ExactCard &activeCar void TabDeckEditorVisual::changeModelIndexToCard(const ExactCard &activeCard) { QString cardName = activeCard.getName(); - QModelIndex index = deckDockWidget->deckModel->findCard(cardName, DECK_ZONE_MAIN); + QModelIndex index = deckStateManager->getModel()->findCard(cardName, DECK_ZONE_MAIN); if (!index.isValid()) { - index = deckDockWidget->deckModel->findCard(cardName, DECK_ZONE_SIDE); + index = deckStateManager->getModel()->findCard(cardName, DECK_ZONE_SIDE); } if (!deckDockWidget->getSelectionModel()->hasSelection()) { deckDockWidget->getSelectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); @@ -182,7 +183,7 @@ void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event, auto card = instance->getCard(); // Get the model index for the card - QModelIndex idx = deckDockWidget->deckModel->findCard(card.getName(), zoneName); + QModelIndex idx = deckStateManager->getModel()->findCard(card.getName(), zoneName); if (!idx.isValid()) { return; } @@ -191,8 +192,8 @@ void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event, // Double click = swap if (event->type() == QEvent::MouseButtonDblClick && event->button() == Qt::LeftButton) { - deckDockWidget->actSwapCard(card, zoneName); - idx = deckDockWidget->deckModel->findCard(card.getName(), zoneName); + deckStateManager->swapCardAtIndex(idx); + idx = deckStateManager->getModel()->findCard(card.getName(), zoneName); sel->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect); return; } diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp index 7551954f3..5098696dd 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp @@ -2,6 +2,7 @@ #include "../../../interface/widgets/dialogs/dlg_load_deck_from_clipboard.h" #include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h" +#include "../deck_editor/deck_state_manager.h" #include @@ -60,7 +61,7 @@ void VisualDatabaseDisplayNameFilterWidget::retranslateUi() void VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck() { - DeckListModel *deckListModel = deckEditor->deckDockWidget->deckModel; + DeckListModel *deckListModel = deckEditor->deckStateManager->getModel(); if (!deckListModel) return; 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 6f479616e..ece3bc2f8 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -5,14 +5,15 @@ DeckListModel::DeckListModel(QObject *parent) : QAbstractItemModel(parent), lastKnownColumn(1), lastKnownOrder(Qt::AscendingOrder) { - // This class will leak the decklist object. We cannot safely delete it in the dtor because the deckList field is a - // non-owning pointer and another deckList might have been assigned to it. - // `DeckListModel::cleanList` also leaks for the same reason. - // TODO: fix the leak - deckList = new DeckList; + deckList = QSharedPointer(new DeckList()); root = new InnerDecklistNode; } +DeckListModel::DeckListModel(QObject *parent, const QSharedPointer &deckList) : DeckListModel(parent) +{ + setDeckList(deckList); +} + DeckListModel::~DeckListModel() { delete root; @@ -586,13 +587,13 @@ void DeckListModel::setActiveFormat(const QString &_format) void DeckListModel::cleanList() { - setDeckList(new DeckList); + setDeckList(QSharedPointer(new DeckList())); } /** * @param _deck The deck. */ -void DeckListModel::setDeckList(DeckList *_deck) +void DeckListModel::setDeckList(const QSharedPointer &_deck) { if (deckList != _deck) { deckList = _deck; 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 b6292d689..e6f10c072 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -245,6 +245,7 @@ signals: public: explicit DeckListModel(QObject *parent = nullptr); + explicit DeckListModel(QObject *parent, const QSharedPointer &deckList); ~DeckListModel() override; /** @@ -314,11 +315,12 @@ public: * @brief Removes all cards and resets the model. */ void cleanList(); - [[nodiscard]] DeckList *getDeckList() const + + [[nodiscard]] QSharedPointer getDeckList() const { return deckList; } - void setDeckList(DeckList *_deck); + void setDeckList(const QSharedPointer &_deck); /** * @brief Apply a function to every card in the deck tree. @@ -351,8 +353,8 @@ public: [[nodiscard]] QList getZones() const; private: - DeckList *deckList; /**< Pointer to the deck loader providing the underlying data. */ - InnerDecklistNode *root; /**< Root node of the model tree. */ + QSharedPointer deckList; /**< Pointer to the decklist providing the underlying data. */ + InnerDecklistNode *root; /**< Root node of the model tree. */ DeckListModelGroupCriteria::Type activeGroupCriteria = DeckListModelGroupCriteria::MAIN_TYPE; int lastKnownColumn; /**< Last column used for sorting. */ Qt::SortOrder lastKnownOrder; /**< Last known sort order. */ From 9f90de2242ec30e519169a00873ff5fac0f163b6 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Wed, 31 Dec 2025 17:55:31 +0100 Subject: [PATCH 100/325] change the release channel based on version string (#6447) * change the release channel based on version string * Apply suggestions from code review * format --- .../src/client/settings/cache_settings.cpp | 19 +++++++++++++++++-- cockatrice/src/interface/window_main.cpp | 18 +++++++++++++++++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 7a518df67..fde8e9b34 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -2,6 +2,7 @@ #include "../network/update/client/release_channel.h" #include "card_counter_settings.h" +#include "version_string.h" #include #include @@ -198,7 +199,13 @@ SettingsCache::SettingsCache() mbDownloadSpoilers = settings->value("personal/downloadspoilers", false).toBool(); - checkUpdatesOnStartup = settings->value("personal/startupUpdateCheck", true).toBool(); + if (settings->contains("personal/startupUpdateCheck")) { + checkUpdatesOnStartup = settings->value("personal/startupUpdateCheck", true).toBool(); + } else if (QString(VERSION_STRING).contains("custom", Qt::CaseInsensitive)) { + checkUpdatesOnStartup = false; // do not run auto updater on custom version + } else { + checkUpdatesOnStartup = true; // default to run auto updater + } startupCardUpdateCheckPromptForUpdate = settings->value("personal/startupCardUpdateCheckPromptForUpdate", true).toBool(); startupCardUpdateCheckAlwaysUpdate = settings->value("personal/startupCardUpdateCheckAlwaysUpdate", false).toBool(); @@ -206,7 +213,15 @@ SettingsCache::SettingsCache() lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate(); notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool(); notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool(); - updateReleaseChannel = settings->value("personal/updatereleasechannel", 0).toInt(); + + if (settings->contains("personal/updatereleasechannel")) { + updateReleaseChannel = settings->value("personal/updatereleasechannel").toInt(); + } else if (QString(VERSION_STRING).contains("beta", Qt::CaseInsensitive)) { + // default to beta if this is a beta release + updateReleaseChannel = 1; + } else { + updateReleaseChannel = 0; // stable + } lang = settings->value("personal/lang").toString(); keepalive = settings->value("personal/keepalive", 3).toInt(); diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 41113185c..2e135d170 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -938,9 +938,25 @@ void MainWindow::startupConfigCheck() const auto reloadOk0 = QtConcurrent::run([] { CardDatabaseManager::getInstance()->loadCardDatabases(); }); } - qCInfo(WindowMainStartupShortcutsLog) << "[MainWindow] Migrating shortcuts after update detected."; + qCInfo(WindowMainStartupShortcutsLog) << "Migrating shortcuts after update detected."; SettingsCache::instance().shortcuts().migrateShortcuts(); + if (SettingsCache::instance().getCheckUpdatesOnStartup()) { + if (QString(VERSION_STRING).contains("custom", Qt::CaseInsensitive)) { + qCInfo(WindowMainStartupShortcutsLog) << "Update has changed to custom version, disabling auto update"; + SettingsCache::instance().setCheckUpdatesOnStartup(Qt::Unchecked); + } else { + int channel = 0; + if (QString(VERSION_STRING).contains("beta", Qt::CaseInsensitive)) { + channel = 1; + } + if (SettingsCache::instance().getUpdateReleaseChannelIndex() != channel) { + qCInfo(WindowMainStartupShortcutsLog) << "Update has changed beta state, updating release channel."; + SettingsCache::instance().setUpdateReleaseChannelIndex(channel); + } + } + } + SettingsCache::instance().setClientVersion(VERSION_STRING); } else { // previous config from this version found From 28c800dd3795f736d72b86ea3bbe8b6bd420f9ec Mon Sep 17 00:00:00 2001 From: tooomm Date: Wed, 31 Dec 2025 17:56:24 +0100 Subject: [PATCH 101/325] Docs: Add & link xsd schema information (#6439) * Add xsd scheme information * further references as list --- .../card_database_schema_and_parsing.md | 13 +++++++++++++ .../deck_management/creating_decks.md | 8 +++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/doc/doxygen/extra-pages/developer_documentation/card_database_schema_and_parsing.md b/doc/doxygen/extra-pages/developer_documentation/card_database_schema_and_parsing.md index 1de6fda4f..7b4bdafc3 100644 --- a/doc/doxygen/extra-pages/developer_documentation/card_database_schema_and_parsing.md +++ b/doc/doxygen/extra-pages/developer_documentation/card_database_schema_and_parsing.md @@ -1,3 +1,16 @@ @page card_database_schema_and_parsing Card Database Schema and Parsing +# Card Database Schemas + +Cockatrice uses `XML files` to store information of available cards to be used in the app (`cards.xml`). +The token file follows the schema of the card database (`tokens.xml`), too. + +Saved decks (`.xml`) use a different schema. + +- [XSD Schema for `Card Databases`](https://github.com/Cockatrice/Cockatrice/blob/master/doc/carddatabase_v4/cards.xsd) v4 +- [XSD Schema for `Decks`](https://github.com/Cockatrice/Cockatrice/blob/master/doc/deck.xsd) v1 + + +# Card Database Parsing + TODO diff --git a/doc/doxygen/extra-pages/user_documentation/deck_management/creating_decks.md b/doc/doxygen/extra-pages/user_documentation/deck_management/creating_decks.md index bfe578afd..153320c3d 100644 --- a/doc/doxygen/extra-pages/user_documentation/deck_management/creating_decks.md +++ b/doc/doxygen/extra-pages/user_documentation/deck_management/creating_decks.md @@ -8,10 +8,8 @@ by selecting "Deck Editor" or "Visual Deck Editor" under the "Tabs" application # Further References -See @ref editing_decks for information on how to modify the attributes and contents of a deck in the Deck Editor +- See @ref editing_decks for information on how to modify the attributes and contents of a deck in the Deck Editor widgets. - -See @ref exporting_decks for information on how to store and persist your deck either in-client or to external +- See @ref exporting_decks for information on how to store and persist your deck either in-client or to external services. - -See @ref importing_decks for information on how to import existing decks either in-client or from external services \ No newline at end of file +- See @ref importing_decks for information on how to import existing decks either in-client or from external services From 36d82807652961a80787f1ba939adff04ff76be8 Mon Sep 17 00:00:00 2001 From: tooomm Date: Wed, 31 Dec 2025 17:57:19 +0100 Subject: [PATCH 102/325] Readme: Reorder `Contribute` section (#6435) * Reorder contribute section * fix * Wording updates in related section --- README.md | 46 +++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 3d01e9f3d..c08aee0e7 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,10 @@ Latest beta version: # Related Repositories -- [Magic-Token](https://github.com/Cockatrice/Magic-Token): MtG token data to use in Cockatrice -- [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Script to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) to use in Cockatrice +- [Magic-Token](https://github.com/Cockatrice/Magic-Token): File with MtG token data for use in Cockatrice +- [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Code to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) for use in Cockatrice - [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official Cockatrice webpage -- [Cockatrice @Flathub](https://github.com/flathub/io.github.Cockatrice.cockatrice): Configuration for our Linux `flatpak` package +- [io.github.Cockatrice.cockatrice](https://github.com/flathub/io.github.Cockatrice.cockatrice): Configuration of our Linux `flatpak` package hosted at [Flathub](https://flathub.org/en/apps/io.github.Cockatrice.cockatrice) # Community Resources [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA) @@ -55,7 +55,6 @@ Latest beta version: Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other projet contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out. - [Official Website](https://cockatrice.github.io) - [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki) -- [Official Code Documentation](https://cockatrice.github.io/docs) - [Official Discord](https://discord.gg/3Z9yzmA) - [reddit r/Cockatrice](https://reddit.com/r/cockatrice) @@ -64,6 +63,23 @@ Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other p # Contribute +

    + Code | + Documentation | + Translation +

    + +#### Repository Activity +![Cockatrice Repo Analytics](https://repobeats.axiom.co/api/embed/c7cec938789a5bbaeb4182a028b4dbb96db8f181.svg "Cockatrice Repo Analytics by Repobeats") + +
    +Kudos to all our amazing contributors ❤️ +
    + + +
    + Made with contrib.rocks +
    ### Code @@ -79,21 +95,17 @@ We'll happily advice on how best to implement a feature, or we can show you wher You can also have a look at our `Todo List` in our [Code Documentation](https://cockatrice.github.io/docs) or search the repo for [`\todo` comments](https://github.com/search?q=repo%3ACockatrice%2FCockatrice%20%5Ctodo&type=code). +### Documentation [![CI Docs](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml/badge.svg?event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml?query=event%3Apush) + +There are various places where useful information for different needs are maintained: +- [Official Code Documentation](https://cockatrice.github.io/docs/) +- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki) `Community supported` +- [Official Webpage](https://cockatrice.github.io/) +- [Official README](https://github.com/Cockatrice/Cockatrice/blob/master/README.md) `This file` + Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide. -#### Repository Activity -![Cockatrice Repo Analytics](https://repobeats.axiom.co/api/embed/c7cec938789a5bbaeb4182a028b4dbb96db8f181.svg "Cockatrice Repo Analytics by Repobeats") - -
    -Kudos to all our amazing contributors ❤️ -
    - - -
    - Made with contrib.rocks -
    - -### Translations [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://explore.transifex.com/cockatrice/cockatrice/) +### Translation [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://explore.transifex.com/cockatrice/cockatrice/) Cockatrice uses Transifex to manage translations. You can help us bring Cockatrice, Oracle and Webatrice to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/).
    From df9a8b22723cbda5e8d5bd9742457d3efc194d10 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Wed, 31 Dec 2025 19:45:49 +0100 Subject: [PATCH 103/325] [VDE] Deck Analytics Widgets overhaul (#6463) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [VDE] Deck Analytics Widgets overhaul Took 2 minutes Took 3 minutes Took 3 minutes * Qt5 version guards. Took 33 minutes Took 3 seconds * Include QtMath Took 3 minutes Took 8 seconds * Use getCards() Took 4 minutes * Non pointer stuff Took 52 seconds * Add a newline to the tooltip Took 2 minutes Took 27 seconds * Fix build failure on macOS 15 * Rename some things. Took 17 minutes Took 11 seconds Took 18 seconds * Address overloads, fix default configuration. Took 1 hour 9 minutes Took 8 seconds * Fix mana curve default config. Took 4 minutes * Namespace to Qt libs Took 5 minutes * Selection overlay is transparent for mouse events. Took 2 minutes * Brace initialize. Took 8 minutes * Debian 11. Took 5 minutes --------- Co-authored-by: Lukas Brübach Co-authored-by: RickyRister --- cockatrice/CMakeLists.txt | 36 +- .../abstract_analytics_panel_widget.cpp | 48 +++ .../abstract_analytics_panel_widget.h | 61 +++ .../add_analytics_panel_dialog.cpp | 32 ++ .../add_analytics_panel_dialog.h | 29 ++ .../analytics_panel_widget_factory.cpp | 33 ++ .../analytics_panel_widget_factory.h | 44 +++ .../analytics_panel_widget_registrar.cpp | 1 + .../analytics_panel_widget_registrar.h | 17 + .../draw_probability_config.cpp | 28 ++ .../draw_probability_config.h | 19 + .../draw_probability_config_dialog.cpp | 92 +++++ .../draw_probability_config_dialog.h | 44 +++ .../draw_probability_widget.cpp | 236 +++++++++++ .../draw_probability_widget.h | 54 +++ .../mana_base/mana_base_config.cpp | 32 ++ .../mana_base/mana_base_config.h | 19 + .../mana_base/mana_base_config_dialog.cpp | 67 ++++ .../mana_base/mana_base_config_dialog.h | 42 ++ .../mana_base/mana_base_widget.cpp | 115 ++++++ .../mana_base/mana_base_widget.h | 51 +++ .../mana_curve/mana_curve_category_widget.cpp | 121 ++++++ .../mana_curve/mana_curve_category_widget.h | 32 ++ .../mana_curve/mana_curve_config.cpp | 41 ++ .../mana_curve/mana_curve_config.h | 21 + .../mana_curve/mana_curve_config_dialog.cpp | 91 +++++ .../mana_curve/mana_curve_config_dialog.h | 44 +++ .../mana_curve/mana_curve_total_widget.cpp | 78 ++++ .../mana_curve/mana_curve_total_widget.h | 32 ++ .../mana_curve/mana_curve_widget.cpp | 148 +++++++ .../mana_curve/mana_curve_widget.h | 50 +++ .../mana_devotion/mana_devotion_config.cpp | 31 ++ .../mana_devotion/mana_devotion_config.h | 18 + .../mana_devotion_config_dialog.cpp | 62 +++ .../mana_devotion_config_dialog.h | 42 ++ .../mana_devotion/mana_devotion_widget.cpp | 123 ++++++ .../mana_devotion/mana_devotion_widget.h | 45 +++ .../mana_distribution_config.cpp | 36 ++ .../mana_distribution_config.h | 20 + .../mana_distribution_config_dialog.cpp | 83 ++++ .../mana_distribution_config_dialog.h | 45 +++ ...ana_distribution_single_display_widget.cpp | 49 +++ .../mana_distribution_single_display_widget.h | 28 ++ .../mana_distribution_widget.cpp | 129 ++++++ .../mana_distribution_widget.h | 45 +++ .../deck_analytics/deck_analytics_widget.cpp | 307 +++++++++++++-- .../deck_analytics/deck_analytics_widget.h | 63 ++- .../deck_list_statistics_analyzer.cpp | 141 ++++++- .../deck_list_statistics_analyzer.h | 129 +++++- .../deck_analytics/mana_base_widget.cpp | 71 ---- .../widgets/deck_analytics/mana_base_widget.h | 38 -- .../deck_analytics/mana_curve_widget.cpp | 68 ---- .../deck_analytics/mana_curve_widget.h | 37 -- .../deck_analytics/mana_devotion_widget.cpp | 66 ---- .../deck_analytics/mana_devotion_widget.h | 37 -- .../deck_analytics/resizable_panel.cpp | 367 ++++++++++++++++++ .../widgets/deck_analytics/resizable_panel.h | 79 ++++ .../bars/bar_chart_background_widget.cpp | 43 ++ .../charts/bars/bar_chart_background_widget.h | 23 ++ .../display/charts/bars/bar_chart_widget.cpp | 215 ++++++++++ .../display/charts/bars/bar_chart_widget.h | 52 +++ .../display/{ => charts/bars}/bar_widget.cpp | 0 .../display/{ => charts/bars}/bar_widget.h | 0 .../display/{ => charts/bars}/color_bar.cpp | 1 - .../display/{ => charts/bars}/color_bar.h | 0 .../{ => charts/bars}/percent_bar_widget.cpp | 0 .../{ => charts/bars}/percent_bar_widget.h | 0 .../charts/bars/segmented_bar_widget.cpp | 140 +++++++ .../charts/bars/segmented_bar_widget.h | 38 ++ .../general/display/charts/pies/color_pie.cpp | 205 ++++++++++ .../general/display/charts/pies/color_pie.h | 44 +++ ...api_response_deck_entry_display_widget.cpp | 2 +- ...i_response_card_inclusion_display_widget.h | 2 +- ...api_response_card_synergy_display_widget.h | 2 +- .../tab_deck_editor_visual.cpp | 2 +- .../tab_deck_editor_visual_tab_widget.cpp | 7 +- .../tab_deck_editor_visual_tab_widget.h | 3 +- .../visual_deck_editor_sample_hand_widget.cpp | 11 +- .../visual_deck_editor_sample_hand_widget.h | 8 +- .../libcockatrice/utility/color.h | 37 ++ .../libcockatrice/utility/qt_utils.h | 14 + 81 files changed, 4372 insertions(+), 394 deletions(-) create mode 100644 cockatrice/src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/add_analytics_panel_dialog.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/add_analytics_panel_dialog.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_factory.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_factory.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_registrar.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_registrar.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config_dialog.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config_dialog.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_widget.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_widget.h delete mode 100644 cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.cpp delete mode 100644 cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.h delete mode 100644 cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.cpp delete mode 100644 cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.h delete mode 100644 cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp delete mode 100644 cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.h create mode 100644 cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp create mode 100644 cockatrice/src/interface/widgets/deck_analytics/resizable_panel.h create mode 100644 cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_background_widget.cpp create mode 100644 cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_background_widget.h create mode 100644 cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp create mode 100644 cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.h rename cockatrice/src/interface/widgets/general/display/{ => charts/bars}/bar_widget.cpp (100%) rename cockatrice/src/interface/widgets/general/display/{ => charts/bars}/bar_widget.h (100%) rename cockatrice/src/interface/widgets/general/display/{ => charts/bars}/color_bar.cpp (99%) rename cockatrice/src/interface/widgets/general/display/{ => charts/bars}/color_bar.h (100%) rename cockatrice/src/interface/widgets/general/display/{ => charts/bars}/percent_bar_widget.cpp (100%) rename cockatrice/src/interface/widgets/general/display/{ => charts/bars}/percent_bar_widget.h (100%) create mode 100644 cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp create mode 100644 cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.h create mode 100644 cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.cpp create mode 100644 cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index c6a29969f..8ebd177db 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -144,11 +144,31 @@ set(cockatrice_SOURCES src/interface/widgets/cards/card_size_widget.cpp src/interface/widgets/cards/deck_card_zone_display_widget.cpp src/interface/widgets/cards/deck_preview_card_picture_widget.cpp + src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.cpp + src/interface/widgets/deck_analytics/add_analytics_panel_dialog.cpp + src/interface/widgets/deck_analytics/analytics_panel_widget_factory.cpp + src/interface/widgets/deck_analytics/analytics_panel_widget_registrar.cpp src/interface/widgets/deck_analytics/deck_analytics_widget.cpp src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp - src/interface/widgets/deck_analytics/mana_base_widget.cpp - src/interface/widgets/deck_analytics/mana_curve_widget.cpp - src/interface/widgets/deck_analytics/mana_devotion_widget.cpp + src/interface/widgets/deck_analytics/resizable_panel.cpp + src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config.cpp + src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config_dialog.cpp + src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp + src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config.cpp + src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.cpp + src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp + src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config.cpp + src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.cpp + src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.cpp + src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config.cpp + src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.cpp + src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.cpp + src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.cpp + src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.cpp + src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_widget.cpp + src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp + src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp + src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp @@ -160,13 +180,17 @@ set(cockatrice_SOURCES src/interface/widgets/general/background_sources.cpp src/interface/widgets/general/display/background_plate_widget.cpp src/interface/widgets/general/display/banner_widget.cpp - src/interface/widgets/general/display/bar_widget.cpp - src/interface/widgets/general/display/color_bar.cpp src/interface/widgets/general/display/dynamic_font_size_label.cpp src/interface/widgets/general/display/dynamic_font_size_push_button.cpp src/interface/widgets/general/display/labeled_input.cpp - src/interface/widgets/general/display/percent_bar_widget.cpp src/interface/widgets/general/display/shadow_background_label.cpp + src/interface/widgets/general/display/charts/bars/bar_widget.cpp + src/interface/widgets/general/display/charts/bars/color_bar.cpp + src/interface/widgets/general/display/charts/bars/percent_bar_widget.cpp + src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp + src/interface/widgets/general/display/charts/bars/bar_chart_background_widget.cpp + src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp + src/interface/widgets/general/display/charts/pies/color_pie.cpp src/interface/widgets/general/home_styled_button.cpp src/interface/widgets/general/home_widget.cpp src/interface/widgets/general/layout_containers/flow_widget.cpp 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 new file mode 100644 index 000000000..bad883d27 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.cpp @@ -0,0 +1,48 @@ +#include "abstract_analytics_panel_widget.h" + +#include "deck_list_statistics_analyzer.h" + +#include + +AbstractAnalyticsPanelWidget::AbstractAnalyticsPanelWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer) + : QWidget(parent), analyzer(analyzer) +{ + layout = new QVBoxLayout(this); + + bannerAndSettingsContainer = new QWidget(this); + + bannerAndSettingsLayout = new QHBoxLayout(bannerAndSettingsContainer); + bannerAndSettingsContainer->setLayout(bannerAndSettingsLayout); + bannerWidget = new BannerWidget(this, "Analytics Widget", Qt::Vertical, 100); + bannerWidget->setMaximumHeight(100); + + bannerAndSettingsLayout->addWidget(bannerWidget, 1); + + // config button + configureButton = new QPushButton(tr("Configure"), this); + configureButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + connect(configureButton, &QPushButton::clicked, this, &AbstractAnalyticsPanelWidget::applyConfigFromDialog); + bannerAndSettingsLayout->addWidget(configureButton, 0); + + layout->addWidget(bannerAndSettingsContainer); + + connect(analyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &AbstractAnalyticsPanelWidget::updateDisplay); +} + +bool AbstractAnalyticsPanelWidget::applyConfigFromDialog() +{ + QDialog *dlg = createConfigDialog(this); + if (!dlg) { + return false; + } + + bool ok = dlg->exec() == QDialog::Accepted; + if (ok) { + // dialog must expose its final config as JSON + auto newCfg = extractConfigFromDialog(dlg); + loadConfig(newCfg); + updateDisplay(); + } + dlg->deleteLater(); + return ok; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.h b/cockatrice/src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.h new file mode 100644 index 000000000..23374a9e1 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.h @@ -0,0 +1,61 @@ +#ifndef COCKATRICE_DECK_ANALYTICS_WIDGET_BASE_H +#define COCKATRICE_DECK_ANALYTICS_WIDGET_BASE_H + +#include "../general/display/banner_widget.h" + +#include +#include +#include +#include + +class DeckListStatisticsAnalyzer; + +class AbstractAnalyticsPanelWidget : public QWidget +{ + Q_OBJECT +public slots: + virtual void updateDisplay() = 0; + // Widgets must return a config dialog + virtual QDialog *createConfigDialog(QWidget *parent) = 0; + +public: + explicit AbstractAnalyticsPanelWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer); + + void setDisplayTitle(const QString &title) + { + displayTitle = title; + if (bannerWidget) { + bannerWidget->setText(displayTitle); + } + } + + QString displayTitleText() const + { + return displayTitle; + } + + virtual QJsonObject saveConfig() const + { + return {}; + } + virtual void loadConfig(const QJsonObject &) + { + } + + // Unified helper to run config dialog and update widget + bool applyConfigFromDialog(); + + // Dialog → JSON must be supplied by each subclass + virtual QJsonObject extractConfigFromDialog(QDialog *dlg) const = 0; + +protected: + DeckListStatisticsAnalyzer *analyzer; + QVBoxLayout *layout; + QWidget *bannerAndSettingsContainer; + QHBoxLayout *bannerAndSettingsLayout; + QString displayTitle; + BannerWidget *bannerWidget; + QPushButton *configureButton; +}; + +#endif // COCKATRICE_DECK_ANALYTICS_WIDGET_BASE_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/add_analytics_panel_dialog.cpp b/cockatrice/src/interface/widgets/deck_analytics/add_analytics_panel_dialog.cpp new file mode 100644 index 000000000..c83e6d982 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/add_analytics_panel_dialog.cpp @@ -0,0 +1,32 @@ +#include "add_analytics_panel_dialog.h" + +#include "analytics_panel_widget_factory.h" + +#include +#include + +AddAnalyticsPanelDialog::AddAnalyticsPanelDialog(QWidget *parent) : QDialog(parent) +{ + setWindowTitle(tr("Add Analytics Panel")); + + layout = new QVBoxLayout(this); + + typeCombo = new QComboBox(this); + + // Populate using descriptors + const auto widgets = AnalyticsPanelWidgetFactory::instance().availableWidgets(); + + for (const auto &desc : widgets) { + // Show translated title to user + typeCombo->addItem(desc.title, desc.type); + } + + layout->addWidget(typeCombo); + + buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + + layout->addWidget(buttons); + + connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/add_analytics_panel_dialog.h b/cockatrice/src/interface/widgets/deck_analytics/add_analytics_panel_dialog.h new file mode 100644 index 000000000..aa44734c2 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/add_analytics_panel_dialog.h @@ -0,0 +1,29 @@ + +#ifndef COCKATRICE_ADD_ANALYTICS_PANEL_DIALOG_H +#define COCKATRICE_ADD_ANALYTICS_PANEL_DIALOG_H + +#include "analytics_panel_widget_factory.h" + +#include +#include +#include +#include + +class AddAnalyticsPanelDialog : public QDialog +{ + Q_OBJECT +public: + explicit AddAnalyticsPanelDialog(QWidget *parent); + + QString selectedType() const + { + return typeCombo->currentData().toString(); + } + +private: + QVBoxLayout *layout; + QComboBox *typeCombo; + QDialogButtonBox *buttons; +}; + +#endif // COCKATRICE_ADD_ANALYTICS_PANEL_DIALOG_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_factory.cpp b/cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_factory.cpp new file mode 100644 index 000000000..7af641689 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_factory.cpp @@ -0,0 +1,33 @@ +#include "analytics_panel_widget_factory.h" + +#include "abstract_analytics_panel_widget.h" + +AnalyticsPanelWidgetFactory &AnalyticsPanelWidgetFactory::instance() +{ + static AnalyticsPanelWidgetFactory f; + return f; +} + +void AnalyticsPanelWidgetFactory::registerWidget(const Descriptor &desc) +{ + widgets.insert(desc.type, desc); +} + +AbstractAnalyticsPanelWidget * +AnalyticsPanelWidgetFactory::create(const QString &type, QWidget *parent, DeckListStatisticsAnalyzer *analyzer) const +{ + auto it = widgets.find(type); + if (it == widgets.end()) + return nullptr; + + auto w = it->creator(parent, analyzer); + + w->setDisplayTitle(it->title); + + return w; +} + +QList AnalyticsPanelWidgetFactory::availableWidgets() const +{ + return widgets.values(); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_factory.h b/cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_factory.h new file mode 100644 index 000000000..6c5856d70 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_factory.h @@ -0,0 +1,44 @@ +#ifndef COCKATRICE_DECK_ANALYTICS_WIDGET_FACTORY_H +#define COCKATRICE_DECK_ANALYTICS_WIDGET_FACTORY_H + +#include +#include +#include +#include +#include + +class AbstractAnalyticsPanelWidget; +class DeckListStatisticsAnalyzer; + +class AnalyticsPanelWidgetFactory +{ +public: + using Creator = std::function; + + struct Descriptor + { + QString type; // stable ID ("manaProdDevotion") + QString title; // translated, user-facing + Creator creator; + }; + + static AnalyticsPanelWidgetFactory &instance(); + + // NEW: richer registration + void registerWidget(const Descriptor &desc); + + AbstractAnalyticsPanelWidget * + create(const QString &type, QWidget *parent, DeckListStatisticsAnalyzer *analyzer) const; + + // NEW: expose widgets to UI + QList availableWidgets() const; + +private: + AnalyticsPanelWidgetFactory() = default; // Ensure private constructor + AnalyticsPanelWidgetFactory(const AnalyticsPanelWidgetFactory &) = delete; + AnalyticsPanelWidgetFactory &operator=(const AnalyticsPanelWidgetFactory &) = delete; + + QMap widgets; +}; + +#endif diff --git a/cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_registrar.cpp b/cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_registrar.cpp new file mode 100644 index 000000000..d4129a3d0 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_registrar.cpp @@ -0,0 +1 @@ +#include "analytics_panel_widget_registrar.h" diff --git a/cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_registrar.h b/cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_registrar.h new file mode 100644 index 000000000..70d6df94f --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analytics_panel_widget_registrar.h @@ -0,0 +1,17 @@ +#ifndef COCKATRICE_DECK_ANALYTICS_WIDGET_REGISTRAR_H +#define COCKATRICE_DECK_ANALYTICS_WIDGET_REGISTRAR_H + +#include "analytics_panel_widget_factory.h" + +class AnalyticsPanelWidgetRegistrar +{ +public: + AnalyticsPanelWidgetRegistrar(const QString &type, + const QString &title, + AnalyticsPanelWidgetFactory::Creator creator) + { + AnalyticsPanelWidgetFactory::instance().registerWidget({type, title, creator}); + } +}; + +#endif // COCKATRICE_DECK_ANALYTICS_WIDGET_REGISTRAR_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config.cpp new file mode 100644 index 000000000..51129a4b4 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config.cpp @@ -0,0 +1,28 @@ +#include "draw_probability_config.h" + +QJsonObject DrawProbabilityConfig::toJson() const +{ + QJsonObject o; + o["criteria"] = criteria; + o["atLeast"] = atLeast; + o["quantity"] = quantity; + o["drawn"] = drawn; + return o; +} +DrawProbabilityConfig DrawProbabilityConfig::fromJson(const QJsonObject &o) +{ + DrawProbabilityConfig cfg; + if (o.contains("criteria")) { + cfg.criteria = o["criteria"].toString(); + } + if (o.contains("atLeast")) { + cfg.atLeast = o["atLeast"].toBool(true); + } + if (o.contains("quantity")) { + cfg.quantity = o["quantity"].toInt(1); + } + if (o.contains("drawn")) { + cfg.drawn = o["drawn"].toInt(7); + } + return cfg; +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config.h new file mode 100644 index 000000000..bbe61a68e --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config.h @@ -0,0 +1,19 @@ +#ifndef COCKATRICE_DRAW_PROBABILITY_CONFIG_H +#define COCKATRICE_DRAW_PROBABILITY_CONFIG_H + +#include +#include + +struct DrawProbabilityConfig +{ + QString criteria = "name"; // name, type, subtype, cmc + bool atLeast = true; // true = at least, false = exactly + int quantity = 1; // N + int drawn = 7; // M + + QJsonObject toJson() const; + + static DrawProbabilityConfig fromJson(const QJsonObject &o); +}; + +#endif diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config_dialog.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config_dialog.cpp new file mode 100644 index 000000000..71f88c0fc --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config_dialog.cpp @@ -0,0 +1,92 @@ +#include "draw_probability_config_dialog.h" + +#include +#include +#include +#include +#include + +DrawProbabilityConfigDialog::DrawProbabilityConfigDialog(QWidget *parent) : QDialog(parent) +{ + form = new QFormLayout(this); + + // Criteria + labelCriteria = new QLabel(this); + criteria = new QComboBox(this); + criteria->addItem(QString(), "name"); + criteria->addItem(QString(), "type"); + criteria->addItem(QString(), "subtype"); + criteria->addItem(QString(), "cmc"); + form->addRow(labelCriteria, criteria); + + // Exactness + labelExactness = new QLabel(this); + exactness = new QComboBox(this); + exactness->addItem(QString(), true); + exactness->addItem(QString(), false); + form->addRow(labelExactness, exactness); + + // Quantity + labelQuantity = new QLabel(this); + quantity = new QSpinBox(this); + quantity->setRange(1, 60); + form->addRow(labelQuantity, quantity); + + // Drawn + labelDrawn = new QLabel(this); + drawn = new QSpinBox(this); + drawn->setRange(1, 60); + drawn->setValue(7); + form->addRow(labelDrawn, drawn); + + // Button box + auto *bb = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + form->addWidget(bb); + + connect(bb, &QDialogButtonBox::accepted, this, &DrawProbabilityConfigDialog::accept); + connect(bb, &QDialogButtonBox::rejected, this, &QDialog::reject); + + retranslateUi(); +} + +void DrawProbabilityConfigDialog::retranslateUi() +{ + setWindowTitle(tr("Draw Probability Settings")); + + labelCriteria->setText(tr("Criteria:")); + criteria->setItemText(0, tr("Card Name")); + criteria->setItemText(1, tr("Type")); + criteria->setItemText(2, tr("Subtype")); + criteria->setItemText(3, tr("Mana Value")); + + labelExactness->setText(tr("Exactness:")); + exactness->setItemText(0, tr("At least")); + exactness->setItemText(1, tr("Exactly")); + + labelQuantity->setText(tr("Quantity (N):")); + labelDrawn->setText(tr("Cards drawn (M):")); + + // i18n-friendly suffixes + quantity->setSuffix(tr(" cards")); + drawn->setSuffix(tr(" cards")); +} + +void DrawProbabilityConfigDialog::setFromConfig(const DrawProbabilityConfig &_config) +{ + cfg = _config; + + criteria->setCurrentIndex(criteria->findData(_config.criteria)); + exactness->setCurrentIndex(exactness->findData(_config.atLeast)); + quantity->setValue(_config.quantity); + drawn->setValue(_config.drawn); +} + +void DrawProbabilityConfigDialog::accept() +{ + cfg.criteria = criteria->currentData().toString(); + cfg.atLeast = exactness->currentData().toBool(); + cfg.quantity = quantity->value(); + cfg.drawn = drawn->value(); + + QDialog::accept(); +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config_dialog.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config_dialog.h new file mode 100644 index 000000000..44b1f0eec --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config_dialog.h @@ -0,0 +1,44 @@ +#pragma once + +#include "draw_probability_config.h" + +#include +#include + +class QComboBox; +class QSpinBox; +class QLabel; + +class DrawProbabilityConfigDialog : public QDialog +{ + Q_OBJECT +public: + explicit DrawProbabilityConfigDialog(QWidget *parent = nullptr); + + void retranslateUi(); + + void setFromConfig(const DrawProbabilityConfig &_config); + DrawProbabilityConfig result() const + { + return cfg; + } + +protected: + void accept() override; + +private: + DrawProbabilityConfig cfg; + + QFormLayout *form; + + // Widgets + QComboBox *criteria; + QComboBox *exactness; + QSpinBox *quantity; + QSpinBox *drawn; + + QLabel *labelCriteria; + QLabel *labelExactness; + QLabel *labelQuantity; + QLabel *labelDrawn; +}; diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp new file mode 100644 index 000000000..a8bec834f --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp @@ -0,0 +1,236 @@ +#include "draw_probability_widget.h" + +#include "draw_probability_config_dialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DrawProbabilityWidget::DrawProbabilityWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer) + : AbstractAnalyticsPanelWidget(parent, analyzer) +{ + controls = new QWidget(this); + controlLayout = new QHBoxLayout(controls); + + labelPrefix = new QLabel(this); + controlLayout->addWidget(labelPrefix); + + criteriaCombo = new QComboBox(this); + // Give these things item-data so we can translate the actual user-facing strings + criteriaCombo->addItem(QString(), "name"); + criteriaCombo->addItem(QString(), "type"); + criteriaCombo->addItem(QString(), "subtype"); + criteriaCombo->addItem(QString(), "cmc"); + controlLayout->addWidget(criteriaCombo); + + exactnessCombo = new QComboBox(this); + exactnessCombo->addItem(QString(), true); // At least + exactnessCombo->addItem(QString(), false); // Exactly + controlLayout->addWidget(exactnessCombo); + + quantitySpin = new QSpinBox(this); + quantitySpin->setRange(1, 60); + controlLayout->addWidget(quantitySpin); + + labelMiddle = new QLabel(this); + controlLayout->addWidget(labelMiddle); + + drawnSpin = new QSpinBox(this); + drawnSpin->setRange(1, 60); + drawnSpin->setValue(7); + controlLayout->addWidget(drawnSpin); + + labelSuffix = new QLabel(this); + controlLayout->addWidget(labelSuffix); + + labelPrefix->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + labelMiddle->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + labelSuffix->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + + controlLayout->addStretch(1); + layout->addWidget(controls); + + // Table + resultTable = new QTableWidget(this); + resultTable->setColumnCount(3); + resultTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + layout->addWidget(resultTable); + + // Connections + connect(criteriaCombo, QOverload::of(&QComboBox::currentIndexChanged), this, [this] { + config.criteria = criteriaCombo->currentData().toString(); + updateDisplay(); + }); + + connect(exactnessCombo, QOverload::of(&QComboBox::currentIndexChanged), this, [this] { + config.atLeast = exactnessCombo->currentData().toBool(); + updateDisplay(); + }); + + connect(quantitySpin, QOverload::of(&QSpinBox::valueChanged), this, [this](int v) { + config.quantity = v; + updateDisplay(); + }); + + connect(drawnSpin, QOverload::of(&QSpinBox::valueChanged), this, [this](int v) { + config.drawn = v; + updateDisplay(); + }); + + retranslateUi(); + applyConfigToToolbar(); + updateFilterOptions(); +} + +void DrawProbabilityWidget::retranslateUi() +{ + bannerWidget->setText(tr("Draw Probability")); + + labelPrefix->setText(tr("Probability of drawing")); + + criteriaCombo->setItemText(0, tr("Card Name")); + criteriaCombo->setItemText(1, tr("Type")); + criteriaCombo->setItemText(2, tr("Subtype")); + criteriaCombo->setItemText(3, tr("Mana Value")); + + exactnessCombo->setItemText(0, tr("At least")); + exactnessCombo->setItemText(1, tr("Exactly")); + + labelMiddle->setText(tr("card(s) having drawn at least")); + labelSuffix->setText(tr("cards")); + + resultTable->setHorizontalHeaderLabels({tr("Category"), tr("Qty"), tr("Odds (%)")}); +} + +QDialog *DrawProbabilityWidget::createConfigDialog(QWidget *parent) +{ + auto *dlg = new DrawProbabilityConfigDialog(parent); + dlg->setFromConfig(config); + return dlg; +} + +QJsonObject DrawProbabilityWidget::extractConfigFromDialog(QDialog *dlg) const +{ + auto *dp = qobject_cast(dlg); + return dp ? dp->result().toJson() : QJsonObject{}; +} + +void DrawProbabilityWidget::applyConfigToToolbar() +{ + auto setComboByData = [](QComboBox *combo, const QVariant &value) { + int idx = combo->findData(value); + if (idx >= 0) { + combo->setCurrentIndex(idx); + } + }; + + setComboByData(criteriaCombo, config.criteria); + setComboByData(exactnessCombo, config.atLeast); + + quantitySpin->setValue(config.quantity); + drawnSpin->setValue(config.drawn); +} + +void DrawProbabilityWidget::updateDisplay() +{ + updateFilterOptions(); +} + +void DrawProbabilityWidget::loadConfig(const QJsonObject &cfg) +{ + config = DrawProbabilityConfig::fromJson(cfg); + applyConfigToToolbar(); + updateFilterOptions(); +} + +void DrawProbabilityWidget::updateFilterOptions() +{ + if (!analyzer->getModel()->getDeckList()) { + return; + } + + const QString criteria = config.criteria; + const bool atLeast = config.atLeast; + const int quantity = config.quantity; + const int drawn = config.drawn; + + QMap categoryCounts; + int totalDeckCards = 0; + + const auto nodes = analyzer->getModel()->getDeckList()->getCardNodes(); + for (auto *node : nodes) { + CardInfoPtr info = CardDatabaseManager::query()->getCard({node->getName()}).getCardPtr(); + if (!info) { + continue; + } + + totalDeckCards += node->getNumber(); + + QStringList categories; + if (criteria == "name") { + categories << info->getName(); + } else if (criteria == "type") { + categories = info->getMainCardType().split(' ', Qt::SkipEmptyParts); + } else if (criteria == "subtype") { + categories = info->getCardType().split(' ', Qt::SkipEmptyParts); + } else if (criteria == "cmc") { + categories << QString::number(info->getCmc().toInt()); + } + + for (const QString &cat : categories) { + categoryCounts[cat] += node->getNumber(); + } + } + + resultTable->setRowCount(categoryCounts.size()); + + int row = 0; + for (auto it = categoryCounts.cbegin(); it != categoryCounts.cend(); ++it, ++row) { + const QString &cat = it.key(); + const int copies = it.value(); + + double probability = 0.0; + if (atLeast) { + for (int k = quantity; k <= drawn && k <= copies; ++k) { + probability += hypergeometricProbability(totalDeckCards, copies, drawn, k); + } + } else { + probability = hypergeometricProbability(totalDeckCards, copies, drawn, quantity); + } + + resultTable->setItem(row, 0, new QTableWidgetItem(cat)); + resultTable->setItem(row, 1, new QTableWidgetItem(QString::number(copies))); + resultTable->setItem(row, 2, new QTableWidgetItem(QString::number(probability * 100.0, 'f', 2))); + } +} + +double DrawProbabilityWidget::hypergeometricProbability(int N, int K, int n, int k) +{ + if (k < 0 || k > n || K > N || n > N) { + return 0.0; + } + + double logP = 0.0; + for (int i = 1; i <= k; ++i) { + logP += qLn(double(K - k + i) / i); + } + for (int i = 1; i <= n - k; ++i) { + logP += qLn(double(N - K - (n - k) + i) / i); + } + for (int i = 1; i <= n; ++i) { + logP -= qLn(double(N - n + i) / i); + } + + return qExp(logP); +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.h new file mode 100644 index 000000000..80015999f --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.h @@ -0,0 +1,54 @@ +#ifndef COCKATRICE_DRAW_PROBABILITY_WIDGET_H +#define COCKATRICE_DRAW_PROBABILITY_WIDGET_H + +#include "../../abstract_analytics_panel_widget.h" +#include "../../deck_list_statistics_analyzer.h" +#include "draw_probability_config.h" + +#include +#include +#include +#include + +class DrawProbabilityWidget : public AbstractAnalyticsPanelWidget +{ + Q_OBJECT +public: + DrawProbabilityWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer); + + QDialog *createConfigDialog(QWidget *parent) override; + QJsonObject extractConfigFromDialog(QDialog *dlg) const override; + void applyConfigToToolbar(); + +public slots: + void updateDisplay() override; + void loadConfig(const QJsonObject &cfg) override; + void retranslateUi(); + +private slots: + void updateFilterOptions(); + +private: + DrawProbabilityConfig config; + + QWidget *controls; + QHBoxLayout *controlLayout; + QLabel *labelPrefix; + QLabel *labelMiddle; + QLabel *labelSuffix; + QLineEdit *cardNameEdit; + QComboBox *criteriaCombo; // Card Name / Type / Subtype / Mana Value + QComboBox *filterCombo; // The actual value + QComboBox *exactnessCombo; // At least / Exactly + QSpinBox *quantitySpin; // N + QSpinBox *drawnSpin; // M + + QSpinBox *manaValueSpin; + + QTableWidget *resultTable; + + double hypergeometricProbability(int N, int K, int n, int k); + double calculateProbability(int totalCards, int copies, int drawn, bool atLeast); +}; + +#endif // COCKATRICE_DRAW_PROBABILITY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config.cpp new file mode 100644 index 000000000..2f9f60752 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config.cpp @@ -0,0 +1,32 @@ +#include "mana_base_config.h" + +QJsonObject ManaBaseConfig::toJson() const +{ + QJsonObject jsonObject; + QJsonArray jsonArray; + jsonObject["displayType"] = displayType; + for (auto &filter : filters) { + jsonArray.append(filter); + } + jsonObject["filters"] = jsonArray; + return jsonObject; +} + +ManaBaseConfig ManaBaseConfig::fromJson(const QJsonObject &o) + +{ + ManaBaseConfig config; + + if (o.contains("displayType")) { + config.displayType = o["displayType"].toString(); + } + + if (o.contains("filters")) { + config.filters.clear(); + for (auto v : o["filters"].toArray()) { + config.filters << v.toString(); + } + } + + return config; +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config.h new file mode 100644 index 000000000..d01f88b8b --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config.h @@ -0,0 +1,19 @@ + +#ifndef COCKATRICE_MANA_BASE_CONFIG_H +#define COCKATRICE_MANA_BASE_CONFIG_H + +#include +#include +#include + +struct ManaBaseConfig +{ + QString displayType; // "pie" or "bar" or "combinedBar" + QStringList filters; // which colors to show, empty = all + + QJsonObject toJson() const; + + static ManaBaseConfig fromJson(const QJsonObject &o); +}; + +#endif // COCKATRICE_MANA_BASE_CONFIG_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.cpp new file mode 100644 index 000000000..3317486ea --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.cpp @@ -0,0 +1,67 @@ +#include "mana_base_config_dialog.h" + +#include + +ManaBaseConfigDialog::ManaBaseConfigDialog(DeckListStatisticsAnalyzer *analyzer, + ManaBaseConfig initial, + QWidget *parent) + : QDialog(parent), config(initial) +{ + layout = new QVBoxLayout(this); + + displayTypeLabel = new QLabel(this); + layout->addWidget(displayTypeLabel); + + displayType = new QComboBox(this); + layout->addWidget(displayType); + + filterLabel = new QLabel(this); + layout->addWidget(filterLabel); + + filterList = new QListWidget(this); + filterList->setSelectionMode(QAbstractItemView::MultiSelection); + layout->addWidget(filterList); + + QStringList colors = analyzer->getManaBase().keys(); + colors.sort(); + filterList->addItems(colors); + + // select initial filters + for (int i = 0; i < filterList->count(); ++i) { + if (config.filters.contains(filterList->item(i)->text())) + filterList->item(i)->setSelected(true); + } + + buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + layout->addWidget(buttons); + + connect(buttons, &QDialogButtonBox::accepted, this, &ManaBaseConfigDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &ManaBaseConfigDialog::reject); + + retranslateUi(); +} + +void ManaBaseConfigDialog::retranslateUi() +{ + setWindowTitle(tr("Mana Base Configuration")); + + displayTypeLabel->setText(tr("Display type:")); + + displayType->clear(); + displayType->addItems({tr("pie"), tr("bar"), tr("combinedBar")}); + + filterLabel->setText(tr("Filter Colors (optional):")); + + buttons->button(QDialogButtonBox::Ok)->setText(tr("OK")); + buttons->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); +} + +void ManaBaseConfigDialog::accept() +{ + config.displayType = displayType->currentText(); + config.filters.clear(); + for (auto *item : filterList->selectedItems()) { + config.filters << item->text(); + } + QDialog::accept(); +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.h new file mode 100644 index 000000000..a816f154f --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.h @@ -0,0 +1,42 @@ + +#ifndef COCKATRICE_MANA_BASE_ADD_DIALOG_H +#define COCKATRICE_MANA_BASE_ADD_DIALOG_H + +#include "../../deck_list_statistics_analyzer.h" +#include "mana_base_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class ManaBaseConfigDialog : public QDialog +{ + Q_OBJECT +public: + ManaBaseConfigDialog(DeckListStatisticsAnalyzer *analyzer, ManaBaseConfig initial = {}, QWidget *parent = nullptr); + void retranslateUi(); + + void accept() override; + + ManaBaseConfig result() const + { + return config; + } + +private: + ManaBaseConfig config; + QVBoxLayout *layout; + QLabel *displayTypeLabel; + QComboBox *displayType; + QLabel *filterLabel; + QListWidget *filterList; + QDialogButtonBox *buttons; +}; + +#endif // COCKATRICE_MANA_BASE_ADD_DIALOG_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp new file mode 100644 index 000000000..d38314b8c --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp @@ -0,0 +1,115 @@ +#include "mana_base_widget.h" + +#include "../../../general/display/charts/bars/bar_widget.h" +#include "../../../general/display/charts/bars/color_bar.h" +#include "../../../general/display/charts/pies/color_pie.h" +#include "../../analytics_panel_widget_registrar.h" +#include "mana_base_config_dialog.h" + +#include +#include + +namespace +{ + +AnalyticsPanelWidgetRegistrar registerManaBase{ + "manaBase", ManaBaseWidget::tr("Mana Base"), + [](QWidget *parent, DeckListStatisticsAnalyzer *analyzer) { return new ManaBaseWidget(parent, analyzer); }}; + +} // anonymous namespace + +ManaBaseWidget::ManaBaseWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaBaseConfig cfg) + : AbstractAnalyticsPanelWidget(parent, analyzer), config(std::move(cfg)) +{ + barContainer = new QWidget(this); + barLayout = new QHBoxLayout(barContainer); + layout->addWidget(barContainer); + + updateDisplay(); +} + +void ManaBaseWidget::updateDisplay() +{ + // Clear previous widgets + while (QLayoutItem *item = barLayout->takeAt(0)) { + if (item->widget()) { + item->widget()->deleteLater(); + } + delete item; + } + + auto &pipCount = analyzer->getProductionPipCount(); + auto &cardCount = analyzer->getProductionCardCount(); + + QHash manaMap; + for (auto key : pipCount.keys()) { + manaMap[key] = pipCount[key]; + } + + // Apply filters + if (!config.filters.isEmpty()) { + QHash filtered; + for (auto f : config.filters) { + if (manaMap.contains(f)) { + filtered[f] = manaMap[f]; + } + } + manaMap = filtered; + } + + // Determine maximum for bar charts + int highest = 1; + for (auto val : manaMap) { + highest = std::max(highest, val); + } + + // Convert to QMap for ColorBar / ColorPie (sorted) + QMap mapSorted; + for (auto it = manaMap.begin(); it != manaMap.end(); ++it) { + mapSorted.insert(it.key(), it.value()); + } + + // Choose display mode + if (config.displayType == "bar") { + QHash colors = {{"W", QColor(248, 231, 185)}, {"U", QColor(14, 104, 171)}, + {"B", QColor(21, 11, 0)}, {"R", QColor(211, 32, 42)}, + {"G", QColor(0, 115, 62)}, {"C", QColor(150, 150, 150)}}; + + for (auto color : manaMap.keys()) { + QString label = QString("%1 %2 (%3)").arg(color).arg(manaMap[color]).arg(cardCount.value(color)); + + BarWidget *bar = new BarWidget(label, manaMap[color], highest, colors.value(color, Qt::gray), this); + + barLayout->addWidget(bar); + } + } else if (config.displayType == "combinedBar") { + ColorBar *cb = new ColorBar(mapSorted, this); + cb->setMinimumHeight(30); + barLayout->addWidget(cb); + } else if (config.displayType == "pie") { + ColorPie *pie = new ColorPie(mapSorted, this); + pie->setMinimumSize(200, 200); + barLayout->addWidget(pie); + } + + update(); +} +QSize ManaBaseWidget::sizeHint() const +{ + return QSize(800, 150); +} + +QDialog *ManaBaseWidget::createConfigDialog(QWidget *parent) +{ + ManaBaseConfigDialog *dlg = new ManaBaseConfigDialog(analyzer, config, parent); + return dlg; +} + +QJsonObject ManaBaseWidget::extractConfigFromDialog(QDialog *dlg) const +{ + auto *mc = qobject_cast(dlg); + if (!mc) { + return {}; + } + return mc->result().toJson(); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.h new file mode 100644 index 000000000..39380c07e --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.h @@ -0,0 +1,51 @@ +/** + * @file mana_base_widget.h + * @ingroup DeckEditorAnalyticsWidgets + * @brief TODO: Document this. + */ + +#ifndef MANA_BASE_WIDGET_H +#define MANA_BASE_WIDGET_H + +#include "../../../general/display/banner_widget.h" +#include "../../abstract_analytics_panel_widget.h" +#include "../../deck_list_statistics_analyzer.h" +#include "mana_base_config.h" + +#include +#include +#include +#include +#include + +class ManaBaseWidget : public AbstractAnalyticsPanelWidget +{ + Q_OBJECT + +public slots: + QSize sizeHint() const override; + void updateDisplay() override; + QDialog *createConfigDialog(QWidget *parent) override; + +public: + ManaBaseWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaBaseConfig cfg = {}); + + QJsonObject saveConfig() const override + { + return config.toJson(); + } + void loadConfig(const QJsonObject &o) override + { + config = ManaBaseConfig::fromJson(o); + updateDisplay(); + } + + QJsonObject extractConfigFromDialog(QDialog *dlg) const override; + +private: + ManaBaseConfig config; + QWidget *barContainer; + QHBoxLayout *barLayout; +}; + +#endif // MANA_BASE_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp new file mode 100644 index 000000000..a0ccedc74 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp @@ -0,0 +1,121 @@ +#include "mana_curve_category_widget.h" + +#include "libcockatrice/utility/color.h" +#include "libcockatrice/utility/qt_utils.h" +#include "mana_curve_config.h" +#include "mana_curve_total_widget.h" + +constexpr int MIN_ROW_HEIGHT = 100; // Minimum readable height per row + +ManaCurveCategoryWidget::ManaCurveCategoryWidget(QWidget *parent) : QWidget(parent) +{ + layout = new QVBoxLayout(this); + layout->setSpacing(4); + layout->setContentsMargins(0, 0, 0, 0); + + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); +} + +// Same as minimum for now +QSize ManaCurveCategoryWidget::sizeHint() const +{ + if (layout->isEmpty()) { + return QSize(0, 0); + } + + // Calculate exact height needed for all rows + int rowCount = layout->count(); + + int totalHeight = rowCount * MIN_ROW_HEIGHT; + totalHeight += (rowCount - 1) * layout->spacing(); + + return QSize(0, totalHeight); +} + +QSize ManaCurveCategoryWidget::minimumSizeHint() const +{ + if (layout->isEmpty()) { + return QSize(0, 0); + } + + // Calculate actual minimum based on number of rows + int rowCount = layout->count(); + + int totalHeight = rowCount * MIN_ROW_HEIGHT; + totalHeight += (rowCount - 1) * layout->spacing(); + + return QSize(0, totalHeight); +} + +void ManaCurveCategoryWidget::updateDisplay(int minCmc, + int maxCmc, + int highest, + QHash> qCategoryCounts, + QHash> qCategoryCards, + const ManaCurveConfig &config) +{ + // Clear previous content + QtUtils::clearLayoutRec(layout); + + if (!config.showCategoryRows) { + return; // nothing to show + } + + // Collect categories + QStringList categories = qCategoryCounts.keys(); + + // Apply filters + if (!config.filters.isEmpty()) { + QStringList filtered; + for (const QString &cat : categories) { + if (config.filters.contains(cat)) { + filtered.append(cat); + } + } + categories = filtered; + } + + std::sort(categories.begin(), categories.end()); + + for (const QString &cat : categories) { + QWidget *row = new QWidget(this); + row->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + row->setFixedHeight(MIN_ROW_HEIGHT); + + QHBoxLayout *rowLayout = new QHBoxLayout(row); + rowLayout->setContentsMargins(0, 0, 0, 0); + rowLayout->setSpacing(4); + + QLabel *categoryLabel = new QLabel(cat, row); + categoryLabel->setFixedWidth(80); + rowLayout->addWidget(categoryLabel); + + QVector catBars; + const auto cmcCounts = qCategoryCounts.value(cat); + const auto cmcCards = qCategoryCards.value(cat); + + for (int cmc = minCmc; cmc <= maxCmc; ++cmc) { + int val = cmcCounts.value(cmc, 0); + QStringList cards = cmcCards.value(cmc); + + QVector segments; + if (val > 0) { + segments.push_back({cat, val, cards, GameSpecificColors::MTG::colorHelper(cat)}); + } + + catBars.push_back({QString::number(cmc), segments}); + } + + auto *catChart = new BarChartWidget(row); + catChart->setHighest(highest); + catChart->setBars(catBars); + catChart->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + rowLayout->addWidget(catChart); + layout->addWidget(row); + } + + // Update geometry after adding all widgets + updateGeometry(); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.h new file mode 100644 index 000000000..c6aa6f1f0 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.h @@ -0,0 +1,32 @@ +#ifndef COCKATRICE_MANA_CURVE_CATEGORY_WIDGET_H +#define COCKATRICE_MANA_CURVE_CATEGORY_WIDGET_H + +#include "../../../general/display/charts/bars/bar_chart_widget.h" +#include "mana_curve_config.h" + +#include +#include +#include + +class ManaCurveCategoryWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ManaCurveCategoryWidget(QWidget *parent); + void updateDisplay(int minCmc, + int maxCmc, + int highest, + QHash> qCategoryCounts, + QHash> qCategoryCards, + const ManaCurveConfig &config); + +public slots: + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + +private: + QVBoxLayout *layout; +}; + +#endif // COCKATRICE_MANA_CURVE_CATEGORY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config.cpp new file mode 100644 index 000000000..0dfef1b66 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config.cpp @@ -0,0 +1,41 @@ +#include "mana_curve_config.h" + +QJsonObject ManaCurveConfig::toJson() const +{ + QJsonObject jsonObject; + jsonObject["groupBy"] = groupBy; + QJsonArray jsonArray; + for (auto &filter : filters) { + jsonArray.append(filter); + } + jsonObject["filters"] = jsonArray; + jsonObject["showMain"] = showMain; + jsonObject["showCategoryRows"] = showCategoryRows; + return jsonObject; +} + +ManaCurveConfig ManaCurveConfig::fromJson(const QJsonObject &o) +{ + ManaCurveConfig config; + + if (o.contains("groupBy")) { + config.groupBy = o["groupBy"].toString(); + } + + if (o.contains("filters")) { + config.filters.clear(); + for (auto v : o["filters"].toArray()) { + config.filters << v.toString(); + } + } + + if (o.contains("showMain")) { + config.showMain = o["showMain"].toBool(true); + } + + if (o.contains("showCategoryRows")) { + config.showCategoryRows = o["showCategoryRows"].toBool(true); + } + + return config; +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config.h new file mode 100644 index 000000000..55222dd98 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config.h @@ -0,0 +1,21 @@ + +#ifndef COCKATRICE_MANA_CURVE_CONFIG_H +#define COCKATRICE_MANA_CURVE_CONFIG_H + +#include +#include +#include + +struct ManaCurveConfig +{ + QString groupBy = "type"; // "type", "color", "subtype", etc. + QStringList filters; // empty = all + bool showMain = true; + bool showCategoryRows = true; + + QJsonObject toJson() const; + + static ManaCurveConfig fromJson(const QJsonObject &o); +}; + +#endif // COCKATRICE_MANA_CURVE_CONFIG_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.cpp new file mode 100644 index 000000000..38199656c --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.cpp @@ -0,0 +1,91 @@ +#include "mana_curve_config_dialog.h" + +#include +#include +#include +#include +#include +#include + +ManaCurveConfigDialog::ManaCurveConfigDialog(DeckListStatisticsAnalyzer *analyzer, QWidget *parent) + : QDialog(parent), analyzer(analyzer) +{ + auto *lay = new QVBoxLayout(this); + + labelGroupBy = new QLabel(this); + lay->addWidget(labelGroupBy); + + groupBy = new QComboBox(this); + lay->addWidget(groupBy); + + labelFilters = new QLabel(this); + lay->addWidget(labelFilters); + + filterList = new QListWidget(this); + filterList->setSelectionMode(QAbstractItemView::MultiSelection); + lay->addWidget(filterList); + + showMain = new QCheckBox(this); + showMain->setChecked(true); + lay->addWidget(showMain); + + showCatRows = new QCheckBox(this); + showCatRows->setChecked(true); + lay->addWidget(showCatRows); + + buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + lay->addWidget(buttons); + connect(buttons, &QDialogButtonBox::accepted, this, &ManaCurveConfigDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &ManaCurveConfigDialog::reject); + + // populate dynamic data + QStringList cats = analyzer->getManaCurveByType().keys(); + cats.append(analyzer->getManaCurveByColor().keys()); + cats.removeDuplicates(); + cats.sort(); + filterList->addItems(cats); + + groupBy->addItems({"type", "color", "subtype", "power", "toughness"}); + + retranslateUi(); +} + +void ManaCurveConfigDialog::retranslateUi() +{ + labelGroupBy->setText(tr("Group By:")); + groupBy->setItemText(0, tr("type")); + groupBy->setItemText(1, tr("color")); + groupBy->setItemText(2, tr("subtype")); + groupBy->setItemText(3, tr("power")); + groupBy->setItemText(4, tr("toughness")); + + labelFilters->setText(tr("Filters (optional):")); + + showMain->setText(tr("Show main bar row")); + showCatRows->setText(tr("Show per-category rows")); +} + +void ManaCurveConfigDialog::setFromConfig(const ManaCurveConfig &cfg) +{ + groupBy->setCurrentText(cfg.groupBy); + // restore filters + for (int i = 0; i < filterList->count(); ++i) + filterList->item(i)->setSelected(cfg.filters.contains(filterList->item(i)->text())); + + showMain->setChecked(cfg.showMain); + showCatRows->setChecked(cfg.showCategoryRows); +} + +void ManaCurveConfigDialog::accept() +{ + cfg.groupBy = groupBy->currentText(); + + cfg.filters.clear(); + for (auto *item : filterList->selectedItems()) + cfg.filters << item->text(); + + cfg.showMain = showMain->isChecked(); + cfg.showCategoryRows = showCatRows->isChecked(); + + QDialog::accept(); +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.h new file mode 100644 index 000000000..17e6776e5 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.h @@ -0,0 +1,44 @@ +#ifndef COCKATRICE_MANA_CURVE_ADD_DIALOG_H +#define COCKATRICE_MANA_CURVE_ADD_DIALOG_H + +#include "../../deck_list_statistics_analyzer.h" +#include "mana_curve_config.h" + +#include +#include +#include + +class QListWidget; +class QCheckBox; +class QComboBox; + +class ManaCurveConfigDialog : public QDialog +{ + Q_OBJECT +public: + explicit ManaCurveConfigDialog(DeckListStatisticsAnalyzer *analyzer, QWidget *parent = nullptr); + void retranslateUi(); + void setFromConfig(const ManaCurveConfig &cfg); + + ManaCurveConfig result() const + { + return cfg; + } + +private: + ManaCurveConfig cfg; + DeckListStatisticsAnalyzer *analyzer; + + QLabel *labelGroupBy; + QComboBox *groupBy; + QLabel *labelFilters; + QListWidget *filterList; + QDialogButtonBox *buttons; + QCheckBox *showMain; + QCheckBox *showCatRows; + +private slots: + void accept() override; +}; + +#endif // COCKATRICE_MANA_CURVE_ADD_DIALOG_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp new file mode 100644 index 000000000..f059ff873 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp @@ -0,0 +1,78 @@ +#include "mana_curve_total_widget.h" + +#include "../../../general/display/charts/bars/bar_chart_widget.h" +#include "libcockatrice/utility/color.h" +#include "libcockatrice/utility/qt_utils.h" +#include "mana_curve_config.h" + +#include + +ManaCurveTotalWidget::ManaCurveTotalWidget(QWidget *parent) : QWidget(parent) +{ + layout = new QHBoxLayout(this); + + label = new QLabel(this); + label->setFixedWidth(80); + layout->addWidget(label); + + barChart = new BarChartWidget(this); + layout->addWidget(barChart, 1); + + setMinimumHeight(200); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +} + +QSize ManaCurveTotalWidget::sizeHint() const +{ + return {0, 280}; +} + +QSize ManaCurveTotalWidget::minimumSizeHint() const +{ + return {0, 200}; +} + +void ManaCurveTotalWidget::updateDisplay(const QString &categoryName, + int minCmc, + int maxCmc, + int highest, + const QMap> &cmcMap, + const QMap> &cardsMap, + const ManaCurveConfig &config) +{ + QVector mainBars; + mainBars.reserve(maxCmc - minCmc + 1); + + for (int cmc = minCmc; cmc <= maxCmc; ++cmc) { + QVector segments; + + const auto cmcIt = cmcMap.constFind(cmc); + if (cmcIt != cmcMap.cend()) { + for (auto it = cmcIt->cbegin(); it != cmcIt->cend(); ++it) { + const QString &category = it.key(); + + if (!config.filters.isEmpty() && !config.filters.contains(category)) + continue; + + const int value = it.value(); + + QStringList cards; + const auto catIt = cardsMap.constFind(category); + if (catIt != cardsMap.cend()) + cards = catIt->value(cmc); + + segments.push_back({category, value, cards, GameSpecificColors::MTG::colorHelper(category)}); + } + } + + std::sort(segments.begin(), segments.end(), + [](const BarSegment &a, const BarSegment &b) { return a.category < b.category; }); + + mainBars.push_back({QString::number(cmc), segments}); + } + + label->setText(categoryName); + + barChart->setHighest(highest); + barChart->setBars(mainBars); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.h new file mode 100644 index 000000000..203f75345 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.h @@ -0,0 +1,32 @@ +#ifndef COCKATRICE_MANA_CURVE_TOTAL_WIDGET_H +#define COCKATRICE_MANA_CURVE_TOTAL_WIDGET_H +#include "../../../general/display/charts/bars/bar_chart_widget.h" +#include "mana_curve_config.h" + +#include +#include +#include + +class ManaCurveTotalWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ManaCurveTotalWidget(QWidget *parent); + QSize sizeHint() const; + QSize minimumSizeHint() const; + void updateDisplay(const QString &categoryName, + int minCmc, + int maxCmc, + int highest, + const QMap> &cmcMap, + const QMap> &cardsMap, + const ManaCurveConfig &config); + +private: + QHBoxLayout *layout; + QLabel *label; + BarChartWidget *barChart; +}; + +#endif // COCKATRICE_MANA_CURVE_TOTAL_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.cpp new file mode 100644 index 000000000..e09ecfe87 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.cpp @@ -0,0 +1,148 @@ +#include "mana_curve_widget.h" + +#include "../../../general/display/charts/bars/bar_chart_background_widget.h" +#include "../../../general/display/charts/bars/bar_chart_widget.h" +#include "../../../general/display/charts/bars/segmented_bar_widget.h" +#include "../../analytics_panel_widget_registrar.h" +#include "../../deck_list_statistics_analyzer.h" +#include "libcockatrice/utility/color.h" +#include "libcockatrice/utility/qt_utils.h" +#include "mana_curve_config_dialog.h" + +#include +#include +#include +#include +#include + +namespace +{ + +AnalyticsPanelWidgetRegistrar registerManaCurve{ + "manaCurve", ManaCurveWidget::tr("Mana Curve"), + [](QWidget *parent, DeckListStatisticsAnalyzer *analyzer) { return new ManaCurveWidget(parent, analyzer); }}; + +} // anonymous namespace + +ManaCurveWidget::ManaCurveWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaCurveConfig cfg) + : AbstractAnalyticsPanelWidget(parent, analyzer), config(cfg) +{ + setLayout(layout); + + totalWidget = new ManaCurveTotalWidget(this); + totalWidget->setHidden(true); + layout->addWidget(totalWidget); + + categoryWidget = new ManaCurveCategoryWidget(this); + categoryWidget->setHidden(true); + layout->addWidget(categoryWidget); + + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + connect(analyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaCurveWidget::updateDisplay); + + updateDisplay(); +} + +QDialog *ManaCurveWidget::createConfigDialog(QWidget *parent) +{ + auto *dlg = new ManaCurveConfigDialog(analyzer, parent); + dlg->setFromConfig(config); + return dlg; +} + +QJsonObject ManaCurveWidget::extractConfigFromDialog(QDialog *dlg) const +{ + auto *mc = qobject_cast(dlg); + return mc ? mc->result().toJson() : QJsonObject{}; +} + +static void buildMapsByCategory(const QHash> &categoryCounts, + const QHash> &categoryCards, + QMap> &outCmcMap, + QMap> &outCardsMap) +{ + outCmcMap.clear(); + outCardsMap.clear(); + + for (auto catIt = categoryCounts.cbegin(); catIt != categoryCounts.cend(); ++catIt) { + const QString &category = catIt.key(); + const auto &countsByCmc = catIt.value(); + + for (auto it = countsByCmc.cbegin(); it != countsByCmc.cend(); ++it) + outCmcMap[it.key()][category] = it.value(); + } + + for (auto catIt = categoryCards.cbegin(); catIt != categoryCards.cend(); ++catIt) { + const QString &category = catIt.key(); + const auto &cardsByCmc = catIt.value(); + + for (auto it = cardsByCmc.cbegin(); it != cardsByCmc.cend(); ++it) + outCardsMap[category][it.key()] = it.value(); + } +} + +static void findGlobalCmcRange(const QHash> &categoryCounts, int &minCmc, int &maxCmc) +{ + minCmc = 0; + maxCmc = 0; + + for (const auto &countsByCmc : categoryCounts) { + for (auto it = countsByCmc.cbegin(); it != countsByCmc.cend(); ++it) + maxCmc = qMax(maxCmc, it.key()); + } +} + +void ManaCurveWidget::updateDisplay() +{ + QHash> categoryCounts; + QHash> categoryCards; + + if (config.groupBy == "color") { + categoryCounts = analyzer->getManaCurveByColor(); + categoryCards = analyzer->getManaCurveCardsByColor(); + } else if (config.groupBy == "subtype") { + categoryCounts = analyzer->getManaCurveBySubtype(); + categoryCards = analyzer->getManaCurveCardsBySubtype(); + } else if (config.groupBy == "power") { + categoryCounts = analyzer->getManaCurveByPower(); + categoryCards = analyzer->getManaCurveCardsByPower(); + } else { + categoryCounts = analyzer->getManaCurveByType(); + categoryCards = analyzer->getManaCurveCardsByType(); + } + + QMap> cmcMap; + QMap> cardsMap; + buildMapsByCategory(categoryCounts, categoryCards, cmcMap, cardsMap); + + int minCmc = 0; + int maxCmc = 0; + findGlobalCmcRange(categoryCounts, minCmc, maxCmc); + + int highest = 1; + for (int cmc = minCmc; cmc <= maxCmc; ++cmc) { + int sum = 0; + + const auto cmcIt = cmcMap.constFind(cmc); + if (cmcIt != cmcMap.cend()) { + for (auto it = cmcIt->cbegin(); it != cmcIt->cend(); ++it) { + if (!config.filters.isEmpty() && !config.filters.contains(it.key())) { + continue; + } + + sum += it.value(); + } + } + + highest = qMax(highest, sum); + } + + totalWidget->updateDisplay(config.groupBy, minCmc, maxCmc, highest, cmcMap, cardsMap, config); + + totalWidget->setVisible(config.showMain); + + categoryWidget->updateDisplay(minCmc, maxCmc, highest, categoryCounts, categoryCards, config); + + categoryWidget->setVisible(config.showCategoryRows); +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.h new file mode 100644 index 000000000..da59da9a8 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.h @@ -0,0 +1,50 @@ +/** + * @file mana_curve_widget.h + * @ingroup DeckEditorAnalyticsWidgets + * @brief TODO: Document this. + */ + +#ifndef MANA_CURVE_WIDGET_H +#define MANA_CURVE_WIDGET_H + +#include "../../abstract_analytics_panel_widget.h" +#include "mana_curve_category_widget.h" +#include "mana_curve_config.h" +#include "mana_curve_total_widget.h" + +#include + +class SegmentedBarWidget; +class DeckListStatisticsAnalyzer; + +class ManaCurveWidget : public AbstractAnalyticsPanelWidget +{ + Q_OBJECT + +public slots: + // QSize sizeHint() const override; + void updateDisplay() override; + QDialog *createConfigDialog(QWidget *parent) override; + +public: + ManaCurveWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaCurveConfig cfg = {}); + + QJsonObject saveConfig() const override + { + return config.toJson(); + } + void loadConfig(const QJsonObject &o) override + { + config = ManaCurveConfig::fromJson(o); + updateDisplay(); + }; + + QJsonObject extractConfigFromDialog(QDialog *dlg) const override; + +private: + ManaCurveConfig config; + ManaCurveTotalWidget *totalWidget; + ManaCurveCategoryWidget *categoryWidget; +}; + +#endif // MANA_CURVE_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config.cpp new file mode 100644 index 000000000..ee57cb1e2 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config.cpp @@ -0,0 +1,31 @@ +#include "mana_devotion_config.h" + +QJsonObject ManaDevotionConfig::toJson() const +{ + QJsonObject jsonObject; + QJsonArray jsonArray; + jsonObject["displayType"] = displayType; + for (auto &filter : filters) { + jsonArray.append(filter); + } + jsonObject["filters"] = jsonArray; + return jsonObject; +} + +ManaDevotionConfig ManaDevotionConfig::fromJson(const QJsonObject &o) +{ + ManaDevotionConfig config; + + if (o.contains("displayType")) { + config.displayType = o["displayType"].toString(); + } + + if (o.contains("filters")) { + config.filters.clear(); + for (auto v : o["filters"].toArray()) { + config.filters << v.toString(); + } + } + + return config; +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config.h new file mode 100644 index 000000000..08be146e5 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config.h @@ -0,0 +1,18 @@ +#ifndef COCKATRICE_MANA_DEVOTION_CONFIG_H +#define COCKATRICE_MANA_DEVOTION_CONFIG_H + +#include +#include +#include + +struct ManaDevotionConfig +{ + QString displayType; // "pie" or "bar" or "combinedBar" + QStringList filters; // which colors to show, empty = all + + QJsonObject toJson() const; + + static ManaDevotionConfig fromJson(const QJsonObject &o); +}; + +#endif // COCKATRICE_MANA_DEVOTION_CONFIG_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.cpp new file mode 100644 index 000000000..80fd03928 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.cpp @@ -0,0 +1,62 @@ +#include "mana_devotion_config_dialog.h" + +ManaDevotionConfigDialog::ManaDevotionConfigDialog(DeckListStatisticsAnalyzer *analyzer, + ManaDevotionConfig initial, + QWidget *parent) + : QDialog(parent), config(initial) +{ + layout = new QVBoxLayout(this); + + labelDisplayType = new QLabel(this); + layout->addWidget(labelDisplayType); + + displayType = new QComboBox(this); + layout->addWidget(displayType); + + labelFilters = new QLabel(this); + layout->addWidget(labelFilters); + + filterList = new QListWidget(this); + filterList->setSelectionMode(QAbstractItemView::MultiSelection); + + QStringList colors = analyzer->getDevotionPipCount().keys(); + colors.sort(); + filterList->addItems(colors); + layout->addWidget(filterList); + + // select initial filters + for (int i = 0; i < filterList->count(); ++i) { + if (config.filters.contains(filterList->item(i)->text())) + filterList->item(i)->setSelected(true); + } + + buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + layout->addWidget(buttons); + connect(buttons, &QDialogButtonBox::accepted, this, &ManaDevotionConfigDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &ManaDevotionConfigDialog::reject); + + // populate combo box items + displayType->addItems({"pie", "bar", "combinedBar"}); + + retranslateUi(); +} + +void ManaDevotionConfigDialog::retranslateUi() +{ + labelDisplayType->setText(tr("Display type:")); + displayType->setItemText(0, tr("pie")); + displayType->setItemText(1, tr("bar")); + displayType->setItemText(2, tr("combinedBar")); + + labelFilters->setText(tr("Filter Colors (optional):")); +} + +void ManaDevotionConfigDialog::accept() +{ + config.displayType = displayType->currentText(); + config.filters.clear(); + for (auto *item : filterList->selectedItems()) { + config.filters << item->text(); + } + QDialog::accept(); +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.h new file mode 100644 index 000000000..fd804e2af --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.h @@ -0,0 +1,42 @@ + +#ifndef COCKATRICE_MANA_DEVOTION_ADD_DIALOG_H +#define COCKATRICE_MANA_DEVOTION_ADD_DIALOG_H + +#include "../../deck_list_statistics_analyzer.h" +#include "mana_devotion_config.h" + +#include +#include +#include +#include +#include +#include +#include + +class ManaDevotionConfigDialog : public QDialog +{ + Q_OBJECT +public: + ManaDevotionConfigDialog(DeckListStatisticsAnalyzer *analyzer, + ManaDevotionConfig initial = {}, + QWidget *parent = nullptr); + void retranslateUi(); + + void accept() override; + + ManaDevotionConfig result() const + { + return config; + } + +private: + ManaDevotionConfig config; + QVBoxLayout *layout; + QLabel *labelDisplayType; + QComboBox *displayType; + QLabel *labelFilters; + QListWidget *filterList; + QDialogButtonBox *buttons; +}; + +#endif // COCKATRICE_MANA_DEVOTION_ADD_DIALOG_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.cpp new file mode 100644 index 000000000..709577ff2 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.cpp @@ -0,0 +1,123 @@ +#include "mana_devotion_widget.h" + +#include "../../../general/display/charts/bars/bar_widget.h" +#include "../../../general/display/charts/bars/color_bar.h" +#include "../../../general/display/charts/pies/color_pie.h" +#include "../../analytics_panel_widget_registrar.h" +#include "../../deck_list_statistics_analyzer.h" +#include "mana_devotion_config_dialog.h" + +#include +#include + +namespace +{ + +AnalyticsPanelWidgetRegistrar registerManaDevotion{ + "manaDevotion", ManaDevotionWidget::tr("Mana Devotion"), + [](QWidget *parent, DeckListStatisticsAnalyzer *analyzer) { return new ManaDevotionWidget(parent, analyzer); }}; + +} // anonymous namespace + +ManaDevotionWidget::ManaDevotionWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaDevotionConfig cfg) + : AbstractAnalyticsPanelWidget(parent, analyzer), config(std::move(cfg)) +{ + barContainer = new QWidget(this); + barLayout = new QHBoxLayout(barContainer); + barContainer->setLayout(barLayout); + layout->addWidget(barContainer); + + updateDisplay(); +} + +void ManaDevotionWidget::updateDisplay() +{ + // Clear previous widgets + while (QLayoutItem *item = barLayout->takeAt(0)) { + if (item->widget()) { + item->widget()->deleteLater(); + } + delete item; + } + + auto &pipCount = analyzer->getDevotionPipCount(); + auto &cardCount = analyzer->getDevotionCardCount(); + + // Convert keys to single QChar form + QHash devoMap; + for (auto key : pipCount.keys()) { + devoMap[key[0]] = pipCount[key]; + } + + // Apply filters + if (!config.filters.isEmpty()) { + QHash filtered; + for (auto f : config.filters) { + if (devoMap.contains(f[0])) { + filtered[f[0]] = devoMap[f[0]]; + } + } + devoMap = filtered; + } + + // Determine maximum for bar charts + int highest = 1; + for (auto val : devoMap) { + highest = std::max(highest, val); + } + + // Convert to QMap for ColorBar / ColorPie + QMap mapSorted; + for (auto it = devoMap.begin(); it != devoMap.end(); ++it) { + mapSorted.insert(QString(it.key()), it.value()); + } + + // Color map + QHash colors = {{'W', QColor(248, 231, 185)}, {'U', QColor(14, 104, 171)}, + {'B', QColor(21, 11, 0)}, {'R', QColor(211, 32, 42)}, + {'G', QColor(0, 115, 62)}, {'C', QColor(150, 150, 150)}}; + + // Choose display mode + if (config.displayType == "bar") { + // One BarWidget per devotion color + for (auto c : devoMap.keys()) { + QString label = QString("%1 %2 (%3)").arg(c).arg(devoMap[c]).arg(cardCount.value(QString(c))); + + BarWidget *bar = new BarWidget(label, devoMap[c], highest, colors.value(c, Qt::gray), this); + + barLayout->addWidget(bar); + } + } else if (config.displayType == "combinedBar") { + // Stacked devotion bar + ColorBar *cb = new ColorBar(mapSorted, this); + cb->setMinimumHeight(30); + barLayout->addWidget(cb); + } else if (config.displayType == "pie") { + // Devotion pie chart + ColorPie *pie = new ColorPie(mapSorted, this); + pie->setMinimumSize(200, 200); + barLayout->addWidget(pie); + } + + update(); +} + +QDialog *ManaDevotionWidget::createConfigDialog(QWidget *parent) +{ + ManaDevotionConfigDialog *dlg = new ManaDevotionConfigDialog(analyzer, config, parent); + return dlg; +} + +QJsonObject ManaDevotionWidget::extractConfigFromDialog(QDialog *dlg) const +{ + auto *mc = qobject_cast(dlg); + if (!mc) { + return {}; + } + return mc->result().toJson(); +} + +QSize ManaDevotionWidget::sizeHint() const +{ + return QSize(800, 150); +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.h new file mode 100644 index 000000000..833f12938 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.h @@ -0,0 +1,45 @@ +/** + * @file mana_devotion_widget.h + * @ingroup DeckEditorAnalyticsWidgets + * @brief TODO: Document this. + */ + +#ifndef MANA_DEVOTION_WIDGET_H +#define MANA_DEVOTION_WIDGET_H +#include "../../../general/display/banner_widget.h" +#include "../../abstract_analytics_panel_widget.h" +#include "mana_devotion_config.h" + +#include + +class ManaDevotionWidget : public AbstractAnalyticsPanelWidget +{ + Q_OBJECT + +public slots: + QSize sizeHint() const override; + void updateDisplay() override; + QDialog *createConfigDialog(QWidget *parent) override; + +public: + ManaDevotionWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer, ManaDevotionConfig cfg = {}); + + QJsonObject saveConfig() const override + { + return config.toJson(); + } + void loadConfig(const QJsonObject &o) override + { + config = ManaDevotionConfig::fromJson(o); + updateDisplay(); + } + + QJsonObject extractConfigFromDialog(QDialog *dlg) const override; + +private: + ManaDevotionConfig config; + QWidget *barContainer; + QHBoxLayout *barLayout; +}; + +#endif // MANA_DEVOTION_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.cpp new file mode 100644 index 000000000..f70f32d5b --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.cpp @@ -0,0 +1,36 @@ +#include "mana_distribution_config.h" + +QJsonObject ManaDistributionConfig::toJson() const +{ + QJsonObject o; + o["displayType"] = displayType; + + QJsonArray jsonArray; + for (auto &s : filters) { + jsonArray.append(s); + } + o["filters"] = jsonArray; + + o["showColorRows"] = showColorRows; + return o; +} + +ManaDistributionConfig ManaDistributionConfig::fromJson(const QJsonObject &o) +{ + ManaDistributionConfig config; + if (o.contains("displayType")) { + config.displayType = o["displayType"].toString(); + } + + if (o.contains("filters")) { + config.filters.clear(); + for (auto v : o["filters"].toArray()) + config.filters << v.toString(); + } + + if (o.contains("showColorRows")) { + config.showColorRows = o["showColorRows"].toBool(true); + } + + return config; +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.h new file mode 100644 index 000000000..d1aa0f48e --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.h @@ -0,0 +1,20 @@ +#ifndef COCKATRICE_MANA_DISTRIBUTION_CONFIG_H +#define COCKATRICE_MANA_DISTRIBUTION_CONFIG_H + +#include +#include +#include +#include + +struct ManaDistributionConfig +{ + QString displayType = "pie"; // "pie" or "bar" + QStringList filters; // empty = all colors + bool showColorRows = true; + + QJsonObject toJson() const; + + static ManaDistributionConfig fromJson(const QJsonObject &o); +}; + +#endif // COCKATRICE_MANA_DISTRIBUTION_CONFIG_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.cpp new file mode 100644 index 000000000..7fe4d94e4 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.cpp @@ -0,0 +1,83 @@ +#include "mana_distribution_config_dialog.h" + +#include +#include +#include +#include +#include +#include + +static const QStringList kColors = {"W", "U", "B", "R", "G", "C"}; + +ManaDistributionConfigDialog::ManaDistributionConfigDialog(DeckListStatisticsAnalyzer *analyzer, QWidget *parent) + : QDialog(parent), analyzer(analyzer) +{ + auto *lay = new QVBoxLayout(this); + + // Labels + labelDisplayType = new QLabel(this); + lay->addWidget(labelDisplayType); + + displayType = new QComboBox(this); + lay->addWidget(displayType); + + labelFilters = new QLabel(this); + lay->addWidget(labelFilters); + + filterList = new QListWidget(this); + filterList->setSelectionMode(QAbstractItemView::MultiSelection); + filterList->addItems(kColors); // dynamic/fixed, no translation needed + lay->addWidget(filterList); + + showColorRows = new QCheckBox(this); + showColorRows->setChecked(true); + lay->addWidget(showColorRows); + + buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + lay->addWidget(buttons); + connect(buttons, &QDialogButtonBox::accepted, this, &ManaDistributionConfigDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &ManaDistributionConfigDialog::reject); + + displayType->addItems({"pie", "bar"}); // combo items + + retranslateUi(); +} + +void ManaDistributionConfigDialog::retranslateUi() +{ + labelDisplayType->setText(tr("Top display type:")); + displayType->setItemText(0, tr("pie")); + displayType->setItemText(1, tr("bar")); + + labelFilters->setText(tr("Colors:")); + + showColorRows->setText(tr("Show per-color rows")); + + // QDialogButtonBox buttons are automatically translated +} + +void ManaDistributionConfigDialog::setFromConfig(const ManaDistributionConfig &cfgIn) +{ + cfg = cfgIn; + + displayType->setCurrentText(cfg.displayType); + + for (int i = 0; i < filterList->count(); ++i) + filterList->item(i)->setSelected(cfg.filters.contains(filterList->item(i)->text())); + + showColorRows->setChecked(cfg.showColorRows); +} + +void ManaDistributionConfigDialog::accept() +{ + cfg.displayType = displayType->currentText(); + + // Filters + cfg.filters.clear(); + for (auto *item : filterList->selectedItems()) + cfg.filters << item->text(); + + cfg.showColorRows = showColorRows->isChecked(); + + QDialog::accept(); +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.h new file mode 100644 index 000000000..57c88e29d --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.h @@ -0,0 +1,45 @@ +#ifndef COCKATRICE_MANA_DISTRIBUTION_ADD_DIALOG_H +#define COCKATRICE_MANA_DISTRIBUTION_ADD_DIALOG_H + +#include "mana_distribution_config.h" + +#include +#include +#include +#include + +class QComboBox; +class QListWidget; +class QCheckBox; +class DeckListStatisticsAnalyzer; + +class ManaDistributionConfigDialog : public QDialog +{ + Q_OBJECT +public: + explicit ManaDistributionConfigDialog(DeckListStatisticsAnalyzer *analyzer, QWidget *parent = nullptr); + void retranslateUi(); + + void setFromConfig(const ManaDistributionConfig &cfg); + const ManaDistributionConfig &config() const + { + return cfg; + } + +public slots: + void accept() override; + +private: + DeckListStatisticsAnalyzer *analyzer; + + QLabel *labelDisplayType; + QComboBox *displayType; + QLabel *labelFilters; + QListWidget *filterList; + QCheckBox *showColorRows; + QDialogButtonBox *buttons; + + ManaDistributionConfig cfg; +}; + +#endif // COCKATRICE_MANA_DISTRIBUTION_ADD_DIALOG_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp new file mode 100644 index 000000000..8a5e14858 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp @@ -0,0 +1,49 @@ +#include "mana_distribution_single_display_widget.h" + +#include "../../../cards/additional_info/mana_symbol_widget.h" + +#include + +ManaDistributionSingleDisplayWidget::ManaDistributionSingleDisplayWidget(const QString &colorSymbol, QWidget *parent) + : QWidget(parent) +{ + auto layout = new QVBoxLayout(this); + layout->setAlignment(Qt::AlignHCenter); + + symbolLabel = new ManaSymbolWidget(this, colorSymbol, true, false); + symbolLabel->setFixedSize(40, 40); + + devotionBar = new QProgressBar(this); + devotionBar->setRange(0, 100); + devotionBar->setTextVisible(false); + + devotionLabel = new QLabel(this); + devotionLabel->setAlignment(Qt::AlignCenter); + + productionBar = new QProgressBar(this); + productionBar->setRange(0, 100); + productionBar->setTextVisible(false); + + productionLabel = new QLabel(this); + productionLabel->setAlignment(Qt::AlignCenter); + + layout->addWidget(symbolLabel); + layout->addWidget(devotionBar); + layout->addWidget(devotionLabel); + layout->addWidget(productionBar); + layout->addWidget(productionLabel); + + setLayout(layout); +} + +void ManaDistributionSingleDisplayWidget::setDevotion(int pips, int cards, int percent) +{ + devotionBar->setValue(percent); + devotionLabel->setText(QString(tr("%1 pips (%2 cards)")).arg(pips).arg(cards)); +} + +void ManaDistributionSingleDisplayWidget::setProduction(int pips, int cards, int percent) +{ + productionBar->setValue(percent); + productionLabel->setText(QString(tr("%1 mana (%2 cards)")).arg(pips).arg(cards)); +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.h new file mode 100644 index 000000000..e33d0cec1 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.h @@ -0,0 +1,28 @@ +#ifndef COCKATRICE_MANA_DISTRIBUTION_SINGLE_DISPLAY_WIDGET_H +#define COCKATRICE_MANA_DISTRIBUTION_SINGLE_DISPLAY_WIDGET_H + +#include +#include +#include +#include + +class ManaDistributionSingleDisplayWidget : public QWidget +{ + Q_OBJECT +public: + explicit ManaDistributionSingleDisplayWidget(const QString &colorSymbol, QWidget *parent = nullptr); + + void setDevotion(int pips, int cards, int percent); + void setProduction(int pips, int cards, int percent); + +private: + QLabel *symbolLabel; + + QProgressBar *devotionBar; + QLabel *devotionLabel; + + QProgressBar *productionBar; + QLabel *productionLabel; +}; + +#endif // COCKATRICE_MANA_DISTRIBUTION_SINGLE_DISPLAY_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_widget.cpp new file mode 100644 index 000000000..8eaea1426 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_widget.cpp @@ -0,0 +1,129 @@ +#include "mana_distribution_widget.h" + +#include "../../analytics_panel_widget_registrar.h" +#include "mana_distribution_config_dialog.h" + +#include +#include +#include +#include + +namespace +{ +AnalyticsPanelWidgetRegistrar registerManaDistribution{ + "manaProdDevotion", ManaDistributionWidget::tr("Mana Production + Devotion"), + [](QWidget *parent, DeckListStatisticsAnalyzer *analyzer) { return new ManaDistributionWidget(parent, analyzer); }}; + +} // anonymous namespace + +static const QStringList kColors = {"W", "U", "B", "R", "G", "C"}; + +ManaDistributionWidget::ManaDistributionWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer) + : AbstractAnalyticsPanelWidget(parent, analyzer) +{ + container = new QWidget(this); + containerLayout = new QVBoxLayout(container); + + devotionBarTop = new ColorBar({}, this); + devotionPieTop = new ColorPie({}, this); + productionBarTop = new ColorBar({}, this); + productionPieTop = new ColorPie({}, this); + + containerLayout->addWidget(devotionBarTop); + containerLayout->addWidget(devotionPieTop); + containerLayout->addWidget(productionBarTop); + containerLayout->addWidget(productionPieTop); + + devotionPieTop->hide(); + productionPieTop->hide(); + + row = new QHBoxLayout(); + containerLayout->addLayout(row); + + for (const QString &c : kColors) { + auto *w = new ManaDistributionSingleDisplayWidget(c, this); + row->addWidget(w); + rows[c] = w; + } + + layout->addWidget(container); +} + +void ManaDistributionWidget::updateDisplay() +{ + const auto &devPips = analyzer->getDevotionPipCount(); + const auto &devCards = analyzer->getDevotionCardCount(); + const auto &prodPips = analyzer->getProductionPipCount(); + const auto &prodCards = analyzer->getProductionCardCount(); + + QStringList filtered = config.filters.isEmpty() ? kColors : config.filters; + + QMap devMap, prodMap; + for (const QString &c : filtered) { + devMap[c] = devPips.value(c, 0); + prodMap[c] = prodPips.value(c, 0); + } + + bool showPie = (config.displayType == "pie"); + + devotionBarTop->setVisible(!showPie); + productionBarTop->setVisible(!showPie); + + devotionPieTop->setVisible(showPie); + productionPieTop->setVisible(showPie); + + if (showPie) { + devotionPieTop->setColors(devMap); + productionPieTop->setColors(prodMap); + } else { + devotionBarTop->setColors(devMap); + productionBarTop->setColors(prodMap); + } + + for (const QString &c : kColors) { + auto *w = rows.value(c); + + if (!w) { + continue; + } + + bool visible = config.showColorRows && filtered.contains(c); + w->setVisible(visible); + if (!visible) { + continue; + } + + int dp = devPips.value(c, 0); + int dc = devCards.value(c, 0); + int pp = prodPips.value(c, 0); + int pc = prodCards.value(c, 0); + + // Compute percentages + int totalDev = 0; + int totalProd = 0; + for (const QString &cc : filtered) { + totalDev += devPips.value(cc, 0); + totalProd += prodPips.value(cc, 0); + } + + int devPct = (totalDev > 0) ? int(100.0 * dp / totalDev) : 0; + int prodPct = (totalProd > 0) ? int(100.0 * pp / totalProd) : 0; + + w->setDevotion(dp, dc, devPct); + w->setProduction(pp, pc, prodPct); + } +} + +QDialog *ManaDistributionWidget::createConfigDialog(QWidget *parent) +{ + auto *dlg = new ManaDistributionConfigDialog(analyzer, parent); + dlg->setWindowTitle(tr("Mana Distribution Settings")); + dlg->setFromConfig(config); + + connect(dlg, &QDialog::accepted, [this, dlg]() { + config = dlg->config(); + updateDisplay(); + }); + + return dlg; +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_widget.h b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_widget.h new file mode 100644 index 000000000..2d834f3af --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_widget.h @@ -0,0 +1,45 @@ +#ifndef COCKATRICE_MANA_DISTRIBUTION_WIDGET_H +#define COCKATRICE_MANA_DISTRIBUTION_WIDGET_H + +#include "../../../general/display/charts/bars/color_bar.h" +#include "../../../general/display/charts/pies/color_pie.h" +#include "../../abstract_analytics_panel_widget.h" +#include "../../deck_list_statistics_analyzer.h" +#include "mana_distribution_config.h" +#include "mana_distribution_single_display_widget.h" + +#include +#include +#include +#include + +class ManaDistributionWidget : public AbstractAnalyticsPanelWidget +{ + Q_OBJECT +public: + explicit ManaDistributionWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer); + + void updateDisplay() override; + QDialog *createConfigDialog(QWidget *parent) override; + QJsonObject extractConfigFromDialog(QDialog *) const override + { + return {}; + } + +private: + ManaDistributionConfig config; + + QWidget *container; + QVBoxLayout *containerLayout; + + QVBoxLayout *topLayout; + ColorBar *devotionBarTop; + ColorPie *devotionPieTop; + ColorBar *productionBarTop; + ColorPie *productionPieTop; + + QHBoxLayout *row; + QMap rows; +}; + +#endif // COCKATRICE_MANA_DISTRIBUTION_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp index 45393bb5d..ea61302f0 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp @@ -1,35 +1,298 @@ #include "deck_analytics_widget.h" -DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListModel *_deckListModel) - : QWidget(parent), deckListModel(_deckListModel) -{ - mainLayout = new QVBoxLayout(); - setLayout(mainLayout); +#include "abstract_analytics_panel_widget.h" +#include "add_analytics_panel_dialog.h" +#include "analytics_panel_widget_factory.h" +#include "analyzer_modules/mana_base/mana_base_config.h" +#include "analyzer_modules/mana_curve/mana_curve_config.h" +#include "analyzer_modules/mana_devotion/mana_devotion_config.h" +#include "deck_list_statistics_analyzer.h" +#include "resizable_panel.h" +#include +#include +#include +#include +#include +#include +#include + +DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnalyzer *_statsAnalyzer) + : QWidget(parent), statsAnalyzer(_statsAnalyzer) +{ + layout = new QVBoxLayout(this); + + // Controls + controlContainer = new QWidget(this); + controlLayout = new QHBoxLayout(controlContainer); + addButton = new QPushButton(this); + removeButton = new QPushButton(this); + saveButton = new QPushButton(this); + loadButton = new QPushButton(this); + controlLayout->addWidget(addButton); + controlLayout->addWidget(removeButton); + controlLayout->addWidget(saveButton); + controlLayout->addWidget(loadButton); + + layout->addWidget(controlContainer); + + connect(addButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::onAddPanel); + connect(removeButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::onRemoveSelected); + connect(saveButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::saveLayout); + connect(loadButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::loadLayout); + + // Scroll area and container scrollArea = new QScrollArea(this); - scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea->setWidgetResizable(true); - mainLayout->addWidget(scrollArea); + scrollArea->setFrameShape(QFrame::NoFrame); - container = new QWidget(scrollArea); - containerLayout = new QVBoxLayout(container); - container->setLayout(containerLayout); - scrollArea->setWidget(container); + panelContainer = new QWidget(scrollArea); + panelLayout = new QVBoxLayout(panelContainer); + panelLayout->setSpacing(8); + panelLayout->setContentsMargins(4, 4, 4, 4); + panelLayout->addStretch(1); // push panels up - deckListStatisticsAnalyzer = new DeckListStatisticsAnalyzer(this, deckListModel); + scrollArea->setWidget(panelContainer); + layout->addWidget(scrollArea); - manaCurveWidget = new ManaCurveWidget(this, deckListStatisticsAnalyzer); - containerLayout->addWidget(manaCurveWidget); + loadLayout(); - manaDevotionWidget = new ManaDevotionWidget(this, deckListStatisticsAnalyzer); - containerLayout->addWidget(manaDevotionWidget); - - manaBaseWidget = new ManaBaseWidget(this, deckListStatisticsAnalyzer); - containerLayout->addWidget(manaBaseWidget); + retranslateUi(); } -void DeckAnalyticsWidget::refreshDisplays() +void DeckAnalyticsWidget::retranslateUi() { - deckListStatisticsAnalyzer->update(); + addButton->setText(tr("Add Panel")); + removeButton->setText(tr("Remove Panel")); + saveButton->setText(tr("Save Layout")); + loadButton->setText(tr("Load Layout")); } + +void DeckAnalyticsWidget::updateDisplays() +{ + statsAnalyzer->analyze(); +} + +void DeckAnalyticsWidget::onAddPanel() +{ + AddAnalyticsPanelDialog dlg(this); + if (dlg.exec() != QDialog::Accepted) { + return; + } + + QString selection = dlg.selectedType(); + if (selection.isEmpty()) { + return; + } + + AbstractAnalyticsPanelWidget *analyticsWidget = + AnalyticsPanelWidgetFactory::instance().create(selection, this, statsAnalyzer); + if (!analyticsWidget) { + return; + } + + if (!analyticsWidget->applyConfigFromDialog()) { + analyticsWidget->deleteLater(); + return; + } + + addPanelInstance(selection, analyticsWidget, analyticsWidget->saveConfig()); +} + +void DeckAnalyticsWidget::addPanelInstance(const QString &typeId, + AbstractAnalyticsPanelWidget *panel, + const QJsonObject &cfg) +{ + panel->loadConfig(cfg); + panel->updateDisplay(); + + auto *resPanel = new ResizablePanel(typeId, panel, panelContainer); + panelWrappers.push_back(resPanel); + + panelLayout->insertWidget(panelLayout->count() - 1, resPanel); + + // Event filter for selection + resPanel->installEventFilter(this); + panel->installEventFilter(this); + + // Connect drag-drop signals + connect(resPanel, &ResizablePanel::dropRequested, this, &DeckAnalyticsWidget::onPanelDropped); +} + +void DeckAnalyticsWidget::onRemoveSelected() +{ + int idx = indexOfSelectedWrapper(); + if (idx < 0) { + return; + } + + ResizablePanel *panel = panelWrappers.takeAt(idx); + selectWrapper(nullptr); + + panel->deleteLater(); +} + +void DeckAnalyticsWidget::saveLayout() +{ + QJsonArray arr; + + for (auto *wrapper : panelWrappers) { + QJsonObject entry; + entry["type"] = wrapper->getTypeId(); + entry["config"] = wrapper->panel->saveConfig(); + entry["height"] = wrapper->getCurrentHeight(); + arr.append(entry); + } + + QSettings s; + s.setValue("deckAnalytics/layout", QString::fromUtf8(QJsonDocument(arr).toJson(QJsonDocument::Compact))); +} + +void DeckAnalyticsWidget::loadLayout() +{ + if (!loadLayoutInternal()) { + addDefaultPanels(); + } +} + +void DeckAnalyticsWidget::addDefaultPanels() +{ + struct DefaultPanel + { + QString type; + QJsonObject cfg; + }; + + // Prepare configs + QJsonObject manaCurveCfg = ManaCurveConfig{}.toJson(); + QJsonObject manaBaseCfg = ManaBaseConfig{"combinedBar", {}}.toJson(); + QJsonObject manaDevotionCfg = ManaDevotionConfig{"combinedBar", {}}.toJson(); + QVector defaults = { + {"manaCurve", manaCurveCfg}, {"manaBase", manaBaseCfg}, {"manaDevotion", manaDevotionCfg}}; + + for (auto &d : defaults) { + AbstractAnalyticsPanelWidget *w = AnalyticsPanelWidgetFactory::instance().create(d.type, this, statsAnalyzer); + if (!w) { + continue; + } + + w->loadConfig(d.cfg); + addPanelInstance(d.type, w, d.cfg); + } +} + +bool DeckAnalyticsWidget::loadLayoutInternal() +{ + QSettings s; + QString layoutData = s.value("deckAnalytics/layout").toString(); + if (layoutData.isEmpty()) { + return false; + } + + QJsonDocument doc = QJsonDocument::fromJson(layoutData.toUtf8()); + if (!doc.isArray()) { + return false; + } + + clearPanels(); + + for (auto v : doc.array()) { + if (!v.isObject()) { + continue; + } + QJsonObject o = v.toObject(); + QString type = o["type"].toString(); + QJsonObject cfg = o["config"].toObject(); + + AbstractAnalyticsPanelWidget *w = AnalyticsPanelWidgetFactory::instance().create(type, this, statsAnalyzer); + if (!w) { + continue; + } + + addPanelInstance(type, w, cfg); + + // Restore height AFTER adding the panel + if (o.contains("height")) { + panelWrappers.last()->setHeightFromSaved(o["height"].toInt()); + } + } + + return true; +} + +void DeckAnalyticsWidget::clearPanels() +{ + selectWrapper(nullptr); + while (!panelWrappers.isEmpty()) { + ResizablePanel *p = panelWrappers.takeLast(); + p->deleteLater(); + } +} + +bool DeckAnalyticsWidget::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::MouseButtonPress) { + for (auto *p : panelWrappers) { + if (obj == p || obj == p->panel) { + selectWrapper(p); + break; + } + } + } + return QWidget::eventFilter(obj, event); +} + +void DeckAnalyticsWidget::selectWrapper(ResizablePanel *w) +{ + // Same wrapper + if (selectedWrapper == w) { + return; + } + // Deselect the old one + if (selectedWrapper) { + selectedWrapper->setSelected(false); + } + // Set current + selectedWrapper = w; + // Finally, select new + if (selectedWrapper) { + selectedWrapper->setSelected(true); + } +} + +int DeckAnalyticsWidget::indexOfSelectedWrapper() const +{ + if (!selectedWrapper) { + return -1; + } + return panelWrappers.indexOf(selectedWrapper); +} + +void DeckAnalyticsWidget::onPanelDropped(ResizablePanel *dragged, ResizablePanel *target, bool insertBefore) +{ + int draggedIdx = panelWrappers.indexOf(dragged); + int targetIdx = panelWrappers.indexOf(target); + + if (draggedIdx == -1 || targetIdx == -1 || draggedIdx == targetIdx) { + return; + } + + // Remove dragged panel from list and layout + panelWrappers.removeAt(draggedIdx); + panelLayout->removeWidget(dragged); + + // Adjust target index if needed + if (draggedIdx < targetIdx) { + targetIdx--; + } + + // Calculate insertion position + int insertIdx = insertBefore ? targetIdx : targetIdx + 1; + + // Insert back into list and layout + panelWrappers.insert(insertIdx, dragged); + panelLayout->insertWidget(insertIdx, dragged); + + // Clear selection + selectWrapper(nullptr); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h index 524362aed..31ee36fbb 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h @@ -1,44 +1,71 @@ /** * @file deck_analytics_widget.h * @ingroup DeckEditorAnalyticsWidgets - * @brief TODO: Document this. + * @brief Main analytics widget container with resizable panels for deck statistics. */ #ifndef DECK_ANALYTICS_WIDGET_H #define DECK_ANALYTICS_WIDGET_H -#include "mana_base_widget.h" -#include "mana_curve_widget.h" -#include "mana_devotion_widget.h" +#include "abstract_analytics_panel_widget.h" +#include "deck_list_statistics_analyzer.h" +#include "resizable_panel.h" -#include +#include #include +#include +#include #include -#include + +class LayoutInspector; class DeckAnalyticsWidget : public QWidget { Q_OBJECT +public slots: + void updateDisplays(); + public: - explicit DeckAnalyticsWidget(QWidget *parent, DeckListModel *deckListModel); - void setDeckList(const DeckList &_deckListModel); - std::map analyzeManaCurve(); - void refreshDisplays(); + explicit DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer); + void retranslateUi(); + +private slots: + void onAddPanel(); + void onRemoveSelected(); + void onPanelDropped(ResizablePanel *dragged, ResizablePanel *target, bool insertBefore); + void saveLayout(); + void loadLayout(); + void addDefaultPanels(); + bool loadLayoutInternal(); + void clearPanels(); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + void selectWrapper(ResizablePanel *panel); + int indexOfSelectedWrapper() const; private: - DeckListModel *deckListModel; - DeckListStatisticsAnalyzer *deckListStatisticsAnalyzer; - QVBoxLayout *mainLayout; + void addPanelInstance(const QString &typeId, AbstractAnalyticsPanelWidget *panel, const QJsonObject &cfg = {}); - QWidget *container; - QVBoxLayout *containerLayout; + QVBoxLayout *layout; + QWidget *controlContainer; + QHBoxLayout *controlLayout; + + QPushButton *addButton; + QPushButton *removeButton; + QPushButton *saveButton; + QPushButton *loadButton; QScrollArea *scrollArea; + QWidget *panelContainer; + QVBoxLayout *panelLayout; - ManaCurveWidget *manaCurveWidget; - ManaDevotionWidget *manaDevotionWidget; - ManaBaseWidget *manaBaseWidget; + QVector panelWrappers; + ResizablePanel *selectedWrapper = nullptr; + + DeckListStatisticsAnalyzer *statsAnalyzer; + LayoutInspector *insp = nullptr; }; #endif // DECK_ANALYTICS_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp index 3ca18d21c..7d0259c72 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp @@ -9,38 +9,93 @@ DeckListStatisticsAnalyzer::DeckListStatisticsAnalyzer(QObject *parent, DeckListModel *_model, - DeckListStatisticsAnalyzerConfig cfg) - : QObject(parent), model(_model), config(cfg) + DeckListStatisticsAnalyzerConfig _config) + : QObject(parent), model(_model), config(_config) { - connect(model, &DeckListModel::dataChanged, this, &DeckListStatisticsAnalyzer::update); + connect(model, &DeckListModel::dataChanged, this, &DeckListStatisticsAnalyzer::analyze); } -void DeckListStatisticsAnalyzer::update() +void DeckListStatisticsAnalyzer::analyze() { - manaBaseMap.clear(); - manaCurveMap.clear(); - manaDevotionMap.clear(); + clearData(); QList cards = model->getCards(); - for (const ExactCard &card : cards) { - // ---- Mana curve ---- + for (auto card : cards) { + auto info = card.getInfo(); + const int cmc = info.getCmc().toInt(); + + // Convert once + QStringList types = info.getMainCardType().split(' '); + QStringList subtypes = info.getCardType().split('-').last().split(" "); + QString colors = info.getColors(); + int power = info.getPowTough().split("/").first().toInt(); + int toughness = info.getPowTough().split("/").last().toInt(); + + // For each copy of card + // ---------------- Mana Curve ---------------- if (config.computeManaCurve) { - manaCurveMap[card.getInfo().getCmc().toInt()]++; + manaCurveMap[cmc]++; } - // ---- Mana base ---- + // per-type curve + for (auto &t : types) { + manaCurveByType[t][cmc]++; + manaCurveCardsByType[t][cmc].append(info.getName()); + } + + // Per-subtype curve + for (auto &st : subtypes) { + manaCurveBySubtype[st][cmc]++; + manaCurveCardsBySubtype[st][cmc].append(info.getName()); + } + + // per-color curve + for (auto &c : colors) { + manaCurveByColor[c][cmc]++; + manaCurveCardsByColor[c][cmc].append(info.getName()); + } + + // Power/toughness + manaCurveByPower[QString::number(power)][cmc]++; + manaCurveCardsByPower[QString::number(power)][cmc].append(info.getName()); + manaCurveByToughness[QString::number(toughness)][cmc]++; + manaCurveCardsByToughness[QString::number(toughness)][cmc].append(info.getName()); + + // ========== Category Counts =========== + for (auto &t : types) { + typeCount[t]++; + } + for (auto &st : subtypes) { + subtypeCount[st]++; + } + for (auto &c : colors) { + colorCount[c]++; + } + manaValueCount[cmc]++; + + // ---------------- Mana Base ---------------- if (config.computeManaBase) { - auto mana = determineManaProduction(card.getInfo().getText()); - for (auto it = mana.begin(); it != mana.end(); ++it) + auto prod = determineManaProduction(info.getText()); + for (auto it = prod.begin(); it != prod.end(); ++it) { + if (it.value() > 0) { + productionPipCount[it.key()] += it.value(); + productionCardCount[it.key()]++; + } manaBaseMap[it.key()] += it.value(); + } } - // ---- Devotion ---- + // ---------------- Devotion ---------------- if (config.computeDevotion) { - auto devo = countManaSymbols(card.getInfo().getManaCost()); - for (auto &d : devo) + auto devo = countManaSymbols(info.getManaCost()); + for (auto &d : devo) { + if (d.second > 0) { + devotionPipCount[QString(d.first)] += d.second; + devotionCardCount[QString(d.first)]++; + } manaDevotionMap[d.first] += d.second; + } } } @@ -112,3 +167,57 @@ std::unordered_map DeckListStatisticsAnalyzer::countManaSymbols(const return manaCounts; } + +// Hypergeometric probability: P(X=k) +double DeckListStatisticsAnalyzer::hypergeometric(int N, int K, int n, int k) +{ + if (k < 0 || k > n || K > N) { + return 0.0; + } + + auto choose = [](int n, int r) -> double { + if (r > n) + return 0.0; + if (r == 0 || r == n) + return 1.0; + double res = 1.0; + for (int i = 1; i <= r; ++i) { + res *= (n - r + i); + res /= i; + } + return res; + }; + + return choose(K, k) * choose(N - K, n - k) / choose(N, n); +} + +void DeckListStatisticsAnalyzer::clearData() +{ + manaBaseMap.clear(); + manaCurveMap.clear(); + manaDevotionMap.clear(); + + devotionPipCount.clear(); + devotionCardCount.clear(); + + productionPipCount.clear(); + productionCardCount.clear(); + + manaCurveByType.clear(); + manaCurveBySubtype.clear(); + manaCurveByColor.clear(); + manaCurveByPower.clear(); + manaCurveByToughness.clear(); + + manaCurveCardsByType.clear(); + manaCurveCardsBySubtype.clear(); + manaCurveCardsByColor.clear(); + manaCurveCardsByPower.clear(); + manaCurveCardsByToughness.clear(); + + typeCount.clear(); + subtypeCount.clear(); + colorCount.clear(); + rarityCount.clear(); + manaValueCount.clear(); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h index 8fa033971..946bb0117 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h @@ -14,6 +14,9 @@ struct DeckListStatisticsAnalyzerConfig bool computeManaBase = true; bool computeManaCurve = true; bool computeDevotion = true; + bool computeCategories = true; + bool computeCurveBreakdowns = true; + bool computeProbabilities = true; }; class DeckListStatisticsAnalyzer : public QObject @@ -23,9 +26,9 @@ class DeckListStatisticsAnalyzer : public QObject public: explicit DeckListStatisticsAnalyzer(QObject *parent, DeckListModel *model, - DeckListStatisticsAnalyzerConfig cfg = DeckListStatisticsAnalyzerConfig()); + DeckListStatisticsAnalyzerConfig _config = DeckListStatisticsAnalyzerConfig()); - void update(); + void analyze(); [[nodiscard]] const QHash &getManaBase() const { @@ -40,6 +43,96 @@ public: return manaDevotionMap; } + const QHash &getDevotionPipCount() const + { + return devotionPipCount; + } + const QHash &getDevotionCardCount() const + { + return devotionCardCount; + } + + const QHash &getProductionPipCount() const + { + return productionPipCount; + } + const QHash &getProductionCardCount() const + { + return productionCardCount; + } + + const QHash &getTypeCount() const + { + return typeCount; + } + const QHash &getSubtypeCount() const + { + return subtypeCount; + } + const QHash &getColorCount() const + { + return colorCount; + } + const QHash &getRarityCount() const + { + return rarityCount; + } + const QHash &getManaValueCount() const + { + return manaValueCount; + } + + const QHash> &getManaCurveByType() const + { + return manaCurveByType; + } + const QHash> &getManaCurveBySubtype() const + { + return manaCurveBySubtype; + } + const QHash> &getManaCurveByColor() const + { + return manaCurveByColor; + } + const QHash> &getManaCurveByPower() const + { + return manaCurveByPower; + } + const QHash> &getManaCurveByToughness() const + { + return manaCurveByToughness; + } + + const QHash> &getManaCurveCardsByType() const + { + return manaCurveCardsByType; + } + + const QHash> &getManaCurveCardsBySubtype() const + { + return manaCurveCardsBySubtype; + } + + const QHash> &getManaCurveCardsByColor() const + { + return manaCurveCardsByColor; + } + + const QHash> &getManaCurveCardsByPower() const + { + return manaCurveCardsByPower; + } + + const QHash> &getManaCurveCardsByToughness() const + { + return manaCurveCardsByToughness; + } + + DeckListModel *getModel() const + { + return model; + } + signals: void statsUpdated(); @@ -47,14 +140,42 @@ private: DeckListModel *model; DeckListStatisticsAnalyzerConfig config; - // Internal result containers QHash manaBaseMap; std::unordered_map manaCurveMap; std::unordered_map manaDevotionMap; - // Internal helper functions + QHash devotionPipCount; // W/U/B/R/G total symbols + QHash devotionCardCount; // how many cards provide devotion + + QHash productionPipCount; // mana produced by cards + QHash productionCardCount; // number of producers + + QHash typeCount; + QHash subtypeCount; + QHash colorCount; + QHash rarityCount; + QHash manaValueCount; + + QHash> manaCurveByType; + QHash> manaCurveBySubtype; + QHash> manaCurveByColor; + QHash> manaCurveByPower; + QHash> manaCurveByToughness; + + QHash> manaCurveCardsByType; + QHash> manaCurveCardsBySubtype; + QHash> manaCurveCardsByColor; + QHash> manaCurveCardsByPower; + QHash> manaCurveCardsByToughness; + + // Not storing card info — only numeric results. + QHash>> probabilityExact; + QHash>> probabilityAtLeast; + QHash determineManaProduction(const QString &); std::unordered_map countManaSymbols(const QString &); + double hypergeometric(int N, int K, int n, int k); + void clearData(); }; #endif // COCKATRICE_DECK_LIST_STATISTICS_ANALYZER_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.cpp deleted file mode 100644 index 2e530db0b..000000000 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "mana_base_widget.h" - -#include "../../deck_loader/deck_loader.h" -#include "../general/display/banner_widget.h" -#include "../general/display/bar_widget.h" - -#include -#include -#include -#include -#include - -ManaBaseWidget::ManaBaseWidget(QWidget *parent, DeckListStatisticsAnalyzer *_deckStatAnalyzer) - : QWidget(parent), deckStatAnalyzer(_deckStatAnalyzer) -{ - layout = new QVBoxLayout(this); - setLayout(layout); - - bannerWidget = new BannerWidget(this, tr("Mana Base"), Qt::Vertical, 100); - bannerWidget->setMaximumHeight(100); - layout->addWidget(bannerWidget); - - barContainer = new QWidget(this); - barLayout = new QHBoxLayout(barContainer); - layout->addWidget(barContainer); - - connect(deckStatAnalyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaBaseWidget::updateDisplay); - - retranslateUi(); -} - -void ManaBaseWidget::retranslateUi() -{ - bannerWidget->setText(tr("Mana Base")); -} - -void ManaBaseWidget::updateDisplay() -{ - // Clear the layout first - QLayoutItem *item; - while ((item = barLayout->takeAt(0)) != nullptr) { - item->widget()->deleteLater(); - delete item; - } - - auto manaBaseMap = deckStatAnalyzer->getManaBase(); - - int highestEntry = 0; - for (auto entry : manaBaseMap) { - if (entry > highestEntry) { - highestEntry = entry; - } - } - - // Define color mapping for mana types - QHash manaColors; - manaColors.insert("W", QColor(248, 231, 185)); - manaColors.insert("U", QColor(14, 104, 171)); - manaColors.insert("B", QColor(21, 11, 0)); - manaColors.insert("R", QColor(211, 32, 42)); - manaColors.insert("G", QColor(0, 115, 62)); - manaColors.insert("C", QColor(150, 150, 150)); - - for (auto manaColor : manaBaseMap.keys()) { - QColor barColor = manaColors.value(manaColor, Qt::gray); - BarWidget *barWidget = new BarWidget(QString(manaColor), manaBaseMap[manaColor], highestEntry, barColor, this); - barLayout->addWidget(barWidget); - } - - update(); -} diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.h b/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.h deleted file mode 100644 index 079449353..000000000 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.h +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @file mana_base_widget.h - * @ingroup DeckEditorAnalyticsWidgets - * @brief TODO: Document this. - */ - -#ifndef MANA_BASE_WIDGET_H -#define MANA_BASE_WIDGET_H - -#include "../general/display/banner_widget.h" -#include "deck_list_statistics_analyzer.h" - -#include -#include -#include -#include -#include - -class ManaBaseWidget : public QWidget -{ - Q_OBJECT - -public: - explicit ManaBaseWidget(QWidget *parent, DeckListStatisticsAnalyzer *deckStatAnalyzer); - void updateDisplay(); - -public slots: - void retranslateUi(); - -private: - DeckListStatisticsAnalyzer *deckStatAnalyzer; - BannerWidget *bannerWidget; - QVBoxLayout *layout; - QWidget *barContainer; - QHBoxLayout *barLayout; -}; - -#endif // MANA_BASE_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.cpp deleted file mode 100644 index c094eb590..000000000 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "mana_curve_widget.h" - -#include "../../../main.h" -#include "../../deck_loader/deck_loader.h" -#include "../general/display/banner_widget.h" -#include "../general/display/bar_widget.h" - -#include -#include -#include -#include - -ManaCurveWidget::ManaCurveWidget(QWidget *parent, DeckListStatisticsAnalyzer *_deckStatAnalyzer) - : QWidget(parent), deckStatAnalyzer(_deckStatAnalyzer) -{ - layout = new QVBoxLayout(this); - setLayout(layout); - - bannerWidget = new BannerWidget(this, tr("Mana Curve"), Qt::Vertical, 100); - bannerWidget->setMaximumHeight(100); - layout->addWidget(bannerWidget); - - barContainer = new QWidget(this); - barLayout = new QHBoxLayout(barContainer); - layout->addWidget(barContainer); - - connect(deckStatAnalyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaCurveWidget::updateDisplay); - - retranslateUi(); -} - -void ManaCurveWidget::retranslateUi() -{ - bannerWidget->setText(tr("Mana Curve")); -} - -void ManaCurveWidget::updateDisplay() -{ - // Clear the layout first - if (barLayout != nullptr) { - QLayoutItem *item; - while ((item = barLayout->takeAt(0)) != nullptr) { - item->widget()->deleteLater(); - delete item; - } - } - - auto manaCurveMap = deckStatAnalyzer->getManaCurve(); - - int highestEntry = 0; - for (const auto &entry : manaCurveMap) { - if (entry.second > highestEntry) { - highestEntry = entry.second; - } - } - - // Convert unordered_map to ordered map to ensure sorting by CMC - std::map sortedManaCurve(manaCurveMap.begin(), manaCurveMap.end()); - - // Add new widgets to the layout in sorted order - for (const auto &entry : sortedManaCurve) { - BarWidget *barWidget = - new BarWidget(QString::number(entry.first), entry.second, highestEntry, QColor(122, 122, 122), this); - barLayout->addWidget(barWidget); - } - - update(); // Update the widget display -} diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.h b/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.h deleted file mode 100644 index fad1fb0f8..000000000 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.h +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @file mana_curve_widget.h - * @ingroup DeckEditorAnalyticsWidgets - * @brief TODO: Document this. - */ - -#ifndef MANA_CURVE_WIDGET_H -#define MANA_CURVE_WIDGET_H - -#include "../general/display/banner_widget.h" -#include "deck_list_statistics_analyzer.h" - -#include -#include -#include -#include - -class ManaCurveWidget : public QWidget -{ - Q_OBJECT - -public: - explicit ManaCurveWidget(QWidget *parent, DeckListStatisticsAnalyzer *deckStatAnalyzer); - void updateDisplay(); - -public slots: - void retranslateUi(); - -private: - DeckListStatisticsAnalyzer *deckStatAnalyzer; - QVBoxLayout *layout; - BannerWidget *bannerWidget; - QWidget *barContainer; - QHBoxLayout *barLayout; -}; - -#endif // MANA_CURVE_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp deleted file mode 100644 index 476bb8077..000000000 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "mana_devotion_widget.h" - -#include "../../deck_loader/deck_loader.h" -#include "../general/display/banner_widget.h" -#include "../general/display/bar_widget.h" - -#include -#include -#include -#include -#include - -ManaDevotionWidget::ManaDevotionWidget(QWidget *parent, DeckListStatisticsAnalyzer *_deckStatAnalyzer) - : QWidget(parent), deckStatAnalyzer(_deckStatAnalyzer) -{ - layout = new QVBoxLayout(this); - setLayout(layout); - - bannerWidget = new BannerWidget(this, tr("Mana Devotion"), Qt::Vertical, 100); - bannerWidget->setMaximumHeight(100); - layout->addWidget(bannerWidget); - - barLayout = new QHBoxLayout(); - layout->addLayout(barLayout); - - connect(deckStatAnalyzer, &DeckListStatisticsAnalyzer::statsUpdated, this, &ManaDevotionWidget::updateDisplay); - - retranslateUi(); -} - -void ManaDevotionWidget::retranslateUi() -{ - bannerWidget->setText(tr("Mana Devotion")); -} - -void ManaDevotionWidget::updateDisplay() -{ - // Clear the layout first - QLayoutItem *item; - while ((item = barLayout->takeAt(0)) != nullptr) { - item->widget()->deleteLater(); - delete item; - } - - auto manaDevotionMap = deckStatAnalyzer->getDevotion(); - - int highestEntry = 0; - for (auto entry : manaDevotionMap) { - if (highestEntry < entry.second) { - highestEntry = entry.second; - } - } - - // Define color mapping for devotion bars - std::unordered_map manaColors = {{'W', QColor(248, 231, 185)}, {'U', QColor(14, 104, 171)}, - {'B', QColor(21, 11, 0)}, {'R', QColor(211, 32, 42)}, - {'G', QColor(0, 115, 62)}, {'C', QColor(150, 150, 150)}}; - - for (auto entry : manaDevotionMap) { - QColor barColor = manaColors.count(entry.first) ? manaColors[entry.first] : Qt::gray; - BarWidget *barWidget = new BarWidget(QString(entry.first), entry.second, highestEntry, barColor, this); - barLayout->addWidget(barWidget); - } - - update(); // Update the widget display -} diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.h b/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.h deleted file mode 100644 index ff2e86159..000000000 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.h +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @file mana_devotion_widget.h - * @ingroup DeckEditorAnalyticsWidgets - * @brief TODO: Document this. - */ - -#ifndef MANA_DEVOTION_WIDGET_H -#define MANA_DEVOTION_WIDGET_H - -#include "../general/display/banner_widget.h" -#include "deck_list_statistics_analyzer.h" - -#include -#include -#include -#include -#include - -class ManaDevotionWidget : public QWidget -{ - Q_OBJECT - -public: - explicit ManaDevotionWidget(QWidget *parent, DeckListStatisticsAnalyzer *deckStatAnalyzer); - void updateDisplay(); - -public slots: - void retranslateUi(); - -private: - DeckListStatisticsAnalyzer *deckStatAnalyzer; - BannerWidget *bannerWidget; - QVBoxLayout *layout; - QHBoxLayout *barLayout; -}; - -#endif // MANA_DEVOTION_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp b/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp new file mode 100644 index 000000000..a0c971a75 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp @@ -0,0 +1,367 @@ +#include "resizable_panel.h" + +#include "libcockatrice/utility/qt_utils.h" + +#include +#include +#include +#include + +ResizablePanel::ResizablePanel(const QString &_typeId, AbstractAnalyticsPanelWidget *analyticsPanel, QWidget *parent) + : QWidget(parent), panel(analyticsPanel), typeId(_typeId) +{ + setAcceptDrops(true); + + auto *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + // Frame for selection highlight + frame = new QFrame(this); + frame->setFrameShape(QFrame::Box); + frame->setLineWidth(2); + frame->setStyleSheet("border: none;"); + + auto *frameLayout = new QVBoxLayout(frame); + frameLayout->setContentsMargins(0, 0, 0, 0); + frameLayout->setSpacing(0); + + // Add the analytics panel + frameLayout->addWidget(analyticsPanel); + + dropIndicator = new QFrame(frame); + dropIndicator->setStyleSheet("background-color: #3daee9;"); + dropIndicator->setFixedHeight(3); + dropIndicator->hide(); // hidden by default + dropIndicator->raise(); // make sure it's above children + + selectionOverlay = new QFrame(frame); + selectionOverlay->setStyleSheet("background-color: rgba(61,174,233,50);"); // semi-transparent blue + selectionOverlay->hide(); // hidden by default + selectionOverlay->raise(); // make sure it is above children + selectionOverlay->setAttribute(Qt::WA_TransparentForMouseEvents); + + // Bottom bar with drag button and resize handle + auto *bottomBar = new QWidget(frame); + auto *bottomLayout = new QHBoxLayout(bottomBar); + bottomLayout->setContentsMargins(0, 0, 0, 0); + bottomLayout->setSpacing(0); + + // Drag button on the left + dragButton = new QPushButton("☰", bottomBar); + dragButton->setFixedSize(40, 8); + dragButton->setCursor(Qt::OpenHandCursor); + dragButton->setStyleSheet("QPushButton { " + "background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #4a4a4a, stop:1 #3a3a3a); " + "border: none; color: #888; font-size: 10px; }" + "QPushButton:hover { background: #5a5a5a; }"); + bottomLayout->addWidget(dragButton); + + // Resize handle fills the rest + resizeHandle = new QWidget(bottomBar); + resizeHandle->setFixedHeight(8); + resizeHandle->setCursor(Qt::SizeVerCursor); + resizeHandle->setStyleSheet("background: qlineargradient(x1:0, y1:0, x2:0, y2:1, " + "stop:0 #3a3a3a, stop:1 #2a2a2a);"); + bottomLayout->addWidget(resizeHandle, 1); + + frameLayout->addWidget(bottomBar); + + mainLayout->addWidget(frame); + + // Set size policy + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + + // Calculate initial height - use panel's size hint if available + int panelHint = analyticsPanel->sizeHint().height(); + int panelMin = analyticsPanel->minimumSizeHint().height(); + + // Start with the larger of panel's hint and panel's minimum hint + currentHeight = qMax(panelHint + 8, panelMin + 8); + updateSizeConstraints(); + + // Install event filters + dragButton->installEventFilter(this); + resizeHandle->installEventFilter(this); + + // Timer for auto-scroll during drag + autoScrollTimer = new QTimer(this); + autoScrollTimer->setInterval(50); + connect(autoScrollTimer, &QTimer::timeout, this, &ResizablePanel::performAutoScroll); +} + +void ResizablePanel::setSelected(bool selected) +{ + if (selected) { + selectionOverlay->setGeometry(0, 0, width(), height()); + selectionOverlay->show(); + } else { + selectionOverlay->hide(); + } +} + +void ResizablePanel::setHeightFromSaved(int h) +{ + if (h > 0) { + currentHeight = qMax(h, getMinimumAllowedHeight()); + updateSizeConstraints(); + } +} + +int ResizablePanel::getCurrentHeight() const +{ + return currentHeight; +} + +QSize ResizablePanel::sizeHint() const +{ + return QSize(width(), currentHeight); +} + +QSize ResizablePanel::minimumSizeHint() const +{ + return QSize(0, getMinimumAllowedHeight()); +} + +// ===================================================================================================================== +// Event Handling +// ===================================================================================================================== + +bool ResizablePanel::eventFilter(QObject *obj, QEvent *event) +{ + if (obj == dragButton) { + if (event->type() == QEvent::MouseButtonPress) { + auto *mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::LeftButton) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + dragStartPos = mouseEvent->globalPosition().toPoint(); +#else + dragStartPos = mouseEvent->globalPos(); +#endif + isDraggingPanel = false; + dragButton->setCursor(Qt::ClosedHandCursor); + } + return false; + } else if (event->type() == QEvent::MouseMove) { + auto *mouseEvent = static_cast(event); + if (mouseEvent->buttons() & Qt::LeftButton) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QPoint currentPos = mouseEvent->globalPosition().toPoint(); +#else + QPoint currentPos = mouseEvent->globalPos(); +#endif + int distance = (currentPos - dragStartPos).manhattanLength(); + if (distance >= 5 && !isDraggingPanel) { + isDraggingPanel = true; + startDrag(); + return true; + } + } + return false; + } else if (event->type() == QEvent::MouseButtonRelease) { + dragButton->setCursor(Qt::OpenHandCursor); + isDraggingPanel = false; + return false; + } + } + + if (obj == resizeHandle) { + if (event->type() == QEvent::MouseButtonPress) { + auto *mouseEvent = static_cast(event); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + resizeStartY = mouseEvent->globalPosition().y(); +#else + resizeStartY = mouseEvent->globalPos().y(); +#endif + isResizing = true; + resizeStartHeight = currentHeight; + resizeHandle->grabMouse(); + return true; + } else if (event->type() == QEvent::MouseMove && isResizing) { + auto *mouseEvent = static_cast(event); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + int deltaY = mouseEvent->globalPosition().y() - resizeStartY; +#else + int deltaY = mouseEvent->globalPos().y() - resizeStartY; +#endif + int newHeight = resizeStartHeight + deltaY; + + int minAllowed = getMinimumAllowedHeight(); + newHeight = qMax(newHeight, minAllowed); + + currentHeight = newHeight; + updateSizeConstraints(); + + return true; + } else if (event->type() == QEvent::MouseButtonRelease) { + isResizing = false; + resizeHandle->releaseMouse(); + return true; + } + } + + return QWidget::eventFilter(obj, event); +} + +void ResizablePanel::dragEnterEvent(QDragEnterEvent *event) +{ + if (event->mimeData()->hasFormat("application/x-resizablepanel")) { + event->acceptProposedAction(); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + showDropIndicator(event->position().y()); +#else + showDropIndicator(event->pos().y()); +#endif + } +} + +void ResizablePanel::dragMoveEvent(QDragMoveEvent *event) +{ + if (event->mimeData()->hasFormat("application/x-resizablepanel")) { + event->acceptProposedAction(); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + showDropIndicator(event->position().y()); + lastDragPos = mapToGlobal(event->position().toPoint()); +#else + showDropIndicator(event->pos().y()); + lastDragPos = mapToGlobal(event->pos()); +#endif + + if (!autoScrollTimer->isActive()) { + autoScrollTimer->start(); + } + } +} + +void ResizablePanel::dragLeaveEvent(QDragLeaveEvent *event) +{ + Q_UNUSED(event); + hideDropIndicator(); + autoScrollTimer->stop(); +} + +void ResizablePanel::dropEvent(QDropEvent *event) +{ + hideDropIndicator(); + autoScrollTimer->stop(); + + if (event->mimeData()->hasFormat("application/x-resizablepanel")) { + QByteArray data = event->mimeData()->data("application/x-resizablepanel"); + quintptr ptr = *reinterpret_cast(data.constData()); + ResizablePanel *draggedPanel = reinterpret_cast(ptr); + + if (draggedPanel && draggedPanel != this) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + bool insertBefore = (event->position().y() < height() / 2); +#else + bool insertBefore = (event->pos().y() < height() / 2); +#endif + emit dropRequested(draggedPanel, this, insertBefore); + event->acceptProposedAction(); + } + } +} + +void ResizablePanel::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + + if (selectionOverlay->isVisible()) { + selectionOverlay->setGeometry(0, 0, width(), height()); + } + + if (dropIndicator->isVisible()) { + dropIndicator->setGeometry(0, dropIndicator->y(), width(), dropIndicator->height()); + } +} + +// ===================================================================================================================== +// Private Helpers +// ===================================================================================================================== + +int ResizablePanel::getMinimumAllowedHeight() const +{ + QSize panelMin = panel->minimumSizeHint(); + int panelMinHeight = (panelMin.isValid() && panelMin.height() > 0) ? panelMin.height() : 100; + return panelMinHeight + 8; +} + +void ResizablePanel::updateSizeConstraints() +{ + setMinimumHeight(currentHeight); + setMaximumHeight(currentHeight); + updateGeometry(); +} + +void ResizablePanel::startDrag() +{ + QDrag *drag = new QDrag(this); + QMimeData *mimeData = new QMimeData; + + quintptr ptr = reinterpret_cast(this); + QByteArray data(reinterpret_cast(&ptr), sizeof(ptr)); + mimeData->setData("application/x-resizablepanel", data); + + drag->setMimeData(mimeData); + + QPixmap pixmap(width(), 40); + pixmap.fill(QColor(58, 58, 58, 200)); + drag->setPixmap(pixmap); + drag->setHotSpot(QPoint(width() / 2, 20)); + + emit dragStarted(this); + + autoScrollTimer->start(); + + Qt::DropAction result = drag->exec(Qt::MoveAction); + Q_UNUSED(result); + + autoScrollTimer->stop(); + dragButton->setCursor(Qt::OpenHandCursor); + isDraggingPanel = false; +} + +void ResizablePanel::performAutoScroll() +{ + QScrollArea *scrollArea = QtUtils::findParentOfType(this); + + if (!scrollArea) { + return; + } + + QScrollBar *scrollBar = scrollArea->verticalScrollBar(); + if (!scrollBar) { + return; + } + + QRect scrollRect = scrollArea->viewport()->rect(); + QPoint scrollTopLeft = scrollArea->viewport()->mapToGlobal(scrollRect.topLeft()); + QRect globalScrollRect(scrollTopLeft, scrollRect.size()); + + const int scrollMargin = 50; + int scrollSpeed = 0; + + if (lastDragPos.y() < globalScrollRect.top() + scrollMargin) { + scrollSpeed = -15; + } else if (lastDragPos.y() > globalScrollRect.bottom() - scrollMargin) { + scrollSpeed = 15; + } + + if (scrollSpeed != 0) { + int newValue = scrollBar->value() + scrollSpeed; + newValue = qBound(scrollBar->minimum(), newValue, scrollBar->maximum()); + scrollBar->setValue(newValue); + } +} + +void ResizablePanel::showDropIndicator(double y) +{ + bool before = (y < height() / 2); + dropIndicator->setGeometry(0, before ? 0 : height() - 3, width(), 3); + dropIndicator->show(); +} + +void ResizablePanel::hideDropIndicator() +{ + dropIndicator->hide(); +} diff --git a/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.h b/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.h new file mode 100644 index 000000000..d958ec64d --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.h @@ -0,0 +1,79 @@ +#ifndef COCKATRICE_RESIZABLE_PANEL_H +#define COCKATRICE_RESIZABLE_PANEL_H + +#include "abstract_analytics_panel_widget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class ResizablePanel : public QWidget +{ + Q_OBJECT +public: + explicit ResizablePanel(const QString &typeId, + AbstractAnalyticsPanelWidget *analyticsPanel, + QWidget *parent = nullptr); + + void setSelected(bool selected); + void setHeightFromSaved(int h); + int getCurrentHeight() const; + + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + + QString getTypeId() const + { + return typeId; + } + + AbstractAnalyticsPanelWidget *panel; + +signals: + void dragStarted(ResizablePanel *panel); + void dropRequested(ResizablePanel *dragged, ResizablePanel *target, bool insertBefore); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dropEvent(QDropEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + +private: + int getMinimumAllowedHeight() const; + void updateSizeConstraints(); + void startDrag(); + void performAutoScroll(); + void showDropIndicator(double y); + void hideDropIndicator(); + + QString typeId; + + QFrame *frame; + QFrame *selectionOverlay; + QFrame *dropIndicator; + QPushButton *dragButton; + QWidget *resizeHandle; + + int currentHeight; + bool isResizing = false; + bool isDraggingPanel = false; + double resizeStartY = 0; + int resizeStartHeight = 0; + + QPoint dragStartPos; + QPoint lastDragPos; + QTimer *autoScrollTimer; +}; + +#endif // COCKATRICE_RESIZABLE_PANEL_H diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_background_widget.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_background_widget.cpp new file mode 100644 index 000000000..f7ca669f3 --- /dev/null +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_background_widget.cpp @@ -0,0 +1,43 @@ +#include "bar_chart_background_widget.h" + +BarChartBackgroundWidget::BarChartBackgroundWidget(QWidget *parent) : QWidget(parent) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +} + +QSize BarChartBackgroundWidget::sizeHint() const +{ + return QSize(100, 150); +} + +void BarChartBackgroundWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + + constexpr int PAD = 4; + constexpr int LABEL_H = 20; + + int left = 46; // axis space + internal padding + int right = width() - PAD; + int top = PAD; + int bottom = height() - PAD - LABEL_H; + + int barAreaHeight = bottom - top; + int barAreaWidth = right - left; + + p.fillRect(QRect(left, top, barAreaWidth, barAreaHeight), QColor(250, 250, 250)); + + int ticks = 5; + for (int i = 0; i <= ticks; i++) { + float r = float(i) / ticks; + int y = bottom - r * barAreaHeight; + + p.setPen(QPen(QColor(180, 180, 180, 120), 1)); + p.drawLine(left, y, right, y); + + p.setPen(Qt::black); + p.drawText(left - 35, y - 6, 32, 12, Qt::AlignRight | Qt::AlignVCenter, QString::number(int(r * highest))); + } +} diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_background_widget.h b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_background_widget.h new file mode 100644 index 000000000..06a17c7c6 --- /dev/null +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_background_widget.h @@ -0,0 +1,23 @@ +#ifndef COCKATRICE_BAR_CHART_BACKGROUND_WIDGET_H +#define COCKATRICE_BAR_CHART_BACKGROUND_WIDGET_H + +#include +#include + +class BarChartBackgroundWidget : public QWidget +{ + Q_OBJECT +public: + int highest = 0; // global maximum (shared across bars) + int barCount = 0; // number of CMC columns + int labelHeight = 20; // reserved for CMC numbers + + explicit BarChartBackgroundWidget(QWidget *parent); +public slots: + QSize sizeHint() const override; + +protected: + void paintEvent(QPaintEvent *event) override; +}; + +#endif // COCKATRICE_BAR_CHART_BACKGROUND_WIDGET_H diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp new file mode 100644 index 000000000..998808307 --- /dev/null +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp @@ -0,0 +1,215 @@ +#include "bar_chart_widget.h" + +#include +#include +#include +#include + +BarChartWidget::BarChartWidget(QWidget *parent) : QWidget(parent) +{ + setMouseTracking(true); +} + +void BarChartWidget::setBars(const QVector &newBars) +{ + bars = newBars; + update(); +} + +void BarChartWidget::setHighest(int h) +{ + highest = qMax(1, h); + update(); +} + +QSize BarChartWidget::sizeHint() const +{ + return QSize(300, 200); +} + +QSize BarChartWidget::minimumSizeHint() const +{ + return QSize(300, 50); +} + +void BarChartWidget::paintEvent(QPaintEvent *) +{ + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + + constexpr int PAD = 4; + constexpr int LABEL_H = 20; + + int w = width(); + int h = height(); + + int left = 46; + int right = w - PAD; + int top = PAD; + int bottom = h - PAD - LABEL_H; + + int barAreaHeight = bottom - top; + int barAreaWidth = right - left; + + int barCount = bars.size(); + if (barCount == 0) + return; + + int spacing = 6; + int barWidth = (barAreaWidth - (barCount - 1) * spacing) / barCount; + + // background + p.fillRect(QRect(left, top, barAreaWidth, barAreaHeight), QColor(250, 250, 250)); + + // y-axis ticks + int ticks = 5; + // qInfo() << "Tick Positions "; + for (int i = 0; i <= ticks; i++) { + float r = float(i) / ticks; + int tickVal = i * highest / ticks; // integer value of tick + int y = bottom - (tickVal * barAreaHeight / highest); + + // qInfo() << "Tick" << i << "value" << int(r * highest) << "y" << y; + + p.setPen(QPen(QColor(180, 180, 180, 120), 1)); + p.drawLine(left, y, right, y); + + p.setPen(Qt::black); + p.drawText(left - 35, y - 6, 32, 12, Qt::AlignRight | Qt::AlignVCenter, QString::number(int(r * highest))); + } + + // draw bars + // qInfo() << "Bar Segments"; + int drawWidth = barWidth / 4; // 1/4 of allocated width + int xOffset = (barWidth - drawWidth) / 2; // center the narrow bar + + for (int i = 0; i < barCount; i++) { + const BarData &bar = bars[i]; + int x = left + i * (barWidth + spacing) + xOffset; // shift to center + int yCurrent = bottom; + + for (int j = 0; j < bar.segments.size(); j++) { + const auto &seg = bar.segments[j]; + int segHeight = (seg.value * barAreaHeight / highest); + if (segHeight < 2 && seg.value > 0) + segHeight = 2; + + int topY = yCurrent - segHeight; + + QRect r(x, topY, drawWidth, segHeight); // use drawWidth instead of barWidth + bool isTop = (j == bar.segments.size() - 1); + + QLinearGradient g(r.topLeft(), r.bottomLeft()); + g.setColorAt(0, seg.color.lighter(120)); + g.setColorAt(1, seg.color.darker(110)); + p.setBrush(g); + p.setPen(Qt::NoPen); + + if (isTop) { + QPainterPath path; + int radius = 6; + + int bx = r.x(); + int by = r.y(); + int bw = r.width(); + int bh = r.height(); + + path.moveTo(bx, by + bh); + path.lineTo(bx, by + radius); + path.quadTo(bx, by, bx + radius, by); + path.lineTo(bx + bw - radius, by); + path.quadTo(bx + bw, by, bx + bw, by + radius); + path.lineTo(bx + bw, by + bh); + path.lineTo(bx, by + bh); + path.closeSubpath(); + + p.drawPath(path); + } else { + p.drawRect(r); + } + + yCurrent -= segHeight; + } + + // draw label below bar + QRect labelRect(left + i * (barWidth + spacing), bottom, barWidth, LABEL_H); + QFont f = p.font(); + f.setBold(true); + p.setFont(f); + p.setPen(Qt::black); + p.drawText(labelRect, Qt::AlignCenter, bar.label); + } +} + +void BarChartWidget::leaveEvent(QEvent *) +{ + hoveredBar = -1; + hoveredSegment = -1; + QToolTip::hideText(); +} + +void BarChartWidget::mouseMoveEvent(QMouseEvent *e) +{ + if (bars.isEmpty()) { + return; + } + + constexpr int PAD = 4; + constexpr int LABEL_H = 20; + int w = width(); + int h = height(); + int left = 46; + int right = w - PAD; + int top = PAD; + int bottom = h - PAD - LABEL_H; + int barAreaHeight = bottom - top; + + int barCount = bars.size(); + int spacing = 6; + int barWidth = (right - left - (barCount - 1) * spacing) / barCount; + + // find hovered bar + int mx = e->pos().x(); + hoveredBar = -1; + for (int i = 0; i < barCount; i++) { + int x0 = left + i * (barWidth + spacing); + if (mx >= x0 && mx <= x0 + barWidth) { + hoveredBar = i; + break; + } + } + if (hoveredBar < 0) { + return; + } + + // find hovered segment + int yCurrent = bottom; + const auto &segments = bars[hoveredBar].segments; + hoveredSegment = -1; + for (int i = 0; i < segments.size(); i++) { + const auto &seg = segments[i]; + int segHeight = (seg.value * barAreaHeight / highest); + if (segHeight < 2 && seg.value > 0) + segHeight = 2; + + int topY = yCurrent - segHeight; + int bottomY = yCurrent; + if (e->pos().y() >= topY && e->pos().y() <= bottomY) { + hoveredSegment = i; + break; + } + yCurrent -= segHeight; + } + + if (hoveredSegment >= 0) { + const auto &s = segments[hoveredSegment]; + QString text = QString("%1: %2 cards\n\n%3").arg(s.category).arg(s.value).arg(s.cards.join("\n")); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QToolTip::showText(e->globalPosition().toPoint(), text, this); +#else + QToolTip::showText(e->globalPos(), text, this); +#endif + } else { + QToolTip::hideText(); + } +} diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.h b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.h new file mode 100644 index 000000000..e80a3f8e8 --- /dev/null +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.h @@ -0,0 +1,52 @@ +#ifndef COCKATRICE_BAR_CHART_WIDGET_H +#define COCKATRICE_BAR_CHART_WIDGET_H + +#include +#include +#include +#include + +struct BarSegment +{ + QString category; + int value; + QStringList cards; + QColor color; +}; + +struct BarData +{ + QString label; + QVector segments; +}; + +class BarChartWidget : public QWidget +{ + Q_OBJECT +public: + explicit BarChartWidget(QWidget *parent = nullptr); + + void setBars(const QVector &bars); + void setHighest(int h); // global max for scaling + int barCount() const + { + return bars.size(); + } + +protected: + void paintEvent(QPaintEvent *event) override; + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + + void mouseMoveEvent(QMouseEvent *event) override; + void leaveEvent(QEvent *event) override; + +private: + QVector bars; + int highest = 1; // global maximum value + + int hoveredBar = -1; + int hoveredSegment = -1; +}; + +#endif // COCKATRICE_BAR_CHART_WIDGET_H diff --git a/cockatrice/src/interface/widgets/general/display/bar_widget.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_widget.cpp similarity index 100% rename from cockatrice/src/interface/widgets/general/display/bar_widget.cpp rename to cockatrice/src/interface/widgets/general/display/charts/bars/bar_widget.cpp diff --git a/cockatrice/src/interface/widgets/general/display/bar_widget.h b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_widget.h similarity index 100% rename from cockatrice/src/interface/widgets/general/display/bar_widget.h rename to cockatrice/src/interface/widgets/general/display/charts/bars/bar_widget.h diff --git a/cockatrice/src/interface/widgets/general/display/color_bar.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.cpp similarity index 99% rename from cockatrice/src/interface/widgets/general/display/color_bar.cpp rename to cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.cpp index d1eb7ef4c..94e2420b5 100644 --- a/cockatrice/src/interface/widgets/general/display/color_bar.cpp +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.cpp @@ -1,4 +1,3 @@ - #include "color_bar.h" #include diff --git a/cockatrice/src/interface/widgets/general/display/color_bar.h b/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.h similarity index 100% rename from cockatrice/src/interface/widgets/general/display/color_bar.h rename to cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.h diff --git a/cockatrice/src/interface/widgets/general/display/percent_bar_widget.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/percent_bar_widget.cpp similarity index 100% rename from cockatrice/src/interface/widgets/general/display/percent_bar_widget.cpp rename to cockatrice/src/interface/widgets/general/display/charts/bars/percent_bar_widget.cpp diff --git a/cockatrice/src/interface/widgets/general/display/percent_bar_widget.h b/cockatrice/src/interface/widgets/general/display/charts/bars/percent_bar_widget.h similarity index 100% rename from cockatrice/src/interface/widgets/general/display/percent_bar_widget.h rename to cockatrice/src/interface/widgets/general/display/charts/bars/percent_bar_widget.h diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp new file mode 100644 index 000000000..e027aabdd --- /dev/null +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp @@ -0,0 +1,140 @@ +#include "segmented_bar_widget.h" + +#include +#include +#include +#include + +SegmentedBarWidget::SegmentedBarWidget(QString label, QVector segments, int total, QWidget *parent) + : QWidget(parent), label(std::move(label)), segments(std::move(segments)), total(total) +{ + setMouseTracking(true); + setMinimumWidth(36); + setMaximumWidth(50); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); +} + +QSize SegmentedBarWidget::sizeHint() const +{ + return QSize(50, 150); +} + +void SegmentedBarWidget::paintEvent(QPaintEvent *) +{ + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + + constexpr int PAD = 4; + constexpr int LABEL_H = 20; + + int w = width(); + int h = height(); + + int barX = PAD; + int barWidth = w - PAD * 2; + + int barTop = PAD; + int barBottom = h - PAD - LABEL_H; + int barHeight = barBottom - barTop; + + int yCurrent = barBottom; + + // draw stacked segments + for (int i = 0; i < segments.size(); i++) { + const auto &seg = segments[i]; + + int segHeight = total > 0 ? (seg.value * barHeight / total) : 0; + if (segHeight < 2) + segHeight = 2; + + QRect r(barX, yCurrent - segHeight, barWidth, segHeight); + bool isTop = (i == segments.size() - 1); + + QLinearGradient g(r.topLeft(), r.bottomLeft()); + g.setColorAt(0, seg.color.lighter(120)); + g.setColorAt(1, seg.color.darker(110)); + p.setBrush(g); + p.setPen(Qt::NoPen); + + if (isTop) { + QPainterPath path; + int radius = 6; + + int x = r.x(); + int y = r.y(); + int w = r.width(); + int h = r.height(); + + path.moveTo(x, y + h); + path.lineTo(x, y + radius); + path.quadTo(x, y, x + radius, y); + path.lineTo(x + w - radius, y); + path.quadTo(x + w, y, x + w, y + radius); + path.lineTo(x + w, y + h); + path.lineTo(x, y + h); + path.closeSubpath(); + + p.drawPath(path); + } else { + p.drawRect(r); + } + + yCurrent -= segHeight; + } + + // draw label + QRect labelRect(0, h - LABEL_H, w, LABEL_H); + QFont f = p.font(); + f.setBold(true); + p.setFont(f); + p.setPen(Qt::black); + p.drawText(labelRect, Qt::AlignCenter, label); +} + +int SegmentedBarWidget::segmentAt(int y) const +{ + int padding = 4; + int labelHeight = 20; + int barHeight = height() - padding * 2 - labelHeight; + int barTop = padding; + int barBottom = barTop + barHeight; + + int currentTop = barBottom; + + for (int i = 0; i < segments.size(); i++) { + int segHeight = total > 0 ? (segments[i].value * barHeight / total) : 0; + if (segHeight < 1) { + segHeight = 1; + } + + int top = currentTop - segHeight; + int bottom = currentTop; + + if (y >= top && y <= bottom) + return i; + + currentTop -= segHeight; + } + return -1; +} + +void SegmentedBarWidget::mouseMoveEvent(QMouseEvent *e) +{ + if (!hovered) { + return; + } + + int idx = segmentAt(e->pos().y()); + if (idx < 0) { + return; + } + + const Segment &s = segments[idx]; + QString text = QString("%1: %2 cards\n%3").arg(s.category).arg(s.value).arg(s.cards.join(", ")); + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QToolTip::showText(e->globalPosition().toPoint(), text, this); +#else + QToolTip::showText(e->globalPos(), text, this); +#endif +} diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.h b/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.h new file mode 100644 index 000000000..4df54e42e --- /dev/null +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.h @@ -0,0 +1,38 @@ +#ifndef COCKATRICE_SEGMENTED_BAR_WIDGET_H +#define COCKATRICE_SEGMENTED_BAR_WIDGET_H + +#include +#include +#include + +class SegmentedBarWidget : public QWidget +{ + Q_OBJECT + +public: + struct Segment + { + QString category; + int value = 0; + QStringList cards; + QColor color; + }; + + QString label; + QVector segments; + float total = 1.0; + + explicit SegmentedBarWidget(QString label, QVector segments, int total, QWidget *parent = nullptr); + QSize sizeHint() const override; + +protected: + void paintEvent(QPaintEvent *event) override; + void mouseMoveEvent(QMouseEvent *e) override; + + int segmentAt(int y) const; + +private: + bool hovered = true; +}; + +#endif // COCKATRICE_SEGMENTED_BAR_WIDGET_H diff --git a/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.cpp b/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.cpp new file mode 100644 index 000000000..e86793083 --- /dev/null +++ b/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.cpp @@ -0,0 +1,205 @@ +#include "color_pie.h" + +#include +#include +#include +#include + +ColorPie::ColorPie(const QMap &_colors, QWidget *parent) : QWidget(parent), colors(_colors) +{ + setMouseTracking(true); +} + +void ColorPie::setColors(const QMap &_colors) +{ + colors = _colors; + update(); +} + +QSize ColorPie::minimumSizeHint() const +{ + return QSize(200, 200); +} + +void ColorPie::paintEvent(QPaintEvent *) +{ + if (colors.isEmpty()) { + return; + } + + int total = 0; + for (int v : colors.values()) { + total += v; + } + + if (total == 0) { + return; + } + + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing, true); + + int w = width(); + int h = height(); + int size = qMin(w, h) - 40; // leave space for labels + QRectF rect((w - size) / 2.0, (h - size) / 2.0, size, size); + + // Draw border + p.setPen(QPen(Qt::black, 1)); + p.setBrush(Qt::NoBrush); + p.drawEllipse(rect); + + // Sorted keys for predictable order + QList sortedKeys = colors.keys(); + std::sort(sortedKeys.begin(), sortedKeys.end()); + + double startAngle = 0.0; + + for (const QString &key : sortedKeys) { + int value = colors[key]; + double ratio = double(value) / total; + + if (ratio <= minRatioThreshold) { + continue; + } + + double spanAngle = ratio * 360.0; + + QColor base = colorFromName(key); + + // Gradient + QRadialGradient grad(rect.center(), size / 2); + grad.setColorAt(0, base.lighter(130)); + grad.setColorAt(1, base.darker(130)); + p.setBrush(grad); + p.setPen(Qt::NoPen); + + // Draw slice + p.drawPie(rect, int(startAngle * 16), int(spanAngle * 16)); + + // Draw percent label + double midAngle = startAngle + spanAngle / 2; + double rad = qDegreesToRadians(midAngle); + double labelRadius = size / 2 + 15; // slightly outside the pie + QPointF center = rect.center(); + QPointF labelPos(center.x() + labelRadius * qCos(rad), center.y() - labelRadius * qSin(rad)); + + QString label = QString("%1%").arg(int(ratio * 100 + 0.5)); + + QFontMetrics fm(p.font()); +#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) + int labelWidth = fm.horizontalAdvance(label); +#else + int labelWidth = fm.width(label); +#endif + QRectF textRect(labelPos.x() - labelWidth / 2.0, labelPos.y() - fm.height() / 2.0, labelWidth, fm.height()); + + p.setPen(Qt::black); + p.drawText(textRect, Qt::AlignCenter, label); + + startAngle += spanAngle; + } +} + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +void ColorPie::enterEvent(QEnterEvent *event) +{ + Q_UNUSED(event); + isHovered = true; +} +#else +void ColorPie::enterEvent(QEvent *event) +{ + Q_UNUSED(event); + isHovered = true; +} +#endif + +void ColorPie::leaveEvent(QEvent *) +{ + isHovered = false; +} + +void ColorPie::mouseMoveEvent(QMouseEvent *event) +{ + if (!isHovered || colors.isEmpty()) { + return; + } + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QPoint p = event->position().toPoint(); + QPoint gp = event->globalPosition().toPoint(); +#else + QPoint p = event->pos(); + QPoint gp = event->globalPos(); +#endif + + QString text = tooltipForPoint(p); + if (!text.isEmpty()) { + QToolTip::showText(gp, text, this); + } +} + +QString ColorPie::tooltipForPoint(const QPoint &pt) const +{ + if (colors.isEmpty()) { + return {}; + } + + int total = 0; + for (int v : colors.values()) + total += v; + if (total == 0) + return {}; + + int w = width(); + int h = height(); + int size = qMin(w, h) - 40; + QPointF center(w / 2.0, h / 2.0); + + QPointF v = pt - center; + double distance = std::sqrt(v.x() * v.x() + v.y() * v.y()); + if (distance > size / 2.0) + return {}; // outside pie + + double angle = std::atan2(-v.y(), v.x()) * 180.0 / M_PI; + if (angle < 0) { + angle += 360.0; + } + + double acc = 0.0; + + QList keys = colors.keys(); + std::sort(keys.begin(), keys.end()); + + for (const QString &key : keys) { + double span = (double(colors[key]) / total) * 360.0; + + if (angle >= acc && angle < acc + span) { + double percent = (100.0 * colors[key]) / total; + return QString("%1: %2 cards (%3%)").arg(key).arg(colors[key]).arg(QString::number(percent, 'f', 1)); + } + acc += span; + } + + return {}; +} + +QColor ColorPie::colorFromName(const QString &name) const +{ + static QMap map = { + {"R", QColor(220, 30, 30)}, {"G", QColor(40, 170, 40)}, {"U", QColor(40, 90, 200)}, + {"W", QColor(235, 235, 230)}, {"B", QColor(30, 30, 30)}, + }; + + if (map.contains(name)) { + return map[name]; + } + + QColor c(name); + if (!c.isValid()) { + c = Qt::gray; + } + + return c; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.h b/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.h new file mode 100644 index 000000000..a8fe784a3 --- /dev/null +++ b/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.h @@ -0,0 +1,44 @@ +#ifndef COCKATRICE_COLOR_PIE_H +#define COCKATRICE_COLOR_PIE_H + +#ifndef COLOR_PIE_H +#define COLOR_PIE_H + +#include +#include +#include + +class ColorPie : public QWidget +{ + Q_OBJECT + +public: + explicit ColorPie(const QMap &_colors = {}, QWidget *parent = nullptr); + + void setColors(const QMap &_colors); + + QSize minimumSizeHint() const override; + +protected: + void paintEvent(QPaintEvent *) override; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + void enterEvent(QEnterEvent *event) override; +#else + void enterEvent(QEvent *event) override; +#endif + void leaveEvent(QEvent *) override; + void mouseMoveEvent(QMouseEvent *event) override; + +private: + QMap colors; + bool isHovered = false; + const double minRatioThreshold = 0.01; // skip tiny slices + + QColor colorFromName(const QString &name) const; + QString tooltipForPoint(const QPoint &pt) const; +}; + +#endif // COLOR_PIE_H + +#endif // COCKATRICE_COLOR_PIE_H diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp index 2998a03bc..736b69ea2 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp @@ -3,7 +3,7 @@ #include "../../../../../card_picture_loader/card_picture_loader.h" #include "../../../../cards/card_info_picture_with_text_overlay_widget.h" #include "../../../../general/display/background_plate_widget.h" -#include "../../../../general/display/color_bar.h" +#include "../../../../general/display/charts/bars/color_bar.h" #include "archidekt_deck_preview_image_display_widget.h" #include diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.h index 0174016f7..43baddb4c 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.h @@ -7,7 +7,7 @@ #ifndef EDHREC_API_RESPONSE_CARD_INCLUSION_DISPLAY_WIDGET_H #define EDHREC_API_RESPONSE_CARD_INCLUSION_DISPLAY_WIDGET_H -#include "../../../../../general/display/percent_bar_widget.h" +#include "../../../../../general/display/charts/bars/percent_bar_widget.h" #include "../../api_response/cards/edhrec_api_response_card_details.h" #include diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.h index 39d26a409..c2e1c018c 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.h @@ -7,7 +7,7 @@ #ifndef EDHREC_API_RESPONSE_CARD_SYNERGY_DISPLAY_WIDGET_H #define EDHREC_API_RESPONSE_CARD_SYNERGY_DISPLAY_WIDGET_H -#include "../../../../../general/display/percent_bar_widget.h" +#include "../../../../../general/display/charts/bars/percent_bar_widget.h" #include "../../api_response/cards/edhrec_api_response_card_details.h" #include diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index f3d573d27..82f96860d 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -85,7 +85,7 @@ void TabDeckEditorVisual::onDeckChanged() { AbstractTabDeckEditor::onDeckModified(); tabContainer->visualDeckView->constructZoneWidgetsFromDeckListModel(); - tabContainer->deckAnalytics->refreshDisplays(); + tabContainer->deckAnalytics->updateDisplays(); tabContainer->sampleHandWidget->setDeckModel(deckStateManager->getModel()); } diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp index 98b6aff00..82aeb05a6 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp @@ -45,10 +45,13 @@ TabDeckEditorVisualTabWidget::TabDeckEditorVisualTabWidget(QWidget *parent, connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::cardClickedDatabaseDisplay, this, &TabDeckEditorVisualTabWidget::onCardClickedDatabaseDisplay); - deckAnalytics = new DeckAnalyticsWidget(this, deckModel); + statsAnalyzer = new DeckListStatisticsAnalyzer(this, deckModel); + statsAnalyzer->analyze(); + + deckAnalytics = new DeckAnalyticsWidget(this, statsAnalyzer); deckAnalytics->setObjectName("deckAnalytics"); - sampleHandWidget = new VisualDeckEditorSampleHandWidget(this, deckModel); + sampleHandWidget = new VisualDeckEditorSampleHandWidget(this, deckModel, statsAnalyzer); this->addNewTab(visualDeckView, tr("Visual Deck View")); this->addNewTab(visualDatabaseDisplay, tr("Visual Database Display")); diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h index 59c577024..9468df425 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h @@ -78,7 +78,8 @@ public: /// Get the total number of tabs. [[nodiscard]] int getTabCount() const; - VisualDeckEditorWidget *visualDeckView; ///< Visual deck editor widget. + VisualDeckEditorWidget *visualDeckView; ///< Visual deck editor widget. + DeckListStatisticsAnalyzer *statsAnalyzer; DeckAnalyticsWidget *deckAnalytics; ///< Deck analytics widget. VisualDatabaseDisplayWidget *visualDatabaseDisplay; ///< Database display widget. PrintingSelector *printingSelector; ///< Printing selector widget. diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp index 0badb76ff..24f521760 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp @@ -3,12 +3,16 @@ #include "../../../client/settings/cache_settings.h" #include "../../deck_loader/deck_loader.h" #include "../cards/card_info_picture_widget.h" +#include "../deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.h" +#include "../deck_analytics/deck_list_statistics_analyzer.h" #include #include -VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *parent, DeckListModel *_deckListModel) - : QWidget(parent), deckListModel(_deckListModel) +VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *parent, + DeckListModel *_deckListModel, + DeckListStatisticsAnalyzer *_statsAnalyzer) + : QWidget(parent), deckListModel(_deckListModel), statsAnalyzer(_statsAnalyzer) { layout = new QVBoxLayout(this); setLayout(layout); @@ -35,6 +39,9 @@ VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *pare flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); layout->addWidget(flowWidget); + drawProbabilityWidget = new DrawProbabilityWidget(this, statsAnalyzer); + layout->addWidget(drawProbabilityWidget); + cardSizeWidget = new CardSizeWidget(this, flowWidget); layout->addWidget(cardSizeWidget); diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h index 751e16a3c..c63c74a4d 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.h @@ -8,6 +8,7 @@ #define VISUAL_DECK_EDITOR_SAMPLE_HAND_WIDGET_H #include "../cards/card_size_widget.h" +#include "../deck_analytics/deck_list_statistics_analyzer.h" #include "../general/layout_containers/flow_widget.h" #include @@ -15,11 +16,14 @@ #include #include +class DrawProbabilityWidget; class VisualDeckEditorSampleHandWidget : public QWidget { Q_OBJECT public: - VisualDeckEditorSampleHandWidget(QWidget *parent, DeckListModel *deckListModel); + VisualDeckEditorSampleHandWidget(QWidget *parent, + DeckListModel *deckListModel, + DeckListStatisticsAnalyzer *statsAnalyzer); QList getRandomCards(int amountToGet); public slots: @@ -29,12 +33,14 @@ public slots: private: DeckListModel *deckListModel; + DeckListStatisticsAnalyzer *statsAnalyzer; QVBoxLayout *layout; QWidget *resetAndHandSizeContainerWidget; QHBoxLayout *resetAndHandSizeLayout; QPushButton *resetButton; QSpinBox *handSizeSpinBox; FlowWidget *flowWidget; + DrawProbabilityWidget *drawProbabilityWidget; CardSizeWidget *cardSizeWidget; }; diff --git a/libcockatrice_utility/libcockatrice/utility/color.h b/libcockatrice_utility/libcockatrice/utility/color.h index 164c6ffd7..bf4759565 100644 --- a/libcockatrice_utility/libcockatrice/utility/color.h +++ b/libcockatrice_utility/libcockatrice/utility/color.h @@ -21,6 +21,43 @@ inline color convertQColorToColor(const QColor &c) result.set_b(c.blue()); return result; } + +namespace GameSpecificColors +{ +namespace MTG +{ +inline QColor colorHelper(const QString &name) +{ + static const QMap colorMap = { + {"W", QColor(245, 245, 220)}, + {"U", QColor(80, 140, 255)}, + {"B", QColor(60, 60, 60)}, + {"R", QColor(220, 60, 50)}, + {"G", QColor(70, 160, 70)}, + {"Creature", QColor(70, 130, 180)}, + {"Instant", QColor(138, 43, 226)}, + {"Sorcery", QColor(199, 21, 133)}, + {"Enchantment", QColor(218, 165, 32)}, + {"Artifact", QColor(169, 169, 169)}, + {"Planeswalker", QColor(210, 105, 30)}, + {"Land", QColor(110, 80, 50)}, + }; + + if (colorMap.contains(name)) + return colorMap[name]; + + if (name.length() == 1 && colorMap.contains(name.toUpper())) + return colorMap[name.toUpper()]; + + uint h = qHash(name); + int r = 100 + (h % 120); + int g = 100 + ((h >> 8) % 120); + int b = 100 + ((h >> 16) % 120); + + return QColor(r, g, b); +} +} // namespace MTG +} // namespace GameSpecificColors #endif inline color makeColor(int r, int g, int b) diff --git a/libcockatrice_utility/libcockatrice/utility/qt_utils.h b/libcockatrice_utility/libcockatrice/utility/qt_utils.h index 855cc8b18..606947143 100644 --- a/libcockatrice_utility/libcockatrice/utility/qt_utils.h +++ b/libcockatrice_utility/libcockatrice/utility/qt_utils.h @@ -15,6 +15,20 @@ template T *findParentOfType(const QObject *obj) } return nullptr; } + +static inline void clearLayoutRec(QLayout *l) +{ + if (!l) + return; + QLayoutItem *it; + while ((it = l->takeAt(0)) != nullptr) { + if (QWidget *w = it->widget()) + w->deleteLater(); + if (QLayout *sub = it->layout()) + clearLayoutRec(sub); + delete it; + } +} } // namespace QtUtils #endif // COCKATRICE_QT_UTILS_H From 987fe9c9e2c29c290458f9a3835b99d6c69e0f75 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 31 Dec 2025 23:35:43 -0800 Subject: [PATCH 104/325] [DeckDockWidget] clean up grouping and format sync (#6467) * [DeckDockWidget] clean up grouping and format sync * refresh legalities in rebuildTree * extract applyActiveGroupCriteria * Fix build failure --- .../deck_editor_deck_dock_widget.cpp | 31 ++++++++++--------- .../deck_editor_deck_dock_widget.h | 2 +- .../models/deck_list/deck_list_model.cpp | 1 + 3 files changed, 19 insertions(+), 15 deletions(-) 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 62195974b..24268e28e 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 @@ -64,6 +64,8 @@ void DeckEditorDeckDockWidget::createDeckDock() connect(deckStateManager, &DeckStateManager::focusIndexChanged, this, &DeckEditorDeckDockWidget::setSelectedIndex); connect(deckStateManager, &DeckStateManager::deckReplaced, this, &DeckEditorDeckDockWidget::syncDisplayWidgetsToModel); + connect(deckStateManager, &DeckStateManager::deckReplaced, this, + &DeckEditorDeckDockWidget::applyActiveGroupCriteria); deckView = new QTreeView(); deckView->setObjectName("deckView"); @@ -174,11 +176,8 @@ void DeckEditorDeckDockWidget::createDeckDock() activeGroupCriteriaComboBox->addItem(tr("Main Type"), DeckListModelGroupCriteria::MAIN_TYPE); activeGroupCriteriaComboBox->addItem(tr("Mana Cost"), DeckListModelGroupCriteria::MANA_COST); activeGroupCriteriaComboBox->addItem(tr("Colors"), DeckListModelGroupCriteria::COLOR); - connect(activeGroupCriteriaComboBox, QOverload::of(&QComboBox::currentIndexChanged), [this]() { - getModel()->setActiveGroupCriteria(static_cast( - activeGroupCriteriaComboBox->currentData(Qt::UserRole).toInt())); - getModel()->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); - }); + connect(activeGroupCriteriaComboBox, qOverload(&QComboBox::currentIndexChanged), this, + &DeckEditorDeckDockWidget::applyActiveGroupCriteria); aIncrement = new QAction(QString(), this); aIncrement->setIcon(QPixmap("theme:icons/increment")); @@ -297,7 +296,6 @@ void DeckEditorDeckDockWidget::initializeFormats() QString format = deckStateManager->getMetadata().gameFormat; if (!format.isEmpty()) { - getModel()->setActiveFormat(format); formatComboBox->setCurrentIndex(formatComboBox->findData(format)); } else { // Ensure no selection is visible initially @@ -429,6 +427,13 @@ void DeckEditorDeckDockWidget::writeBannerCard(int index) deckStateManager->setBannerCard(bannerCard); } +void DeckEditorDeckDockWidget::applyActiveGroupCriteria() +{ + getModel()->setActiveGroupCriteria( + static_cast(activeGroupCriteriaComboBox->currentData(Qt::UserRole).toInt())); + getModel()->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); +} + void DeckEditorDeckDockWidget::updateShowBannerCardComboBox(const bool visible) { bannerCardLabel->setHidden(!visible); @@ -477,16 +482,14 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel() updateBannerCardComboBox(); bannerCardComboBox->blockSignals(false); updateHash(); - sortDeckModelToDeckView(); - deckTagsDisplayWidget->setTags(deckStateManager->getMetadata().tags); -} - -void DeckEditorDeckDockWidget::sortDeckModelToDeckView() -{ - getModel()->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); - getModel()->setActiveFormat(deckStateManager->getMetadata().gameFormat); + formatComboBox->blockSignals(true); formatComboBox->setCurrentIndex(formatComboBox->findData(deckStateManager->getMetadata().gameFormat)); + formatComboBox->blockSignals(false); + + deckTagsDisplayWidget->blockSignals(true); + deckTagsDisplayWidget->setTags(deckStateManager->getMetadata().tags); + deckTagsDisplayWidget->blockSignals(false); } /** diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index 1a82b00d1..c505ac5fb 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -51,7 +51,6 @@ public slots: void selectNextCard(); void updateBannerCardComboBox(); void syncDisplayWidgetsToModel(); - void sortDeckModelToDeckView(); void actAddCard(const ExactCard &card, const QString &zoneName); void actIncrementSelection(); void actDecrementCard(const ExactCard &card, QString zoneName); @@ -100,6 +99,7 @@ private slots: void writeName(); void writeComments(); void writeBannerCard(int); + void applyActiveGroupCriteria(); void setSelectedIndex(const QModelIndex &newCardIndex); void updateHash(); void refreshShortcuts(); 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 ece3bc2f8..ce1c79263 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -599,6 +599,7 @@ void DeckListModel::setDeckList(const QSharedPointer &_deck) deckList = _deck; } rebuildTree(); + refreshCardFormatLegalities(); emit deckReplaced(); } From e7a1f86cbb5dc252ebbfa1855be06b520d625c08 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 1 Jan 2026 14:17:34 +0100 Subject: [PATCH 105/325] [View Zone Widget] Properly calculate titleBar close rect on Windows. (#6468) --- .../src/game/zones/view_zone_widget.cpp | 21 +++++++++++++------ cockatrice/src/game/zones/view_zone_widget.h | 1 - 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/cockatrice/src/game/zones/view_zone_widget.cpp b/cockatrice/src/game/zones/view_zone_widget.cpp index e15a466c3..3ea4eb119 100644 --- a/cockatrice/src/game/zones/view_zone_widget.cpp +++ b/cockatrice/src/game/zones/view_zone_widget.cpp @@ -272,10 +272,11 @@ void ZoneViewWidget::startWindowDrag(QGraphicsSceneMouseEvent *event) QRectF ZoneViewWidget::closeButtonRect(QWidget *styleWidget) const { const QRectF frameRectF = windowFrameRect(); - const QRect titleBarRect(frameRectF.toRect().x(), frameRectF.toRect().y(), frameRectF.toRect().width(), - static_cast(kTitleBarHeight)); // query the style for the close button position (handles macOS top-left placement) + // Title bar rect MUST be local (0,0-based) for QStyle + const QRect titleBarRect(0, 0, static_cast(frameRectF.width()), static_cast(kTitleBarHeight)); + if (styleWidget) { QStyleOptionTitleBar opt; opt.initFrom(styleWidget); @@ -283,19 +284,26 @@ QRectF ZoneViewWidget::closeButtonRect(QWidget *styleWidget) const opt.text = windowTitle(); opt.icon = styleWidget->windowIcon(); opt.titleBarFlags = Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint; + opt.subControls = QStyle::SC_TitleBarCloseButton; opt.activeSubControls = QStyle::SC_TitleBarCloseButton; opt.titleBarState = styleWidget->isActiveWindow() ? Qt::WindowActive : Qt::WindowNoState; - if (styleWidget->isActiveWindow()) + + if (styleWidget->isActiveWindow()) { opt.state |= QStyle::State_Active; - const QRect r = styleWidget->style()->subControlRect(QStyle::CC_TitleBar, &opt, QStyle::SC_TitleBarCloseButton, - styleWidget); + } + + QRect r = styleWidget->style()->subControlRect(QStyle::CC_TitleBar, &opt, QStyle::SC_TitleBarCloseButton, + styleWidget); + if (r.isValid() && !r.isEmpty()) { + // Translate from local-titlebar coords → frame coords + r.translate(frameRectF.topLeft().toPoint()); return QRectF(r); } } - // fallback: square at right end of titlebar (Windows/Linux style) + // Fallback: frame-relative top-right return QRectF(frameRectF.right() - kTitleBarHeight, frameRectF.top(), kTitleBarHeight, kTitleBarHeight); } @@ -349,6 +357,7 @@ bool ZoneViewWidget::windowFrameEvent(QEvent *event) close(); return true; } + startWindowDrag(me); me->accept(); return true; diff --git a/cockatrice/src/game/zones/view_zone_widget.h b/cockatrice/src/game/zones/view_zone_widget.h index 4ed8f74d8..1246192b8 100644 --- a/cockatrice/src/game/zones/view_zone_widget.h +++ b/cockatrice/src/game/zones/view_zone_widget.h @@ -54,7 +54,6 @@ private: ZoneViewZone *zone; QGraphicsWidget *zoneContainer; - QPushButton *closeButton; QScrollBar *scrollBar; ScrollableGraphicsProxyWidget *scrollBarProxy; From c1f93b37abafd4c4d4ef2dc6dac3931ff3af0fb4 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 1 Jan 2026 14:49:09 +0100 Subject: [PATCH 106/325] [TabRoom] Add a setting to hide the new filter toolbar (#6469) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [TabRoom] Add a setting to hide the new filter toolbar Took 56 minutes Took 4 seconds * Proper macro. Took 5 minutes --------- Co-authored-by: Lukas Brübach --- cockatrice/src/client/settings/cache_settings.cpp | 8 ++++++++ cockatrice/src/client/settings/cache_settings.h | 7 +++++++ cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp | 6 ++++++ cockatrice/src/interface/widgets/dialogs/dlg_settings.h | 1 + cockatrice/src/interface/widgets/server/game_selector.cpp | 6 ++++++ 5 files changed, 28 insertions(+) diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index fde8e9b34..2ad1f1ece 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -288,6 +288,7 @@ SettingsCache::SettingsCache() focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool(); showShortcuts = settings->value("menu/showshortcuts", true).toBool(); + showGameSelectorFilterToolbar = settings->value("menu/showgameselectorfiltertoolbar", true).toBool(); displayCardNames = settings->value("cards/displaycardnames", true).toBool(); roundCardCorners = settings->value("cards/roundcardcorners", true).toBool(); overrideAllCardArtWithPersonalPreference = @@ -715,6 +716,13 @@ void SettingsCache::setShowShortcuts(QT_STATE_CHANGED_T _showShortcuts) settings->setValue("menu/showshortcuts", showShortcuts); } +void SettingsCache::setShowGameSelectorFilterToolbar(QT_STATE_CHANGED_T _showGameSelectorFilterToolbar) +{ + showGameSelectorFilterToolbar = static_cast(_showGameSelectorFilterToolbar); + settings->setValue("menu/showgameselectorfiltertoolbar", showGameSelectorFilterToolbar); + emit showGameSelectorFilterToolbarChanged(showGameSelectorFilterToolbar); +} + void SettingsCache::setDisplayCardNames(QT_STATE_CHANGED_T _displayCardNames) { displayCardNames = static_cast(_displayCardNames); diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index df68ce0c4..ca9a2a39b 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -145,6 +145,7 @@ signals: void homeTabBackgroundShuffleFrequencyChanged(); void picDownloadChanged(); void showStatusBarChanged(bool state); + void showGameSelectorFilterToolbarChanged(bool state); void displayCardNamesChanged(); void overrideAllCardArtWithPersonalPreferenceChanged(bool _overrideAllCardArtWithPersonalPreference); void bumpSetsWithCardsInDeckToTopChanged(); @@ -236,6 +237,7 @@ private: bool annotateTokens; QByteArray tabGameSplitterSizes; bool showShortcuts; + bool showGameSelectorFilterToolbar; bool displayCardNames; bool overrideAllCardArtWithPersonalPreference; bool bumpSetsWithCardsInDeckToTop; @@ -553,6 +555,10 @@ public: { return showShortcuts; } + [[nodiscard]] bool getShowGameSelectorFilterToolbar() const + { + return showGameSelectorFilterToolbar; + } [[nodiscard]] bool getDisplayCardNames() const { return displayCardNames; @@ -1017,6 +1023,7 @@ public slots: void setAnnotateTokens(QT_STATE_CHANGED_T _annotateTokens); void setTabGameSplitterSizes(const QByteArray &_tabGameSplitterSizes); void setShowShortcuts(QT_STATE_CHANGED_T _showShortcuts); + void setShowGameSelectorFilterToolbar(QT_STATE_CHANGED_T _showGameSelectorFilterToolbar); void setDisplayCardNames(QT_STATE_CHANGED_T _displayCardNames); void setOverrideAllCardArtWithPersonalPreference(QT_STATE_CHANGED_T _overrideAllCardArt); void setBumpSetsWithCardsInDeckToTop(QT_STATE_CHANGED_T _bumpSetsWithCardsInDeckToTop); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp index 191cc5d0b..fe8840a41 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp @@ -462,8 +462,13 @@ AppearanceSettingsPage::AppearanceSettingsPage() showShortcutsCheckBox.setChecked(settings.getShowShortcuts()); connect(&showShortcutsCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &AppearanceSettingsPage::showShortcutsChanged); + showGameSelectorFilterToolbarCheckBox.setChecked(settings.getShowGameSelectorFilterToolbar()); + connect(&showGameSelectorFilterToolbarCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setShowGameSelectorFilterToolbar); + auto *menuGrid = new QGridLayout; menuGrid->addWidget(&showShortcutsCheckBox, 0, 0); + menuGrid->addWidget(&showGameSelectorFilterToolbarCheckBox, 1, 0); menuGroupBox = new QGroupBox; menuGroupBox->setLayout(menuGrid); @@ -727,6 +732,7 @@ void AppearanceSettingsPage::retranslateUi() menuGroupBox->setTitle(tr("Menu settings")); showShortcutsCheckBox.setText(tr("Show keyboard shortcuts in right-click menus")); + showGameSelectorFilterToolbarCheckBox.setText(tr("Show game filter toolbar above list in room tab")); cardsGroupBox->setTitle(tr("Card rendering")); displayCardNamesCheckBox.setText(tr("Display card names on cards having a picture")); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h index 10134f3a8..06ad3601c 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h @@ -120,6 +120,7 @@ private: QLabel minPlayersForMultiColumnLayoutLabel; QLabel maxFontSizeForCardsLabel; QCheckBox showShortcutsCheckBox; + QCheckBox showGameSelectorFilterToolbarCheckBox; QCheckBox displayCardNamesCheckBox; QCheckBox autoRotateSidewaysLayoutCardsCheckBox; QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox; diff --git a/cockatrice/src/interface/widgets/server/game_selector.cpp b/cockatrice/src/interface/widgets/server/game_selector.cpp index 48a12d5b3..f14cc6d82 100644 --- a/cockatrice/src/interface/widgets/server/game_selector.cpp +++ b/cockatrice/src/interface/widgets/server/game_selector.cpp @@ -73,6 +73,12 @@ GameSelector::GameSelector(AbstractClient *_client, if (showFilters && restoresettings) { quickFilterToolBar = new GameSelectorQuickFilterToolBar(this, tabSupervisor, gameListProxyModel, gameTypeMap); + quickFilterToolBar->setVisible(showFilters && restoresettings && + SettingsCache::instance().getShowGameSelectorFilterToolbar()); + + connect(&SettingsCache::instance(), &SettingsCache::showGameSelectorFilterToolbarChanged, this, [this] { + quickFilterToolBar->setVisible(SettingsCache::instance().getShowGameSelectorFilterToolbar()); + }); } else { quickFilterToolBar = nullptr; } From 93a4647b04ce2b3fe48dd931b5ac260f96433234 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 1 Jan 2026 16:24:47 -0800 Subject: [PATCH 107/325] [DeckList] move SideboardPlan into separate file (#6474) --- libcockatrice_deck_list/CMakeLists.txt | 4 +- .../libcockatrice/deck_list/deck_list.cpp | 56 --------------- .../libcockatrice/deck_list/deck_list.h | 65 +---------------- .../deck_list/sideboard_plan.cpp | 59 +++++++++++++++ .../libcockatrice/deck_list/sideboard_plan.h | 71 +++++++++++++++++++ 5 files changed, 135 insertions(+), 120 deletions(-) create mode 100644 libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.cpp create mode 100644 libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.h diff --git a/libcockatrice_deck_list/CMakeLists.txt b/libcockatrice_deck_list/CMakeLists.txt index a7dd01702..5ccdb5f66 100644 --- a/libcockatrice_deck_list/CMakeLists.txt +++ b/libcockatrice_deck_list/CMakeLists.txt @@ -9,7 +9,9 @@ set(HEADERS libcockatrice/deck_list/tree/inner_deck_list_node.h libcockatrice/deck_list/deck_list.h 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/sideboard_plan.h ) if(Qt6_FOUND) @@ -28,7 +30,7 @@ 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_node_tree.h + libcockatrice/deck_list/sideboard_plan.cpp ) add_dependencies(libcockatrice_deck_list libcockatrice_protocol) diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index b453a57ef..14da1eb28 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -21,62 +21,6 @@ uint qHash(const QRegularExpression &key, uint seed) noexcept } #endif -SideboardPlan::SideboardPlan(const QString &_name, const QList &_moveList) - : name(_name), moveList(_moveList) -{ -} - -void SideboardPlan::setMoveList(const QList &_moveList) -{ - moveList = _moveList; -} - -bool SideboardPlan::readElement(QXmlStreamReader *xml) -{ - while (!xml->atEnd()) { - xml->readNext(); - const QString childName = xml->name().toString(); - if (xml->isStartElement()) { - if (childName == "name") - name = xml->readElementText(); - else if (childName == "move_card_to_zone") { - MoveCard_ToZone m; - while (!xml->atEnd()) { - xml->readNext(); - const QString childName2 = xml->name().toString(); - if (xml->isStartElement()) { - if (childName2 == "card_name") - m.set_card_name(xml->readElementText().toStdString()); - else if (childName2 == "start_zone") - m.set_start_zone(xml->readElementText().toStdString()); - else if (childName2 == "target_zone") - m.set_target_zone(xml->readElementText().toStdString()); - } else if (xml->isEndElement() && (childName2 == "move_card_to_zone")) { - moveList.append(m); - break; - } - } - } - } else if (xml->isEndElement() && (childName == "sideboard_plan")) - return true; - } - return false; -} - -void SideboardPlan::write(QXmlStreamWriter *xml) -{ - xml->writeStartElement("sideboard_plan"); - xml->writeTextElement("name", name); - for (auto &i : moveList) { - xml->writeStartElement("move_card_to_zone"); - xml->writeTextElement("card_name", QString::fromStdString(i.card_name())); - xml->writeTextElement("start_zone", QString::fromStdString(i.start_zone())); - xml->writeTextElement("target_zone", QString::fromStdString(i.target_zone())); - xml->writeEndElement(); - } - xml->writeEndElement(); -} - bool DeckList::Metadata::isEmpty() const { return name.isEmpty() && comments.isEmpty() && bannerCard.isEmpty() && tags.isEmpty(); diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index e8c57be67..ffbfebcf3 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -1,6 +1,6 @@ /** * @file deck_list.h - * @brief Defines the DeckList class and supporting types for managing a full + * @brief Defines the DeckList class, which manages a full * deck structure including cards, zones, sideboard plans, and * serialization to/from multiple formats. This is a logic class which * does not care about Qt or user facing views. @@ -12,12 +12,12 @@ #include "deck_list_memento.h" #include "deck_list_node_tree.h" +#include "sideboard_plan.h" #include "tree/inner_deck_list_node.h" #include #include #include -#include #include class AbstractDecklistNode; @@ -27,67 +27,6 @@ class QIODevice; class QTextStream; class InnerDecklistNode; -/** - * @class SideboardPlan - * @ingroup Decks - * @brief Represents a predefined sideboarding strategy for a deck. - * - * Sideboard plans store a named list of card movements that should be applied - * between the mainboard and sideboard for a specific matchup. Each movement - * is expressed using a `MoveCard_ToZone` protobuf message. - * - * ### Responsibilities: - * - Store the plan name and list of moves. - * - Support XML serialization/deserialization. - * - * ### Typical usage: - * A deck can contain multiple sideboard plans (e.g., "vs Aggro", "vs Control"), - * each describing how to transform the main deck into its intended configuration. - */ -class SideboardPlan -{ -private: - QString name; ///< Human-readable name of this plan. - QList moveList; ///< List of move instructions for this plan. - -public: - /** - * @brief Construct a new SideboardPlan. - * @param _name The plan name. - * @param _moveList Initial list of card move instructions. - */ - explicit SideboardPlan(const QString &_name = QString(), - const QList &_moveList = QList()); - - /** - * @brief Read a SideboardPlan from an XML stream. - * @param xml XML reader positioned at the plan element. - * @return true if parsing succeeded. - */ - bool readElement(QXmlStreamReader *xml); - - /** - * @brief Write this SideboardPlan to XML. - * @param xml Stream to append the serialized element to. - */ - void write(QXmlStreamWriter *xml); - - /// @return The plan name. - [[nodiscard]] QString getName() const - { - return name; - } - - /// @return Const reference to the move list. - [[nodiscard]] const QList &getMoveList() const - { - return moveList; - } - - /// @brief Replace the move list with a new one. - void setMoveList(const QList &_moveList); -}; - /** * @class DeckList * @ingroup Decks diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.cpp new file mode 100644 index 000000000..a78f8adb3 --- /dev/null +++ b/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.cpp @@ -0,0 +1,59 @@ +#include "sideboard_plan.h" + +#include + +SideboardPlan::SideboardPlan(const QString &_name, const QList &_moveList) + : name(_name), moveList(_moveList) +{ +} + +void SideboardPlan::setMoveList(const QList &_moveList) +{ + moveList = _moveList; +} + +bool SideboardPlan::readElement(QXmlStreamReader *xml) +{ + while (!xml->atEnd()) { + xml->readNext(); + const QString childName = xml->name().toString(); + if (xml->isStartElement()) { + if (childName == "name") + name = xml->readElementText(); + else if (childName == "move_card_to_zone") { + MoveCard_ToZone m; + while (!xml->atEnd()) { + xml->readNext(); + const QString childName2 = xml->name().toString(); + if (xml->isStartElement()) { + if (childName2 == "card_name") + m.set_card_name(xml->readElementText().toStdString()); + else if (childName2 == "start_zone") + m.set_start_zone(xml->readElementText().toStdString()); + else if (childName2 == "target_zone") + m.set_target_zone(xml->readElementText().toStdString()); + } else if (xml->isEndElement() && (childName2 == "move_card_to_zone")) { + moveList.append(m); + break; + } + } + } + } else if (xml->isEndElement() && (childName == "sideboard_plan")) + return true; + } + return false; +} + +void SideboardPlan::write(QXmlStreamWriter *xml) +{ + xml->writeStartElement("sideboard_plan"); + xml->writeTextElement("name", name); + for (auto &i : moveList) { + xml->writeStartElement("move_card_to_zone"); + xml->writeTextElement("card_name", QString::fromStdString(i.card_name())); + xml->writeTextElement("start_zone", QString::fromStdString(i.start_zone())); + xml->writeTextElement("target_zone", QString::fromStdString(i.target_zone())); + xml->writeEndElement(); + } + xml->writeEndElement(); +} \ No newline at end of file diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.h b/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.h new file mode 100644 index 000000000..2ca7845b9 --- /dev/null +++ b/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.h @@ -0,0 +1,71 @@ +#ifndef COCKATRICE_SIDEBOARD_PLAN_H +#define COCKATRICE_SIDEBOARD_PLAN_H + +#include +#include + +class QXmlStreamWriter; +class QXmlStreamReader; + +/** + * @class SideboardPlan + * @ingroup Decks + * @brief Represents a predefined sideboarding strategy for a deck. + * + * Sideboard plans store a named list of card movements that should be applied + * between the mainboard and sideboard for a specific matchup. Each movement + * is expressed using a `MoveCard_ToZone` protobuf message. + * + * ### Responsibilities: + * - Store the plan name and list of moves. + * - Support XML serialization/deserialization. + * + * ### Typical usage: + * A deck can contain multiple sideboard plans (e.g., "vs Aggro", "vs Control"), + * each describing how to transform the main deck into its intended configuration. + */ +class SideboardPlan +{ +private: + QString name; ///< Human-readable name of this plan. + QList moveList; ///< List of move instructions for this plan. + +public: + /** + * @brief Construct a new SideboardPlan. + * @param _name The plan name. + * @param _moveList Initial list of card move instructions. + */ + explicit SideboardPlan(const QString &_name = QString(), + const QList &_moveList = QList()); + + /** + * @brief Read a SideboardPlan from an XML stream. + * @param xml XML reader positioned at the plan element. + * @return true if parsing succeeded. + */ + bool readElement(QXmlStreamReader *xml); + + /** + * @brief Write this SideboardPlan to XML. + * @param xml Stream to append the serialized element to. + */ + void write(QXmlStreamWriter *xml); + + /// @return The plan name. + [[nodiscard]] QString getName() const + { + return name; + } + + /// @return Const reference to the move list. + [[nodiscard]] const QList &getMoveList() const + { + return moveList; + } + + /// @brief Replace the move list with a new one. + void setMoveList(const QList &_moveList); +}; + +#endif // COCKATRICE_SIDEBOARD_PLAN_H From 84e6907fa98d1e5572be2e56befd393ac93bcef3 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 2 Jan 2026 00:10:41 -0800 Subject: [PATCH 108/325] [DeckList] Store sideboardPlans by value to fix crash (#6475) --- .../libcockatrice/deck_list/deck_list.cpp | 42 +++++++------------ .../libcockatrice/deck_list/deck_list.h | 13 +++--- .../deck_list/sideboard_plan.cpp | 2 +- .../libcockatrice/deck_list/sideboard_plan.h | 5 +-- 4 files changed, 23 insertions(+), 39 deletions(-) diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index 14da1eb28..a294601fb 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -21,6 +21,8 @@ uint qHash(const QRegularExpression &key, uint seed) noexcept } #endif +static const QString CURRENT_SIDEBOARD_PLAN_KEY = ""; + bool DeckList::Metadata::isEmpty() const { return name.isEmpty() && comments.isEmpty() && bannerCard.isEmpty() && tags.isEmpty(); @@ -37,36 +39,23 @@ DeckList::DeckList(const QString &nativeString) DeckList::DeckList(const Metadata &metadata, const DecklistNodeTree &tree, - const QMap &sideboardPlans) + const QMap &sideboardPlans) : metadata(metadata), sideboardPlans(sideboardPlans), tree(tree) { } -DeckList::~DeckList() +QList DeckList::getCurrentSideboardPlan() const { - QMapIterator i(sideboardPlans); - while (i.hasNext()) - delete i.next().value(); -} + if (!sideboardPlans.contains(CURRENT_SIDEBOARD_PLAN_KEY)) { + return {}; + } -QList DeckList::getCurrentSideboardPlan() -{ - SideboardPlan *current = sideboardPlans.value(QString(), 0); - if (!current) - return QList(); - else - return current->getMoveList(); + return sideboardPlans.value(CURRENT_SIDEBOARD_PLAN_KEY).getMoveList(); } void DeckList::setCurrentSideboardPlan(const QList &plan) { - SideboardPlan *current = sideboardPlans.value(QString(), 0); - if (!current) { - current = new SideboardPlan; - sideboardPlans.insert(QString(), current); - } - - current->setMoveList(plan); + sideboardPlans[CURRENT_SIDEBOARD_PLAN_KEY].setMoveList(plan); } bool DeckList::readElement(QXmlStreamReader *xml) @@ -95,11 +84,9 @@ bool DeckList::readElement(QXmlStreamReader *xml) } else if (childName == "zone") { tree.readZoneElement(xml); } else if (childName == "sideboard_plan") { - SideboardPlan *newSideboardPlan = new SideboardPlan; - if (newSideboardPlan->readElement(xml)) { - sideboardPlans.insert(newSideboardPlan->getName(), newSideboardPlan); - } else { - delete newSideboardPlan; + SideboardPlan newSideboardPlan; + if (newSideboardPlan.readElement(xml)) { + sideboardPlans.insert(newSideboardPlan.getName(), newSideboardPlan); } } } else if (xml->isEndElement() && (childName == "cockatrice_deck")) { @@ -138,9 +125,8 @@ void DeckList::write(QXmlStreamWriter *xml) const tree.write(xml); // Write sideboard plans - QMapIterator i(sideboardPlans); - while (i.hasNext()) { - i.next().value()->write(xml); + for (auto &sideboardPlan : sideboardPlans.values()) { + sideboardPlan.write(xml); } xml->writeEndElement(); // Close "cockatrice_deck" diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index ffbfebcf3..d0ca55342 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -78,9 +78,9 @@ public: }; private: - Metadata metadata; ///< Deck metadata that is stored in the deck file - QMap sideboardPlans; ///< Named sideboard plans. - DecklistNodeTree tree; ///< The deck tree (zones + cards). + Metadata metadata; ///< Deck metadata that is stored in the deck file + QMap sideboardPlans; ///< Named sideboard plans. + DecklistNodeTree tree; ///< The deck tree (zones + cards). /** * @brief Cached deck hash, recalculated lazily. @@ -132,8 +132,7 @@ public: /// @brief Construct from components DeckList(const Metadata &metadata, const DecklistNodeTree &tree, - const QMap &sideboardPlans = {}); - virtual ~DeckList(); + const QMap &sideboardPlans = {}); /** * @brief Gets a pointer to the underlying node tree. @@ -186,9 +185,9 @@ public: /// @name Sideboard plans ///@{ - QList getCurrentSideboardPlan(); + QList getCurrentSideboardPlan() const; void setCurrentSideboardPlan(const QList &plan); - const QMap &getSideboardPlans() const + const QMap &getSideboardPlans() const { return sideboardPlans; } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.cpp index a78f8adb3..d991ec98e 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.cpp @@ -44,7 +44,7 @@ bool SideboardPlan::readElement(QXmlStreamReader *xml) return false; } -void SideboardPlan::write(QXmlStreamWriter *xml) +void SideboardPlan::write(QXmlStreamWriter *xml) const { xml->writeStartElement("sideboard_plan"); xml->writeTextElement("name", name); diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.h b/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.h index 2ca7845b9..524217c2d 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/sideboard_plan.h @@ -36,8 +36,7 @@ public: * @param _name The plan name. * @param _moveList Initial list of card move instructions. */ - explicit SideboardPlan(const QString &_name = QString(), - const QList &_moveList = QList()); + explicit SideboardPlan(const QString &_name = "", const QList &_moveList = {}); /** * @brief Read a SideboardPlan from an XML stream. @@ -50,7 +49,7 @@ public: * @brief Write this SideboardPlan to XML. * @param xml Stream to append the serialized element to. */ - void write(QXmlStreamWriter *xml); + void write(QXmlStreamWriter *xml) const; /// @return The plan name. [[nodiscard]] QString getName() const From bbd8671e6e9f12980d3cc5e79f477086133038e3 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 2 Jan 2026 05:32:22 -0800 Subject: [PATCH 109/325] [DeckDockWidget] Fix VDE crash due to not mapping proxy index (#6479) --- .../deck_editor_deck_dock_widget.cpp | 45 +++++++++---------- .../deck_editor_deck_dock_widget.h | 2 +- 2 files changed, 23 insertions(+), 24 deletions(-) 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 24268e28e..014f121e4 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 @@ -578,18 +578,19 @@ void DeckEditorDeckDockWidget::expandAll() } /** - * Gets the index of all the currently selected card nodes in the decklist table. + * Gets the source index of all the currently selected card nodes in the decklist table. * The list is in reverse order of the visual selection, so that rows can be deleted while iterating over them. * - * @return A model index list containing all selected card nodes + * @return A list containing the source indices of all selected card nodes */ -QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodes() const +QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodeSourceIndices() const { auto selectedRows = deckView->selectionModel()->selectedRows(); - const auto notLeafNode = [this](const QModelIndex &index) { - return getModel()->hasChildren(proxy->mapToSource(index)); - }; + const auto mapToSource = [this](const QModelIndex &index) { return proxy->mapToSource(index); }; + std::transform(selectedRows.begin(), selectedRows.end(), selectedRows.begin(), mapToSource); + + const auto notLeafNode = [this](const QModelIndex &sourceIndex) { return getModel()->hasChildren(sourceIndex); }; selectedRows.erase(std::remove_if(selectedRows.begin(), selectedRows.end(), notLeafNode), selectedRows.end()); std::reverse(selectedRows.begin(), selectedRows.end()); @@ -608,10 +609,10 @@ void DeckEditorDeckDockWidget::actAddCard(const ExactCard &card, const QString & void DeckEditorDeckDockWidget::actIncrementSelection() { - auto selectedRows = getSelectedCardNodes(); + auto selectedRows = getSelectedCardNodeSourceIndices(); - for (const auto &index : selectedRows) { - offsetCountAtIndex(index, true); + for (const auto &sourceIndex : selectedRows) { + offsetCountAtIndex(sourceIndex, true); } } @@ -630,7 +631,7 @@ void DeckEditorDeckDockWidget::actSwapCard(const ExactCard &card, const QString void DeckEditorDeckDockWidget::actSwapSelection() { - auto selectedRows = getSelectedCardNodes(); + auto selectedRows = getSelectedCardNodeSourceIndices(); // hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted // TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted @@ -638,8 +639,8 @@ void DeckEditorDeckDockWidget::actSwapSelection() deckView->setSelectionMode(QAbstractItemView::SingleSelection); } - for (const auto ¤tIndex : selectedRows) { - deckStateManager->swapCardAtIndex(currentIndex); + for (const auto &sourceIndex : selectedRows) { + deckStateManager->swapCardAtIndex(sourceIndex); } deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); @@ -659,7 +660,7 @@ void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString z void DeckEditorDeckDockWidget::actDecrementSelection() { - auto selectedRows = getSelectedCardNodes(); + auto selectedRows = getSelectedCardNodeSourceIndices(); // hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted // TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted @@ -667,8 +668,8 @@ void DeckEditorDeckDockWidget::actDecrementSelection() deckView->setSelectionMode(QAbstractItemView::SingleSelection); } - for (const auto &index : selectedRows) { - offsetCountAtIndex(index, false); + for (const auto &sourceIndex : selectedRows) { + offsetCountAtIndex(sourceIndex, false); } deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); @@ -676,7 +677,7 @@ void DeckEditorDeckDockWidget::actDecrementSelection() void DeckEditorDeckDockWidget::actRemoveCard() { - auto selectedRows = getSelectedCardNodes(); + auto selectedRows = getSelectedCardNodeSourceIndices(); // hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted // TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted @@ -684,8 +685,8 @@ void DeckEditorDeckDockWidget::actRemoveCard() deckView->setSelectionMode(QAbstractItemView::SingleSelection); } - for (const auto &row : selectedRows) { - deckStateManager->removeCardAtIndex(row); + for (const auto &sourceIndex : selectedRows) { + deckStateManager->removeCardAtIndex(sourceIndex); } deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); @@ -693,7 +694,7 @@ void DeckEditorDeckDockWidget::actRemoveCard() /** * @brief Increments or decrements the amount of the card node at the index by 1. - * @param idx The proxy index + * @param idx The source index * @param isIncrement If true, increments the count. If false, decrements the count */ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, bool isIncrement) @@ -702,12 +703,10 @@ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, bool i return; } - QModelIndex sourceIndex = proxy->mapToSource(idx); - if (isIncrement) { - deckStateManager->incrementCountAtIndex(sourceIndex); + deckStateManager->incrementCountAtIndex(idx); } else { - deckStateManager->decrementCountAtIndex(sourceIndex); + deckStateManager->decrementCountAtIndex(idx); } } diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index c505ac5fb..39b298550 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -90,7 +90,7 @@ private: QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard; DeckListModel *getModel() const; - [[nodiscard]] QModelIndexList getSelectedCardNodes() const; + [[nodiscard]] QModelIndexList getSelectedCardNodeSourceIndices() const; void offsetCountAtIndex(const QModelIndex &idx, bool isIncrement); private slots: From 2e6f1128bb95165804be707d76bb7bb28dd5718e Mon Sep 17 00:00:00 2001 From: tooomm Date: Fri, 2 Jan 2026 14:38:25 +0100 Subject: [PATCH 110/325] Docs: Fix search help rendering and open external link in new tab (#6440) * Use HTML link to open webpage in new tab * Fix rendering for doxygen --- cockatrice/resources/help/deck_search.md | 4 +++- cockatrice/resources/help/search.md | 4 +++- doc/doxygen/extra-pages/index.md | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cockatrice/resources/help/deck_search.md b/cockatrice/resources/help/deck_search.md index c42e41c42..4e1c5c557 100644 --- a/cockatrice/resources/help/deck_search.md +++ b/cockatrice/resources/help/deck_search.md @@ -1,11 +1,13 @@ @page deck_search_syntax_help Deck Search Syntax Help ## Deck Search Syntax Help ------ + The search bar recognizes a set of special commands.
    In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all searches are case insensitive. +
    +
    Display Name (The deck name, or the filename if the deck name isn't set):
    [red deck wins](#red deck wins) (Any deck with a display name containing the words red, deck, and wins)
    ["red deck wins"](#%22red deck wins%22) (Any deck with a display name containing the exact phrase "red deck wins")
    diff --git a/cockatrice/resources/help/search.md b/cockatrice/resources/help/search.md index 3b3f0cc93..6f25e4013 100644 --- a/cockatrice/resources/help/search.md +++ b/cockatrice/resources/help/search.md @@ -1,10 +1,12 @@ @page search_syntax_help Search Syntax Help ## Search Syntax Help ------ + The search bar recognizes a set of special commands similar to some other card databases.
    In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all searches are case insensitive. +
    +
    Name:
    [birds of paradise](#birds of paradise) (Any card name containing the words birds, of, and paradise)
    ["birds of paradise"](#%22birds of paradise%22) (Any card name containing the exact phrase "birds of paradise")
    diff --git a/doc/doxygen/extra-pages/index.md b/doc/doxygen/extra-pages/index.md index c370a6fc2..b5c2b61e5 100644 --- a/doc/doxygen/extra-pages/index.md +++ b/doc/doxygen/extra-pages/index.md @@ -7,4 +7,4 @@ This is the **main landing page** of the Cockatrice documentation. - Go to the @subpage user_reference page - Review the @subpage developer_reference -Or check out the [Cockatrice Webpage](https://cockatrice.github.io/). +Or check out the Cockatrice Webpage. From bcf3939feef3c9f01515842acefd557d06ede600 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Fri, 2 Jan 2026 16:26:16 +0100 Subject: [PATCH 111/325] Translate cockatrice/cockatrice_en@source.ts in fr (#6480) --- cockatrice/translations/cockatrice_fr.ts | 466 +++++++++++------------ 1 file changed, 233 insertions(+), 233 deletions(-) diff --git a/cockatrice/translations/cockatrice_fr.ts b/cockatrice/translations/cockatrice_fr.ts index 5cd205a48..3bd264358 100644 --- a/cockatrice/translations/cockatrice_fr.ts +++ b/cockatrice/translations/cockatrice_fr.ts @@ -133,7 +133,7 @@ Vérifiez que le répertoire ne soit pas en lecture seule et réessayez. seconds - + secondes @@ -154,7 +154,7 @@ You will not be able to manage printing preferences on a per-deck basis, or see You will have to use the Set Manager, available through Card Database -> Manage Sets. Are you sure you would like to enable this feature? - + L'activation de cette fonction désactivera l'utilisation du sélecteur d'impression.Vous ne pourrez plus gérer les préférences d'impression pour chaque deck ni voir les impressions sélectionnées par d'autres joueurs.Vous devrez utiliser le Gestionnaire d'extensions, accessible via Base de données de cartes -> Gérer les extensions.Êtes-vous sûr de vouloir activer cette fonction ? @@ -165,12 +165,12 @@ You can now choose printings on a per-deck basis in the Deck Editor and configur You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). Are you sure you would like to disable this feature? - + Désactiver cette fonction activera le sélecteur d'impression.Vous pouvez désormais choisir les impressions pour chaque deck dans l'éditeur de deck et configurer l'impression ajoutée par défaut en l'épinglant dans le sélecteur d'impression.Vous pouvez également utiliser le gestionnaire de d'extension pour personnaliser l'ordre de tri des impressions dans le sélecteur d'impression (d'autres ordres de tri, comme l'ordre alphabétique ou la date de parution, sont disponibles).Êtes-vous sûr de vouloir désactiver cette fonction ? Confirm Change - + Confirmer le changement @@ -190,17 +190,17 @@ Are you sure you would like to disable this feature? Home tab background source: - + Source d'arrière-plan de l'onglet Accueil: Home tab background shuffle frequency: - + Fréquence de mélange de l'arrière-plan de l'onglet Accueil: Disabled - + Désactivé @@ -230,7 +230,7 @@ Are you sure you would like to disable this feature? Override all card art with personal set preference (Pre-ProviderID change behavior) - + Remplacer toutes les illustrations de cartes par les préférences personnelles (comportement avant modification du ProviderID) @@ -319,17 +319,17 @@ Are you sure you would like to disable this feature? Theme - + Thème Art crop of random card - + Recadrage d'image d'une carte aléatoire Art crop of background.cod deck file - + Recadrage d'image du fichier background.cod @@ -667,124 +667,124 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa Re&veal to... - + Révéler à... &All players - + &Tous les joueurs View related cards - + Voir les cartes associées Token: - + Jeton : All tokens - + Tout les jetons &Select All - + &Sélectionner Tout S&elect Row - + Sél&ectionne une ligne S&elect Column - + Sél&ectionne une colonne &Play - + &Jouer &Hide - + &Cacher Play &Face Down - + Jouer &Face Cachée &Tap / Untap Turn sideways or back again - + &Engager / Dégager Toggle &normal untapping - + Activer dégagement &normal T&urn Over Turn face up/face down - + Reto&urner &Peek at card face - + &Regarder furtivement la carte face cachée &Clone - + &Cloner Attac&h to card... - + Attac&her à une carte... Unattac&h - + Détac&her &Draw arrow... - + &Tracer une flèche... &Set annotation... - + &Annoter... Ca&rd counters - + Ma&rqueurs sur la carte &Add counter (%1) - + &Ajouter un marqueur (%1) &Remove counter (%1) - + &Retirer un marqueur (%1) &Set counters (%1)... - + &Changer le nombre de marqueurs (%1)... @@ -801,133 +801,133 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa their hand nominative - + sa main %1's hand nominative - + main de %1 their library look at zone - + sa bibliothèque %1's library look at zone - + bibliothèque de %1 of their library top cards of zone, - + de sa bibliothèque of %1's library top cards of zone - + de la bibliothèque de %1 their library reveal zone - + sa bibliothèque %1's library reveal zone - + bibliothèque de %1 their library shuffle - + sa bibliothèque %1's library shuffle - + bibliothèque de %1 their library nominative - + sa bibliothèque %1's library nominative - + bibliothèque de %1 their graveyard nominative - + son cimetière %1's graveyard nominative - + le cimetière de %1 their exile nominative - + sa zone d'exil %1's exile nominative - + la zone d'exil de %1 their sideboard look at zone - + sa réserve %1's sideboard look at zone - + la réserve de %1 their sideboard nominative - + sa réserve %1's sideboard nominative - + la réserve de %1 their custom zone '%1' nominative - + sa zone personnalisée '%1' %1's custom zone '%2' nominative - + La zone personnalisée '%2' de %1 @@ -951,13 +951,13 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa C&ustom Zones - + Zones pe&rsonnalisés View custom zone '%1' - + Voir la zone personnalisée '%1' @@ -993,12 +993,12 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa Show on EDHRec (Commander) - + Afficher sur EDHRec (Commandant) Show on EDHRec (Card) - + Afficher sur EDHRec (carte) @@ -1204,7 +1204,7 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa Load deck from online service... - + Charger un deck depuis un service en ligne... @@ -1671,7 +1671,7 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa Load from website... - + Charger depuis un site web... @@ -1983,7 +1983,7 @@ Voulez-vous convertir le deck au format .cod ? Open decklists in lobby - + Ouvrir les listes de deck dans le lobby @@ -2455,7 +2455,7 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je Hide games with forced open decklists - + Masquer les parties avec les listes de decks ouvertes forcées @@ -2743,7 +2743,7 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Collez ici un lien vers un site de liste de deck pour l'importer.(Archidekt, Deckstats, Moxfield et TappedOut sont supportés.) @@ -2752,28 +2752,28 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je Load Deck from Website - + Charger un deck depuis un site web No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Aucun analyseur disponible pour ce fournisseur de deck.(Archidekt, Deckstats, Moxfield et TappedOut sont supportés.) Network error: %1 - + Erreur réseau : %1 Received empty deck data. - + Données de deck vide reçues. Failed to parse deck data: %1 - + Échec de l'analyse des données du deck : %1 @@ -2784,7 +2784,7 @@ https://archidekt.com/decks/9999999 https://deckstats.net/decks/99999/9999999-your-deck-name/en https://moxfield.com/decks/XYZxx-XYZ99Yyy-xyzXzzz https://tappedout.net/mtg-decks/your-deck-name/ - + L'URL fournie n'est pas reconnue comme une URL de deck valide.Les URL de deck valides ressemblent à ceci :https://archidekt.com/decks/9999999https://deckstats.net/decks/99999/9999999-your-deck-name/enhttps://moxfield.com/decks/XYZxx-XYZ99Yyy-xyzXzzzhttps://tappedout.net/mtg-decks/your-deck-name/ @@ -3495,22 +3495,22 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. kicked by game host or moderator - + Éjecté par l'hôte de la partie ou un modérateur player left the game - + joueur a quitté la partie player disconnected from server - + joueur déconnecté du serveur reason unknown - + raison inconnue @@ -3676,7 +3676,7 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. open decklists - + Ouvrir les listes de deck @@ -3889,47 +3889,47 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. &Graveyard - + &Cimetière &View graveyard - + &Voir le cimetière &Move graveyard to... - + &Déplacer le cimetière vers... &Top of library - + &Dessus de la bibliothèque &Bottom of library - + &Dessous de la bibliothèque &All players - + &Tous les joueurs &Hand - + &Main &Exile - + &Exil Reveal random card to... - + Révéler une carte au hasard à... @@ -3937,63 +3937,63 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. &Hand - + &Main &View hand - + &Voir la main &Sort hand - + &Trier la main Take &mulligan - + Faire un &mulligan &Move hand to... - + &Déplacer la main vers… &Top of library - + &Dessus de la bibliothèque &Bottom of library - + &Dessous de la bibliothèque &Graveyard - + &Cimetière &Exile - + &Exil &Reveal hand to... - + &Révéler la main à... Reveal r&andom card to... - + Révéler une c&arte au hasard à... &All players - + &Tous les joueurs @@ -4001,47 +4001,47 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. Create New Deck - + Créer un Nouveau Deck Browse Decks - + Parcourir les Decks Browse Card Database - + Parcourir la Base de Carte Browse EDHRec - + Parcourir EDHRec View Replays - + Voir les replays Quit - + Quitter Connecting... - + Connection... Connect - + Se connecter Play - + Jouer @@ -4049,193 +4049,193 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. &Library - + &Bibliothèque &View library - + &Voir la bibliothèque View &top cards of library... - + Voir les cartes du &dessus de la bibliothèque... View bottom cards of library... - + Voir les cartes du dessous de la bibliothèque... Reveal &library to... - + Révéler la &bibliothèque à... Lend library to... - + Prêter la bibliothèque à... Reveal &top cards to... - + Révéler les cartes du &dessus à... &Top of library... - + &Dessus de la bibliothèque... &Bottom of library... - + &Dessous de la bibliothèque... &Always reveal top card - + &Toujours révéler la carte du dessus &Always look at top card - + &Toujours voir la carte du dessus &Open deck in deck editor - + &Ouvrir le deck dans l'éditeur &Draw card - + &Piocher une carte D&raw cards... - + P&iocher plusieurs cartes... &Undo last draw - + &Annuler la dernière pioche Shuffle - + Mélanger &Play top card - + &Jouer la carte du dessus Play top card &face down - + Jouer la carte du dessus &face cachée Put top card on &bottom - + Placer la carte du dessus au-&dessous Move top card to grave&yard - + Mettre la carte du dessus dans le cime&tière Move top card to e&xile - + E&xiler la carte du dessus Move top cards to &graveyard... - + Mettre les cartes du dessus dans le &cimetière... Move top cards to &exile... - + &Exiler les cartes du dessus... Put top cards on stack &until... - + Placer les cartes du dessus sur la pile &jusqu'à... Shuffle top cards... - + Mélanger les cartes du dessus... &Draw bottom card - + &Piocher la carte du dessous D&raw bottom cards... - + P&iocher les cartes du dessous... &Play bottom card - + &Jouer la carte du dessous Play bottom card &face down - + Jouer la carte du dessous &face cachée Move bottom card to grave&yard - + Mettre la carte du dessous dans le cime&tière Move bottom card to e&xile - + E&xiler la carte du dessous Move bottom cards to &graveyard... - + Mettre les cartes du dessus dans le &cimetière... Move bottom cards to &exile... - + &Exiler les cartes du dessous... Put bottom card on &top - + Placer la carte du dessous au-&dessus Shuffle bottom cards... - + Mélanger les cartes du dessous... &All players - + &Tous les joueurs Reveal top cards of library - + Révéler les cartes du dessus de la bibliothèque Number of cards: (max. %1) - + Nombre de cartes : (max. %1) @@ -5418,7 +5418,7 @@ Cockactrice va maintenant recharger la base de données de cartes. You are flooding the game. Please wait a couple of seconds. - + Vous floodez la partie. Veuillez patienter quelques secondes. @@ -5804,37 +5804,37 @@ Cockactrice va maintenant recharger la base de données de cartes. Move to - + Déplacer vers &Top of library in random order - + &Dessus de la bibliothèque dans un ordre aléatoire X cards from the top of library... - + X cartes du dessus de la bibliothèque... &Bottom of library in random order - + &Dessous de la bibliothèque dans un ordre aléatoire &Hand - + &Main &Graveyard - + &Cimetière &Exile - + &Exil @@ -6043,7 +6043,7 @@ Cockactrice va maintenant recharger la base de données de cartes. View top cards of library - + Voir les cartes du dessus de la bibliothèque @@ -6058,119 +6058,119 @@ Cockactrice va maintenant recharger la base de données de cartes. Number of cards: (max. %1) - + Nombre de cartes : (max. %1) View bottom cards of library - + Voir les cartes du dessous de la bibliothèque Shuffle top cards of library - + Mélanger les cartes du dessus de la bibliothèque Shuffle bottom cards of library - + Mélanger les cartes du dessous de la bibliothèque Draw hand - + Piocher une main 0 and lower are in comparison to current hand size - + 0 et moins sont en comparaison avec la taille actuelle de la main Draw cards - + Piocher des cartes Move top cards to grave - + Mettre les cartes du dessus dans le cimetière Move top cards to exile - + Exiler les cartes du dessus Move bottom cards to grave - + Mettre les cartes du dessous dans le cimetière Move bottom cards to exile - + Exiler les cartes du dessous Draw bottom cards - + Piocher les cartes du dessous C&reate another %1 token - + C&réer un autre jeton %1 Create tokens - + Créer des jetons Number: - + Nombre : Place card X cards from top of library - + Placer les X cartes depuis le dessus de la bibliothèque Which position should this card be placed: - + Dans quelle position cette carte doit-elle être placée : (max. %1) - + (max. %1) Change power/toughness - + Changer la force/l'endurance Change stats to: - + Changer les stats pour : Set annotation - + Annoter Please enter the new annotation: - + Veuillez entrer la nouvelle annotation : Set counters - + Définir les marqueurs @@ -6178,17 +6178,17 @@ Cockactrice va maintenant recharger la base de données de cartes. Player "%1" - + Joueur "%1" &Counters - + &Marqueurs S&ay - + D&ire @@ -6282,57 +6282,57 @@ Cockactrice va maintenant recharger la base de données de cartes. Power / toughness - + Force / Endurance &Increase power - + &Augmenter la force &Decrease power - + &Diminuer la force I&ncrease toughness - + A&ugmenter l'endurance D&ecrease toughness - + D&iminuer l'endurance In&crease power and toughness - + Au&gmenter la force et l'endurance Dec&rease power and toughness - + Dim&inuer la force et l'endurance Increase power and decrease toughness - + Augmenter la force et diminuer l'endurance Decrease power and increase toughness - + Diminuer la force et augmenter l'endurance Set &power and toughness... - + Changer la &force et l'endurance... Reset p&ower and toughness - + Réinitialiser la f&orce et l'endurance @@ -6565,37 +6565,37 @@ Cockactrice va maintenant recharger la base de données de cartes. &Exile - + &Exil &View exile - + &Voir la zone d'exil &Move exile to... - + &Déplacer la zone d'exil vers... &Top of library - + &Dessus de la bibliothèque &Bottom of library - + &Dessous de la bibliothèque &Hand - + &Main &Graveyard - + &Cimetière @@ -6803,12 +6803,12 @@ Please check your shortcut settings! &Sideboard - + &Réserve &View sideboard - + &Voir la réserve @@ -7382,7 +7382,7 @@ Veuillez entrer un nom: EDHRec: - + EDHRec : @@ -7509,7 +7509,7 @@ Veuillez entrer un nom: Selected cards - + Cartes sélectionnées @@ -7588,7 +7588,7 @@ Veuillez entrer un nom: Home - + Accueil @@ -7841,13 +7841,13 @@ Plus vous entrez d'informations, meilleurs seront les résultats. Get replay share code - + Obtenir le code de partage du replay Look up replay by share code - + Regarder un replay depuis un code de partage @@ -7897,71 +7897,71 @@ Plus vous entrez d'informations, meilleurs seront les résultats. Failed to get code - + Echec d'obtention du code Either this server does not support replay sharing, or does not permit replay sharing for you. - + Soit ce serveur ne prend pas en charge le partage de replay, soit il ne vous autorise pas à partager des replays. Failed - + Échec Could not get replay code - + Impossible d'obtenir le code de replay Replay Share Code - + Code de Partage de Replay Others can use this code to add the replay to their list of remote replays: %1 - + D'autres peuvent utiliser ce code pour ajouter la replay à leur liste de replay distantes :%1 Copy to clipboard - + Copier vers le presse-papier Replay share code - + Code de partage de replay Replay code found - + Code de replay trouvé Replay was added, or you already had access to it. - + Le replay a été ajoutée, ou vous y aviez déjà accès. Replay code not found - + Code de replay non trouvé Failed to submit code - + Échec de l'envoi du code Unexpected error - + Erreur inattendue @@ -8078,7 +8078,7 @@ Plus vous entrez d'informations, meilleurs seront les résultats. Home - + Accueil @@ -8378,22 +8378,22 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre Error - + Erreur This user does not exist. - + Cet utilisateur n'existe pas. You are being ignored by %1 and can't see their games. - + Vous avez été ignoré par %1 et ne pouvez pas voir ses parties. Could not get %1's games. - + Impossible d'obtenir les parties de %1. @@ -8833,32 +8833,32 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre Increment all card counters - + Incrémenter tous les marqueurs de la carte &Untap all permanents - + &Dégager tous les permanents R&oll die... - + L&ancer un dé... &Create token... - + &Créer un jeton... C&reate another token - + C&réer un autre jeton Cr&eate predefined token - + C&réer un jeton prédéfini @@ -9019,7 +9019,7 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre Loading database ... - + Chargement de la base de données... @@ -10000,7 +10000,7 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre Increment all card counters - + Incrémenter tous les marqueurs de la carte @@ -10256,7 +10256,7 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre Sort Hand - + Trier la main @@ -10449,7 +10449,7 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre Home - + Accueil From 73cc0541f518ba68f77ec64b7dd10003000fea4c Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sat, 3 Jan 2026 00:03:11 +0100 Subject: [PATCH 112/325] [Game] Add shortcuts for same size and hand size - 1 mulligans (#6483) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 21 minutes Took 3 seconds Co-authored-by: Lukas Brübach --- .../src/client/settings/shortcuts_settings.h | 6 +++ cockatrice/src/game/player/menu/hand_menu.cpp | 16 ++++++- cockatrice/src/game/player/menu/hand_menu.h | 2 + cockatrice/src/game/player/player_actions.cpp | 42 ++++++++++++++----- cockatrice/src/game/player/player_actions.h | 3 ++ 5 files changed, 57 insertions(+), 12 deletions(-) diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h index 736f0c896..156ee82ea 100644 --- a/cockatrice/src/client/settings/shortcuts_settings.h +++ b/cockatrice/src/client/settings/shortcuts_settings.h @@ -660,6 +660,12 @@ private: {"Player/aMulligan", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Mulligan"), parseSequenceString("Ctrl+M"), ShortcutGroup::Drawing)}, + {"Player/aMulliganSame", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Mulligan (Same hand size)"), + parseSequenceString("Ctrl+Shift+M"), + ShortcutGroup::Drawing)}, + {"Player/aMulliganMinusOne", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Mulligan (Hand size - 1)"), + parseSequenceString("Ctrl+Shift+Alt+M"), + ShortcutGroup::Drawing)}, {"Player/aDrawCard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Draw a Card"), parseSequenceString("Ctrl+D"), ShortcutGroup::Drawing)}, diff --git a/cockatrice/src/game/player/menu/hand_menu.cpp b/cockatrice/src/game/player/menu/hand_menu.cpp index e193d2e4a..019ab925c 100644 --- a/cockatrice/src/game/player/menu/hand_menu.cpp +++ b/cockatrice/src/game/player/menu/hand_menu.cpp @@ -60,6 +60,16 @@ HandMenu::HandMenu(Player *_player, PlayerActions *actions, QWidget *parent) : T connect(aMulligan, &QAction::triggered, actions, &PlayerActions::actMulligan); addAction(aMulligan); + // Mulligan same size + aMulliganSame = new QAction(this); + connect(aMulliganSame, &QAction::triggered, actions, &PlayerActions::actMulliganSameSize); + addAction(aMulliganSame); + + // Mulligan -1 + aMulliganMinusOne = new QAction(this); + connect(aMulliganMinusOne, &QAction::triggered, actions, &PlayerActions::actMulliganMinusOne); + addAction(aMulliganMinusOne); + addSeparator(); mMoveHandMenu = addTearOffMenu(QString()); @@ -104,7 +114,9 @@ void HandMenu::retranslateUi() aSortHandByType->setText(tr("Type")); aSortHandByManaValue->setText(tr("Mana Value")); - aMulligan->setText(tr("Take &mulligan")); + aMulligan->setText(tr("Take &mulligan (Choose hand size)")); + aMulliganSame->setText(tr("Take mulligan (Same hand size)")); + aMulliganMinusOne->setText(tr("Take mulligan (Hand size - 1)")); mMoveHandMenu->setTitle(tr("&Move hand to...")); aMoveHandToTopLibrary->setText(tr("&Top of library")); @@ -128,6 +140,8 @@ void HandMenu::setShortcutsActive() aSortHandByType->setShortcuts(shortcuts.getShortcut("Player/aSortHandByType")); aSortHandByManaValue->setShortcuts(shortcuts.getShortcut("Player/aSortHandByManaValue")); aMulligan->setShortcuts(shortcuts.getShortcut("Player/aMulligan")); + aMulliganSame->setShortcuts(shortcuts.getShortcut("Player/aMulliganSame")); + aMulliganMinusOne->setShortcuts(shortcuts.getShortcut("Player/aMulliganMinusOne")); aRevealHandToAll->setShortcuts(shortcuts.getShortcut("Player/aRevealHandToAll")); aRevealRandomHandCardToAll->setShortcuts(shortcuts.getShortcut("Player/aRevealRandomHandCardToAll")); } diff --git a/cockatrice/src/game/player/menu/hand_menu.h b/cockatrice/src/game/player/menu/hand_menu.h index 2c776b857..51e071a62 100644 --- a/cockatrice/src/game/player/menu/hand_menu.h +++ b/cockatrice/src/game/player/menu/hand_menu.h @@ -46,6 +46,8 @@ private: QAction *aViewHand = nullptr; QAction *aMulligan = nullptr; + QAction *aMulliganSame = nullptr; + QAction *aMulliganMinusOne = nullptr; QMenu *mSortHand = nullptr; QAction *aSortHandByName = nullptr; diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index 444eecb0d..ed77808b0 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -310,28 +310,48 @@ void PlayerActions::actMulligan() { int startSize = SettingsCache::instance().getStartingHandSize(); int handSize = player->getHandZone()->getCards().size(); - int deckSize = player->getDeckZone()->getCards().size() + handSize; // hand is shuffled back into the deck + int deckSize = player->getDeckZone()->getCards().size() + handSize; + bool ok; int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Draw hand"), tr("Number of cards: (max. %1)").arg(deckSize) + '\n' + tr("0 and lower are in comparison to current hand size"), startSize, -handSize, deckSize, 1, &ok); + if (!ok) { return; } - Command_Mulligan cmd; + if (number < 1) { - if (handSize == 0) { - return; - } - cmd.set_number(handSize + number); - } else { - cmd.set_number(number); + number = handSize + number; } + + doMulligan(number); + SettingsCache::instance().setStartingHandSize(number); +} + +void PlayerActions::actMulliganSameSize() +{ + int handSize = player->getHandZone()->getCards().size(); + doMulligan(handSize); +} + +void PlayerActions::actMulliganMinusOne() +{ + int handSize = player->getHandZone()->getCards().size(); + int targetSize = qMax(1, handSize - 1); + doMulligan(targetSize); +} + +void PlayerActions::doMulligan(int number) +{ + if (number < 1) { + return; + } + + Command_Mulligan cmd; + cmd.set_number(number); sendGameCommand(cmd); - if (startSize != number) { - SettingsCache::instance().setStartingHandSize(number); - } } void PlayerActions::actDrawCards() diff --git a/cockatrice/src/game/player/player_actions.h b/cockatrice/src/game/player/player_actions.h index e0f86e4fc..b294b5946 100644 --- a/cockatrice/src/game/player/player_actions.h +++ b/cockatrice/src/game/player/player_actions.h @@ -85,6 +85,9 @@ public slots: void actDrawCards(); void actUndoDraw(); void actMulligan(); + void actMulliganSameSize(); + void actMulliganMinusOne(); + void doMulligan(int number); void actPlay(); void actPlayFacedown(); From 84aefda486defc2d9b9313f8b2d0279e7d4270b2 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 2 Jan 2026 18:55:27 -0800 Subject: [PATCH 113/325] [DeckListModel] add getCardNodes method (#6484) * [DeckListModel] add getCardNodes method * Update one usage --- .../draw_probability/draw_probability_widget.cpp | 2 +- .../libcockatrice/models/deck_list/deck_list_model.cpp | 10 ++++++++++ .../libcockatrice/models/deck_list/deck_list_model.h | 6 ++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp index a8bec834f..92ebea630 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp @@ -168,7 +168,7 @@ void DrawProbabilityWidget::updateFilterOptions() QMap categoryCounts; int totalDeckCards = 0; - const auto nodes = analyzer->getModel()->getDeckList()->getCardNodes(); + const auto nodes = analyzer->getModel()->getCardNodes(); for (auto *node : nodes) { CardInfoPtr info = CardDatabaseManager::query()->getCard({node->getName()}).getCardPtr(); if (!info) { 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 ce1c79263..23dbd7fec 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -637,6 +637,16 @@ QList DeckListModel::getCardsForZone(const QString &zoneName) const return cardNodesToExactCards(nodes); } +QList DeckListModel::getCardNodes() const +{ + return deckList->getCardNodes(); +} + +QList DeckListModel::getCardNodesForZone(const QString &zoneName) const +{ + return deckList->getCardNodes({zoneName}); +} + QList DeckListModel::getCardNames() const { auto nodes = deckList->getCardNodes(); 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 e6f10c072..e7a79d13e 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -339,6 +339,12 @@ public: [[nodiscard]] QList getCards() const; [[nodiscard]] QList getCardsForZone(const QString &zoneName) const; + /** + * @brief Gets a list of all card nodes in the deck. + */ + [[nodiscard]] QList getCardNodes() const; + [[nodiscard]] QList getCardNodesForZone(const QString &zoneName) const; + /** * @brief Gets a deduplicated list of all card names that appear in the model */ From 4fbb9d968267e9545a5e5dc9cf3194897b8600cc Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 3 Jan 2026 01:04:56 -0800 Subject: [PATCH 114/325] [PrintingSelector] optimize amount calculation (#6478) --- .../all_zones_card_amount_widget.cpp | 18 ++++++- .../all_zones_card_amount_widget.h | 3 ++ .../printing_selector/card_amount_widget.cpp | 42 ++++++---------- .../printing_selector/card_amount_widget.h | 4 +- .../printing_selector/printing_selector.cpp | 36 ++++++++++++++ .../printing_selector/printing_selector.h | 7 +++ .../printing_selector_card_display_widget.cpp | 8 +++- .../printing_selector_card_display_widget.h | 2 + .../printing_selector_card_overlay_widget.cpp | 48 +++++++++---------- .../printing_selector_card_overlay_widget.h | 4 ++ 10 files changed, 115 insertions(+), 57 deletions(-) diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp index b1346e4fd..36bccbcc3 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp @@ -72,6 +72,12 @@ void AllZonesCardAmountWidget::adjustFontSize(int scalePercentage) repaint(); } +void AllZonesCardAmountWidget::setAmounts(int mainboardAmount, int sideboardAmount) +{ + buttonBoxMainboard->setAmount(mainboardAmount); + buttonBoxSideboard->setAmount(sideboardAmount); +} + /** * @brief Gets the card count in the mainboard zone. * @@ -79,7 +85,7 @@ void AllZonesCardAmountWidget::adjustFontSize(int scalePercentage) */ int AllZonesCardAmountWidget::getMainboardAmount() { - return buttonBoxMainboard->countCardsInZone(DECK_ZONE_MAIN); + return buttonBoxMainboard->getAmount(); } /** @@ -89,7 +95,15 @@ int AllZonesCardAmountWidget::getMainboardAmount() */ int AllZonesCardAmountWidget::getSideboardAmount() { - return buttonBoxSideboard->countCardsInZone(DECK_ZONE_SIDE); + return buttonBoxSideboard->getAmount(); +} + +/** + * @brief Checks if the amount is at least one in either the mainboard or sideboard. + */ +bool AllZonesCardAmountWidget::isNonZero() +{ + return getMainboardAmount() > 0 || getSideboardAmount() > 0; } /** diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h index 5a03c5f4a..d158d257e 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h @@ -23,6 +23,8 @@ public: const ExactCard &rootCard); int getMainboardAmount(); int getSideboardAmount(); + bool isNonZero(); + #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) void enterEvent(QEnterEvent *event) override; #else @@ -31,6 +33,7 @@ public: public slots: void adjustFontSize(int scalePercentage); + void setAmounts(int mainboardAmount, int sideboardAmount); private: QVBoxLayout *layout; diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index 62c8e2d60..f8002eb89 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -45,20 +45,28 @@ CardAmountWidget::CardAmountWidget(QWidget *parent, connect(decrementButton, &QPushButton::clicked, this, &CardAmountWidget::removePrintingSideboard); } - cardCountInZone = new QLabel(QString::number(countCardsInZone(zoneName)), this); + cardCountInZone = new QLabel(QString::number(amount), this); cardCountInZone->setAlignment(Qt::AlignCenter); layout->addWidget(decrementButton); layout->addWidget(cardCountInZone); layout->addWidget(incrementButton); - // React to model changes - connect(deckStateManager, &DeckStateManager::cardModified, this, &CardAmountWidget::updateCardCount); - // Connect slider for dynamic font size adjustment connect(cardSizeSlider, &QSlider::valueChanged, this, &CardAmountWidget::adjustFontSize); } +int CardAmountWidget::getAmount() +{ + return amount; +} + +void CardAmountWidget::setAmount(int _amount) +{ + amount = _amount; + updateCardCount(); +} + /** * @brief Handles the painting of the widget, drawing a semi-transparent background. * @@ -124,7 +132,7 @@ void CardAmountWidget::adjustFontSize(int scalePercentage) */ void CardAmountWidget::updateCardCount() { - cardCountInZone->setText("" + QString::number(countCardsInZone(zoneName)) + ""); + cardCountInZone->setText("" + QString::number(amount) + ""); layout->invalidate(); layout->activate(); } @@ -169,8 +177,8 @@ void CardAmountWidget::addPrinting(const QString &zone) QString foundProviderId = existing.siblingAtColumn(DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::DisplayRole).toString(); if (foundProviderId.isEmpty()) { - int amount = existing.data(Qt::DisplayRole).toInt(); - extraCopies = amount - 1; // One less because we *always* add one + int existingAmount = existing.data(Qt::DisplayRole).toInt(); + extraCopies = existingAmount - 1; // One less because we *always* add one replacingProviderless = true; } } @@ -246,23 +254,3 @@ void CardAmountWidget::decrementCardHelper(const QString &zone) return model->offsetCountAtIndex(idx, -1); }); } - -/** - * @brief Counts the number of cards in a specific zone (mainboard or sideboard). - * - * @param deckZone The name of the zone (e.g., DECK_ZONE_MAIN or DECK_ZONE_SIDE). - * @return The number of cards in the zone. - */ -int CardAmountWidget::countCardsInZone(const QString &deckZone) -{ - QString uuid = rootCard.getPrinting().getUuid(); - - if (uuid.isEmpty()) { - return 0; // Cards without uuids/providerIds CANNOT match another card, they are undefined for us. - } - - QList cards = deckStateManager->getModel()->getCardsForZone(deckZone); - - return std::count_if(cards.cbegin(), cards.cend(), - [&uuid](const ExactCard &card) { return card.getPrinting().getUuid() == uuid; }); -} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h index 983416782..3051b1691 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h @@ -31,9 +31,10 @@ public: QSlider *cardSizeSlider, const ExactCard &rootCard, const QString &zoneName); - int countCardsInZone(const QString &deckZone); + int getAmount(); public slots: + void setAmount(int _amount); void updateCardCount(); void addPrinting(const QString &zone); @@ -52,6 +53,7 @@ private: QLabel *cardCountInZone; bool hovered; + int amount = 0; void decrementCardHelper(const QString &zoneName); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp index 95d6b2cdf..becfa16a3 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp @@ -78,6 +78,7 @@ PrintingSelector::PrintingSelector(QWidget *parent, AbstractTabDeckEditor *_deck // Connect deck model data change signal to update display connect(deckStateManager, &DeckStateManager::uniqueCardsChanged, this, &PrintingSelector::printingsInDeckChanged); + connect(deckStateManager, &DeckStateManager::cardModified, this, &PrintingSelector::updateCardAmounts); retranslateUi(); } @@ -93,6 +94,36 @@ void PrintingSelector::printingsInDeckChanged() QTimer::singleShot(100, this, &PrintingSelector::updateDisplay); } +/** + * @return A map of uuid to amounts (main, side). + */ +static QMap> tallyUuidCounts(const DeckListModel *model, const QString &cardName) +{ + QMap> map; + + auto mainNodes = model->getCardNodesForZone(DECK_ZONE_MAIN); + for (auto &node : mainNodes) { + if (node->getName() == cardName) { + map[node->getCardProviderId()].first += node->getNumber(); + } + } + + auto sideNodes = model->getCardNodesForZone(DECK_ZONE_SIDE); + for (auto &node : sideNodes) { + if (node->getName() == cardName) { + map[node->getCardProviderId()].second += node->getNumber(); + } + } + + return map; +} + +void PrintingSelector::updateCardAmounts() +{ + auto map = tallyUuidCounts(deckStateManager->getModel(), selectedCard->getName()); + emit cardAmountsChanged(map); +} + /** * @brief Updates the display by clearing the layout and loading new sets for the current card. */ @@ -156,6 +187,8 @@ void PrintingSelector::getAllSetsForCurrentCard() } printingsToUse = sortToolBar->prependPinnedPrintings(printingsToUse, selectedCard->getName()); + auto uuidToAmounts = tallyUuidCounts(deckStateManager->getModel(), selectedCard->getName()); + // Defer widget creation currentIndex = 0; @@ -166,8 +199,11 @@ void PrintingSelector::getAllSetsForCurrentCard() cardSizeWidget->getSlider(), card); flowWidget->addWidget(cardDisplayWidget); cardDisplayWidget->clampSetNameToPicture(); + cardDisplayWidget->updateCardAmounts(uuidToAmounts); connect(cardDisplayWidget, &PrintingSelectorCardDisplayWidget::cardPreferenceChanged, this, &PrintingSelector::updateDisplay); + connect(this, &PrintingSelector::cardAmountsChanged, cardDisplayWidget, + &PrintingSelectorCardDisplayWidget::updateCardAmounts); } // Stop timer when done diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h index e1c07addf..e48eb2f2c 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h @@ -44,6 +44,7 @@ public slots: private slots: void printingsInDeckChanged(); + void updateCardAmounts(); signals: /** @@ -55,6 +56,12 @@ signals: */ void nextCardRequested(); + /** + * The amounts of the printings in the deck has changed + * @param uuidToAmounts Map of uuids to the amounts (maindeck, sideboard) in the deck + */ + void cardAmountsChanged(const QMap> &uuidToAmounts); + private: QVBoxLayout *layout; SettingsButtonWidget *displayOptionsWidget; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp index 92cf2437c..b95c25cbd 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp @@ -27,7 +27,7 @@ PrintingSelectorCardDisplayWidget::PrintingSelectorCardDisplayWidget(QWidget *pa DeckStateManager *deckStateManager, QSlider *cardSizeSlider, const ExactCard &rootCard) - : QWidget(parent) + : QWidget(parent), rootCard(rootCard) { layout = new QVBoxLayout(this); setLayout(layout); @@ -64,3 +64,9 @@ void PrintingSelectorCardDisplayWidget::clampSetNameToPicture() } update(); } + +void PrintingSelectorCardDisplayWidget::updateCardAmounts(const QMap> &uuidToAmounts) +{ + auto [main, side] = uuidToAmounts.value(rootCard.getPrinting().getUuid()); + overlayWidget->updateCardAmounts(main, side); +} diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h index 2637b0e57..64bb72e22 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h @@ -27,11 +27,13 @@ public: public slots: void clampSetNameToPicture(); + void updateCardAmounts(const QMap> &uuidToAmounts); signals: void cardPreferenceChanged(); private: + ExactCard rootCard; QVBoxLayout *layout; SetNameAndCollectorsNumberDisplayWidget *setNameAndCollectorsNumberDisplayWidget; PrintingSelectorCardOverlayWidget *overlayWidget; 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 ac36f2cf4..4298b6dc3 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 @@ -59,12 +59,6 @@ PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *pa allZonesCardAmountWidget = new AllZonesCardAmountWidget(this, deckStateManager, cardSizeSlider, _rootCard); allZonesCardAmountWidget->raise(); // Ensure it's on top of the picture - // Set initial visibility based on amounts - if (allZonesCardAmountWidget->getMainboardAmount() > 0 || allZonesCardAmountWidget->getSideboardAmount() > 0) { - allZonesCardAmountWidget->setVisible(true); - } else { - allZonesCardAmountWidget->setVisible(false); - } // Attempt to cast the parent to PrintingSelectorCardDisplayWidget if (const auto *parentWidget = qobject_cast(parent)) { @@ -113,8 +107,7 @@ void PrintingSelectorCardOverlayWidget::resizeEvent(QResizeEvent *event) /** * @brief Handles the mouse enter event when the cursor enters the overlay widget area. * - * When the cursor enters the widget, the card information is updated, and the card amount widget - * is displayed if the amounts are zero for both the mainboard and sideboard. + * When the cursor enters the widget, the card amount widget becomes visible regardless of whether the amounts are zero. * * @param event The event triggered when the mouse enters the widget. */ @@ -126,16 +119,27 @@ void PrintingSelectorCardOverlayWidget::enterEvent(QEvent *event) { QWidget::enterEvent(event); deckEditor->updateCard(rootCard); - - // Check if either mainboard or sideboard amount is greater than 0 - if (allZonesCardAmountWidget->getMainboardAmount() > 0 || allZonesCardAmountWidget->getSideboardAmount() > 0) { - // Don't change visibility if amounts are greater than 0 - return; - } - - // Show the widget if amounts are 0 - allZonesCardAmountWidget->setVisible(true); + updateVisibility(); } + +void PrintingSelectorCardOverlayWidget::updateCardAmounts(int mainboardAmount, int sideboardAmount) +{ + allZonesCardAmountWidget->setAmounts(mainboardAmount, sideboardAmount); + updateVisibility(); +} + +/** + * @brief Sets the visibility of the widgets depending on the amounts and whether the mouse is hovering over. + */ +void PrintingSelectorCardOverlayWidget::updateVisibility() +{ + if (allZonesCardAmountWidget->isNonZero() || underMouse()) { + allZonesCardAmountWidget->setVisible(true); + } else { + allZonesCardAmountWidget->setVisible(false); + } +} + /** * @brief Updates the pin badge visibility and position based on the card's pinned state. * @@ -182,15 +186,7 @@ void PrintingSelectorCardOverlayWidget::updatePinBadgeVisibility() void PrintingSelectorCardOverlayWidget::leaveEvent(QEvent *event) { QWidget::leaveEvent(event); - - // Check if either mainboard or sideboard amount is greater than 0 - if (allZonesCardAmountWidget->getMainboardAmount() > 0 || allZonesCardAmountWidget->getSideboardAmount() > 0) { - // Don't hide the widget if amounts are greater than 0 - return; - } - - // Hide the widget if amounts are 0 - allZonesCardAmountWidget->setVisible(false); + updateVisibility(); } /** 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 8550612bd..ae2307c45 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 @@ -38,7 +38,11 @@ protected: signals: void cardPreferenceChanged(); +public slots: + void updateCardAmounts(int mainboardAmount, int sideboardAmount); + private slots: + void updateVisibility(); void updatePinBadgeVisibility(); private: From b88a98b09a9d1d34081cfd73e1ca4c641568a21a Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sat, 3 Jan 2026 18:58:59 +0100 Subject: [PATCH 115/325] Translate cockatrice/cockatrice_en@source.ts in fr (#6488) 100% translated source file: 'cockatrice/cockatrice_en@source.ts' on 'fr'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- cockatrice/translations/cockatrice_fr.ts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/cockatrice/translations/cockatrice_fr.ts b/cockatrice/translations/cockatrice_fr.ts index 3bd264358..76a42dcd3 100644 --- a/cockatrice/translations/cockatrice_fr.ts +++ b/cockatrice/translations/cockatrice_fr.ts @@ -154,7 +154,13 @@ You will not be able to manage printing preferences on a per-deck basis, or see You will have to use the Set Manager, available through Card Database -> Manage Sets. Are you sure you would like to enable this feature? - L'activation de cette fonction désactivera l'utilisation du sélecteur d'impression.Vous ne pourrez plus gérer les préférences d'impression pour chaque deck ni voir les impressions sélectionnées par d'autres joueurs.Vous devrez utiliser le Gestionnaire d'extensions, accessible via Base de données de cartes -> Gérer les extensions.Êtes-vous sûr de vouloir activer cette fonction ? + L'activation de cette fonction désactivera l'utilisation du sélecteur d'impression. + +Vous ne pourrez plus gérer les préférences d'impression pour chaque deck ni voir les impressions sélectionnées par d'autres joueurs. + +Vous devrez utiliser le Gestionnaire d'extensions, accessible via Base de données de cartes -> Gérer les extensions. + +Êtes-vous sûr de vouloir activer cette fonction ? @@ -165,7 +171,13 @@ You can now choose printings on a per-deck basis in the Deck Editor and configur You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). Are you sure you would like to disable this feature? - Désactiver cette fonction activera le sélecteur d'impression.Vous pouvez désormais choisir les impressions pour chaque deck dans l'éditeur de deck et configurer l'impression ajoutée par défaut en l'épinglant dans le sélecteur d'impression.Vous pouvez également utiliser le gestionnaire de d'extension pour personnaliser l'ordre de tri des impressions dans le sélecteur d'impression (d'autres ordres de tri, comme l'ordre alphabétique ou la date de parution, sont disponibles).Êtes-vous sûr de vouloir désactiver cette fonction ? + Désactiver cette fonction activera le sélecteur d'impression. + +Vous pouvez désormais choisir les impressions pour chaque deck dans l'éditeur de deck et configurer l'impression ajoutée par défaut en l'épinglant dans le sélecteur d'impression. + +Vous pouvez également utiliser le gestionnaire de d'extension pour personnaliser l'ordre de tri des impressions dans le sélecteur d'impression (d'autres ordres de tri, comme l'ordre alphabétique ou la date de parution, sont disponibles). + +Êtes-vous sûr de vouloir désactiver cette fonction ? @@ -2784,7 +2796,13 @@ https://archidekt.com/decks/9999999 https://deckstats.net/decks/99999/9999999-your-deck-name/en https://moxfield.com/decks/XYZxx-XYZ99Yyy-xyzXzzz https://tappedout.net/mtg-decks/your-deck-name/ - L'URL fournie n'est pas reconnue comme une URL de deck valide.Les URL de deck valides ressemblent à ceci :https://archidekt.com/decks/9999999https://deckstats.net/decks/99999/9999999-your-deck-name/enhttps://moxfield.com/decks/XYZxx-XYZ99Yyy-xyzXzzzhttps://tappedout.net/mtg-decks/your-deck-name/ + L'URL fournie n'est pas reconnue comme une URL de deck valide. +Les URL de deck valides ressemblent à ceci: + +https://archidekt.com/decks/9999999 +https://deckstats.net/decks/99999/9999999-your-deck-name/en +https://moxfield.com/decks/XYZxx-XYZ99Yyy-xyzXzzz +https://tappedout.net/mtg-decks/your-deck-name/ From 72a85b58cf1c3d188aacb92b295d57a4b45a5524 Mon Sep 17 00:00:00 2001 From: Bruno Alexandre Rosa <1791393+brunoalr@users.noreply.github.com> Date: Sun, 4 Jan 2026 01:00:05 +0100 Subject: [PATCH 116/325] ci: make fat qt libs thin (#6281) * ci: strip fat qt binaries * parallelize * cache thin qt * print libs * change qt install dir in the action * move qt install logic to separate job * lookup only * debug: show contents of QTDIR * enableCrossOsArchive also when saving * check one dir up * change install dir * keep debugging * try deleting cache * force delete cache * pass gh_token * pass missing params * use api * change cache key, disable cross os archive * move job directly to steps * add comments * set cache param directly * address comments * fixup * Update .ci/thin_macos_qtlib.sh * resolve qt version * move resolution to separate script * use single line for run: * improve error handling in new scripts --------- Co-authored-by: ebbit1q --- .ci/compile.sh | 12 +++++++ .ci/resolve_latest_aqt_qt_version.sh | 40 +++++++++++++++++++++ .ci/thin_macos_qtlib.sh | 25 +++++++++++++ .github/workflows/desktop-build.yml | 54 +++++++++++++++++++++++----- 4 files changed, 123 insertions(+), 8 deletions(-) create mode 100755 .ci/resolve_latest_aqt_qt_version.sh create mode 100755 .ci/thin_macos_qtlib.sh diff --git a/.ci/compile.sh b/.ci/compile.sh index 0af79868b..424527f96 100755 --- a/.ci/compile.sh +++ b/.ci/compile.sh @@ -156,6 +156,18 @@ function ccachestatsverbose() { # Compile if [[ $RUNNER_OS == macOS ]]; then + # QTDIR is needed for macOS since we actually only use the cached thin Qt binaries instead of the install-qt-action, + # which sets a few environment variables + if QTDIR=$(find "$GITHUB_WORKSPACE/Qt" -depth -maxdepth 2 -name macos -type d -print -quit); then + echo "found QTDIR at $QTDIR" + else + echo "could not find QTDIR!" + exit 2 + fi + # the qtdir is located at Qt/[qtversion]/macos + # we use find to get the first subfolder with the name "macos" + # this works independent of the qt version as there should be only one version installed on the runner at a time + export QTDIR if [[ $TARGET_MACOS_VERSION ]]; then # CMAKE_OSX_DEPLOYMENT_TARGET is a vanilla cmake flag needed to compile to target macOS version diff --git a/.ci/resolve_latest_aqt_qt_version.sh b/.ci/resolve_latest_aqt_qt_version.sh new file mode 100755 index 000000000..154c15321 --- /dev/null +++ b/.ci/resolve_latest_aqt_qt_version.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# This script is used to resolve the latest patch version of Qt using aqtinstall. +# It interprets wildcards to get the latest patch version. E.g. "6.6.*" -> "6.6.3". + +# This script is meant to be used by the ci enironment. +# It uses the runner's GITHUB_OUTPUT env variable. + +# Usage example: .ci/resolve_latest_aqt_qt_version.sh "6.6.*" + +qt_spec=$1 +if [[ ! $qt_spec ]]; then + echo "usage: $0 [version]" + exit 2 +fi + +# If version is already specific (no wildcard), use it as-is +if [[ $qt_spec != *"*" ]]; then + echo "version $qt_spec is already resolved" + echo "version=$qt_spec" >> "$GITHUB_OUTPUT" + exit 0 +fi + +if ! hash aqt; then + echo "aqt could not be found, has aqtinstall been installed?" + exit 2 +fi + +# Resolve latest patch +if ! qt_resolved=$(aqt list-qt mac desktop --spec "$qt_spec" --latest-version); then + exit 1 +fi + +echo "resolved $qt_spec to $qt_resolved" +if [[ ! $qt_resolved ]]; then + echo "Error: Could not resolve Qt version for $qt_spec" + exit 1 +fi + +echo "version=$qt_resolved" >> "$GITHUB_OUTPUT" diff --git a/.ci/thin_macos_qtlib.sh b/.ci/thin_macos_qtlib.sh new file mode 100755 index 000000000..6c3cc6720 --- /dev/null +++ b/.ci/thin_macos_qtlib.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# The macos binaries from aqt are fat (universal), so we thin them to the target architecture to reduce the size of +# the packages and caches using lipo. + +# This script is meant to be used by the ci enironment on macos runners only. +# It uses the runner's GITHUB_WORKSPACE env variable. +arch=$(uname -m) +nproc=$(sysctl -n hw.ncpu) + +function thin() { + local libfile=$1 + if [[ $(file -b --mime-type "$libfile") == application/x-mach-binary* ]]; then + echo "Processing $libfile" + lipo "$libfile" -thin "$arch" -output "$libfile" + fi + return 0 +} +export -f thin # export to allow use in xargs +export arch +set -eo pipefail + +echo "::group::Thinning Qt libraries to $arch using $nproc cores" +find "$GITHUB_WORKSPACE/Qt" -type f -print0 | xargs -0 -n1 -P"$nproc" -I{} bash -c "thin '{}'" +echo "::endgroup::" diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 9f4ee4984..5b954d021 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -262,7 +262,6 @@ jobs: qt_version: 6.6.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets - cache_qt: false # qt caches take too much space for macOS (1.1Gi) cmake_generator: Ninja use_ccache: 1 @@ -278,7 +277,6 @@ jobs: qt_version: 6.6.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets - cache_qt: false cmake_generator: Ninja use_ccache: 1 @@ -294,7 +292,6 @@ jobs: qt_version: 6.6.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets - cache_qt: false cmake_generator: Ninja use_ccache: 1 @@ -307,7 +304,6 @@ jobs: qt_version: 6.6.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets - cache_qt: false cmake_generator: Ninja use_ccache: 1 @@ -320,7 +316,6 @@ jobs: artifact_name: Windows7-installer qt_version: 5.15.* qt_arch: win64_msvc2019_64 - cache_qt: true cmake_generator: "Visual Studio 17 2022" cmake_generator_platform: x64 @@ -334,7 +329,6 @@ jobs: qt_version: 6.6.* qt_arch: win64_msvc2019_64 qt_modules: qtimageformats qtmultimedia qtwebsockets - cache_qt: true cmake_generator: "Visual Studio 17 2022" cmake_generator_platform: x64 @@ -375,13 +369,57 @@ jobs: key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}} restore-keys: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}- - - name: Install Qt ${{matrix.qt_version}} + - name: Install aqtinstall + if: matrix.os == 'macOS' + run: pipx install aqtinstall + + # Checking if there's a newer, uncached version of Qt available to install via aqtinstall + - name: Resolve latest Qt patch version + if: matrix.os == 'macOS' + id: resolve_qt_version + shell: bash + # Ouputs the version of Qt to install via aqtinstall + run: .ci/resolve_latest_aqt_qt_version.sh "${{matrix.qt_version}}" + + - name: Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries (${{ matrix.soc }} macOS) + if: matrix.os == 'macOS' + id: restore_qt + uses: actions/cache/restore@v4 + with: + path: ${{ github.workspace }}/Qt + key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }} + + # Using jurplel/install-qt-action to install Qt without using brew + # qt build using vcpkg either just fails or takes too long to build + - name: Install fat Qt ${{ steps.resolve_qt_version.outputs.version }} (${{ matrix.soc }} macOS) + if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' + uses: jurplel/install-qt-action@v4 + with: + cache: false + version: ${{ steps.resolve_qt_version.outputs.version }} + arch: ${{matrix.qt_arch}} + modules: ${{matrix.qt_modules}} + dir: ${{github.workspace}} + + - name: Thin Qt libraries (${{ matrix.soc }} macOS) + if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' + run: .ci/thin_macos_qtlib.sh + + - name: Cache thin Qt libraries (${{ matrix.soc }} macOS) + if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: ${{ github.workspace }}/Qt + key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }} + + - name: Install Qt ${{matrix.qt_version}} (Windows) + if: matrix.os == 'Windows' uses: jurplel/install-qt-action@v4 with: version: ${{matrix.qt_version}} arch: ${{matrix.qt_arch}} modules: ${{matrix.qt_modules}} - cache: ${{matrix.cache_qt}} + cache: true - name: Setup vcpkg cache id: vcpkg-cache From f16c552d970649e47ace5572f72e75ed3bc05c69 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 3 Jan 2026 16:08:39 -0800 Subject: [PATCH 117/325] [PrintingSelector] Don't refresh display if "bump cards to top" is off (#6486) --- .../widgets/printing_selector/printing_selector.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp index becfa16a3..94deaf135 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp @@ -90,8 +90,10 @@ void PrintingSelector::retranslateUi() void PrintingSelector::printingsInDeckChanged() { - // Delay the update to avoid race conditions - QTimer::singleShot(100, this, &PrintingSelector::updateDisplay); + if (SettingsCache::instance().getBumpSetsWithCardsInDeckToTop()) { + // Delay the update to avoid race conditions + QTimer::singleShot(100, this, &PrintingSelector::updateDisplay); + } } /** From 746f2af044cd495380f34332112539ce0d45509d Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 3 Jan 2026 19:19:04 -0800 Subject: [PATCH 118/325] [DeckListModel] optimize by iterating over cardNodes instead of ExactCards (#6485) * [DeckListModel] optimize by iterating over cardNodes instead of ExactCards * fix build failure * another optimization * fix build failure --- .../cards/deck_card_zone_display_widget.cpp | 10 ++- .../draw_probability_widget.cpp | 2 +- .../deck_list_statistics_analyzer.cpp | 73 +++++++++++-------- .../visual_deck_editor_sample_hand_widget.cpp | 19 ++++- .../models/deck_list/deck_list_model.cpp | 29 -------- .../models/deck_list/deck_list_model.h | 10 --- 6 files changed, 67 insertions(+), 76 deletions(-) diff --git a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp index 4742467b1..7d45d9cc9 100644 --- a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp @@ -2,6 +2,7 @@ #include "card_group_display_widgets/flat_card_group_display_widget.h" #include "card_group_display_widgets/overlapped_card_group_display_widget.h" +#include "libcockatrice/card/database/card_database_manager.h" #include #include @@ -230,10 +231,13 @@ QList DeckCardZoneDisplayWidget::getGroupCriteriaValueList() { QList groupCriteriaValues; - QList cardsInZone = deckListModel->getCardsForZone(zoneName); + QList nodes = deckListModel->getCardNodesForZone(zoneName); - for (const ExactCard &cardInZone : cardsInZone) { - groupCriteriaValues.append(cardInZone.getInfo().getProperty(activeGroupCriteria)); + for (auto node : nodes) { + CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName()); + if (info) { + groupCriteriaValues.append(info->getProperty(activeGroupCriteria)); + } } groupCriteriaValues.removeDuplicates(); diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp index 92ebea630..146ab6f1c 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp @@ -170,7 +170,7 @@ void DrawProbabilityWidget::updateFilterOptions() const auto nodes = analyzer->getModel()->getCardNodes(); for (auto *node : nodes) { - CardInfoPtr info = CardDatabaseManager::query()->getCard({node->getName()}).getCardPtr(); + CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName()); if (!info) { continue; } diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp index 7d0259c72..a1dc1ab55 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp @@ -19,82 +19,91 @@ void DeckListStatisticsAnalyzer::analyze() { clearData(); - QList cards = model->getCards(); + QList nodes = model->getCardNodes(); - for (auto card : cards) { - auto info = card.getInfo(); - const int cmc = info.getCmc().toInt(); + for (auto node : nodes) { + CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName()); + if (!info) { + continue; + } + + const int amount = node->getNumber(); + QStringList copiesOfName; + for (int i = 0; i < amount; i++) { + copiesOfName.append(node->getName()); + } // Convert once - QStringList types = info.getMainCardType().split(' '); - QStringList subtypes = info.getCardType().split('-').last().split(" "); - QString colors = info.getColors(); - int power = info.getPowTough().split("/").first().toInt(); - int toughness = info.getPowTough().split("/").last().toInt(); + const int cmc = info->getCmc().toInt(); + QStringList types = info->getMainCardType().split(' '); + QStringList subtypes = info->getCardType().split('-').last().split(" "); + QString colors = info->getColors(); + int power = info->getPowTough().split("/").first().toInt(); + int toughness = info->getPowTough().split("/").last().toInt(); // For each copy of card // ---------------- Mana Curve ---------------- if (config.computeManaCurve) { - manaCurveMap[cmc]++; + manaCurveMap[cmc] += amount; } // per-type curve for (auto &t : types) { - manaCurveByType[t][cmc]++; - manaCurveCardsByType[t][cmc].append(info.getName()); + manaCurveByType[t][cmc] += amount; + manaCurveCardsByType[t][cmc] << copiesOfName; } // Per-subtype curve for (auto &st : subtypes) { - manaCurveBySubtype[st][cmc]++; - manaCurveCardsBySubtype[st][cmc].append(info.getName()); + manaCurveBySubtype[st][cmc] += amount; + manaCurveCardsBySubtype[st][cmc] << copiesOfName; } // per-color curve for (auto &c : colors) { - manaCurveByColor[c][cmc]++; - manaCurveCardsByColor[c][cmc].append(info.getName()); + manaCurveByColor[c][cmc] += amount; + manaCurveCardsByColor[c][cmc] << copiesOfName; } // Power/toughness - manaCurveByPower[QString::number(power)][cmc]++; - manaCurveCardsByPower[QString::number(power)][cmc].append(info.getName()); - manaCurveByToughness[QString::number(toughness)][cmc]++; - manaCurveCardsByToughness[QString::number(toughness)][cmc].append(info.getName()); + manaCurveByPower[QString::number(power)][cmc] += amount; + manaCurveCardsByPower[QString::number(power)][cmc] << copiesOfName; + manaCurveByToughness[QString::number(toughness)][cmc] += amount; + manaCurveCardsByToughness[QString::number(toughness)][cmc] << copiesOfName; // ========== Category Counts =========== for (auto &t : types) { - typeCount[t]++; + typeCount[t] += amount; } for (auto &st : subtypes) { - subtypeCount[st]++; + subtypeCount[st] += amount; } for (auto &c : colors) { - colorCount[c]++; + colorCount[c] += amount; } - manaValueCount[cmc]++; + manaValueCount[cmc] += amount; // ---------------- Mana Base ---------------- if (config.computeManaBase) { - auto prod = determineManaProduction(info.getText()); + auto prod = determineManaProduction(info->getText()); for (auto it = prod.begin(); it != prod.end(); ++it) { if (it.value() > 0) { - productionPipCount[it.key()] += it.value(); - productionCardCount[it.key()]++; + productionPipCount[it.key()] += it.value() * amount; + productionCardCount[it.key()] += amount; } - manaBaseMap[it.key()] += it.value(); + manaBaseMap[it.key()] += it.value() * amount; } } // ---------------- Devotion ---------------- if (config.computeDevotion) { - auto devo = countManaSymbols(info.getManaCost()); + auto devo = countManaSymbols(info->getManaCost()); for (auto &d : devo) { if (d.second > 0) { - devotionPipCount[QString(d.first)] += d.second; - devotionCardCount[QString(d.first)]++; + devotionPipCount[QString(d.first)] += d.second * amount; + devotionCardCount[QString(d.first)] += amount; } - manaDevotionMap[d.first] += d.second; + manaDevotionMap[d.first] += d.second * amount; } } } diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp index 24f521760..fb5ae780c 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp @@ -81,13 +81,30 @@ void VisualDeckEditorSampleHandWidget::updateDisplay() } } +static QList cardNodesToExactCards(QList nodes) +{ + QList cards; + for (auto node : nodes) { + ExactCard card = CardDatabaseManager::query()->getCard(node->toCardRef()); + if (card) { + for (int k = 0; k < node->getNumber(); ++k) { + cards.append(card); + } + } else { + qDebug() << "Card not found in database!"; + } + } + + return cards; +} + QList VisualDeckEditorSampleHandWidget::getRandomCards(int amountToGet) { QList randomCards; if (!deckListModel) return randomCards; - QList mainDeckCards = deckListModel->getCardsForZone(DECK_ZONE_MAIN); + QList mainDeckCards = cardNodesToExactCards(deckListModel->getCardNodesForZone(DECK_ZONE_MAIN)); if (mainDeckCards.isEmpty()) return randomCards; 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 23dbd7fec..499208a6d 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -608,35 +608,6 @@ void DeckListModel::forEachCard(const std::functionforEachCard(func); } -static QList cardNodesToExactCards(QList nodes) -{ - QList cards; - for (auto node : nodes) { - ExactCard card = CardDatabaseManager::query()->getCard(node->toCardRef()); - if (card) { - for (int k = 0; k < node->getNumber(); ++k) { - cards.append(card); - } - } else { - qDebug() << "Card not found in database!"; - } - } - - return cards; -} - -QList DeckListModel::getCards() const -{ - auto nodes = deckList->getCardNodes(); - return cardNodesToExactCards(nodes); -} - -QList DeckListModel::getCardsForZone(const QString &zoneName) const -{ - auto nodes = deckList->getCardNodes({zoneName}); - return cardNodesToExactCards(nodes); -} - QList DeckListModel::getCardNodes() const { return deckList->getCardNodes(); 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 e7a79d13e..133e7a947 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -329,16 +329,6 @@ public: */ void forEachCard(const std::function &func); - /** - * @brief Creates a list consisting of the entries of the model mapped into ExactCards (with each entry looked up - * in the card database). - * If a card node has number > 1, it will be added that many times to the list. - * If an entry's card is not found in the card database, that entry will be left out of the list. - * @return An ordered list of ExactCards - */ - [[nodiscard]] QList getCards() const; - [[nodiscard]] QList getCardsForZone(const QString &zoneName) const; - /** * @brief Gets a list of all card nodes in the deck. */ From 2d5e8deb75b3bd3b6a32b4c5aef2feeb615391ec Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 4 Jan 2026 21:34:32 -0800 Subject: [PATCH 119/325] [Server_AbstractParticipant] Rename bool getters (#6492) * [Server_AbstractParticipant] Rename bool getters * reformat --- .../remote/game/server_abstract_participant.h | 4 +-- .../remote/game/server_abstract_player.cpp | 2 +- .../server/remote/game/server_game.cpp | 27 +++++++++---------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.h index f60d56dcf..a24fa5799 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.h @@ -89,11 +89,11 @@ public: { return playerId; } - bool getSpectator() const + bool isSpectator() const { return spectator; } - bool getJudge() const + bool isJudge() const { return judge; } 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 aaf7f1332..a5bb16d13 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 @@ -1516,7 +1516,7 @@ Server_AbstractPlayer::cmdRevealCards(const Command_RevealCards &cmd, ResponseCo zone->addWritePermission(cmd.player_id()); } - if (getJudge()) { + if (isJudge()) { ges.setOverwriteOwnership(true); } 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 5cd6c8bf8..d8574db99 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -189,7 +189,7 @@ void Server_Game::pingClockTimeout() if (participant == nullptr) continue; - if (!participant->getSpectator()) { + if (!participant->isSpectator()) { ++playerCount; } @@ -200,7 +200,7 @@ void Server_Game::pingClockTimeout() } if ((participant->getPingTime() != -1) && - (!participant->getSpectator() || participant->getPlayerId() == hostId)) { + (!participant->isSpectator() || participant->getPlayerId() == hostId)) { allPlayersInactive = false; } } @@ -222,7 +222,7 @@ QMap Server_Game::getPlayers() const // copies poi QMutexLocker locker(&gameMutex); for (int id : participants.keys()) { auto *participant = participants[id]; - if (!participant->getSpectator()) { + if (!participant->isSpectator()) { players[id] = static_cast(participant); } } @@ -232,7 +232,7 @@ QMap Server_Game::getPlayers() const // copies poi Server_AbstractPlayer *Server_Game::getPlayer(int id) const { auto *participant = participants.value(id); - if (!participant->getSpectator()) { + if (!participant->isSpectator()) { return static_cast(participant); } else { return nullptr; @@ -250,7 +250,7 @@ int Server_Game::getSpectatorCount() const int result = 0; for (Server_AbstractParticipant *participant : participants.values()) { - if (participant->getSpectator()) + if (participant->isSpectator()) ++result; } return result; @@ -295,8 +295,8 @@ void Server_Game::sendGameStateToPlayers() // send game state info to clients according to their role in the game for (auto *participant : participants.values()) { GameEventContainer *gec; - if (participant->getSpectator()) { - if (spectatorsSeeEverything || participant->getJudge()) { + if (participant->isSpectator()) { + if (spectatorsSeeEverything || participant->isJudge()) { gec = prepareGameEvent(omniscientEvent, -1); } else { gec = prepareGameEvent(spectatorNormalEvent, -1); @@ -527,7 +527,7 @@ void Server_Game::removeParticipant(Server_AbstractParticipant *participant, Eve gameId, participant->getPlayerId()); participants.remove(participant->getPlayerId()); - bool spectator = participant->getSpectator(); + bool spectator = participant->isSpectator(); GameEventStorage ges; if (!spectator) { auto *player = static_cast(participant); @@ -728,8 +728,8 @@ void Server_Game::createGameJoinedEvent(Server_AbstractParticipant *joiningParti getInfo(*event1.mutable_game_info()); event1.set_host_id(hostId); event1.set_player_id(joiningParticipant->getPlayerId()); - event1.set_spectator(joiningParticipant->getSpectator()); - event1.set_judge(joiningParticipant->getJudge()); + event1.set_spectator(joiningParticipant->isSpectator()); + event1.set_judge(joiningParticipant->isJudge()); event1.set_resuming(resuming); if (resuming) { const QStringList &allGameTypes = room->getGameTypes(); @@ -747,7 +747,7 @@ void Server_Game::createGameJoinedEvent(Server_AbstractParticipant *joiningParti event2.set_active_player_id(activePlayer); event2.set_active_phase(activePhase); - bool omniscient = joiningParticipant->getSpectator() && (spectatorsSeeEverything || joiningParticipant->getJudge()); + bool omniscient = joiningParticipant->isSpectator() && (spectatorsSeeEverything || joiningParticipant->isJudge()); for (auto *participant : participants.values()) { participant->getInfo(event2.add_player_list(), joiningParticipant, omniscient, true); } @@ -763,9 +763,8 @@ void Server_Game::sendGameEventContainer(GameEventContainer *cont, cont->set_game_id(gameId); for (auto *participant : participants.values()) { - const bool playerPrivate = - (participant->getPlayerId() == privatePlayerId) || - (participant->getSpectator() && (spectatorsSeeEverything || participant->getJudge())); + const bool playerPrivate = (participant->getPlayerId() == privatePlayerId) || + (participant->isSpectator() && (spectatorsSeeEverything || participant->isJudge())); if ((recipients.testFlag(GameEventStorageItem::SendToPrivate) && playerPrivate) || (recipients.testFlag(GameEventStorageItem::SendToOthers) && !playerPrivate)) participant->sendGameEvent(*cont); From 731c487ccb92830b5431a8a8529b6be7f3fee224 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 4 Jan 2026 22:43:40 -0800 Subject: [PATCH 120/325] [ServerGame] null check participant in getPlayer (#6493) --- .../libcockatrice/network/server/remote/game/server_game.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d8574db99..c04876696 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -232,7 +232,7 @@ QMap Server_Game::getPlayers() const // copies poi Server_AbstractPlayer *Server_Game::getPlayer(int id) const { auto *participant = participants.value(id); - if (!participant->isSpectator()) { + if (participant && !participant->isSpectator()) { return static_cast(participant); } else { return nullptr; From 489ce416c33e800496eff29e2f33247dbcd8551a Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 4 Jan 2026 23:31:10 -0800 Subject: [PATCH 121/325] [VDS] Add search query option for comments (#6477) --- cockatrice/resources/help/deck_search.md | 5 +++++ cockatrice/src/filters/deck_filter_string.cpp | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/cockatrice/resources/help/deck_search.md b/cockatrice/resources/help/deck_search.md index 4e1c5c557..2ddad9e66 100644 --- a/cockatrice/resources/help/deck_search.md +++ b/cockatrice/resources/help/deck_search.md @@ -29,6 +29,11 @@ searches are case insensitive.
    Format:
    [f:standard](#f:standard) (Any deck with format set to standard)
    +
    Comments:
    +
    [c:good](#c:good) (Any deck with comments containing the word good)
    +
    [c:good c:deck](#c:good c:deck) (Any deck with comments containing the words good and deck)
    +
    [c:"good deck"](#c:%22good deck%22) (Any deck with comments containing the exact phrase "good deck")
    +
    Deck Contents (Uses [card search expressions](#cardSearchSyntaxHelp)):
    [[plains]] (Any deck that contains at least one card with "plains" in its name)
    [[t:legendary]] (Any deck that contains at least one legendary)
    diff --git a/cockatrice/src/filters/deck_filter_string.cpp b/cockatrice/src/filters/deck_filter_string.cpp index 5cce5d323..42a77fe5a 100644 --- a/cockatrice/src/filters/deck_filter_string.cpp +++ b/cockatrice/src/filters/deck_filter_string.cpp @@ -13,7 +13,7 @@ QueryPartList <- ComplexQueryPart ( ws ("AND" ws)? ComplexQueryPart)* ws* ComplexQueryPart <- SomewhatComplexQueryPart ws "OR" ws ComplexQueryPart / SomewhatComplexQueryPart SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart -QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / FormatQuery / GenericQuery +QueryPart <- NotQuery / DeckContentQuery / DeckNameQuery / FileNameQuery / PathQuery / FormatQuery / CommentQuery / GenericQuery NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart @@ -25,6 +25,7 @@ DeckNameQuery <- ([Dd] 'eck')? [Nn] 'ame'? [:] String FileNameQuery <- [Ff] ([Nn] / 'ile' ([Nn] 'ame')?) [:] String PathQuery <- [Pp] 'ath'? [:] String FormatQuery <- [Ff] 'ormat'? [:] String +CommentQuery <- [Cc] ('omment' 's'?)? [:] String GenericQuery <- String @@ -166,6 +167,14 @@ static void setupParserRules() }; }; + search["CommentQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { + auto value = std::any_cast(sv[0]); + return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) { + auto comments = deck->deckLoader->getDeck().deckList.getComments(); + return comments.contains(value, Qt::CaseInsensitive); + }; + }; + search["GenericQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter { auto name = std::any_cast(sv[0]); return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) { From d50297bbe65b65dca95348c8156f7c6b1d8cb030 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Mon, 5 Jan 2026 00:03:22 -0800 Subject: [PATCH 122/325] [AnalyticsPanel] Use cogwheel icon for configure button (#6494) --- .../widgets/deck_analytics/abstract_analytics_panel_widget.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 bad883d27..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 @@ -19,7 +19,8 @@ AbstractAnalyticsPanelWidget::AbstractAnalyticsPanelWidget(QWidget *parent, Deck bannerAndSettingsLayout->addWidget(bannerWidget, 1); // config button - configureButton = new QPushButton(tr("Configure"), this); + configureButton = new QPushButton(this); + configureButton->setIcon(QPixmap("theme:icons/cogwheel")); configureButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); connect(configureButton, &QPushButton::clicked, this, &AbstractAnalyticsPanelWidget::applyConfigFromDialog); bannerAndSettingsLayout->addWidget(configureButton, 0); From ee2699413ca4a48dcd7e72f9ded81b760b05d111 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Mon, 5 Jan 2026 00:06:22 -0800 Subject: [PATCH 123/325] [TabDeckEditor] Make card database a dock widget (#6472) * [TabDeckEditor] Make card database a dock widget * delete eventFilter implementation in abstract --- cockatrice/CMakeLists.txt | 1 + .../deck_editor_card_database_dock_widget.cpp | 65 +++++++++++++++++++ .../deck_editor_card_database_dock_widget.h | 32 +++++++++ .../deck_editor_database_display_widget.cpp | 6 +- .../deck_editor_database_display_widget.h | 2 +- .../widgets/tabs/abstract_tab_deck_editor.cpp | 63 ++---------------- .../widgets/tabs/abstract_tab_deck_editor.h | 18 ++--- .../widgets/tabs/tab_deck_editor.cpp | 36 +++++++++- .../tabs/tab_visual_database_display.cpp | 6 +- .../tab_deck_editor_visual.cpp | 8 +-- .../visual_database_display_widget.cpp | 20 +++--- 11 files changed, 171 insertions(+), 86 deletions(-) create mode 100644 cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp create mode 100644 cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 8ebd177db..85e1cd00f 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -170,6 +170,7 @@ set(cockatrice_SOURCES src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp + src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp new file mode 100644 index 000000000..5abc6e619 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp @@ -0,0 +1,65 @@ +#include "deck_editor_card_database_dock_widget.h" + +DeckEditorCardDatabaseDockWidget::DeckEditorCardDatabaseDockWidget(AbstractTabDeckEditor *parent) : QDockWidget(parent) +{ + setObjectName("databaseDisplayDock"); + setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable); + + createDatabaseDisplayDock(parent); + + retranslateUi(); +} + +void DeckEditorCardDatabaseDockWidget::createDatabaseDisplayDock(AbstractTabDeckEditor *deckEditor) +{ + databaseDisplayWidget = new DeckEditorDatabaseDisplayWidget(this, deckEditor); + + auto *frame = new QVBoxLayout; + frame->setObjectName("databaseDisplayFrame"); + frame->addWidget(databaseDisplayWidget); + + auto *dockContents = new QWidget(); + dockContents->setObjectName("databaseDisplayDockContents"); + dockContents->setLayout(frame); + setWidget(dockContents); + + installEventFilter(deckEditor); + connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged); + + // connect signals + connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::cardChanged, deckEditor, + &AbstractTabDeckEditor::updateCard); + connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::addCardToMainDeck, deckEditor, + &AbstractTabDeckEditor::actAddCard); + connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::addCardToSideboard, deckEditor, + &AbstractTabDeckEditor::actAddCardToSideboard); + connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromMainDeck, deckEditor, + &AbstractTabDeckEditor::actDecrementCard); + connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromSideboard, deckEditor, + &AbstractTabDeckEditor::actDecrementCardFromSideboard); +} + +CardDatabase *DeckEditorCardDatabaseDockWidget::getDatabase() const +{ + return databaseDisplayWidget->databaseModel->getDatabase(); +} + +void DeckEditorCardDatabaseDockWidget::retranslateUi() +{ + setWindowTitle(tr("Card Database")); +} + +void DeckEditorCardDatabaseDockWidget::setFilterTree(FilterTree *filterTree) +{ + databaseDisplayWidget->setFilterTree(filterTree); +} + +void DeckEditorCardDatabaseDockWidget::clearAllDatabaseFilters() +{ + databaseDisplayWidget->clearAllDatabaseFilters(); +} +void DeckEditorCardDatabaseDockWidget::highlightAllSearchEdit() +{ + databaseDisplayWidget->searchEdit->setSelection(0, databaseDisplayWidget->searchEdit->text().length()); +} diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.h new file mode 100644 index 000000000..6ad442075 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.h @@ -0,0 +1,32 @@ +#ifndef COCKATRICE_DECK_EDITOR_CARD_DATABASE_DOCK_WIDGET_H +#define COCKATRICE_DECK_EDITOR_CARD_DATABASE_DOCK_WIDGET_H + +#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h" + +#include + +class AbstractTabDeckEditor; +class CardDatabase; +class DeckEditorDatabaseDisplayWidget; +class FilterTree; + +class DeckEditorCardDatabaseDockWidget : public QDockWidget +{ +public: + explicit DeckEditorCardDatabaseDockWidget(AbstractTabDeckEditor *parent); + + DeckEditorDatabaseDisplayWidget *databaseDisplayWidget; + + CardDatabase *getDatabase() const; + void setFilterTree(FilterTree *filterTree); + +public slots: + void retranslateUi(); + void clearAllDatabaseFilters(); + void highlightAllSearchEdit(); + +private: + void createDatabaseDisplayDock(AbstractTabDeckEditor *deckEditor); +}; + +#endif // COCKATRICE_DECK_EDITOR_CARD_DATABASE_DOCK_WIDGET_H 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 f5549a98c..590fd1d1b 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 @@ -21,10 +21,10 @@ static bool canBeCommander(const CardInfo &cardInfo) cardInfo.getText().contains("can be your commander", Qt::CaseInsensitive); } -DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(AbstractTabDeckEditor *parent) - : QWidget(parent), deckEditor(parent) +DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor) + : QWidget(parent), deckEditor(deckEditor) { - setObjectName("centralWidget"); + setObjectName("databaseDisplayWidget"); centralFrame = new QVBoxLayout(this); centralFrame->setObjectName("centralFrame"); diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h index 593be7825..16ae6e255 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.h @@ -23,7 +23,7 @@ class DeckEditorDatabaseDisplayWidget : public QWidget Q_OBJECT public: - explicit DeckEditorDatabaseDisplayWidget(AbstractTabDeckEditor *parent); + explicit DeckEditorDatabaseDisplayWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor); AbstractTabDeckEditor *deckEditor; SearchLineEdit *searchEdit; CardDatabaseModel *databaseModel; 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 29bcc1ab1..53540b6f9 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -55,7 +55,7 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta deckStateManager = new DeckStateManager(this); - databaseDisplayDockWidget = new DeckEditorDatabaseDisplayWidget(this); + cardDatabaseDockWidget = new DeckEditorCardDatabaseDockWidget(this); deckDockWidget = new DeckEditorDeckDockWidget(this); cardInfoDockWidget = new DeckEditorCardInfoDockWidget(this); filterDockWidget = new DeckEditorFilterDockWidget(this); @@ -68,21 +68,9 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta connect(deckStateManager, &DeckStateManager::isModifiedChanged, this, &AbstractTabDeckEditor::onDeckModified); connect(deckDockWidget, &DeckEditorDeckDockWidget::selectedCardChanged, this, &AbstractTabDeckEditor::updateCard); - // Connect database display signals to this tab - connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::cardChanged, this, - &AbstractTabDeckEditor::updateCard); - connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::addCardToMainDeck, this, - &AbstractTabDeckEditor::actAddCard); - connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::addCardToSideboard, this, - &AbstractTabDeckEditor::actAddCardToSideboard); - connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromMainDeck, this, - &AbstractTabDeckEditor::actDecrementCard); - connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromSideboard, this, - &AbstractTabDeckEditor::actDecrementCardFromSideboard); - // Connect filter signals - connect(filterDockWidget, &DeckEditorFilterDockWidget::clearAllDatabaseFilters, databaseDisplayDockWidget, - &DeckEditorDatabaseDisplayWidget::clearAllDatabaseFilters); + connect(filterDockWidget, &DeckEditorFilterDockWidget::clearAllDatabaseFilters, cardDatabaseDockWidget, + &DeckEditorCardDatabaseDockWidget::clearAllDatabaseFilters); // Connect shortcut changes connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, @@ -122,7 +110,7 @@ void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, const QString & { deckStateManager->addCard(card, zoneName); - databaseDisplayDockWidget->searchEdit->setSelection(0, databaseDisplayDockWidget->searchEdit->text().length()); + cardDatabaseDockWidget->highlightAllSearchEdit(); } /** @@ -544,21 +532,21 @@ void AbstractTabDeckEditor::actExportDeckDecklistXyz() /** @brief Analyzes the deck using DeckStats. */ void AbstractTabDeckEditor::actAnalyzeDeckDeckstats() { - auto *interface = new DeckStatsInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(), this); + auto *interface = new DeckStatsInterface(*cardDatabaseDockWidget->getDatabase(), this); interface->analyzeDeck(deckStateManager->getDeckList()); } /** @brief Analyzes the deck using TappedOut. */ void AbstractTabDeckEditor::actAnalyzeDeckTappedout() { - auto *interface = new TappedOutInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(), this); + auto *interface = new TappedOutInterface(*cardDatabaseDockWidget->getDatabase(), this); interface->analyzeDeck(deckStateManager->getDeckList()); } /** @brief Applies a new filter tree to the database display. */ void AbstractTabDeckEditor::filterTreeChanged(FilterTree *filterTree) { - databaseDisplayDockWidget->setFilterTree(filterTree); + cardDatabaseDockWidget->setFilterTree(filterTree); } /** @@ -571,43 +559,6 @@ void AbstractTabDeckEditor::closeEvent(QCloseEvent *event) event->accept(); } -/** - * @brief Event filter for dock visibility and geometry changes. - * @param o Object sending the event. - * @param e Event. - * @return False always. - */ -bool AbstractTabDeckEditor::eventFilter(QObject *o, QEvent *e) -{ - if (e->type() == QEvent::Close) { - if (o == cardInfoDockWidget) { - aCardInfoDockVisible->setChecked(false); - aCardInfoDockFloating->setEnabled(false); - } else if (o == deckDockWidget) { - aDeckDockVisible->setChecked(false); - aDeckDockFloating->setEnabled(false); - } else if (o == filterDockWidget) { - aFilterDockVisible->setChecked(false); - aFilterDockFloating->setEnabled(false); - } else if (o == printingSelectorDockWidget) { - aPrintingSelectorDockVisible->setChecked(false); - aPrintingSelectorDockFloating->setEnabled(false); - } - } - - if (o == this && e->type() == QEvent::Hide) { - LayoutsSettings &layouts = SettingsCache::instance().layouts(); - layouts.setDeckEditorLayoutState(saveState()); - layouts.setDeckEditorGeometry(saveGeometry()); - layouts.setDeckEditorCardSize(cardInfoDockWidget->size()); - layouts.setDeckEditorFilterSize(filterDockWidget->size()); - layouts.setDeckEditorDeckSize(deckDockWidget->size()); - layouts.setDeckEditorPrintingSelectorSize(printingSelectorDockWidget->size()); - } - - return false; -} - /** @brief Shows a confirmation dialog before closing. */ bool AbstractTabDeckEditor::confirmClose() { diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h index 69b2b4a1c..025a65cad 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h @@ -8,6 +8,7 @@ #ifndef TAB_GENERIC_DECK_EDITOR_H #define TAB_GENERIC_DECK_EDITOR_H +#include "../interface/widgets/deck_editor/deck_editor_card_database_dock_widget.h" #include "../interface/widgets/deck_editor/deck_editor_card_info_dock_widget.h" #include "../interface/widgets/deck_editor/deck_editor_database_display_widget.h" #include "../interface/widgets/deck_editor/deck_editor_deck_dock_widget.h" @@ -27,7 +28,7 @@ class CardInfoFrameWidget; class DeckLoader; class DeckEditorMenu; class DeckEditorCardInfoDockWidget; -class DeckEditorDatabaseDisplayWidget; +class DeckEditorCardDatabaseDockWidget; class DeckEditorDeckDockWidget; class DeckEditorFilterDockWidget; class DeckEditorPrintingSelectorDockWidget; @@ -126,7 +127,7 @@ public: // UI Elements DeckStateManager *deckStateManager; DeckEditorMenu *deckMenu; ///< Menu for deck operations - DeckEditorDatabaseDisplayWidget *databaseDisplayDockWidget; ///< Database dock + DeckEditorCardDatabaseDockWidget *cardDatabaseDockWidget; ///< Database dock DeckEditorCardInfoDockWidget *cardInfoDockWidget; ///< Card info dock DeckEditorDeckDockWidget *deckDockWidget; ///< Deck dock DeckEditorFilterDockWidget *filterDockWidget; ///< Filter dock @@ -245,9 +246,6 @@ protected slots: /** @brief Handles dock close events. */ void closeEvent(QCloseEvent *event) override; - /** @brief Event filter for dock state changes. */ - bool eventFilter(QObject *o, QEvent *e) override; - /** @brief Slot triggered when a dock visibility changes. Pure virtual. */ virtual void dockVisibleTriggered() = 0; @@ -295,11 +293,15 @@ protected: virtual void openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation); // UI Menu Elements - QMenu *viewMenu, *cardInfoDockMenu, *deckDockMenu, *filterDockMenu, *printingSelectorDockMenu; + QMenu *viewMenu, *cardInfoDockMenu, *cardDatabaseDockMenu, *deckDockMenu, *filterDockMenu, + *printingSelectorDockMenu; QAction *aResetLayout; - QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aDeckDockVisible, *aDeckDockFloating; - QAction *aFilterDockVisible, *aFilterDockFloating, *aPrintingSelectorDockVisible, *aPrintingSelectorDockFloating; + QAction *aCardInfoDockVisible, *aCardInfoDockFloating; + QAction *aCardDatabaseDockVisible, *aCardDatabaseDockFloating; + QAction *aDeckDockVisible, *aDeckDockFloating; + QAction *aFilterDockVisible, *aFilterDockFloating; + QAction *aPrintingSelectorDockVisible, *aPrintingSelectorDockFloating; }; #endif // TAB_GENERIC_DECK_EDITOR_H diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index 286252e13..defd7827a 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -55,6 +55,7 @@ void TabDeckEditor::createMenus() viewMenu = new QMenu(this); cardInfoDockMenu = viewMenu->addMenu(QString()); + cardDatabaseDockMenu = viewMenu->addMenu(QString()); deckDockMenu = viewMenu->addMenu(QString()); filterDockMenu = viewMenu->addMenu(QString()); printingSelectorDockMenu = viewMenu->addMenu(QString()); @@ -67,6 +68,14 @@ void TabDeckEditor::createMenus() aCardInfoDockFloating->setCheckable(true); connect(aCardInfoDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered); + // Card Database dock + aCardDatabaseDockVisible = cardDatabaseDockMenu->addAction(QString()); + aCardDatabaseDockVisible->setCheckable(true); + connect(aCardDatabaseDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered); + aCardDatabaseDockFloating = cardDatabaseDockMenu->addAction(QString()); + aCardDatabaseDockFloating->setCheckable(true); + connect(aCardDatabaseDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered); + // Deck dock aDeckDockVisible = deckDockMenu->addAction(QString()); aDeckDockVisible->setCheckable(true); @@ -126,18 +135,22 @@ void TabDeckEditor::retranslateUi() { deckMenu->retranslateUi(); cardInfoDockWidget->retranslateUi(); + cardDatabaseDockWidget->retranslateUi(); deckDockWidget->retranslateUi(); filterDockWidget->retranslateUi(); printingSelectorDockWidget->retranslateUi(); viewMenu->setTitle(tr("&View")); cardInfoDockMenu->setTitle(tr("Card Info")); + cardDatabaseDockMenu->setTitle(tr("Card Database")); deckDockMenu->setTitle(tr("Deck")); filterDockMenu->setTitle(tr("Filters")); printingSelectorDockMenu->setTitle(tr("Printing")); aCardInfoDockVisible->setText(tr("Visible")); aCardInfoDockFloating->setText(tr("Floating")); + aCardDatabaseDockVisible->setText(tr("Visible")); + aCardDatabaseDockFloating->setText(tr("Floating")); aDeckDockVisible->setText(tr("Visible")); aDeckDockFloating->setText(tr("Floating")); aFilterDockVisible->setText(tr("Visible")); @@ -171,7 +184,6 @@ void TabDeckEditor::showPrintingSelector() void TabDeckEditor::loadLayout() { LayoutsSettings &layouts = SettingsCache::instance().layouts(); - setCentralWidget(databaseDisplayDockWidget); auto &layoutState = layouts.getDeckEditorLayoutState(); if (layoutState.isNull()) @@ -189,16 +201,19 @@ void TabDeckEditor::loadLayout() } aCardInfoDockVisible->setChecked(!cardInfoDockWidget->isHidden()); + aCardDatabaseDockVisible->setChecked(!cardDatabaseDockWidget->isHidden()); aFilterDockVisible->setChecked(!filterDockWidget->isHidden()); aDeckDockVisible->setChecked(!deckDockWidget->isHidden()); aPrintingSelectorDockVisible->setChecked(!printingSelectorDockWidget->isHidden()); aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked()); + aCardDatabaseDockFloating->setChecked(aCardDatabaseDockVisible->isChecked()); aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked()); aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked()); aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked()); aCardInfoDockFloating->setChecked(cardInfoDockWidget->isFloating()); + aCardDatabaseDockFloating->setChecked(cardDatabaseDockWidget->isFloating()); aFilterDockFloating->setChecked(filterDockWidget->isFloating()); aDeckDockFloating->setChecked(deckDockWidget->isFloating()); aPrintingSelectorDockFloating->setChecked(printingSelectorDockWidget->isFloating()); @@ -226,27 +241,31 @@ void TabDeckEditor::restartLayout() // Update menu checkboxes aCardInfoDockVisible->setChecked(true); + aCardDatabaseDockVisible->setChecked(true); aDeckDockVisible->setChecked(true); aFilterDockVisible->setChecked(true); aPrintingSelectorDockVisible->setChecked(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); aCardInfoDockFloating->setChecked(false); + aCardDatabaseDockFloating->setChecked(false); aDeckDockFloating->setChecked(false); aFilterDockFloating->setChecked(false); aPrintingSelectorDockFloating->setChecked(false); - setCentralWidget(databaseDisplayDockWidget); + addDockWidget(Qt::LeftDockWidgetArea, cardDatabaseDockWidget); addDockWidget(Qt::RightDockWidgetArea, deckDockWidget); addDockWidget(Qt::RightDockWidgetArea, cardInfoDockWidget); addDockWidget(Qt::RightDockWidgetArea, filterDockWidget); addDockWidget(Qt::RightDockWidgetArea, printingSelectorDockWidget); // Show/hide and reset floating + cardDatabaseDockWidget->setFloating(false); deckDockWidget->setFloating(false); cardInfoDockWidget->setFloating(false); filterDockWidget->setFloating(false); printingSelectorDockWidget->setFloating(false); + cardDatabaseDockWidget->setVisible(true); deckDockWidget->setVisible(true); cardInfoDockWidget->setVisible(true); filterDockWidget->setVisible(true); @@ -269,6 +288,9 @@ void TabDeckEditor::freeDocksSize() deckDockWidget->setMinimumSize(minSize); deckDockWidget->setMaximumSize(maxSize); + cardDatabaseDockWidget->setMinimumSize(minSize); + cardDatabaseDockWidget->setMaximumSize(maxSize); + cardInfoDockWidget->setMinimumSize(minSize); cardInfoDockWidget->setMaximumSize(maxSize); @@ -286,6 +308,9 @@ void TabDeckEditor::dockVisibleTriggered() if (o == aCardInfoDockVisible) { cardInfoDockWidget->setHidden(!aCardInfoDockVisible->isChecked()); aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked()); + } else if (o == aCardDatabaseDockVisible) { + cardDatabaseDockWidget->setHidden(!aCardDatabaseDockVisible->isChecked()); + aCardDatabaseDockFloating->setEnabled(aCardDatabaseDockVisible->isChecked()); } else if (o == aDeckDockVisible) { deckDockWidget->setHidden(!aDeckDockVisible->isChecked()); aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked()); @@ -304,6 +329,8 @@ void TabDeckEditor::dockFloatingTriggered() QObject *o = sender(); if (o == aCardInfoDockFloating) cardInfoDockWidget->setFloating(aCardInfoDockFloating->isChecked()); + else if (o == aCardDatabaseDockFloating) + cardDatabaseDockWidget->setFloating(aCardDatabaseDockFloating->isChecked()); else if (o == aDeckDockFloating) deckDockWidget->setFloating(aDeckDockFloating->isChecked()); else if (o == aFilterDockFloating) @@ -318,6 +345,8 @@ void TabDeckEditor::dockTopLevelChanged(bool topLevel) QObject *o = sender(); if (o == cardInfoDockWidget) aCardInfoDockFloating->setChecked(topLevel); + else if (o == aCardDatabaseDockFloating) + aCardDatabaseDockFloating->setChecked(topLevel); else if (o == deckDockWidget) aDeckDockFloating->setChecked(topLevel); else if (o == filterDockWidget) @@ -338,6 +367,9 @@ bool TabDeckEditor::eventFilter(QObject *o, QEvent *e) if (o == cardInfoDockWidget) { aCardInfoDockVisible->setChecked(false); aCardInfoDockFloating->setEnabled(false); + } else if (o == cardDatabaseDockWidget) { + aCardDatabaseDockVisible->setChecked(false); + aCardDatabaseDockFloating->setEnabled(false); } else if (o == deckDockWidget) { aDeckDockVisible->setChecked(false); aDeckDockFloating->setEnabled(false); diff --git a/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.cpp b/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.cpp index d45cdded0..5e8fb8670 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.cpp @@ -6,9 +6,9 @@ TabVisualDatabaseDisplay::TabVisualDatabaseDisplay(TabSupervisor *_tabSupervisor { deckEditor = new TabDeckEditor(_tabSupervisor); deckEditor->setHidden(true); - visualDatabaseDisplayWidget = - new VisualDatabaseDisplayWidget(this, deckEditor, deckEditor->databaseDisplayDockWidget->databaseModel, - deckEditor->databaseDisplayDockWidget->databaseDisplayModel); + visualDatabaseDisplayWidget = new VisualDatabaseDisplayWidget( + this, deckEditor, deckEditor->cardDatabaseDockWidget->databaseDisplayWidget->databaseModel, + deckEditor->cardDatabaseDockWidget->databaseDisplayWidget->databaseDisplayModel); setCentralWidget(visualDatabaseDisplayWidget); diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 82f96860d..b24013319 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -50,7 +50,7 @@ TabDeckEditorVisual::TabDeckEditorVisual(TabSupervisor *_tabSupervisor) : Abstra refreshShortcuts(); loadLayout(); - databaseDisplayDockWidget->setHidden(true); + cardDatabaseDockWidget->setHidden(true); } /** @brief Creates the central frame containing the tab container. */ @@ -62,9 +62,9 @@ void TabDeckEditorVisual::createCentralFrame() centralFrame = new QVBoxLayout; centralWidget->setLayout(centralFrame); - tabContainer = new TabDeckEditorVisualTabWidget(centralWidget, this, deckStateManager->getModel(), - databaseDisplayDockWidget->databaseModel, - databaseDisplayDockWidget->databaseDisplayModel); + tabContainer = new TabDeckEditorVisualTabWidget( + centralWidget, this, deckStateManager->getModel(), cardDatabaseDockWidget->databaseDisplayWidget->databaseModel, + cardDatabaseDockWidget->databaseDisplayWidget->databaseDisplayModel); connect(tabContainer, &TabDeckEditorVisualTabWidget::cardChanged, this, &TabDeckEditorVisual::changeModelIndexAndCardInfo); 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 c5eaa171a..6112faedf 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 @@ -74,25 +74,27 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, searchKeySignals.setObjectName("searchKeySignals"); connect(searchEdit, &SearchLineEdit::textChanged, this, &VisualDatabaseDisplayWidget::updateSearch); - connect(&searchKeySignals, &KeySignals::onEnter, deckEditor->databaseDisplayDockWidget, + + DeckEditorDatabaseDisplayWidget *databaseDisplayWidget = deckEditor->cardDatabaseDockWidget->databaseDisplayWidget; + connect(&searchKeySignals, &KeySignals::onEnter, databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck); - connect(&searchKeySignals, &KeySignals::onCtrlAltEqual, deckEditor->databaseDisplayDockWidget, + connect(&searchKeySignals, &KeySignals::onCtrlAltEqual, databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck); - connect(&searchKeySignals, &KeySignals::onCtrlAltRBracket, deckEditor->databaseDisplayDockWidget, + connect(&searchKeySignals, &KeySignals::onCtrlAltRBracket, databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard); - connect(&searchKeySignals, &KeySignals::onCtrlAltMinus, deckEditor->databaseDisplayDockWidget, + connect(&searchKeySignals, &KeySignals::onCtrlAltMinus, databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::actDecrementCardFromMainDeck); - connect(&searchKeySignals, &KeySignals::onCtrlAltLBracket, deckEditor->databaseDisplayDockWidget, + connect(&searchKeySignals, &KeySignals::onCtrlAltLBracket, databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::actDecrementCardFromSideboard); - connect(&searchKeySignals, &KeySignals::onCtrlAltEnter, deckEditor->databaseDisplayDockWidget, + connect(&searchKeySignals, &KeySignals::onCtrlAltEnter, databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard); - connect(&searchKeySignals, &KeySignals::onCtrlEnter, deckEditor->databaseDisplayDockWidget, + connect(&searchKeySignals, &KeySignals::onCtrlEnter, databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard); - connect(&searchKeySignals, &KeySignals::onCtrlC, deckEditor->databaseDisplayDockWidget, + connect(&searchKeySignals, &KeySignals::onCtrlC, databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::copyDatabaseCellContents); connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(searchEdit); }); - databaseView = deckEditor->databaseDisplayDockWidget->getDatabaseView(); + databaseView = databaseDisplayWidget->getDatabaseView(); databaseView->setFocusProxy(searchEdit); databaseView->setItemDelegate(nullptr); databaseView->setVisible(false); From 85c9d8a9ff5dd6db13a9c45d3bfe74c406c3b01d Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Mon, 5 Jan 2026 01:18:38 -0800 Subject: [PATCH 124/325] [DeckEditor] Fix tokens being added to maindeck (#6495) --- .../widgets/deck_editor/deck_editor_deck_dock_widget.cpp | 3 +-- .../interface/widgets/deck_editor/deck_state_manager.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) 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 014f121e4..afc1f41b2 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 @@ -597,13 +597,12 @@ QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodeSourceIndices() con return selectedRows; } -void DeckEditorDeckDockWidget::actAddCard(const ExactCard &card, const QString &_zoneName) +void DeckEditorDeckDockWidget::actAddCard(const ExactCard &card, const QString &zoneName) { if (!card) { return; } - QString zoneName = card.getInfo().getIsToken() ? DECK_ZONE_TOKENS : _zoneName; deckStateManager->addCard(card, zoneName); } 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 628d33d51..7b99e8c6a 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp @@ -174,11 +174,13 @@ QModelIndex DeckStateManager::addCard(const ExactCard &card, const QString &zone return {}; } + QString zone = card.getInfo().getIsToken() ? DECK_ZONE_TOKENS : zoneName; + QString reason = tr("Added (%1): %2 (%3) %4") - .arg(zoneName, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(), + .arg(zone, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(), card.getPrinting().getProperty("num")); - QModelIndex idx = modifyDeck(reason, [&card, &zoneName](auto model) { return model->addCard(card, zoneName); }); + QModelIndex idx = modifyDeck(reason, [&card, &zone](auto model) { return model->addCard(card, zone); }); if (idx.isValid()) { emit focusIndexChanged(idx); From 192dac0396b4851b6ba74ab7c9f7a148d7b3a977 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Mon, 5 Jan 2026 09:28:59 -0800 Subject: [PATCH 125/325] [DeckListModel] Consolidate methods and signals for card change (#6466) --- .../deck_list_statistics_analyzer.cpp | 2 +- .../deck_editor_deck_dock_widget.cpp | 2 +- .../deck_editor/deck_state_manager.cpp | 3 +- .../dialogs/dlg_select_set_for_cards.cpp | 2 +- .../printing_selector/card_amount_widget.cpp | 2 +- .../models/deck_list/deck_list_model.cpp | 41 +++++++++++++++++++ .../models/deck_list/deck_list_model.h | 40 ++++++++++++++++++ 7 files changed, 86 insertions(+), 6 deletions(-) diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp index a1dc1ab55..ad8afb766 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp @@ -12,7 +12,7 @@ DeckListStatisticsAnalyzer::DeckListStatisticsAnalyzer(QObject *parent, DeckListStatisticsAnalyzerConfig _config) : QObject(parent), model(_model), config(_config) { - connect(model, &DeckListModel::dataChanged, this, &DeckListStatisticsAnalyzer::analyze); + connect(model, &DeckListModel::cardsChanged, this, &DeckListStatisticsAnalyzer::analyze); } void DeckListStatisticsAnalyzer::analyze() 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 afc1f41b2..bf7688bb0 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 @@ -154,7 +154,7 @@ void DeckEditorDeckDockWidget::createDeckDock() bannerCardLabel->setText(tr("Banner Card")); bannerCardLabel->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible()); bannerCardComboBox = new QComboBox(this); - connect(getModel(), &DeckListModel::dataChanged, this, [this]() { + connect(getModel(), &DeckListModel::cardNodesChanged, this, [this]() { // Delay the update to avoid race conditions QTimer::singleShot(100, this, &DeckEditorDeckDockWidget::updateBannerCardComboBox); }); 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 7b99e8c6a..d45416b17 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp @@ -11,8 +11,7 @@ DeckStateManager::DeckStateManager(QObject *parent) setModified(true); emit historyChanged(); }); - connect(deckListModel, &DeckListModel::rowsInserted, this, &DeckStateManager::uniqueCardsChanged); - connect(deckListModel, &DeckListModel::rowsRemoved, this, &DeckStateManager::uniqueCardsChanged); + connect(deckListModel, &DeckListModel::cardNodesChanged, this, &DeckStateManager::uniqueCardsChanged); } const DeckList &DeckStateManager::getDeckList() const 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 6ed6b67a4..5d9e2679c 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 @@ -152,7 +152,7 @@ static bool swapPrinting(DeckListModel *model, const QString &modifiedSet, const return false; } int amount = model->data(idx.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT), Qt::DisplayRole).toInt(); - model->removeRow(idx.row(), idx.parent()); + model->removeCardAtIndex(idx); CardInfoPtr cardInfo = CardDatabaseManager::query()->getCardInfo(cardName); PrintingInfo printing = CardDatabaseManager::query()->getSpecificPrinting(cardName, modifiedSet, ""); for (int i = 0; i < amount; i++) { diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index f8002eb89..1c09a42c1 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -152,7 +152,7 @@ static QModelIndex addAndReplacePrintings(DeckListModel *model, // Check if a card without a providerId already exists in the deckModel and replace it, if so. if (existing.isValid() && existing != newCardIndex && replaceProviderless) { model->offsetCountAtIndex(newCardIndex, extraCopies); - model->removeRow(existing.row(), existing.parent()); + model->removeCardAtIndex(existing); } // Set Index and Focus as if the user had just clicked the new card and modify the deckEditor saveState 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 499208a6d..447d868a8 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -12,6 +12,15 @@ DeckListModel::DeckListModel(QObject *parent) DeckListModel::DeckListModel(QObject *parent, const QSharedPointer &deckList) : DeckListModel(parent) { setDeckList(deckList); + + // forward change signals + connect(this, &DeckListModel::cardAddedAt, this, &DeckListModel::cardsChanged); + connect(this, &DeckListModel::cardRemoved, this, &DeckListModel::cardsChanged); + connect(this, &DeckListModel::deckReplaced, this, &DeckListModel::cardsChanged); + + connect(this, &DeckListModel::cardNodeAddedAt, this, &DeckListModel::cardNodesChanged); + connect(this, &DeckListModel::cardNodeRemoved, this, &DeckListModel::cardNodesChanged); + connect(this, &DeckListModel::deckReplaced, this, &DeckListModel::cardNodesChanged); } DeckListModel::~DeckListModel() @@ -421,6 +430,7 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam card.getName(), printingInfo.getUuid(), printingInfo.getProperty("num"))); const auto cardSetName = printingInfo.getSet().isNull() ? "" : printingInfo.getSet()->getCorrectedShortName(); + bool cardNodeAdded = false; if (!cardNode) { // Determine the correct index int insertRow = findSortedInsertRow(groupNode, cardInfo); @@ -432,6 +442,8 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam beginInsertRows(parentIndex, insertRow, insertRow); cardNode = new DecklistModelCardNode(decklistCard, groupNode, insertRow); endInsertRows(); + + cardNodeAdded = true; } else { cardNode->setNumber(cardNode->getNumber() + 1); cardNode->setCardSetShortName(cardSetName); @@ -444,6 +456,10 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam emitRecursiveUpdates(parentIndex); auto index = nodeToIndex(cardNode); + if (cardNodeAdded) { + emit cardNodeAddedAt(index); + } + emit cardAddedAt(index); return index; @@ -468,17 +484,42 @@ bool DeckListModel::offsetCountAtIndex(const QModelIndex &idx, int offset) if (newCount <= 0) { removeRow(idx.row(), idx.parent()); + emit cardNodeRemoved(); } else { setData(numberIndex, newCount, Qt::EditRole); } if (offset > 0) { emit cardAddedAt(idx); + } else if (offset < 0) { + emit cardRemoved(); } return true; } +bool DeckListModel::removeCardAtIndex(const QModelIndex &idx) +{ + if (!idx.isValid()) { + return false; + } + + auto *node = static_cast(idx.internalPointer()); + auto *card = dynamic_cast(node); + + if (!card) { + return false; + } + + bool success = removeRow(idx.row(), idx.parent()); + + if (success) { + emit cardRemoved(); + } + + return success; +} + int DeckListModel::findSortedInsertRow(const InnerDecklistNode *parent, const CardInfoPtr &cardInfo) const { if (!cardInfo) { 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 133e7a947..86d36b7f9 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.h @@ -197,6 +197,12 @@ public: * InnerDecklistNode containers and supports grouping, sorting, adding/removing * cards, and printing decklists. * + * Outside code should refrain from modifying the model with methods inherited from QAbstractItemModel, such as with + * `setData` or `removeRow`. + * Instead, use the custom methods on this class to modify the model, such as `addCard`, `offsetCountAtIndex`, or + * `removeCardAtIndex`. + * This ensures the custom signals for this class are correctly emitted. + * * Signals: * - deckHashChanged(): emitted when the deck contents change in a way that * affects its hash. @@ -231,6 +237,11 @@ signals: */ void deckHashChanged(); + /** + * @brief Emitted whenever the cards in the deck changes. This includes when the deck is replaced. + */ + void cardsChanged(); + /** * @brief Emitted whenever a card is added to the deck, regardless of whether it's an entirely new card or an * existing card that got incremented. @@ -238,6 +249,28 @@ signals: */ void cardAddedAt(const QModelIndex &index); + /** + * @brief Emitted whenever a card is removed from the deck, regardless of whether a card node was removed or an + * existing card got decremented. + */ + void cardRemoved(); + + /** + * @brief Emitted whenever a card node is added or removed. This includes when the deck is replaced. + */ + void cardNodesChanged(); + + /** + * @brief Emitted whenever a new card node is added. + * @param index The index of the card node that got added. + */ + void cardNodeAddedAt(const QModelIndex &index); + + /** + * @brief Emitted whenever a card node is removed. + */ + void cardNodeRemoved(); + /** * @brief Emitted whenever the deck in the model has been replaced with a new one */ @@ -311,6 +344,13 @@ public: */ bool offsetCountAtIndex(const QModelIndex &idx, int offset); + /** + * @brief Removes the card node at the index + * @param idx The index of a card node. No-ops if the index is invalid or not a card node. + * @return Whether the node was removed. + */ + bool removeCardAtIndex(const QModelIndex &idx); + /** * @brief Removes all cards and resets the model. */ From b86853b65ccdd40ded9997b5ec47793bfb922043 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 21:18:21 +0100 Subject: [PATCH 126/325] Bump actions/cache from 4 to 5 (#6496) --- .github/workflows/desktop-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 5b954d021..684b375cc 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -384,7 +384,7 @@ jobs: - name: Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries (${{ matrix.soc }} macOS) if: matrix.os == 'macOS' id: restore_qt - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: path: ${{ github.workspace }}/Qt key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }} @@ -407,7 +407,7 @@ jobs: - name: Cache thin Qt libraries (${{ matrix.soc }} macOS) if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 with: path: ${{ github.workspace }}/Qt key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }} From 0a2fdb05ad010e42efbf45fe90002b61cc476085 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 6 Jan 2026 02:42:35 -0800 Subject: [PATCH 127/325] [VDS] Try to fix memory leak by properly parenting widgets (#6498) * [VDS] Try to fix memory leak by properly parenting widgets * format --- .../src/interface/widgets/cards/card_info_picture_widget.cpp | 2 +- .../visual_deck_storage/deck_preview/deck_preview_widget.cpp | 2 +- .../visual_deck_storage/deck_preview/deck_preview_widget.h | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) 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 85d210e1a..7206c05c9 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp @@ -47,7 +47,7 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool _hoverT originalPos = this->pos(); // Create the animation - animation = new QPropertyAnimation(this, "pos"); + animation = new QPropertyAnimation(this, "pos", this); animation->setDuration(200); // 200ms animation duration animation->setEasingCurve(QEasingCurve::OutQuad); 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 4c782c989..f98e37e16 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 @@ -92,7 +92,7 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess) bannerCardComboBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); bannerCardComboBox->setObjectName("bannerCardComboBox"); bannerCardComboBox->setCurrentText(deckLoader->getDeck().deckList.getBannerCard().name); - bannerCardComboBox->installEventFilter(new NoScrollFilter()); + bannerCardComboBox->installEventFilter(new NoScrollFilter(bannerCardComboBox)); connect(bannerCardComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &DeckPreviewWidget::setBannerCard); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h index e5975db71..85f0ce76e 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h @@ -82,6 +82,11 @@ private slots: class NoScrollFilter : public QObject { Q_OBJECT +public: + explicit NoScrollFilter(QObject *parent = nullptr) : QObject(parent) + { + } + protected: bool eventFilter(QObject *obj, QEvent *event) override { From 6340c4a6b712fb409c06e1e915a0cc2048b3b319 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:35:53 +0100 Subject: [PATCH 128/325] Update translation source strings (#6465) --- cockatrice/cockatrice_en@source.ts | 4692 ++++++++++++++++------------ oracle/oracle_en@source.ts | 214 +- 2 files changed, 2861 insertions(+), 2045 deletions(-) diff --git a/cockatrice/cockatrice_en@source.ts b/cockatrice/cockatrice_en@source.ts index 53e6a7a96..9a2f56f07 100644 --- a/cockatrice/cockatrice_en@source.ts +++ b/cockatrice/cockatrice_en@source.ts @@ -4,7 +4,7 @@ AbstractCounter - + &Set counter... @@ -12,12 +12,12 @@ AbstractCounterDialog - + Set counter - + New value for counter '%1': @@ -25,12 +25,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -38,79 +38,81 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. + + Add Analytics Panel AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -118,12 +120,12 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard - + Sideboard @@ -131,22 +133,22 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error - + Could not create themes directory at '%1'. - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -157,7 +159,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -168,152 +170,165 @@ Are you sure you would like to disable this feature? - + Confirm Change - + Theme settings - + Current theme: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + + Show game filter toolbar above list in room tab + + + + Card rendering - + Display card names on cards having a picture - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout - + Display hand horizontally (wastes space) - + Enable left justification - + Table grid layout - + Invert vertical coordinate - + Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -335,111 +350,111 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name - + ban &IP address - + ban client I&D - + Ban type - + &permanent ban - + &temporary ban - + &Days: - + &Hours: - + &Minutes: - + Duration of the ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. - + Please enter the reason for the ban that will be visible to the banned person. - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Ban user from server - + + - - + Error - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. - + You must have a value in the name ban when selecting the name ban checkbox. - + You must have a value in the ip ban when selecting the ip ban checkbox. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. @@ -470,32 +485,32 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name - + Sets - + Mana cost - + Card type - + P/T - + Color(s) @@ -503,96 +518,101 @@ This is only saved for moderators and cannot be seen by the banned person. CardFilter - + AND Logical conjunction operator used in card filter - + OR Logical disjunction operator used in card filter - + AND NOT Negated logical conjunction operator used in card filter - + OR NOT Negated logical disjunction operator used in card filter - + Name - + + Name (Exact) + + + + Type - + Color - + Text - + Set - + Mana Cost - + Mana Value - + Rarity - + Power - + Toughness - + Loyalty - + Format - + Main Type - + Sub Type @@ -600,22 +620,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoFrameWidget - + Image - + Description - + Both - + View transformation @@ -623,22 +643,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -651,12 +671,22 @@ This is only saved for moderators and cannot be seen by the banned person. - + + Set: + + + + + Collector Number: + + + + Related cards: - + Unknown card: @@ -664,124 +694,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -789,7 +819,7 @@ This is only saved for moderators and cannot be seen by the banned person. CardSizeWidget - + Card Size @@ -797,133 +827,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative - + %1's hand nominative - - - their library - look at zone - - - - - %1's library - look at zone - - - - - of their library - top cards of zone, - - - - - of %1's library - top cards of zone - - their library - reveal zone + look at zone %1's library + look at zone + + + + + of their library + top cards of zone, + + + + + of %1's library + top cards of zone + + + + + their library reveal zone - + + %1's library + reveal zone + + + + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - - - their graveyard - nominative - - - - - %1's graveyard - nominative - - - - - their exile - nominative - - - - - %1's exile - nominative - - + their graveyard + nominative + + + + + %1's graveyard + nominative + + + + + their exile + nominative + + + + + %1's exile + nominative + + + + their sideboard look at zone - + %1's sideboard look at zone - - - their sideboard - nominative - - - - - %1's sideboard - nominative - - - their custom zone '%1' + their sideboard nominative + %1's sideboard + nominative + + + + + their custom zone '%1' + nominative + + + + %1's custom zone '%2' nominative @@ -932,7 +962,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml3Parser - + Parse error at line %1 col %2: @@ -940,7 +970,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml4Parser - + Parse error at line %1 col %2: @@ -959,6 +989,37 @@ This is only saved for moderators and cannot be seen by the banned person. + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -970,47 +1031,47 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add to Deck - + Add to Sideboard - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1018,87 +1079,97 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing - + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + + Format: + + + + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1106,17 +1177,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1124,114 +1195,114 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1239,7 +1310,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1247,194 +1318,227 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers - - + + Success - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + Add URL - - + + URL: - - + + Edit URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL - + Remove URL - + Day(s) - + Updating... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count - + Set - + Number - + Provider ID - + Card @@ -1442,12 +1546,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1455,17 +1559,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1473,7 +1577,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -1481,62 +1585,62 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -1549,93 +1653,151 @@ This is only saved for moderators and cannot be seen by the banned person. - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1653,74 +1815,74 @@ This is only saved for moderators and cannot be seen by the banned person. DeckViewContainer - + Load deck... - + Load remote deck... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start - + Force start - + Sideboard unlocked - + Sideboard locked - - + + Error - + The selected file could not be loaded. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -1753,143 +1915,143 @@ This will kick all non-ready players from the game. - + Known Hosts - + Delete the currently selected saved server - + Refresh the server list with known public servers - + New Host - + Name: - + &Host: - + &Port: - + Player &name: - + P&assword: - + &Save password - + A&uto connect - + Automatically connect to the most recent login when Cockatrice opens - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + Forgot password? - + &Connect - + Server - + Login - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning - + You need to name your new connection profile. - + Connect Warning - + The player name can't be empty. @@ -1897,117 +2059,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings - + &Description: - + P&layers: - + General - + Game type - + &Password: - + Only &buddies can join - + Only &registered users can join - + Joining restrictions - + &Spectators can watch - + Spectators &need a password to watch - + Spectators can &chat - + Spectators can see &hands - + Create game as spectator - + Spectators - + Starting life total: - + Open decklists in lobby - + + Create game as judge + + + + Game setup options - + &Clear - + Create game - + Game information - + Error - + Server error. @@ -2015,97 +2182,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: - + Token - + C&olor: - + white - + blue - + black - + red - + green - + multicolor - + colorless - + &P/T: - + &Annotation: - + &Destroy token when it leaves the table - + Create face-down (Only hides name) - + Token data - + Show &all tokens - + Show tokens from this &deck - + Choose token from list - + Create token @@ -2113,53 +2280,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2167,39 +2334,39 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. - + Browse... - + Change avatar - + Open Image - + Image Files (*.png *.jpg *.bmp) - + Invalid image chosen. @@ -2207,17 +2374,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2225,38 +2392,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: - + New password: - + Confirm new password: - + Change password - - + + Error - + Your password is too short. - + The new passwords don't match. @@ -2264,93 +2431,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: - + C&olor: - + white - + blue - + black - + red - + green - + multicolor - + colorless - + &P/T: - + &Annotation: - + Token data - - + + Add token - + Remove token - + Edit custom tokens - + Please enter the name of the token: - + Error - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. @@ -2443,7 +2610,8 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games not created by buddy + Hide games not created by buddies + Hide games not created by buddy @@ -2530,52 +2698,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: - + &Port: - + Player &name: - + Email: - + Reset Password Challenge - + Reset Password Challenge Error - + The email address can't be empty. @@ -2583,37 +2751,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: - + &Port: - + Player &name: - + Reset Password Request - + Reset Password Error - + The player name can't be empty. @@ -2621,86 +2789,86 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: - + &Port: - + Player &name: - + Token: - - + + New Password: - + Reset Password - + The player name can't be empty. - - - - + + + + Reset Password Error - + The token can't be empty. - + The new password can't be empty. - + Error - + Your password is too short. - + The passwords do not match. @@ -2716,17 +2884,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard - + Error - + Invalid deck list. @@ -2734,43 +2902,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2784,7 +2952,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck @@ -2792,37 +2960,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -2830,91 +2998,91 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: - + &Port: - + Player &name: - + P&assword: - + Password (again): - + Email: - + Email (again): - + Country: - + Undefined - + Real name: - + Register to server - - - - + + + + Registration Warning - + Your password is too short. - + Your passwords do not match, please try again. - + Your email addresses do not match, please try again. - + The player name can't be empty. @@ -2940,40 +3108,55 @@ Your email will be used to verify your account. DlgSelectSetForCards - + Unmodified Cards: - + Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -2984,7 +3167,7 @@ Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -2995,7 +3178,7 @@ Would you like to change your database location setting? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3004,21 +3187,21 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3027,59 +3210,59 @@ Would you like to change your database location setting? - - - + + + Error - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings - + General - + Appearance - + User Interface - + Card Sources - + Chat - + Sound - + Shortcuts @@ -3087,39 +3270,39 @@ Would you like to change your database location setting? DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3127,17 +3310,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next - + Previous - + Tip of the Day @@ -3145,164 +3328,164 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel - + Reinstall - + Cancel Download - + Open Download Page - + Check for Client Updates - - - - + + + + Error - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - + Downloading update: %1 - + Checking for updates... - + Finished checking for updates - + No Update Available - + Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. - + Current version - + Selected release channel + + + + Update Available + + + + + + A new version of Cockatrice is available! + + + + + + New version + + - Update Available - - - - - - A new version of Cockatrice is available! - - - - - - New version - - - - - Released - - + + Changelog - + Do you want to update now? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + + Update Error - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -3310,21 +3493,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing - + Copy to clipboard - + Debug Log + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3394,33 +3709,39 @@ You may have to manually download the new version. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3436,22 +3757,22 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. @@ -3459,22 +3780,22 @@ You may have to manually download the new version. GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -3482,133 +3803,186 @@ You may have to manually download the new version. GameSelector - - - - - - + + + + + + Error - + Please join the appropriate room first. - + Wrong password. - + Spectators are not allowed in this game. - + The game is already full. - + The game does not exist any more. - + This game is only open to registered users. - + This game is only open to its creator's buddies. - + You are being ignored by the creator of this game. - + Join Game - + Spectate Game - + Game Information - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game - + Password: - + Please join the respective room first. - + &Filter games - + C&lear filter - + C&reate - + &Join - + + Join as judge + + + + J&oin as spectator - + + Join as judge spectator + + + + Games shown: %1 / %2 - + Games + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day - + %1%2 hr short age in hours @@ -3617,12 +3991,12 @@ You may have to manually download the new version. - + new - + %1%2 min short age in minutes @@ -3631,83 +4005,83 @@ You may have to manually download the new version. - + password - + buddies only - + reg. users only - + open decklists - - + + can chat - + see hands - + can see hands - + not allowed - + Room - + Age - + Description - + Creator - + Type - + Restrictions - + Players - + Spectators @@ -3715,143 +4089,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path - + Personal settings - + Language: - + Paths (editing disabled in portable mode) - + Paths - + How to help with translations - + Decks directory: - + Filters directory: - + Replays directory: - + Pictures directory: - + Card database: - + Custom database directory: - + Token database: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup - + Last update check on %1 (%2 days ago) @@ -3859,47 +4233,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -3907,111 +4281,141 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - - &Sort hand + + Sort hand by... - - Take &mulligan + + Name - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - Reveal r&andom card to... + + + All players - - - &All players + + Reveal r&andom card to... HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + + Browse Archidekt + + + + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4213,60 +4617,60 @@ You may have to manually download the new version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -4276,602 +4680,602 @@ Reason for shutdown: %1 - + Scheduled server shutdown - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Number of players - + Please enter the number of players. - - + + Player %1 - + Load replay - + About Cockatrice - + Version - + Cockatrice Webpage - + Project Manager: - + Past Project Managers: - + Developers: - + Our Developers - + Help Develop! - + Translators: - + Our Translators - + Help Translate! - + Support: - + Report an Issue - + Troubleshooting - + F.A.Q. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error - + Server timeout - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - + + + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 + - Requesting forgotten password to %1 as %2... - + &Connect... - + &Disconnect - + Start &local game... - + &Watch replay... - + &Full screen - + &Register to server... - + &Restore password... - + &Settings... - + &Exit - + A&ctions - + &Cockatrice - + C&ard Database - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + Reload card database - + Tabs - + &Help - + &About Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -4881,81 +5285,81 @@ Do you want to enable it/them? - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information - + A card database update is already running. - + Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -4963,359 +5367,529 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play - + from their graveyard - + from exile - + from their hand - + the top card of %1's library - + the top card of their library - + from the top of %1's library - + from the top of their library - + the bottom card of %1's library - + the bottom card of their library - + from the bottom of %1's library - + from the bottom of their library - + from %1's library - + from their library - + from sideboard - + from the stack - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. - + a card - + %1 gives %2 control over %3. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. - + %1 puts %2%3 into their graveyard. - + %1 exiles %2%3. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. - + %1 plays %2%3. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). @@ -5323,12 +5897,12 @@ Cockatrice will now reload the card database. - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural @@ -5337,72 +5911,72 @@ Cockatrice will now reload the card database. - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. - + The game has started. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. - + %1 is now watching the game. - + You have been kicked out of the game. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). @@ -5410,28 +5984,28 @@ Cockatrice will now reload the card database. - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural @@ -5440,107 +6014,107 @@ Cockatrice will now reload the card database. - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. - + %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). @@ -5548,7 +6122,7 @@ Cockatrice will now reload the card database. - + %1 removes %2 "%3" counter(s) from %4 (now %5). @@ -5556,97 +6130,97 @@ Cockatrice will now reload the card database. - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -5654,110 +6228,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message - - + + Message: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -5874,62 +6448,62 @@ Cockatrice will now reload the card database. Phase - + Unknown Phase - + Untap - + Upkeep - + Draw - + First Main - + Beginning of Combat - + Declare Attackers - + Declare Blockers - + Combat Damage - + End of Combat - + Second Main - + End/Cleanup @@ -5995,7 +6569,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -6004,134 +6578,134 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - + Move top cards to grave - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6139,17 +6713,17 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters - + S&ay @@ -6157,7 +6731,7 @@ Cockatrice will now reload the card database. PrintingSelector - + Display Navigation Buttons @@ -6165,22 +6739,22 @@ Cockatrice will now reload the card database. PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6196,17 +6770,17 @@ Cockatrice will now reload the card database. PrintingSelectorCardSelectionWidget - + Previous Card in Deck - + Bulk Selection - + Next Card in Deck @@ -6214,28 +6788,28 @@ Cockatrice will now reload the card database. PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -6301,37 +6875,37 @@ Cockatrice will now reload the card database. QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -6339,42 +6913,42 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - + All files (*.*) - + Cockatrice replays (*.cor) - + Maindeck - + Sideboard - + Tokens - + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? @@ -6382,92 +6956,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -6564,37 +7138,37 @@ Cockatrice will now reload the card database. RoomSelector - + Rooms - + Joi&n - + Room - + Description - + Permissions - + Players - + Games @@ -6635,27 +7209,27 @@ Cockatrice will now reload the card database. SetsModel - + Enabled - + Set type - + Set code - + Long name - + Release date @@ -6663,53 +7237,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -6717,12 +7291,12 @@ Cockatrice will now reload the card database. ShortcutTreeView - + Action - + Shortcut @@ -6730,13 +7304,13 @@ Cockatrice will now reload the card database. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -6763,12 +7337,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -6776,27 +7350,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -6804,48 +7378,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -7009,56 +7583,123 @@ Please check your shortcut settings! + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info - + Deck - + Filters - + &View - + + Card Database + + + + Printing - - - - + + + + + Visible - - - - + + + + + Floating - + Reset layout - + Deck: %1 @@ -7066,61 +7707,61 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + + + + Visible - - - - + + + + Floating - + Reset layout @@ -7128,22 +7769,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7151,133 +7792,133 @@ Please check your shortcut settings! TabDeckStorage - + Local file system - + Server deck storage - - + + Open in deck editor - + Rename deck or folder - + Upload deck - + Download deck + - - - + + New folder + - Delete - + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + Error - + Rename failed - - + + Invalid deck file - + Enter deck name - + This decklist does not have a name. Please enter a name: - + Unnamed deck - + Failed to upload deck to server - + Delete local file - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + Name of new folder: @@ -7290,17 +7931,17 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 @@ -7341,7 +7982,7 @@ Please enter a name: - + EDHRec: @@ -7349,197 +7990,197 @@ Please enter a name: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + Un&concede - - - + + + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + Selected cards - + &View - - + + + - Visible - - + + + - Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game - + Are you sure you want to leave this game? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. @@ -7547,7 +8188,7 @@ Please enter a name: TabHome - + Home @@ -7555,157 +8196,157 @@ Please enter a name: TabLog - + Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -7751,180 +8392,180 @@ The more information you put in, the more specific your results will be. TabReplays - + Local file system - + Server replay storage - - + + Watch replay - + Rename - - + + New folder - - + + Delete - + Open replays folder - + Download replay - + Toggle expiration lock - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay @@ -7990,30 +8631,30 @@ The more information you put in, the more specific your results will be. + - Error - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -8021,92 +8662,97 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - - - Home - - - - - &Visual Deck Storage - - - - - Visual Database Display - - - Server + Archidekt - Account + Home - Deck Storage + &Visual Deck Storage - Game Replays + Visual Database Display - Administration + Server + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + Logs - + Are you sure? - + There are still open games. Are you sure you want to quit? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8114,38 +8760,38 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -8154,7 +8800,12 @@ Please refrain from engaging in this activity or further actions may be taken ag TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display @@ -8234,7 +8885,7 @@ Please refrain from engaging in this activity or further actions may be taken ag UpdateDownloader - + Could not open the file for reading. @@ -8242,206 +8893,206 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details - + Private &chat - + Show this user's &games - + Add to &buddy list - + Remove from &buddy list - + Add to &ignore list - + Remove from &ignore list - + Kick from &game - + Warn user - + View user's war&n history - + Ban from &server - + View user's &ban history - + &Promote user to moderator - + Dem&ote user from moderator - + Promote user to &judge - + Demote user from judge - + View admin notes - - - + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - + Failed to get admin notes. - - + + Success - + Successfully promoted user. - + Successfully demoted user. - + + - Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -8629,137 +9280,142 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default - - - Close card view window when last card is removed - - - - - Auto focus search bar when card view window is opened - - - - - Annotate card text on tokens - - - - - Use tear-off menus, allowing right click menus to persist on screen - - - - - Notifications settings - - - - - Enable notifications in taskbar - - - - - Notify in the taskbar for game events while you are spectating - - - - - Notify in the taskbar when users in your buddy list connect - - - - - Animation settings - - - - - &Tap/untap animation - - - Deck editor/storage settings + Do not delete &arrows inside of subphases - Open deck in new tab by default + Close card view window when last card is removed - Use visual deck storage in game lobby + Auto focus search bar when card view window is opened - Use selection animation for Visual Deck Storage + Annotate card text on tokens + + + + + Use tear-off menus, allowing right click menus to persist on screen - When adding a tag in the visual deck storage to a .txt deck: + Notifications settings + + + + + Enable notifications in taskbar - do nothing + Notify in the taskbar for game events while you are spectating + + + + + Notify in the taskbar when users in your buddy list connect - ask to convert to .cod + Animation settings + + + + + &Tap/untap animation - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -8767,22 +9423,22 @@ Please refrain from engaging in this activity or further actions may be taken ag UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 @@ -8790,32 +9446,32 @@ Please refrain from engaging in this activity or further actions may be taken ag UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -8823,22 +9479,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match - + Mode: Includes - + Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -8846,21 +9502,49 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8886,28 +9570,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - Load from Deck + Filter by name... (Exact match) - Apply all card names in currently loaded deck as exact match name filters + Load from Deck - Load from Clipboard + Apply all card names in currently loaded deck as exact match name filters + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -8971,50 +9655,115 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - + + Sort by: + + + + + Filter by: + + + + Save and load filters - + Filter by exact card name - + Filter by card sub-type - + Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9022,46 +9771,25 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -9117,7 +9845,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9125,22 +9853,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -9148,17 +9876,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + Visual Deck Storage Settings @@ -9166,43 +9894,43 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Warn user for misconduct - - + + Error - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. @@ -9210,133 +9938,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -9344,72 +10072,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing - + pile view @@ -9417,7 +10145,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English @@ -9425,12 +10153,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -9438,1005 +10166,1041 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window - - + + Deck Editor - + Game Lobby - + Card Counters - + Player Counters - + Power and Toughness - + Game Phases - + Playing Area - + Move Selected Card - + View - + Move Top Card - + Move Bottom Card - + Gameplay - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - - + + Replays - + Tabs - + Check for Card Updates... - + Connect... - + Disconnect - + Exit - + Full screen - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters - + Clear Selected Filter - + Close - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card - + Load Deck... - - + + Load Deck from Clipboard... - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck - + Open Custom Pictures Folder - + Print Deck... - + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... - + Load Remote Deck... - + Set Ready to Start - + Toggle Sideboard Lock - + Add Green Counter - + Remove Green Counter - + Set Green Counters... - + Add Red Counter - + Remove Red Counter - + Set Red Counters... - + Add Life Counter - + Show Status Bar - + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Increment all card counters - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap - + Upkeep - + Draw - + First Main Phase - + Start Combat - + Attack - + Block - + Damage - + End Combat - + Second Main Phase - + End - + Next Phase - + Next Phase Action - + Next Turn - + Hide Card in Reveal Window - + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library - - - - + + + + Exile - - - - + + + + Graveyard - - + + + Hand - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - - Sort Hand - - - - + Library - + Sideboard - + Top Cards of Library - + Bottom Cards of Library - + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede - + Roll Dice... - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/oracle/oracle_en@source.ts b/oracle/oracle_en@source.ts index 3bdd8bef8..53ad27b4d 100644 --- a/oracle/oracle_en@source.ts +++ b/oracle/oracle_en@source.ts @@ -4,22 +4,22 @@ IntroPage - + Introduction - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. - + Interface language: - + Version: @@ -27,134 +27,134 @@ LoadSetsPage - + Source selection - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. - + Download URL: - + Local file: - + Restore default URL - + Choose file... - + Load sets file - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + + + Error - + The provided URL is not valid. - + Downloading (0MB) - + Please choose a file. - + Cannot open file '%1'. - + Downloading (%1MB) - + Network error: %1. - + Parsing file - + Xz extraction failed. - + Sorry, this version of Oracle does not support xz compressed files. - + Failed to open Zip archive: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. - + Zip extraction failed: %1. - + Sorry, this version of Oracle does not support zipped files. - + Failed to interpret downloaded data. - + Do you want to download the uncompressed file instead? - + The file was retrieved successfully, but it does not contain any sets data. @@ -162,42 +162,57 @@ LoadSpoilersPage - + Save spoiler database - + XML; spoiler database (*.xml) - + + spoiler + + + + Spoilers import - + Please specify a compatible source for spoiler data. - + Download URL: - + + Local file: + + + + Restore default URL - + + Choose file... + + + + The spoiler database will be saved at the following location: - + Save to a custom path (not recommended) @@ -205,42 +220,57 @@ LoadTokensPage - + Save token database - + XML; token database (*.xml) - + + tokens + + + + Tokens import - + Please specify a compatible source for token data. - + Download URL: - + + Local file: + + + + Restore default URL - + + Choose file... + + + + The token database will be saved at the following location: - + Save to a custom path (not recommended) @@ -248,7 +278,7 @@ OracleImporter - + Dummy set containing tokens @@ -256,7 +286,7 @@ OracleWizard - + Oracle Importer @@ -264,22 +294,22 @@ OutroPage - + Finished - + The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. @@ -287,73 +317,73 @@ SaveSetsPage - - + + Error - + No set has been imported. - + Sets imported - + A cockatrice database file of %1 MB has been downloaded. - + The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: - + Save to a custom path (not recommended) - + &Save - + Import finished: %1 cards. - + %1: %2 cards imported - + Save card database - + XML; card database (*.xml) - + The file could not be saved to %1 @@ -361,34 +391,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error - + The provided URL is not valid: - + Downloading (0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) - + Network error: %1. - + The file could not be saved to %1 @@ -537,7 +589,7 @@ i18n - + English From 7c7755b61d3f4cdaa4c5bb128b02d78cb16aa90c Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 6 Jan 2026 22:41:40 -0800 Subject: [PATCH 129/325] [VDE] Fix crash vy adding null check for card in PrintingSelector (#6500) --- .../interface/widgets/printing_selector/printing_selector.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp index 94deaf135..506395789 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp @@ -122,6 +122,10 @@ static QMap> tallyUuidCounts(const DeckListModel *model void PrintingSelector::updateCardAmounts() { + if (selectedCard.isNull()) { + return; + } + auto map = tallyUuidCounts(deckStateManager->getModel(), selectedCard->getName()); emit cardAmountsChanged(map); } From 0deaa9d9b4ab4e1dc1fe0cb90285b5aea5204bbe Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 9 Jan 2026 18:27:54 -0800 Subject: [PATCH 130/325] [DeckEditor] Don't change widget focus when adding card (#6503) --- .../widgets/deck_editor/deck_editor_deck_dock_widget.cpp | 7 +++++-- .../widgets/deck_editor/deck_editor_deck_dock_widget.h | 2 +- .../interface/widgets/deck_editor/deck_state_manager.cpp | 4 ++-- .../src/interface/widgets/deck_editor/deck_state_manager.h | 3 ++- .../widgets/printing_selector/card_amount_widget.cpp | 2 +- 5 files changed, 11 insertions(+), 7 deletions(-) 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 bf7688bb0..73bafe54f 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 @@ -459,12 +459,15 @@ void DeckEditorDeckDockWidget::syncBannerCardComboBoxSelectionWithDeck() } } -void DeckEditorDeckDockWidget::setSelectedIndex(const QModelIndex &newCardIndex) +void DeckEditorDeckDockWidget::setSelectedIndex(const QModelIndex &newCardIndex, bool preserveWidgetFocus) { deckView->clearSelection(); deckView->setCurrentIndex(newCardIndex); recursiveExpand(newCardIndex); - deckView->setFocus(Qt::FocusReason::MouseFocusReason); + + if (!preserveWidgetFocus) { + deckView->setFocus(Qt::FocusReason::MouseFocusReason); + } } void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel() diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index 39b298550..8dddf5882 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -100,7 +100,7 @@ private slots: void writeComments(); void writeBannerCard(int); void applyActiveGroupCriteria(); - void setSelectedIndex(const QModelIndex &newCardIndex); + void setSelectedIndex(const QModelIndex &newCardIndex, bool preserveWidgetFocus); void updateHash(); void refreshShortcuts(); void updateShowBannerCardComboBox(bool visible); 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 d45416b17..412954bb8 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp @@ -182,7 +182,7 @@ QModelIndex DeckStateManager::addCard(const ExactCard &card, const QString &zone QModelIndex idx = modifyDeck(reason, [&card, &zone](auto model) { return model->addCard(card, zone); }); if (idx.isValid()) { - emit focusIndexChanged(idx); + emit focusIndexChanged(idx, true); } return idx; @@ -208,7 +208,7 @@ QModelIndex DeckStateManager::decrementCard(const ExactCard &card, const QString } if (idx.isValid()) { - emit focusIndexChanged(idx); + emit focusIndexChanged(idx, true); } return idx; diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h index 0f3ba3255..4f1ec7e04 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h @@ -290,8 +290,9 @@ signals: /** * The selected card on any views connected to this deck should be changed to this index. * @param index The model index + * @param preserveWidgetFocus Whether to keep the widget focus unchanged */ - void focusIndexChanged(QModelIndex index); + void focusIndexChanged(QModelIndex index, bool preserveWidgetFocus); }; #endif // COCKATRICE_DECK_STATE_MANAGER_H \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index 1c09a42c1..25222f437 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -198,7 +198,7 @@ void CardAmountWidget::addPrinting(const QString &zone) }); if (newCardIndex.isValid()) { - emit deckStateManager->focusIndexChanged(newCardIndex); + emit deckStateManager->focusIndexChanged(newCardIndex, false); } } From 9ab398f08d5eca5a18335800acffaaa86d4ccda9 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sun, 11 Jan 2026 12:48:31 +0100 Subject: [PATCH 131/325] [Doxygen] Add documentation for beta channel. (#6510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Doxygen] Add documentation for beta channel. Took 36 minutes * [Doxygen] Use default theme. Took 13 minutes --------- Co-authored-by: Lukas Brübach --- .../extra-pages/user_documentation/index.md | 14 ++++- .../user_documentation/settings/beta.md | 54 ++++++++++++++++++ doc/doxygen/images/release_channel_beta.png | Bin 0 -> 93843 bytes 3 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 doc/doxygen/extra-pages/user_documentation/settings/beta.md create mode 100644 doc/doxygen/images/release_channel_beta.png diff --git a/doc/doxygen/extra-pages/user_documentation/index.md b/doc/doxygen/extra-pages/user_documentation/index.md index 33104991a..29e76eff6 100644 --- a/doc/doxygen/extra-pages/user_documentation/index.md +++ b/doc/doxygen/extra-pages/user_documentation/index.md @@ -1,9 +1,17 @@ @page user_reference User Reference - -- @subpage search_syntax_help -- @subpage deck_search_syntax_help +# Deck Management + - @subpage creating_decks - @subpage importing_decks - @subpage editing_decks - @subpage exporting_decks + +# Release Channels + +- @subpage beta_release + +# Syntax Help + +- @subpage search_syntax_help +- @subpage deck_search_syntax_help \ No newline at end of file diff --git a/doc/doxygen/extra-pages/user_documentation/settings/beta.md b/doc/doxygen/extra-pages/user_documentation/settings/beta.md new file mode 100644 index 000000000..733af79f5 --- /dev/null +++ b/doc/doxygen/extra-pages/user_documentation/settings/beta.md @@ -0,0 +1,54 @@ +@page beta_release Beta Release + +The beta version of Cockatrice features all the newest features before they make their way into the latest stable +release. +It allows us to test these features ahead of time and you to give feedback on them. +We release new betas very frequently and usually release a follow-up beta with a fix within a day or two if we break +anything. In contrast, stable releases are released much less frequently but ensured to be stable. + +# What to Expect from the Beta + +Using the beta and giving feedback on it helps us improve Cockatrice a lot. +We do not perform any automatic data collection so your voluntary feedback is the only way for us to get a sense of what +you enjoy, expect, or would like to change. + +We highly appreciate your active participation and feedback. + +That being said, some words of warning should be issued: + +The beta release may contain unfinished features, bugs, or performance issues. +It is intended for testing and feedback and may be less stable than the default release. + +We recommend using the beta only if you are comfortable encountering issues and helping us improve Cockatrice. + +# Switching to the Beta + +To switch to the Beta, navigate to your settings, using the Tab "Cockatrice → Settings" or Ctrl + Shift + P (default +shortcut). + +Within the settings, in the first tab 'General', the first section 'Personal Settings' contains the setting 'Release +Channel'. + +\image html release_channel_beta.png + +Switch this to 'Beta'. + +Afterwards, close your settings, and use the 'Help' tab to search for a client update. You should be prompted to install +the beta version. + +# Switching back to Stable + +Follow the same steps as above, but set Release Channel to 'Default'. + +# Giving Feedback + +If you encounter a bug or unexpected behavior while using the beta, please report it on: + +- GitHub: https://github.com/Cockatrice/Cockatrice/issues +- Discord: https://discord.gg/4hNswvHeFd in #beta-testing + +When reporting issues, please include: + +- Your operating system +- The beta version number (found in 'Help → About Cockatrice') +- Steps to reproduce the problem, if possible diff --git a/doc/doxygen/images/release_channel_beta.png b/doc/doxygen/images/release_channel_beta.png new file mode 100644 index 0000000000000000000000000000000000000000..00869a9cf2ac24caadeafa0209a6a0968394f70b GIT binary patch literal 93843 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%0AvLK06l0PwF*pFV(^b07*qoM6N<$g3B%0+5i9m literal 0 HcmV?d00001 From 52547bbfe8ee1d9829b890252bd599b8f7e9a844 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 14:41:16 +0100 Subject: [PATCH 132/325] Updates for project Cockatrice and language de (#6508) * Translate oracle/oracle_en@source.ts in de 100% translated source file: 'oracle/oracle_en@source.ts' on 'de'. * Translate oracle/oracle_en@source.ts in de 100% translated source file: 'oracle/oracle_en@source.ts' on 'de'. --------- Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- oracle/translations/oracle_de.ts | 216 +++++++++++++++++++------------ 1 file changed, 134 insertions(+), 82 deletions(-) diff --git a/oracle/translations/oracle_de.ts b/oracle/translations/oracle_de.ts index b3f1d9259..bdf344048 100644 --- a/oracle/translations/oracle_de.ts +++ b/oracle/translations/oracle_de.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Einführung - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Dieser Assistent wird eine Liste aller Editionen, Karten und Spielsteine importieren, die von Cockatrice genutzt werden. - + Interface language: Interfacesprache: - + Version: Version: @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection Quellenauswahl - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Bitte geben Sie eine kompatible Quelle für die Liste der Editionen und Karten an. Sie können eine URL-Adresse zum Herunterladen angeben oder eine existierende Datei von Ihrem Computer verwenden. - + Download URL: Download URL: - + Local file: Lokale Datei: - + Restore default URL Standard-URL wiederherstellen - + Choose file... Datei auswählen... - + Load sets file Editionsdatei wird geladen - + Sets file (%1) Sets JSON file (%1) Sets Datei (%1) - - - - - - - + + + + + + + Error Fehler - + The provided URL is not valid. Die eingegebene URL ist nicht gültig. - + Downloading (0MB) Herunterladen (0MB) - + Please choose a file. - Bitte wählen Sie eine Datei. + Bitte wähle eine Datei aus. - + Cannot open file '%1'. Datei '%1' kann nicht geöffnet werden. - + Downloading (%1MB) Herunterladen (%1MB) - + Network error: %1. Netzwerkfehler: %1. - + Parsing file Datei wird verarbeitet - + Xz extraction failed. Fehler beim Extrahieren der xz Datei. - + Sorry, this version of Oracle does not support xz compressed files. Es tut uns Leid, diese Version von Oracle unterstützt keine xz komprimierten Dateien. - + Failed to open Zip archive: %1. Fehler beim Öffnen des Zip Archivs: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Fehler beim Extrahieren: Das Zip Archiv enthält mehr als eine Datei. - + Zip extraction failed: %1. Fehler beim Extrahieren: %1. - + Sorry, this version of Oracle does not support zipped files. Es tut uns Leid, diese Version von Oracle unterstützt keine Zip Archive. - + Failed to interpret downloaded data. Interpretation der heruntergeladenen Daten fehlgeschlagen. - + Do you want to download the uncompressed file instead? Möchten Sie stattdessen die unkomprimierte Datei herunterladen? - + The file was retrieved successfully, but it does not contain any sets data. Die Datei wurde erfolgreich abgerufen, sie enthält aber keine Editionsdaten. @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database Speichere Spoilerdatenbank - + XML; spoiler database (*.xml) XML; Spoilerdatenbank (*.xml) - + + spoiler + Spoiler + + + Spoilers import Spoilerimport - + Please specify a compatible source for spoiler data. Bitte geben Sie eine kompatible Quelle für Spoilerdaten an. - + Download URL: Download URL: - + + Local file: + Lokale Datei: + + + Restore default URL Standard-URL wiederherstellen - + + Choose file... + Datei auswählen... + + + The spoiler database will be saved at the following location: Die Spoilerdatenbank wird in folgendem Pfad gespeichert: - + Save to a custom path (not recommended) Speichere in benutzerdefiniertem Pfad (nicht empfohlen) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database Speichere Spielsteindatenbank - + XML; token database (*.xml) XML; Spielsteindatenbank (*.xml) - + + tokens + Spielsteine + + + Tokens import Spielsteinimport - + Please specify a compatible source for token data. Bitte geben Sie eine kompatible Quelle für Spielsteindaten an. - + Download URL: Download URL: - + + Local file: + Lokale Datei: + + + Restore default URL Standard-URL wiederherstellen - + + Choose file... + Datei auswählen... + + + The token database will be saved at the following location: Die Spielsteindatenbank wird in folgendem Pfad gespeichert: - + Save to a custom path (not recommended) Speichere in benutzerdefiniertem Pfad (nicht empfohlen) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Platzhalter Edition mit Spielsteinen @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle Importer @@ -262,22 +292,22 @@ OutroPage - + Finished Fertig - + The wizard has finished. Der Wizard ist fertig. - + You can now start using Cockatrice with the newly updated cards. Sie können nun Cockatrice mit den aktuellen Karten verwenden. - + If the card databases don't reload automatically, restart the Cockatrice client. Falls die Datenbanken nicht automatisch neu geladen werden, starten Sie bitte Cockatrice neu. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error Fehler - + No set has been imported. Es wurden keine Editionen importiert. - + Sets imported Editionen wurden importiert - + A cockatrice database file of %1 MB has been downloaded. Eine Cockatrice-Datenbankdatei der Größe %1 MB wurde heruntergeladen. - + The following sets have been found: Die folgenden Sets wurden gefunden: - + Press "Save" to store the imported cards in the Cockatrice database. Drücken Sie "Speichern", um die importierten Karten in der Datenbank zu speichern. - + The card database will be saved at the following location: Die Kartendatenbank wird in folgendem Pfad gespeichert: - + Save to a custom path (not recommended) Speichere in benutzerdefiniertem Pfad (nicht empfohlen) - + &Save &Speichern - + Import finished: %1 cards. Importieren abgeschlossen: %1 Karten. - + %1: %2 cards imported %1: %2 Karten importiert. - + Save card database Kartendatenbank speichern - + XML; card database (*.xml) XML; Kartendatenbank (*.xml) - + The file could not be saved to %1 Die Datei konnte nicht gespeichert werden: %1 @@ -360,34 +390,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + Lade %1 Datei + + + + %1 file (%1) + %1 Datei (%1) + + + + + + + Error Fehler - + The provided URL is not valid: Die bereitgestellte URL ist nicht gültig: - + Downloading (0MB) Herunterladen (0MB) - + + Please choose a file. + Bitte wähle eine Datei aus. + + + + Cannot open file '%1'. + Datei '%1' kann nicht geöffnet werden. + + + Downloading (%1MB) Herunterladen (%1MB) - + Network error: %1. Netzwerkfehler: %1. - + The file could not be saved to %1 Die Datei konnte nicht in %1 gespeichert werden @@ -536,7 +588,7 @@ i18n - + English Deutsch (German) From a0d13598605a9f685b382d887122df0c2d07763c Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Tue, 13 Jan 2026 09:08:03 +0100 Subject: [PATCH 133/325] [VDE] Minor cleanups, possibly fullscreen width-lock fix (#6438) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor some constructor things to their own methods. * Saner size policies, no manual resize management. Took 15 seconds Took 23 seconds * VDE doesn't need to manually resize either. Took 6 minutes * Add plate comments and re-order .cpp to be more structured. Took 9 minutes Took 30 seconds * Add plate comments and re-order DeckCardZoneDisplay.cpp to be more structured Took 7 minutes Took 5 seconds * Add plate comments and re-order CardGroupDisplayWidget.cpp to be more structured Took 7 minutes Took 4 minutes * Include declaration. Took 3 minutes --------- Co-authored-by: Lukas Brübach --- .../card_group_display_widget.cpp | 77 +++-- .../cards/deck_card_zone_display_widget.cpp | 110 +++---- .../cards/deck_card_zone_display_widget.h | 1 - .../visual_deck_editor_widget.cpp | 273 ++++++++++-------- .../visual_deck_editor_widget.h | 9 +- 5 files changed, 264 insertions(+), 206 deletions(-) diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp index fdac9d0f0..8e6ebed63 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp @@ -39,6 +39,34 @@ CardGroupDisplayWidget::CardGroupDisplayWidget(QWidget *parent, connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &CardGroupDisplayWidget::onCardRemoval); } +// Just here so it can get overwritten in subclasses. +void CardGroupDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); +} + +// ===================================================================================================================== +// User Interaction +// ===================================================================================================================== + +void CardGroupDisplayWidget::mousePressEvent(QMouseEvent *event) +{ + QWidget::mousePressEvent(event); + if (selectionModel) { + selectionModel->clearSelection(); + } +} + +void CardGroupDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card) +{ + emit cardClicked(event, card); +} + +void CardGroupDisplayWidget::onHover(const ExactCard &card) +{ + emit cardHovered(card); +} + void CardGroupDisplayWidget::onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { auto proxyModel = qobject_cast(selectionModel->model()); @@ -76,15 +104,9 @@ void CardGroupDisplayWidget::onSelectionChanged(const QItemSelection &selected, } } -void CardGroupDisplayWidget::clearAllDisplayWidgets() -{ - for (auto idx : indexToWidgetMap.keys()) { - auto displayWidget = indexToWidgetMap.value(idx); - removeFromLayout(displayWidget); - indexToWidgetMap.remove(idx); - delete displayWidget; - } -} +// ===================================================================================================================== +// Display Widget Management +// ===================================================================================================================== QWidget *CardGroupDisplayWidget::constructWidgetForIndex(QPersistentModelIndex index) { @@ -134,6 +156,20 @@ void CardGroupDisplayWidget::updateCardDisplays() } } +void CardGroupDisplayWidget::clearAllDisplayWidgets() +{ + for (auto idx : indexToWidgetMap.keys()) { + auto displayWidget = indexToWidgetMap.value(idx); + removeFromLayout(displayWidget); + indexToWidgetMap.remove(idx); + delete displayWidget; + } +} + +// ===================================================================================================================== +// DeckListModel Signal Responses +// ===================================================================================================================== + void CardGroupDisplayWidget::onCardAddition(const QModelIndex &parent, int first, int last) { if (!trackedIndex.isValid()) { @@ -178,27 +214,4 @@ void CardGroupDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeSort clearAllDisplayWidgets(); updateCardDisplays(); -} - -void CardGroupDisplayWidget::mousePressEvent(QMouseEvent *event) -{ - QWidget::mousePressEvent(event); - if (selectionModel) { - selectionModel->clearSelection(); - } -} - -void CardGroupDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card) -{ - emit cardClicked(event, card); -} - -void CardGroupDisplayWidget::onHover(const ExactCard &card) -{ - emit cardHovered(card); -} - -void CardGroupDisplayWidget::resizeEvent(QResizeEvent *event) -{ - QWidget::resizeEvent(event); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp index 7d45d9cc9..a8a97a4ca 100644 --- a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.cpp @@ -23,6 +23,7 @@ DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent, displayType(_displayType), bannerOpacity(bannerOpacity), subBannerOpacity(subBannerOpacity), cardSizeWidget(_cardSizeWidget) { + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); layout = new QVBoxLayout(this); setLayout(layout); @@ -46,6 +47,20 @@ DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent, connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &DeckCardZoneDisplayWidget::onCategoryRemoval); } +// ===================================================================================================================== +// User Interaction +// ===================================================================================================================== + +void DeckCardZoneDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card) +{ + emit cardClicked(event, card, zoneName); +} + +void DeckCardZoneDisplayWidget::onHover(const ExactCard &card) +{ + emit cardHovered(card); +} + void DeckCardZoneDisplayWidget::onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { for (auto &range : selected) { @@ -69,17 +84,9 @@ void DeckCardZoneDisplayWidget::onSelectionChanged(const QItemSelection &selecte } } -void DeckCardZoneDisplayWidget::cleanupInvalidCardGroup(CardGroupDisplayWidget *displayWidget) -{ - cardGroupLayout->removeWidget(displayWidget); - displayWidget->setParent(nullptr); - for (auto idx : indexToWidgetMap.keys()) { - if (!idx.isValid()) { - indexToWidgetMap.remove(idx); - } - } - delete displayWidget; -} +// ===================================================================================================================== +// Display Widget Management +// ===================================================================================================================== void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex index) { @@ -141,6 +148,45 @@ void DeckCardZoneDisplayWidget::displayCards() } } +void DeckCardZoneDisplayWidget::refreshDisplayType(const DisplayType &_displayType) +{ + displayType = _displayType; + QLayoutItem *item; + while ((item = cardGroupLayout->takeAt(0)) != nullptr) { + if (item->widget()) { + item->widget()->deleteLater(); + } else if (item->layout()) { + item->layout()->deleteLater(); + } + delete item; + } + + indexToWidgetMap.clear(); + + // We gotta wait for all the deleteLater's to finish so we fire after the next event cycle + + auto timer = new QTimer(this); + timer->setSingleShot(true); + connect(timer, &QTimer::timeout, this, [this]() { displayCards(); }); + timer->start(); +} + +void DeckCardZoneDisplayWidget::cleanupInvalidCardGroup(CardGroupDisplayWidget *displayWidget) +{ + cardGroupLayout->removeWidget(displayWidget); + displayWidget->setParent(nullptr); + for (auto idx : indexToWidgetMap.keys()) { + if (!idx.isValid()) { + indexToWidgetMap.remove(idx); + } + } + delete displayWidget; +} + +// ===================================================================================================================== +// DeckListModel Signal Responses +// ===================================================================================================================== + void DeckCardZoneDisplayWidget::onCategoryAddition(const QModelIndex &parent, int first, int last) { if (!trackedIndex.isValid()) { @@ -173,48 +219,6 @@ void DeckCardZoneDisplayWidget::onCategoryRemoval(const QModelIndex &parent, int } } -void DeckCardZoneDisplayWidget::resizeEvent(QResizeEvent *event) -{ - QWidget::resizeEvent(event); - for (QObject *child : layout->children()) { - QWidget *widget = qobject_cast(child); - if (widget) { - widget->setMaximumWidth(width()); - } - } -} -void DeckCardZoneDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card) -{ - emit cardClicked(event, card, zoneName); -} -void DeckCardZoneDisplayWidget::onHover(const ExactCard &card) -{ - emit cardHovered(card); -} - -void DeckCardZoneDisplayWidget::refreshDisplayType(const DisplayType &_displayType) -{ - displayType = _displayType; - QLayoutItem *item; - while ((item = cardGroupLayout->takeAt(0)) != nullptr) { - if (item->widget()) { - item->widget()->deleteLater(); - } else if (item->layout()) { - item->layout()->deleteLater(); - } - delete item; - } - - indexToWidgetMap.clear(); - - // We gotta wait for all the deleteLater's to finish so we fire after the next event cycle - - auto timer = new QTimer(this); - timer->setSingleShot(true); - connect(timer, &QTimer::timeout, this, [this]() { displayCards(); }); - timer->start(); -} - void DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged(QString _activeGroupCriteria) { activeGroupCriteria = _activeGroupCriteria; diff --git a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h index d5603017c..dc0f3d734 100644 --- a/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h +++ b/cockatrice/src/interface/widgets/cards/deck_card_zone_display_widget.h @@ -40,7 +40,6 @@ public: QPersistentModelIndex trackedIndex; QString zoneName; void addCardsToOverlapWidget(); - void resizeEvent(QResizeEvent *event) override; public slots: void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card); 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 7b70e2b6d..9d2aadc63 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 @@ -38,6 +38,36 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, mainLayout->setContentsMargins(9, 0, 9, 5); mainLayout->setSpacing(0); + initializeDisplayOptionsAndSearchWidget(); + + initializeScrollAreaAndZoneContainer(); + + cardSizeWidget = new CardSizeWidget(this, nullptr, SettingsCache::instance().getVisualDeckEditorCardSize()); + connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(), + &SettingsCache::setVisualDeckEditorCardSize); + + mainLayout->addWidget(displayOptionsAndSearch); + mainLayout->addWidget(scrollArea); + mainLayout->addWidget(cardSizeWidget); + + connectDeckListModel(); + + constructZoneWidgetsFromDeckListModel(); + + if (selectionModel) { + connect(selectionModel, &QItemSelectionModel::selectionChanged, this, + &VisualDeckEditorWidget::onSelectionChanged); + } + + retranslateUi(); +} + +// ===================================================================================================================== +// Constructor helpers +// ===================================================================================================================== + +void VisualDeckEditorWidget::initializeSearchBarAndCompleter() +{ searchBar = new QLineEdit(this); connect(searchBar, &QLineEdit::returnPressed, this, [=, this]() { if (!searchBar->hasFocus()) @@ -109,12 +139,10 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, emit cardAdditionRequested(card); } }); +} - displayOptionsAndSearch = new QWidget(this); - displayOptionsAndSearchLayout = new QHBoxLayout(displayOptionsAndSearch); - displayOptionsAndSearchLayout->setAlignment(Qt::AlignLeft); - displayOptionsAndSearch->setLayout(displayOptionsAndSearchLayout); - +void VisualDeckEditorWidget::initializeDisplayOptionsWidget() +{ displayOptionsWidget = new VisualDeckDisplayOptionsWidget(this); connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::displayTypeChanged, this, &VisualDeckEditorWidget::displayTypeChanged); @@ -122,11 +150,26 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, &VisualDeckEditorWidget::activeGroupCriteriaChanged); connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::sortCriteriaChanged, this, &VisualDeckEditorWidget::activeSortCriteriaChanged); +} + +void VisualDeckEditorWidget::initializeDisplayOptionsAndSearchWidget() +{ + initializeSearchBarAndCompleter(); + + initializeDisplayOptionsWidget(); + + displayOptionsAndSearch = new QWidget(this); + displayOptionsAndSearchLayout = new QHBoxLayout(displayOptionsAndSearch); + displayOptionsAndSearchLayout->setAlignment(Qt::AlignLeft); + displayOptionsAndSearch->setLayout(displayOptionsAndSearchLayout); displayOptionsAndSearchLayout->addWidget(displayOptionsWidget); displayOptionsAndSearchLayout->addWidget(searchBar); displayOptionsAndSearchLayout->addWidget(searchPushButton); +} +void VisualDeckEditorWidget::initializeScrollAreaAndZoneContainer() +{ scrollArea = new QScrollArea(this); scrollArea->setWidgetResizable(true); scrollArea->setMinimumSize(0, 0); @@ -136,31 +179,19 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); zoneContainer = new QWidget(scrollArea); + zoneContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); zoneContainerLayout = new QVBoxLayout(zoneContainer); zoneContainer->setLayout(zoneContainerLayout); scrollArea->addScrollBarWidget(zoneContainer, Qt::AlignHCenter); scrollArea->setWidget(zoneContainer); +} - cardSizeWidget = new CardSizeWidget(this, nullptr, SettingsCache::instance().getVisualDeckEditorCardSize()); - connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(), - &SettingsCache::setVisualDeckEditorCardSize); - - mainLayout->addWidget(displayOptionsAndSearch); - mainLayout->addWidget(scrollArea); - mainLayout->addWidget(cardSizeWidget); - +void VisualDeckEditorWidget::connectDeckListModel() +{ connect(deckListModel, &DeckListModel::modelReset, this, &VisualDeckEditorWidget::decklistModelReset); connect(deckListModel, &DeckListModel::dataChanged, this, &VisualDeckEditorWidget::decklistDataChanged); connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &VisualDeckEditorWidget::onCardAddition); connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &VisualDeckEditorWidget::onCardRemoval); - constructZoneWidgetsFromDeckListModel(); - - if (selectionModel) { - connect(selectionModel, &QItemSelectionModel::selectionChanged, this, - &VisualDeckEditorWidget::onSelectionChanged); - } - - retranslateUi(); } void VisualDeckEditorWidget::retranslateUi() @@ -171,96 +202,9 @@ void VisualDeckEditorWidget::retranslateUi() "preferred printing to the deck on pressing enter")); } -void VisualDeckEditorWidget::setSelectionModel(QItemSelectionModel *model) -{ - if (selectionModel == model) { - return; - } - - if (selectionModel) { - // TODO: Possibly disconnect old ones? - } - - selectionModel = model; - - if (selectionModel) { - connect(selectionModel, &QItemSelectionModel::selectionChanged, this, - &VisualDeckEditorWidget::onSelectionChanged); - } -} - -void VisualDeckEditorWidget::onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) -{ - for (auto &range : selected) { - for (int row = range.top(); row <= range.bottom(); ++row) { - QModelIndex idx = range.model()->index(row, 0, range.parent()); - auto it = indexToWidgetMap.find(QPersistentModelIndex(idx)); - if (it != indexToWidgetMap.end()) { - // it.value()->setHighlighted(true); - } - } - } - - for (auto &range : deselected) { - for (int row = range.top(); row <= range.bottom(); ++row) { - QModelIndex idx = range.model()->index(row, 0, range.parent()); - auto it = indexToWidgetMap.find(QPersistentModelIndex(idx)); - if (it != indexToWidgetMap.end()) { - // it.value()->setHighlighted(false); - } - } - } -} - -void VisualDeckEditorWidget::clearAllDisplayWidgets() -{ - for (auto idx : indexToWidgetMap.keys()) { - auto displayWidget = indexToWidgetMap.value(idx); - zoneContainerLayout->removeWidget(displayWidget); - indexToWidgetMap.remove(idx); - delete displayWidget; - } -} - -void VisualDeckEditorWidget::cleanupInvalidZones(DeckCardZoneDisplayWidget *displayWidget) -{ - zoneContainerLayout->removeWidget(displayWidget); - for (auto idx : indexToWidgetMap.keys()) { - if (!idx.isValid()) { - indexToWidgetMap.remove(idx); - } - } - delete displayWidget; -} - -void VisualDeckEditorWidget::onCardAddition(const QModelIndex &parent, int first, int last) -{ - if (parent == deckListModel->getRoot()) { - for (int i = first; i <= last; i++) { - QPersistentModelIndex index = QPersistentModelIndex(deckListModel->index(i, 0, deckListModel->getRoot())); - - if (indexToWidgetMap.contains(index)) { - continue; - } - - constructZoneWidgetForIndex(index); - } - } -} - -void VisualDeckEditorWidget::onCardRemoval(const QModelIndex &parent, int first, int last) -{ - Q_UNUSED(parent); - Q_UNUSED(first); - Q_UNUSED(last); - for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) { - if (!idx.isValid()) { - zoneContainerLayout->removeWidget(indexToWidgetMap.value(idx)); - indexToWidgetMap.value(idx)->deleteLater(); - indexToWidgetMap.remove(idx); - } - } -} +// ===================================================================================================================== +// Display Widget Management +// ===================================================================================================================== void VisualDeckEditorWidget::constructZoneWidgetForIndex(QPersistentModelIndex persistent) { @@ -312,10 +256,58 @@ void VisualDeckEditorWidget::updateZoneWidgets() { } -void VisualDeckEditorWidget::resizeEvent(QResizeEvent *event) +void VisualDeckEditorWidget::clearAllDisplayWidgets() { - QWidget::resizeEvent(event); - zoneContainer->setMaximumWidth(scrollArea->viewport()->width()); + for (auto idx : indexToWidgetMap.keys()) { + auto displayWidget = indexToWidgetMap.value(idx); + zoneContainerLayout->removeWidget(displayWidget); + indexToWidgetMap.remove(idx); + delete displayWidget; + } +} + +void VisualDeckEditorWidget::cleanupInvalidZones(DeckCardZoneDisplayWidget *displayWidget) +{ + zoneContainerLayout->removeWidget(displayWidget); + for (auto idx : indexToWidgetMap.keys()) { + if (!idx.isValid()) { + indexToWidgetMap.remove(idx); + } + } + delete displayWidget; +} + +// ===================================================================================================================== +// DeckModel Signals Management +// ===================================================================================================================== + +void VisualDeckEditorWidget::onCardAddition(const QModelIndex &parent, int first, int last) +{ + if (parent == deckListModel->getRoot()) { + for (int i = first; i <= last; i++) { + QPersistentModelIndex index = QPersistentModelIndex(deckListModel->index(i, 0, deckListModel->getRoot())); + + if (indexToWidgetMap.contains(index)) { + continue; + } + + constructZoneWidgetForIndex(index); + } + } +} + +void VisualDeckEditorWidget::onCardRemoval(const QModelIndex &parent, int first, int last) +{ + Q_UNUSED(parent); + Q_UNUSED(first); + Q_UNUSED(last); + for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) { + if (!idx.isValid()) { + zoneContainerLayout->removeWidget(indexToWidgetMap.value(idx)); + indexToWidgetMap.value(idx)->deleteLater(); + indexToWidgetMap.remove(idx); + } + } } void VisualDeckEditorWidget::decklistModelReset() @@ -334,6 +326,17 @@ void VisualDeckEditorWidget::decklistDataChanged(QModelIndex topLeft, QModelInde updateZoneWidgets(); } +// ===================================================================================================================== +// User Interaction +// ===================================================================================================================== + +void VisualDeckEditorWidget::onCardClick(QMouseEvent *event, + CardInfoPictureWithTextOverlayWidget *instance, + QString zoneName) +{ + emit cardClicked(event, instance, zoneName); +} + void VisualDeckEditorWidget::onHover(const ExactCard &hoveredCard) { // If user has any card selected, ignore hover @@ -348,9 +351,43 @@ void VisualDeckEditorWidget::onHover(const ExactCard &hoveredCard) // highlightHoveredCard(hoveredCard); } -void VisualDeckEditorWidget::onCardClick(QMouseEvent *event, - CardInfoPictureWithTextOverlayWidget *instance, - QString zoneName) +void VisualDeckEditorWidget::setSelectionModel(QItemSelectionModel *model) { - emit cardClicked(event, instance, zoneName); + if (selectionModel == model) { + return; + } + + if (selectionModel) { + // TODO: Possibly disconnect old ones? + } + + selectionModel = model; + + if (selectionModel) { + connect(selectionModel, &QItemSelectionModel::selectionChanged, this, + &VisualDeckEditorWidget::onSelectionChanged); + } +} + +void VisualDeckEditorWidget::onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) +{ + for (auto &range : selected) { + for (int row = range.top(); row <= range.bottom(); ++row) { + QModelIndex idx = range.model()->index(row, 0, range.parent()); + auto it = indexToWidgetMap.find(QPersistentModelIndex(idx)); + if (it != indexToWidgetMap.end()) { + // it.value()->setHighlighted(true); + } + } + } + + for (auto &range : deselected) { + for (int row = range.top(); row <= range.bottom(); ++row) { + QModelIndex idx = range.model()->index(row, 0, range.parent()); + auto it = indexToWidgetMap.find(QPersistentModelIndex(idx)); + if (it != indexToWidgetMap.end()) { + // it.value()->setHighlighted(false); + } + } + } } diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h index 3a60d09a7..5789155b8 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h @@ -39,7 +39,6 @@ public: explicit VisualDeckEditorWidget(QWidget *parent, DeckListModel *deckListModel, QItemSelectionModel *selectionModel); void retranslateUi(); void clearAllDisplayWidgets(); - void resizeEvent(QResizeEvent *event) override; void setDeckList(const DeckList &_deckListModel); @@ -70,6 +69,13 @@ signals: void cardAdditionRequested(const ExactCard &card); void displayTypeChanged(DisplayType displayType); +protected: + void initializeSearchBarAndCompleter(); + void initializeDisplayOptionsWidget(); + void initializeDisplayOptionsAndSearchWidget(); + void initializeScrollAreaAndZoneContainer(); + void connectDeckListModel(); + protected slots: void onHover(const ExactCard &hoveredCard); void onCardClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName); @@ -91,7 +97,6 @@ private: QWidget *zoneContainer; QVBoxLayout *zoneContainerLayout; // OverlapControlWidget *overlapControlWidget; - QWidget *container; QHash indexToWidgetMap; }; From b19312be70e8014b7f6793a52571366cbb346e60 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 14 Jan 2026 00:48:26 -0800 Subject: [PATCH 134/325] [TabDeckEditor] Consolidate dockWidget management (#6499) --- .../widgets/tabs/abstract_tab_deck_editor.cpp | 18 +- .../widgets/tabs/abstract_tab_deck_editor.h | 26 +- .../widgets/tabs/tab_deck_editor.cpp | 246 ++++++------------ .../tab_deck_editor_visual.cpp | 209 +++++---------- 4 files changed, 178 insertions(+), 321 deletions(-) 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 53540b6f9..e41feb0c1 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -77,6 +77,20 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta &AbstractTabDeckEditor::refreshShortcuts); } +void AbstractTabDeckEditor::registerDockWidget(QDockWidget *widget) +{ + QMenu *menu = viewMenu->addMenu(QString()); + + QAction *aVisible = menu->addAction(QString()); + aVisible->setCheckable(true); + connect(aVisible, &QAction::triggered, this, &AbstractTabDeckEditor::dockVisibleTriggered); + QAction *aFloating = menu->addAction(QString()); + aFloating->setCheckable(true); + connect(aFloating, &QAction::triggered, this, &AbstractTabDeckEditor::dockFloatingTriggered); + + dockToActions.insert(widget, {menu, aVisible, aFloating}); +} + /** * @brief Updates the card info dock and printing selector. * @param card The card to display. @@ -167,8 +181,8 @@ void AbstractTabDeckEditor::setDeck(const LoadedDeck &_deck) deckStateManager->replaceDeck(_deck); CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(_deck.deckList.getCardRefList())); - aDeckDockVisible->setChecked(true); - deckDockWidget->setVisible(aDeckDockVisible->isChecked()); + dockToActions.value(deckDockWidget).aVisible->setChecked(true); + deckDockWidget->setVisible(dockToActions.value(deckDockWidget).aVisible->isChecked()); } /** @brief Creates a new deck. Handles opening in new tab if needed. */ diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h index 025a65cad..66a56f39b 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h @@ -275,6 +275,22 @@ protected: NEW_TAB ///< Open deck in a new tab }; + /** + * @brief The actions associated with managing a QDockWidget + */ + struct DockActions + { + QMenu *menu; ///< The menu containing the actions + QAction *aVisible; ///< The menu action that toggles visibility + QAction *aFloating; ///< The menu action that toggles floating + }; + + /** + * @brief registers a QDockWidget as a managed dock widget. Creates the associated actions and menu, adds them to + * the viewMenu, and connects those actions to the tab's slots. + */ + void registerDockWidget(QDockWidget *widget); + /** @brief Confirms deck open action based on settings and modified state. * @param openInSameTabIfBlank Whether to reuse same tab if blank. * @return Selected DeckOpenLocation. @@ -293,15 +309,11 @@ protected: virtual void openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation); // UI Menu Elements - QMenu *viewMenu, *cardInfoDockMenu, *cardDatabaseDockMenu, *deckDockMenu, *filterDockMenu, - *printingSelectorDockMenu; + QMenu *viewMenu; QAction *aResetLayout; - QAction *aCardInfoDockVisible, *aCardInfoDockFloating; - QAction *aCardDatabaseDockVisible, *aCardDatabaseDockFloating; - QAction *aDeckDockVisible, *aDeckDockFloating; - QAction *aFilterDockVisible, *aFilterDockFloating; - QAction *aPrintingSelectorDockVisible, *aPrintingSelectorDockFloating; + + QMap dockToActions; }; #endif // TAB_GENERIC_DECK_EDITOR_H diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index defd7827a..3da078dda 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -54,58 +54,18 @@ void TabDeckEditor::createMenus() viewMenu = new QMenu(this); - cardInfoDockMenu = viewMenu->addMenu(QString()); - cardDatabaseDockMenu = viewMenu->addMenu(QString()); - deckDockMenu = viewMenu->addMenu(QString()); - filterDockMenu = viewMenu->addMenu(QString()); - printingSelectorDockMenu = viewMenu->addMenu(QString()); - - // Card Info dock - aCardInfoDockVisible = cardInfoDockMenu->addAction(QString()); - aCardInfoDockVisible->setCheckable(true); - connect(aCardInfoDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered); - aCardInfoDockFloating = cardInfoDockMenu->addAction(QString()); - aCardInfoDockFloating->setCheckable(true); - connect(aCardInfoDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered); - - // Card Database dock - aCardDatabaseDockVisible = cardDatabaseDockMenu->addAction(QString()); - aCardDatabaseDockVisible->setCheckable(true); - connect(aCardDatabaseDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered); - aCardDatabaseDockFloating = cardDatabaseDockMenu->addAction(QString()); - aCardDatabaseDockFloating->setCheckable(true); - connect(aCardDatabaseDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered); - - // Deck dock - aDeckDockVisible = deckDockMenu->addAction(QString()); - aDeckDockVisible->setCheckable(true); - connect(aDeckDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered); - aDeckDockFloating = deckDockMenu->addAction(QString()); - aDeckDockFloating->setCheckable(true); - connect(aDeckDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered); - - // Filter dock - aFilterDockVisible = filterDockMenu->addAction(QString()); - aFilterDockVisible->setCheckable(true); - connect(aFilterDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered); - aFilterDockFloating = filterDockMenu->addAction(QString()); - aFilterDockFloating->setCheckable(true); - connect(aFilterDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered); - - // Printing selector dock - aPrintingSelectorDockVisible = printingSelectorDockMenu->addAction(QString()); - aPrintingSelectorDockVisible->setCheckable(true); - connect(aPrintingSelectorDockVisible, &QAction::triggered, this, &TabDeckEditor::dockVisibleTriggered); - aPrintingSelectorDockFloating = printingSelectorDockMenu->addAction(QString()); - aPrintingSelectorDockFloating->setCheckable(true); - connect(aPrintingSelectorDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered); + registerDockWidget(cardInfoDockWidget); + registerDockWidget(cardDatabaseDockWidget); + registerDockWidget(deckDockWidget); + registerDockWidget(filterDockWidget); + registerDockWidget(printingSelectorDockWidget); if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { - printingSelectorDockMenu->setEnabled(false); + dockToActions[printingSelectorDockWidget].menu->setEnabled(false); } connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this, - [this](bool enabled) { printingSelectorDockMenu->setEnabled(!enabled); }); + [this](bool enabled) { dockToActions[printingSelectorDockWidget].menu->setEnabled(!enabled); }); viewMenu->addSeparator(); @@ -141,22 +101,18 @@ void TabDeckEditor::retranslateUi() printingSelectorDockWidget->retranslateUi(); viewMenu->setTitle(tr("&View")); - cardInfoDockMenu->setTitle(tr("Card Info")); - cardDatabaseDockMenu->setTitle(tr("Card Database")); - deckDockMenu->setTitle(tr("Deck")); - filterDockMenu->setTitle(tr("Filters")); - printingSelectorDockMenu->setTitle(tr("Printing")); - aCardInfoDockVisible->setText(tr("Visible")); - aCardInfoDockFloating->setText(tr("Floating")); - aCardDatabaseDockVisible->setText(tr("Visible")); - aCardDatabaseDockFloating->setText(tr("Floating")); - aDeckDockVisible->setText(tr("Visible")); - aDeckDockFloating->setText(tr("Floating")); - aFilterDockVisible->setText(tr("Visible")); - aFilterDockFloating->setText(tr("Floating")); - aPrintingSelectorDockVisible->setText(tr("Visible")); - aPrintingSelectorDockFloating->setText(tr("Floating")); + dockToActions[cardInfoDockWidget].menu->setTitle(tr("Card Info")); + dockToActions[cardDatabaseDockWidget].menu->setTitle(tr("Card Database")); + dockToActions[deckDockWidget].menu->setTitle(tr("Deck")); + dockToActions[filterDockWidget].menu->setTitle(tr("Filters")); + dockToActions[printingSelectorDockWidget].menu->setTitle(tr("Printing")); + + for (auto &actions : dockToActions.values()) { + actions.aVisible->setText(tr("Visible")); + actions.aFloating->setText(tr("Floating")); + } + aResetLayout->setText(tr("Reset layout")); } @@ -174,7 +130,7 @@ void TabDeckEditor::showPrintingSelector() { printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr()); printingSelectorDockWidget->printingSelector->updateDisplay(); - aPrintingSelectorDockVisible->setChecked(true); + dockToActions[printingSelectorDockWidget].aVisible->setChecked(true); printingSelectorDockWidget->setVisible(true); } @@ -196,27 +152,22 @@ void TabDeckEditor::loadLayout() if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { if (!printingSelectorDockWidget->isHidden()) { printingSelectorDockWidget->setHidden(true); - aPrintingSelectorDockVisible->setChecked(false); + dockToActions[printingSelectorDockWidget].aVisible->setChecked(true); } } - aCardInfoDockVisible->setChecked(!cardInfoDockWidget->isHidden()); - aCardDatabaseDockVisible->setChecked(!cardDatabaseDockWidget->isHidden()); - aFilterDockVisible->setChecked(!filterDockWidget->isHidden()); - aDeckDockVisible->setChecked(!deckDockWidget->isHidden()); - aPrintingSelectorDockVisible->setChecked(!printingSelectorDockWidget->isHidden()); + for (auto it = dockToActions.begin(); it != dockToActions.end(); ++it) { + QDockWidget *dockWidget = it.key(); + const DockActions &actions = it.value(); - aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked()); - aCardDatabaseDockFloating->setChecked(aCardDatabaseDockVisible->isChecked()); - aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked()); - aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked()); - aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked()); + actions.aVisible->setChecked(!dockWidget->isHidden()); + actions.aFloating->setEnabled(actions.aVisible->isChecked()); + actions.aFloating->setChecked(dockWidget->isFloating()); + } - aCardInfoDockFloating->setChecked(cardInfoDockWidget->isFloating()); - aCardDatabaseDockFloating->setChecked(cardDatabaseDockWidget->isFloating()); - aFilterDockFloating->setChecked(filterDockWidget->isFloating()); - aDeckDockFloating->setChecked(deckDockWidget->isFloating()); - aPrintingSelectorDockFloating->setChecked(printingSelectorDockWidget->isFloating()); + // special case for cardDatabaseDock + auto &actions = dockToActions[cardDatabaseDockWidget]; + actions.aFloating->setChecked(actions.aVisible->isChecked()); cardInfoDockWidget->setMinimumSize(layouts.getDeckEditorCardSize()); cardInfoDockWidget->setMaximumSize(layouts.getDeckEditorCardSize()); @@ -240,17 +191,22 @@ void TabDeckEditor::restartLayout() { // Update menu checkboxes - aCardInfoDockVisible->setChecked(true); - aCardDatabaseDockVisible->setChecked(true); - aDeckDockVisible->setChecked(true); - aFilterDockVisible->setChecked(true); - aPrintingSelectorDockVisible->setChecked(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); + for (auto it = dockToActions.begin(); it != dockToActions.end(); ++it) { + QDockWidget *dockWidget = it.key(); + const DockActions &actions = it.value(); - aCardInfoDockFloating->setChecked(false); - aCardDatabaseDockFloating->setChecked(false); - aDeckDockFloating->setChecked(false); - aFilterDockFloating->setChecked(false); - aPrintingSelectorDockFloating->setChecked(false); + actions.aVisible->setEnabled(true); + actions.aFloating->setEnabled(false); + + // Show/hide and reset floating + dockWidget->setVisible(true); + dockWidget->setFloating(false); + } + + // Printing selector special case + dockToActions[printingSelectorDockWidget].aVisible->setChecked( + !SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); + printingSelectorDockWidget->setVisible(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); addDockWidget(Qt::LeftDockWidgetArea, cardDatabaseDockWidget); addDockWidget(Qt::RightDockWidgetArea, deckDockWidget); @@ -258,19 +214,6 @@ void TabDeckEditor::restartLayout() addDockWidget(Qt::RightDockWidgetArea, filterDockWidget); addDockWidget(Qt::RightDockWidgetArea, printingSelectorDockWidget); - // Show/hide and reset floating - cardDatabaseDockWidget->setFloating(false); - deckDockWidget->setFloating(false); - cardInfoDockWidget->setFloating(false); - filterDockWidget->setFloating(false); - printingSelectorDockWidget->setFloating(false); - - cardDatabaseDockWidget->setVisible(true); - deckDockWidget->setVisible(true); - cardInfoDockWidget->setVisible(true); - filterDockWidget->setVisible(true); - printingSelectorDockWidget->setVisible(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); - splitDockWidget(cardInfoDockWidget, printingSelectorDockWidget, Qt::Horizontal); splitDockWidget(printingSelectorDockWidget, deckDockWidget, Qt::Horizontal); splitDockWidget(cardInfoDockWidget, printingSelectorDockWidget, Qt::Horizontal); @@ -285,41 +228,26 @@ void TabDeckEditor::freeDocksSize() const QSize minSize(100, 100); const QSize maxSize(5000, 5000); - deckDockWidget->setMinimumSize(minSize); - deckDockWidget->setMaximumSize(maxSize); - - cardDatabaseDockWidget->setMinimumSize(minSize); - cardDatabaseDockWidget->setMaximumSize(maxSize); - - cardInfoDockWidget->setMinimumSize(minSize); - cardInfoDockWidget->setMaximumSize(maxSize); - - filterDockWidget->setMinimumSize(minSize); - filterDockWidget->setMaximumSize(maxSize); - - printingSelectorDockWidget->setMinimumSize(minSize); - printingSelectorDockWidget->setMaximumSize(maxSize); + for (auto dockWidget : dockToActions.keys()) { + dockWidget->setMinimumSize(minSize); + dockWidget->setMaximumSize(maxSize); + } } /** @brief Handles dock visibility toggling from menu actions. */ void TabDeckEditor::dockVisibleTriggered() { QObject *o = sender(); - if (o == aCardInfoDockVisible) { - cardInfoDockWidget->setHidden(!aCardInfoDockVisible->isChecked()); - aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked()); - } else if (o == aCardDatabaseDockVisible) { - cardDatabaseDockWidget->setHidden(!aCardDatabaseDockVisible->isChecked()); - aCardDatabaseDockFloating->setEnabled(aCardDatabaseDockVisible->isChecked()); - } else if (o == aDeckDockVisible) { - deckDockWidget->setHidden(!aDeckDockVisible->isChecked()); - aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked()); - } else if (o == aFilterDockVisible) { - filterDockWidget->setHidden(!aFilterDockVisible->isChecked()); - aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked()); - } else if (o == aPrintingSelectorDockVisible) { - printingSelectorDockWidget->setHidden(!aPrintingSelectorDockVisible->isChecked()); - aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked()); + + for (auto it = dockToActions.begin(); it != dockToActions.end(); ++it) { + QDockWidget *dockWidget = it.key(); + const DockActions &actions = it.value(); + + if (o == actions.aVisible) { + dockWidget->setHidden(!actions.aVisible->isChecked()); + actions.aFloating->setEnabled(actions.aVisible->isChecked()); + return; + } } } @@ -327,32 +255,28 @@ void TabDeckEditor::dockVisibleTriggered() void TabDeckEditor::dockFloatingTriggered() { QObject *o = sender(); - if (o == aCardInfoDockFloating) - cardInfoDockWidget->setFloating(aCardInfoDockFloating->isChecked()); - else if (o == aCardDatabaseDockFloating) - cardDatabaseDockWidget->setFloating(aCardDatabaseDockFloating->isChecked()); - else if (o == aDeckDockFloating) - deckDockWidget->setFloating(aDeckDockFloating->isChecked()); - else if (o == aFilterDockFloating) - filterDockWidget->setFloating(aFilterDockFloating->isChecked()); - else if (o == aPrintingSelectorDockFloating) - printingSelectorDockWidget->setFloating(aPrintingSelectorDockFloating->isChecked()); + + for (auto it = dockToActions.begin(); it != dockToActions.end(); ++it) { + QDockWidget *dockWidget = it.key(); + const DockActions &actions = it.value(); + + if (o == actions.aFloating) { + dockWidget->setFloating(actions.aFloating->isChecked()); + return; + } + } } /** @brief Syncs menu state with dock floating changes. */ void TabDeckEditor::dockTopLevelChanged(bool topLevel) { QObject *o = sender(); - if (o == cardInfoDockWidget) - aCardInfoDockFloating->setChecked(topLevel); - else if (o == aCardDatabaseDockFloating) - aCardDatabaseDockFloating->setChecked(topLevel); - else if (o == deckDockWidget) - aDeckDockFloating->setChecked(topLevel); - else if (o == filterDockWidget) - aFilterDockFloating->setChecked(topLevel); - else if (o == printingSelectorDockWidget) - aPrintingSelectorDockFloating->setChecked(topLevel); + + auto dockWidget = qobject_cast(o); + if (dockToActions.contains(dockWidget)) { + DockActions actions = dockToActions.value(dockWidget); + actions.aFloating->setChecked(topLevel); + } } /** @@ -364,21 +288,11 @@ void TabDeckEditor::dockTopLevelChanged(bool topLevel) bool TabDeckEditor::eventFilter(QObject *o, QEvent *e) { if (e->type() == QEvent::Close) { - if (o == cardInfoDockWidget) { - aCardInfoDockVisible->setChecked(false); - aCardInfoDockFloating->setEnabled(false); - } else if (o == cardDatabaseDockWidget) { - aCardDatabaseDockVisible->setChecked(false); - aCardDatabaseDockFloating->setEnabled(false); - } else if (o == deckDockWidget) { - aDeckDockVisible->setChecked(false); - aDeckDockFloating->setEnabled(false); - } else if (o == filterDockWidget) { - aFilterDockVisible->setChecked(false); - aFilterDockFloating->setEnabled(false); - } else if (o == printingSelectorDockWidget) { - aPrintingSelectorDockVisible->setChecked(false); - aPrintingSelectorDockFloating->setEnabled(false); + auto dockWidget = qobject_cast(o); + if (dockToActions.contains(dockWidget)) { + DockActions actions = dockToActions.value(dockWidget); + actions.aVisible->setChecked(false); + actions.aFloating->setEnabled(false); } } diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index b24013319..8a92e1bc2 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -97,45 +97,17 @@ void TabDeckEditorVisual::createMenus() viewMenu = new QMenu(this); - cardInfoDockMenu = viewMenu->addMenu(QString()); - deckDockMenu = viewMenu->addMenu(QString()); - filterDockMenu = viewMenu->addMenu(QString()); - printingSelectorDockMenu = viewMenu->addMenu(QString()); - - aCardInfoDockVisible = cardInfoDockMenu->addAction(QString()); - aCardInfoDockVisible->setCheckable(true); - connect(aCardInfoDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); - aCardInfoDockFloating = cardInfoDockMenu->addAction(QString()); - aCardInfoDockFloating->setCheckable(true); - connect(aCardInfoDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); - - aDeckDockVisible = deckDockMenu->addAction(QString()); - aDeckDockVisible->setCheckable(true); - connect(aDeckDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); - aDeckDockFloating = deckDockMenu->addAction(QString()); - aDeckDockFloating->setCheckable(true); - connect(aDeckDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); - - aFilterDockVisible = filterDockMenu->addAction(QString()); - aFilterDockVisible->setCheckable(true); - connect(aFilterDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); - aFilterDockFloating = filterDockMenu->addAction(QString()); - aFilterDockFloating->setCheckable(true); - connect(aFilterDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); - - aPrintingSelectorDockVisible = printingSelectorDockMenu->addAction(QString()); - aPrintingSelectorDockVisible->setCheckable(true); - connect(aPrintingSelectorDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); - aPrintingSelectorDockFloating = printingSelectorDockMenu->addAction(QString()); - aPrintingSelectorDockFloating->setCheckable(true); - connect(aPrintingSelectorDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); + registerDockWidget(cardInfoDockWidget); + registerDockWidget(deckDockWidget); + registerDockWidget(filterDockWidget); + registerDockWidget(printingSelectorDockWidget); if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { - printingSelectorDockMenu->setEnabled(false); + dockToActions[printingSelectorDockWidget].menu->setEnabled(false); } connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this, - [this](bool enabled) { printingSelectorDockMenu->setEnabled(!enabled); }); + [this](bool enabled) { dockToActions[printingSelectorDockWidget].menu->setEnabled(!enabled); }); viewMenu->addSeparator(); @@ -269,7 +241,7 @@ void TabDeckEditorVisual::showPrintingSelector() { printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr()); printingSelectorDockWidget->printingSelector->updateDisplay(); - aPrintingSelectorDockVisible->setChecked(true); + dockToActions[printingSelectorDockWidget].aVisible->setChecked(true); printingSelectorDockWidget->setVisible(true); } @@ -311,24 +283,18 @@ void TabDeckEditorVisual::loadLayout() if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { if (!printingSelectorDockWidget->isHidden()) { printingSelectorDockWidget->setHidden(true); - aPrintingSelectorDockVisible->setChecked(false); + dockToActions[printingSelectorDockWidget].aVisible->setChecked(false); } } - aCardInfoDockVisible->setChecked(!cardInfoDockWidget->isHidden()); - aFilterDockVisible->setChecked(!filterDockWidget->isHidden()); - aDeckDockVisible->setChecked(!deckDockWidget->isHidden()); - aPrintingSelectorDockVisible->setChecked(!printingSelectorDockWidget->isHidden()); + for (auto it = dockToActions.begin(); it != dockToActions.end(); ++it) { + QDockWidget *dockWidget = it.key(); + const DockActions &actions = it.value(); - aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked()); - aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked()); - aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked()); - aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked()); - - aCardInfoDockFloating->setChecked(cardInfoDockWidget->isFloating()); - aFilterDockFloating->setChecked(filterDockWidget->isFloating()); - aDeckDockFloating->setChecked(deckDockWidget->isFloating()); - aPrintingSelectorDockFloating->setChecked(printingSelectorDockWidget->isFloating()); + actions.aVisible->setCheckable(dockWidget->isHidden()); + actions.aFloating->setEnabled(actions.aVisible->isChecked()); + actions.aFloating->setChecked(dockWidget->isFloating()); + } cardInfoDockWidget->setMinimumSize(layouts.getDeckEditorCardSize()); cardInfoDockWidget->setMaximumSize(layouts.getDeckEditorCardSize()); @@ -348,31 +314,30 @@ void TabDeckEditorVisual::loadLayout() /** @brief Resets the layout to default positions and dock states. */ void TabDeckEditorVisual::restartLayout() { - aCardInfoDockVisible->setChecked(true); - aDeckDockVisible->setChecked(true); - aFilterDockVisible->setChecked(false); - aPrintingSelectorDockVisible->setChecked(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); + for (auto it = dockToActions.begin(); it != dockToActions.end(); ++it) { + QDockWidget *dockWidget = it.key(); + const DockActions &actions = it.value(); - aCardInfoDockFloating->setChecked(false); - aDeckDockFloating->setChecked(false); - aFilterDockFloating->setChecked(false); - aPrintingSelectorDockFloating->setChecked(false); + actions.aFloating->setEnabled(false); + dockWidget->setFloating(false); + } - setCentralWidget(centralWidget); - addDockWidget(Qt::RightDockWidgetArea, deckDockWidget); - addDockWidget(Qt::RightDockWidgetArea, cardInfoDockWidget); - addDockWidget(Qt::RightDockWidgetArea, filterDockWidget); - addDockWidget(Qt::RightDockWidgetArea, printingSelectorDockWidget); + dockToActions[cardInfoDockWidget].aVisible->setChecked(true); + dockToActions[deckDockWidget].aVisible->setChecked(true); + dockToActions[filterDockWidget].aVisible->setChecked(false); + dockToActions[printingSelectorDockWidget].aVisible->setChecked( + !SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); deckDockWidget->setVisible(true); cardInfoDockWidget->setVisible(true); filterDockWidget->setVisible(false); printingSelectorDockWidget->setVisible(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); - deckDockWidget->setFloating(false); - cardInfoDockWidget->setFloating(false); - filterDockWidget->setFloating(false); - printingSelectorDockWidget->setFloating(false); + setCentralWidget(centralWidget); + addDockWidget(Qt::RightDockWidgetArea, deckDockWidget); + addDockWidget(Qt::RightDockWidgetArea, cardInfoDockWidget); + addDockWidget(Qt::RightDockWidgetArea, filterDockWidget); + addDockWidget(Qt::RightDockWidgetArea, printingSelectorDockWidget); splitDockWidget(cardInfoDockWidget, printingSelectorDockWidget, Qt::Vertical); splitDockWidget(cardInfoDockWidget, deckDockWidget, Qt::Horizontal); @@ -391,22 +356,16 @@ void TabDeckEditorVisual::retranslateUi() filterDockWidget->setWindowTitle(tr("Filters")); viewMenu->setTitle(tr("&View")); - cardInfoDockMenu->setTitle(tr("Card Info")); - deckDockMenu->setTitle(tr("Deck")); - filterDockMenu->setTitle(tr("Filters")); - printingSelectorDockMenu->setTitle(tr("Printing")); - aCardInfoDockVisible->setText(tr("Visible")); - aCardInfoDockFloating->setText(tr("Floating")); + dockToActions[cardInfoDockWidget].menu->setTitle(tr("Card Info")); + dockToActions[deckDockWidget].menu->setTitle(tr("Deck")); + dockToActions[filterDockWidget].menu->setTitle(tr("Filters")); + dockToActions[printingSelectorDockWidget].menu->setTitle(tr("Printing")); - aDeckDockVisible->setText(tr("Visible")); - aDeckDockFloating->setText(tr("Floating")); - - aFilterDockVisible->setText(tr("Visible")); - aFilterDockFloating->setText(tr("Floating")); - - aPrintingSelectorDockVisible->setText(tr("Visible")); - aPrintingSelectorDockFloating->setText(tr("Floating")); + for (auto &actions : dockToActions.values()) { + actions.aVisible->setText(tr("Visible")); + actions.aFloating->setText(tr("Floating")); + } aResetLayout->setText(tr("Reset layout")); } @@ -419,18 +378,11 @@ void TabDeckEditorVisual::retranslateUi() bool TabDeckEditorVisual::eventFilter(QObject *o, QEvent *e) { if (e->type() == QEvent::Close) { - if (o == cardInfoDockWidget) { - aCardInfoDockVisible->setChecked(false); - aCardInfoDockFloating->setEnabled(false); - } else if (o == deckDockWidget) { - aDeckDockVisible->setChecked(false); - aDeckDockFloating->setEnabled(false); - } else if (o == filterDockWidget) { - aFilterDockVisible->setChecked(false); - aFilterDockFloating->setEnabled(false); - } else if (o == printingSelectorDockWidget) { - aPrintingSelectorDockVisible->setChecked(false); - aPrintingSelectorDockFloating->setEnabled(false); + auto dockWidget = qobject_cast(o); + if (dockToActions.contains(dockWidget)) { + DockActions actions = dockToActions.value(dockWidget); + actions.aVisible->setChecked(false); + actions.aFloating->setEnabled(false); } } @@ -450,28 +402,15 @@ bool TabDeckEditorVisual::eventFilter(QObject *o, QEvent *e) void TabDeckEditorVisual::dockVisibleTriggered() { QObject *o = sender(); - if (o == aCardInfoDockVisible) { - cardInfoDockWidget->setHidden(!aCardInfoDockVisible->isChecked()); - aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked()); - return; - } + for (auto it = dockToActions.begin(); it != dockToActions.end(); ++it) { + QDockWidget *dockWidget = it.key(); + const DockActions &actions = it.value(); - if (o == aDeckDockVisible) { - deckDockWidget->setHidden(!aDeckDockVisible->isChecked()); - aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked()); - return; - } - - if (o == aFilterDockVisible) { - filterDockWidget->setHidden(!aFilterDockVisible->isChecked()); - aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked()); - return; - } - - if (o == aPrintingSelectorDockVisible) { - printingSelectorDockWidget->setHidden(!aPrintingSelectorDockVisible->isChecked()); - aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked()); - return; + if (o == actions.aVisible) { + dockWidget->setHidden(!actions.aVisible->isChecked()); + actions.aFloating->setEnabled(actions.aVisible->isChecked()); + return; + } } } @@ -479,24 +418,15 @@ void TabDeckEditorVisual::dockVisibleTriggered() void TabDeckEditorVisual::dockFloatingTriggered() { QObject *o = sender(); - if (o == aCardInfoDockFloating) { - cardInfoDockWidget->setFloating(aCardInfoDockFloating->isChecked()); - return; - } - if (o == aDeckDockFloating) { - deckDockWidget->setFloating(aDeckDockFloating->isChecked()); - return; - } + for (auto it = dockToActions.begin(); it != dockToActions.end(); ++it) { + QDockWidget *dockWidget = it.key(); + const DockActions &actions = it.value(); - if (o == aFilterDockFloating) { - filterDockWidget->setFloating(aFilterDockFloating->isChecked()); - return; - } - - if (o == aPrintingSelectorDockFloating) { - printingSelectorDockWidget->setFloating(aPrintingSelectorDockFloating->isChecked()); - return; + if (o == actions.aFloating) { + dockWidget->setFloating(actions.aFloating->isChecked()); + return; + } } } @@ -504,23 +434,10 @@ void TabDeckEditorVisual::dockFloatingTriggered() void TabDeckEditorVisual::dockTopLevelChanged(bool topLevel) { QObject *o = sender(); - if (o == cardInfoDockWidget) { - aCardInfoDockFloating->setChecked(topLevel); - return; - } - if (o == deckDockWidget) { - aDeckDockFloating->setChecked(topLevel); - return; - } - - if (o == filterDockWidget) { - aFilterDockFloating->setChecked(topLevel); - return; - } - - if (o == printingSelectorDockWidget) { - aPrintingSelectorDockFloating->setChecked(topLevel); - return; + auto dockWidget = qobject_cast(o); + if (dockToActions.contains(dockWidget)) { + DockActions actions = dockToActions.value(dockWidget); + actions.aFloating->setChecked(topLevel); } } From cc5e2ab10aab45be5f93c47ec6ecc9acd16bbc78 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Wed, 14 Jan 2026 10:05:38 +0100 Subject: [PATCH 135/325] [VDE] Change sort quick settings button icon from gear to sort arrow (#6514) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [VDE] Change sort quick settings button icon from gear to sort arrow Took 12 minutes * Actually include the icon. Took 4 minutes Took 13 seconds --------- Co-authored-by: Lukas Brübach --- cockatrice/cockatrice.qrc | 1 + cockatrice/resources/icons/sort_arrow_down.svg | 1 + .../visual_deck_editor/visual_deck_display_options_widget.cpp | 1 + 3 files changed, 3 insertions(+) create mode 100644 cockatrice/resources/icons/sort_arrow_down.svg diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc index 99f5bff23..0f7bca2f1 100644 --- a/cockatrice/cockatrice.qrc +++ b/cockatrice/cockatrice.qrc @@ -38,6 +38,7 @@ resources/icons/search.svg resources/icons/settings.svg resources/icons/share.svg + resources/icons/sort_arrow_down.svg resources/icons/spectator.svg resources/icons/swap.svg resources/icons/sync.svg diff --git a/cockatrice/resources/icons/sort_arrow_down.svg b/cockatrice/resources/icons/sort_arrow_down.svg new file mode 100644 index 000000000..adf10f9f0 --- /dev/null +++ b/cockatrice/resources/icons/sort_arrow_down.svg @@ -0,0 +1 @@ + \ No newline at end of file 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 b169e73cb..f0bee5d7d 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 @@ -45,6 +45,7 @@ VisualDeckDisplayOptionsWidget::VisualDeckDisplayOptionsWidget(QWidget *parent) sortByLabel = new QLabel(this); sortCriteriaButton = new SettingsButtonWidget(this); + sortCriteriaButton->setButtonIcon(QPixmap("theme:icons/sort_arrow_down")); sortLabel = new QLabel(sortCriteriaButton); sortLabel->setWordWrap(true); From ed1115f4c06e18d6d9c46822b6a8973317a97690 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Wed, 14 Jan 2026 10:05:45 +0100 Subject: [PATCH 136/325] [HomeTab] Add setting to display card info in bottom right for non-theme backgrounds (#6513) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [HomeTab] Add setting to display card info in bottom right for non-theme backgrounds Took 43 minutes Took 9 seconds * [HomeTab] Also hide shuffle frequency setting on theme background source. Took 3 minutes --------- Co-authored-by: Lukas Brübach --- .../src/client/settings/cache_settings.cpp | 8 ++++ .../src/client/settings/cache_settings.h | 7 +++ .../widgets/dialogs/dlg_settings.cpp | 21 +++++++++ .../interface/widgets/dialogs/dlg_settings.h | 3 ++ .../interface/widgets/general/home_widget.cpp | 45 +++++++++++++++++-- 5 files changed, 80 insertions(+), 4 deletions(-) diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 2ad1f1ece..2f0eb98dc 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -239,6 +239,7 @@ SettingsCache::SettingsCache() homeTabBackgroundSource = settings->value("home/background", "themed").toString(); homeTabBackgroundShuffleFrequency = settings->value("home/background/shuffleTimer", 0).toInt(); + homeTabDisplayCardName = settings->value("home/background/displayCardName", true).toBool(); tabVisualDeckStorageOpen = settings->value("tabs/visualDeckStorage", true).toBool(); tabServerOpen = settings->value("tabs/server", true).toBool(); @@ -594,6 +595,13 @@ void SettingsCache::setHomeTabBackgroundShuffleFrequency(int _frequency) emit homeTabBackgroundShuffleFrequencyChanged(); } +void SettingsCache::setHomeTabDisplayCardName(QT_STATE_CHANGED_T _displayCardName) +{ + homeTabDisplayCardName = static_cast(_displayCardName); + settings->setValue("home/background/displayCardName", homeTabDisplayCardName); + emit homeTabDisplayCardNameChanged(); +} + void SettingsCache::setTabVisualDeckStorageOpen(bool value) { tabVisualDeckStorageOpen = value; diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index ca9a2a39b..f4e1000be 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -143,6 +143,7 @@ signals: void themeChanged(); void homeTabBackgroundSourceChanged(); void homeTabBackgroundShuffleFrequencyChanged(); + void homeTabDisplayCardNameChanged(); void picDownloadChanged(); void showStatusBarChanged(bool state); void showGameSelectorFilterToolbarChanged(bool state); @@ -222,6 +223,7 @@ private: bool showTipsOnStartup; QList seenTips; int homeTabBackgroundShuffleFrequency; + bool homeTabDisplayCardName; bool mbDownloadSpoilers; int updateReleaseChannel; int maxFontSize; @@ -413,6 +415,10 @@ public: { return homeTabBackgroundShuffleFrequency; } + [[nodiscard]] bool getHomeTabDisplayCardName() const + { + return homeTabDisplayCardName; + } [[nodiscard]] bool getTabVisualDeckStorageOpen() const { return tabVisualDeckStorageOpen; @@ -1001,6 +1007,7 @@ public slots: void setThemeName(const QString &_themeName); void setHomeTabBackgroundSource(const QString &_backgroundSource); void setHomeTabBackgroundShuffleFrequency(int _frequency); + void setHomeTabDisplayCardName(QT_STATE_CHANGED_T _displayCardName); void setTabVisualDeckStorageOpen(bool value); void setTabServerOpen(bool value); void setTabAccountOpen(bool value); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp index fe8840a41..e97ca8921 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp @@ -438,6 +438,7 @@ AppearanceSettingsPage::AppearanceSettingsPage() connect(&homeTabBackgroundSourceBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { auto type = homeTabBackgroundSourceBox.currentData().value(); SettingsCache::instance().setHomeTabBackgroundSource(BackgroundSources::toId(type)); + updateHomeTabSettingsVisibility(); }); homeTabBackgroundShuffleFrequencySpinBox.setRange(0, 3600); @@ -446,6 +447,12 @@ AppearanceSettingsPage::AppearanceSettingsPage() connect(&homeTabBackgroundShuffleFrequencySpinBox, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), &SettingsCache::setHomeTabBackgroundShuffleFrequency); + homeTabDisplayCardNameCheckBox.setChecked(settings.getHomeTabDisplayCardName()); + connect(&homeTabDisplayCardNameCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setHomeTabDisplayCardName); + + updateHomeTabSettingsVisibility(); + auto *themeGrid = new QGridLayout; themeGrid->addWidget(&themeLabel, 0, 0); themeGrid->addWidget(&themeBox, 0, 1); @@ -454,6 +461,8 @@ AppearanceSettingsPage::AppearanceSettingsPage() themeGrid->addWidget(&homeTabBackgroundSourceBox, 2, 1); themeGrid->addWidget(&homeTabBackgroundShuffleFrequencyLabel, 3, 0); themeGrid->addWidget(&homeTabBackgroundShuffleFrequencySpinBox, 3, 1); + themeGrid->addWidget(&homeTabDisplayCardNameLabel, 4, 0); + themeGrid->addWidget(&homeTabDisplayCardNameCheckBox, 4, 1); themeGroupBox = new QGroupBox; themeGroupBox->setLayout(themeGrid); @@ -650,6 +659,17 @@ void AppearanceSettingsPage::openThemeLocation() } } +void AppearanceSettingsPage::updateHomeTabSettingsVisibility() +{ + bool visible = + SettingsCache::instance().getHomeTabBackgroundSource() != BackgroundSources::toId(BackgroundSources::Theme); + + homeTabBackgroundShuffleFrequencyLabel.setVisible(visible); + homeTabBackgroundShuffleFrequencySpinBox.setVisible(visible); + homeTabDisplayCardNameLabel.setVisible(visible); + homeTabDisplayCardNameCheckBox.setVisible(visible); +} + void AppearanceSettingsPage::showShortcutsChanged(QT_STATE_CHANGED_T value) { SettingsCache::instance().setShowShortcuts(value); @@ -729,6 +749,7 @@ void AppearanceSettingsPage::retranslateUi() homeTabBackgroundSourceLabel.setText(tr("Home tab background source:")); homeTabBackgroundShuffleFrequencyLabel.setText(tr("Home tab background shuffle frequency:")); homeTabBackgroundShuffleFrequencySpinBox.setSpecialValueText(tr("Disabled")); + homeTabDisplayCardNameLabel.setText(tr("Display card name of background in bottom right:")); menuGroupBox->setTitle(tr("Menu settings")); showShortcutsCheckBox.setText(tr("Show keyboard shortcuts in right-click menus")); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h index 06ad3601c..db107c6e2 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h @@ -103,6 +103,7 @@ class AppearanceSettingsPage : public AbstractSettingsPage private slots: void themeBoxChanged(int index); void openThemeLocation(); + void updateHomeTabSettingsVisibility(); void showShortcutsChanged(QT_STATE_CHANGED_T enabled); void overrideAllCardArtWithPersonalPreferenceToggled(QT_STATE_CHANGED_T enabled); @@ -117,6 +118,8 @@ private: QComboBox homeTabBackgroundSourceBox; QLabel homeTabBackgroundShuffleFrequencyLabel; QSpinBox homeTabBackgroundShuffleFrequencySpinBox; + QLabel homeTabDisplayCardNameLabel; + QCheckBox homeTabDisplayCardNameCheckBox; QLabel minPlayersForMultiColumnLayoutLabel; QLabel maxFontSizeForCardsLabel; QCheckBox showShortcutsCheckBox; diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index f35f90256..803a93108 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -44,6 +44,8 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor) &HomeWidget::initializeBackgroundFromSource); connect(&SettingsCache::instance(), &SettingsCache::homeTabBackgroundShuffleFrequencyChanged, this, &HomeWidget::onBackgroundShuffleFrequencyChanged); + // Lambda is cleaner to read than overloading this + connect(&SettingsCache::instance(), &SettingsCache::homeTabDisplayCardNameChanged, this, [this] { repaint(); }); } void HomeWidget::initializeBackgroundFromSource() @@ -297,6 +299,7 @@ QPair HomeWidget::extractDominantColors(const QPixmap &pixmap) void HomeWidget::paintEvent(QPaintEvent *event) { QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); background = background.scaled(size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); @@ -308,13 +311,47 @@ void HomeWidget::paintEvent(QPaintEvent *event) painter.drawPixmap(topLeft, background); // Draw translucent black overlay with rounded corners - QRectF overlayRect(5, 5, width() - 10, height() - 10); // 5px inset + QRectF overlayRect(5, 5, width() - 10, height() - 10); QPainterPath roundedRectPath; - roundedRectPath.addRoundedRect(overlayRect, 20, 20); // 20px corner radius + roundedRectPath.addRoundedRect(overlayRect, 20, 20); - QColor semiTransparentBlack(0, 0, 0, static_cast(255 * 0.33)); // 33% opacity - painter.setRenderHint(QPainter::Antialiasing); + QColor semiTransparentBlack(0, 0, 0, static_cast(255 * 0.33)); painter.fillPath(roundedRectPath, semiTransparentBlack); + // Card name overlay (bottom-right) + QString cardName; + ExactCard card = backgroundSourceCard->getCard(); + if (card) { + cardName = card.getCardPtr()->getName() + " (" + card.getPrinting().getSet()->getCorrectedShortName() + ") " + + card.getPrinting().getProperty("num"); + } + + if (!cardName.isEmpty() && SettingsCache::instance().getHomeTabDisplayCardName()) { + QFont font = painter.font(); + font.setPointSize(14); + font.setBold(true); + painter.setFont(font); + + QFontMetrics fm(font); + constexpr int padding = 10; + constexpr int margin = 15; + + QRect textRect = fm.boundingRect(cardName); + + QRect bgRect(width() - textRect.width() - padding * 2 - margin, + height() - textRect.height() - padding * 2 - margin, textRect.width() + padding * 2, + textRect.height() + padding * 2); + + // Background bubble + painter.setPen(Qt::NoPen); + painter.setBrush(QColor(0, 0, 0, 160)); + painter.drawRoundedRect(bgRect, 8, 8); + + // Text + painter.setPen(Qt::white); + painter.drawText(bgRect.adjusted(padding, padding, -padding, -padding), Qt::AlignRight | Qt::AlignVCenter, + cardName); + } + QWidget::paintEvent(event); } From 21d60ec3f11b88b26d04d58ab916f9476a3f35f5 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 14 Jan 2026 01:49:07 -0800 Subject: [PATCH 137/325] Reduce padding in settings popup (#6504) * Reduce padding in settings popup * reduce padding in PrintingSelectors settings * reduce padding in one of the VDD filter settings --- .../printing_selector/printing_selector_card_sorting_widget.cpp | 1 + .../interface/widgets/quick_settings/settings_popup_widget.cpp | 1 + .../visual_database_display_set_filter_widget.cpp | 2 ++ 3 files changed, 4 insertions(+) diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp index 725e5df90..c04d6bbf1 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp @@ -21,6 +21,7 @@ PrintingSelectorCardSortingWidget::PrintingSelectorCardSortingWidget(PrintingSel { setMinimumWidth(300); sortToolBar = new QHBoxLayout(this); + sortToolBar->setContentsMargins(11, 0, 11, 0); sortOptionsSelector = new QComboBox(this); sortOptionsSelector->setFocusPolicy(Qt::StrongFocus); diff --git a/cockatrice/src/interface/widgets/quick_settings/settings_popup_widget.cpp b/cockatrice/src/interface/widgets/quick_settings/settings_popup_widget.cpp index 9b2e55e0f..8389caea9 100644 --- a/cockatrice/src/interface/widgets/quick_settings/settings_popup_widget.cpp +++ b/cockatrice/src/interface/widgets/quick_settings/settings_popup_widget.cpp @@ -10,6 +10,7 @@ SettingsPopupWidget::SettingsPopupWidget(QWidget *parent) : QWidget(parent, Qt:: { // Main layout for the popup itself layout = new QVBoxLayout(this); + layout->setContentsMargins(5, 5, 5, 5); // Container for the content (with or without scroll area) containerWidget = new QWidget(); diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp index dee89f06a..6e621e6d4 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp @@ -14,6 +14,8 @@ VisualDatabaseDisplayRecentSetFilterSettingsWidget::VisualDatabaseDisplayRecentS : QWidget(parent) { layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + setLayout(layout); filterToMostRecentSetsCheckBox = new QCheckBox(this); From 289b139be98d4c1799e46e647ddd4aaa08f2ae21 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:25:45 +0100 Subject: [PATCH 138/325] [DeckAnalytics] Enforce WUBRGC ordering for analytics. (#6509) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [DeckAnalytics] Enforce WUBRGC ordering for analytics. Took 6 minutes Took 7 seconds * Include QSet Took 51 seconds * Move include out of namespace. Took 6 minutes --------- Co-authored-by: Lukas Brübach --- .../mana_base/mana_base_widget.cpp | 15 +++-- .../general/display/charts/bars/color_bar.cpp | 36 ++++++----- .../general/display/charts/bars/color_bar.h | 2 +- .../general/display/charts/pies/color_pie.cpp | 61 ++++++++++--------- .../general/display/charts/pies/color_pie.h | 2 +- .../libcockatrice/utility/color.h | 28 +++++++++ 6 files changed, 87 insertions(+), 57 deletions(-) diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp index d38314b8c..0093c360b 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp @@ -8,6 +8,7 @@ #include #include +#include namespace { @@ -71,14 +72,16 @@ void ManaBaseWidget::updateDisplay() // Choose display mode if (config.displayType == "bar") { - QHash colors = {{"W", QColor(248, 231, 185)}, {"U", QColor(14, 104, 171)}, - {"B", QColor(21, 11, 0)}, {"R", QColor(211, 32, 42)}, - {"G", QColor(0, 115, 62)}, {"C", QColor(150, 150, 150)}}; + const QList> sortedColors = GameSpecificColors::MTG::sortManaMapWUBRGCFirst(mapSorted); + static const QHash colorMap = { + {"W", QColor(248, 231, 185)}, {"U", QColor(14, 104, 171)}, {"B", QColor(21, 11, 0)}, + {"R", QColor(211, 32, 42)}, {"G", QColor(0, 115, 62)}, {"C", QColor(150, 150, 150)}, + }; - for (auto color : manaMap.keys()) { - QString label = QString("%1 %2 (%3)").arg(color).arg(manaMap[color]).arg(cardCount.value(color)); + for (const auto &[color, count] : sortedColors) { + QString label = QString("%1 %2 (%3)").arg(color).arg(count).arg(cardCount.value(color)); - BarWidget *bar = new BarWidget(label, manaMap[color], highest, colors.value(color, Qt::gray), this); + BarWidget *bar = new BarWidget(label, count, highest, colorMap.value(color, Qt::gray), this); barLayout->addWidget(bar); } diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.cpp index 94e2420b5..ed91ba03d 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.cpp +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.cpp @@ -1,18 +1,21 @@ #include "color_bar.h" +#include "libcockatrice/utility/color.h" + #include #include #include #include -ColorBar::ColorBar(const QMap &_colors, QWidget *parent) : QWidget(parent), colors(_colors) +ColorBar::ColorBar(const QMap &_colors, QWidget *parent) + : QWidget(parent), colors(GameSpecificColors::MTG::sortManaMapWUBRGCFirst(_colors)) { setMouseTracking(true); } void ColorBar::setColors(const QMap &_colors) { - colors = _colors; + colors = GameSpecificColors::MTG::sortManaMapWUBRGCFirst(_colors); update(); } @@ -27,8 +30,8 @@ void ColorBar::paintEvent(QPaintEvent *) return; int total = 0; - for (int v : colors.values()) - total += v; + for (const auto &pair : colors) + total += pair.second; // Prevent divide-by-zero if (total == 0) @@ -50,15 +53,9 @@ void ColorBar::paintEvent(QPaintEvent *) // Clip to inside the border p.setClipRect(bounds.adjusted(2, 2, -2, -2)); - // Ensure predictable order - QList sortedKeys = colors.keys(); - std::sort(sortedKeys.begin(), sortedKeys.end()); // Sort alphabetically - - // Draw each color segment in the sorted order - for (const QString &key : sortedKeys) { - int value = colors[key]; - double ratio = double(value) / total; - + // Draw segments IN ORDER + for (const auto &[key, value] : colors) { + const double ratio = double(value) / total; if (ratio <= minRatioThreshold) { continue; } @@ -122,20 +119,21 @@ void ColorBar::mouseMoveEvent(QMouseEvent *event) QString ColorBar::tooltipForPosition(int x) const { int total = 0; - for (int v : colors.values()) - total += v; + for (const auto &pair : colors) + total += pair.second; if (total == 0) return {}; int pos = 0; - for (auto it = colors.cbegin(); it != colors.cend(); ++it) { - const double ratio = double(it.value()) / total; + + for (const auto &[key, value] : colors) { + const double ratio = double(value) / total; const int segmentWidth = int(ratio * width()); if (x >= pos && x < pos + segmentWidth) { - const double percent = (100.0 * it.value()) / total; - return QString("%1: %2 cards (%3%)").arg(it.key()).arg(it.value()).arg(QString::number(percent, 'f', 1)); + const double percent = (100.0 * value) / total; + return QString("%1: %2 cards (%3%)").arg(key).arg(value).arg(QString::number(percent, 'f', 1)); } pos += segmentWidth; diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.h b/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.h index f61ab6942..0ef68f578 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.h +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/color_bar.h @@ -100,7 +100,7 @@ protected: private: /// Map of color keys to counts used for rendering. - QMap colors; + QList> colors; /// True if the mouse is currently inside the widget. bool isHovered = false; diff --git a/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.cpp b/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.cpp index e86793083..84232b36f 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.cpp +++ b/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.cpp @@ -1,18 +1,21 @@ #include "color_pie.h" +#include "libcockatrice/utility/color.h" + #include #include #include #include -ColorPie::ColorPie(const QMap &_colors, QWidget *parent) : QWidget(parent), colors(_colors) +ColorPie::ColorPie(const QMap &_colors, QWidget *parent) + : QWidget(parent), colors(GameSpecificColors::MTG::sortManaMapWUBRGCFirst(_colors)) { setMouseTracking(true); } void ColorPie::setColors(const QMap &_colors) { - colors = _colors; + colors = GameSpecificColors::MTG::sortManaMapWUBRGCFirst(_colors); update(); } @@ -28,8 +31,8 @@ void ColorPie::paintEvent(QPaintEvent *) } int total = 0; - for (int v : colors.values()) { - total += v; + for (const auto &pair : colors) { + total += pair.second; } if (total == 0) { @@ -41,24 +44,18 @@ void ColorPie::paintEvent(QPaintEvent *) int w = width(); int h = height(); - int size = qMin(w, h) - 40; // leave space for labels + int size = qMin(w, h) - 40; QRectF rect((w - size) / 2.0, (h - size) / 2.0, size, size); - // Draw border + // Border p.setPen(QPen(Qt::black, 1)); p.setBrush(Qt::NoBrush); p.drawEllipse(rect); - // Sorted keys for predictable order - QList sortedKeys = colors.keys(); - std::sort(sortedKeys.begin(), sortedKeys.end()); - double startAngle = 0.0; - for (const QString &key : sortedKeys) { - int value = colors[key]; + for (const auto &[key, value] : colors) { double ratio = double(value) / total; - if (ratio <= minRatioThreshold) { continue; } @@ -67,20 +64,18 @@ void ColorPie::paintEvent(QPaintEvent *) QColor base = colorFromName(key); - // Gradient QRadialGradient grad(rect.center(), size / 2); grad.setColorAt(0, base.lighter(130)); grad.setColorAt(1, base.darker(130)); + p.setBrush(grad); p.setPen(Qt::NoPen); - - // Draw slice p.drawPie(rect, int(startAngle * 16), int(spanAngle * 16)); - // Draw percent label - double midAngle = startAngle + spanAngle / 2; + // Percent label + double midAngle = startAngle + spanAngle / 2.0; double rad = qDegreesToRadians(midAngle); - double labelRadius = size / 2 + 15; // slightly outside the pie + double labelRadius = size / 2 + 15; QPointF center = rect.center(); QPointF labelPos(center.x() + labelRadius * qCos(rad), center.y() - labelRadius * qSin(rad)); @@ -147,10 +142,13 @@ QString ColorPie::tooltipForPoint(const QPoint &pt) const } int total = 0; - for (int v : colors.values()) - total += v; - if (total == 0) + for (const auto &pair : colors) { + total += pair.second; + } + + if (total == 0) { return {}; + } int w = width(); int h = height(); @@ -158,9 +156,9 @@ QString ColorPie::tooltipForPoint(const QPoint &pt) const QPointF center(w / 2.0, h / 2.0); QPointF v = pt - center; - double distance = std::sqrt(v.x() * v.x() + v.y() * v.y()); + double distance = std::hypot(v.x(), v.y()); if (distance > size / 2.0) - return {}; // outside pie + return {}; double angle = std::atan2(-v.y(), v.x()) * 180.0 / M_PI; if (angle < 0) { @@ -169,16 +167,19 @@ QString ColorPie::tooltipForPoint(const QPoint &pt) const double acc = 0.0; - QList keys = colors.keys(); - std::sort(keys.begin(), keys.end()); + for (const auto &[key, value] : colors) { + double ratio = double(value) / total; + if (ratio <= minRatioThreshold) { + continue; + } - for (const QString &key : keys) { - double span = (double(colors[key]) / total) * 360.0; + double span = ratio * 360.0; if (angle >= acc && angle < acc + span) { - double percent = (100.0 * colors[key]) / total; - return QString("%1: %2 cards (%3%)").arg(key).arg(colors[key]).arg(QString::number(percent, 'f', 1)); + double percent = (100.0 * value) / total; + return QString("%1: %2 cards (%3%)").arg(key).arg(value).arg(QString::number(percent, 'f', 1)); } + acc += span; } diff --git a/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.h b/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.h index a8fe784a3..7d71ea3b9 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.h +++ b/cockatrice/src/interface/widgets/general/display/charts/pies/color_pie.h @@ -31,7 +31,7 @@ protected: void mouseMoveEvent(QMouseEvent *event) override; private: - QMap colors; + QList> colors; bool isHovered = false; const double minRatioThreshold = 0.01; // skip tiny slices diff --git a/libcockatrice_utility/libcockatrice/utility/color.h b/libcockatrice_utility/libcockatrice/utility/color.h index bf4759565..f02df3a0e 100644 --- a/libcockatrice_utility/libcockatrice/utility/color.h +++ b/libcockatrice_utility/libcockatrice/utility/color.h @@ -22,6 +22,8 @@ inline color convertQColorToColor(const QColor &c) return result; } +#include + namespace GameSpecificColors { namespace MTG @@ -56,6 +58,32 @@ inline QColor colorHelper(const QString &name) return QColor(r, g, b); } + +inline QList> sortManaMapWUBRGCFirst(const QMap &input) +{ + static const QStringList priorityOrder = {"W", "U", "B", "R", "G", "C"}; + + QList> result; + QSet consumed; + + // 1. Add priority colors in fixed order + for (const QString &key : priorityOrder) { + auto it = input.find(key); + if (it != input.end()) { + result.append({it.key(), it.value()}); + consumed.insert(it.key()); + } + } + + // 2. Add remaining keys (QMap iteration is already sorted) + for (auto it = input.begin(); it != input.end(); ++it) { + if (!consumed.contains(it.key())) { + result.append({it.key(), it.value()}); + } + } + + return result; +} } // namespace MTG } // namespace GameSpecificColors #endif From 47720ff2865f4bd49d49f3569709ce16fdc709b0 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 14 Jan 2026 02:41:03 -0800 Subject: [PATCH 139/325] [ColorIdentityWidget] Refactor (#6506) * [ColorIdentityWidget] Refactor and add setter * rename manaCost field * nvm, just refactor for now * use QtUtils * move clearLayout into populate * add back cardInfo constructor --- .../additional_info/color_identity_widget.cpp | 48 ++++++------------- .../additional_info/color_identity_widget.h | 10 ++-- 2 files changed, 20 insertions(+), 38 deletions(-) diff --git a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp index e8beae61e..98f5a306b 100644 --- a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp @@ -8,30 +8,10 @@ #include #include #include +#include -ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, CardInfoPtr _card) : QWidget(parent), card(_card) -{ - layout = new QHBoxLayout(this); - layout->setSpacing(5); // Small spacing between icons - layout->setContentsMargins(0, 0, 0, 0); - layout->setAlignment(Qt::AlignCenter); // Ensure icons are centered - setLayout(layout); - - // Define the full WUBRG set (White, Blue, Black, Red, Green) - QString fullColorIdentity = "WUBRG"; - - if (card) { - manaCost = card->getColors(); // Get mana cost string - QStringList symbols = parseColorIdentity(manaCost); // Parse mana cost string - - populateManaSymbolWidgets(); - } - connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageDrawUnusedColorIdentitiesChanged, this, - &ColorIdentityWidget::toggleUnusedVisibility); -} - -ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, QString _manaCost) - : QWidget(parent), card(nullptr), manaCost(_manaCost) +ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, const QString &_colorIdentity) + : QWidget(parent), colorIdentity(_colorIdentity) { layout = new QHBoxLayout(this); layout->setSpacing(5); // Small spacing between icons @@ -45,12 +25,21 @@ ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, QString _manaCost) &ColorIdentityWidget::toggleUnusedVisibility); } +ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, const CardInfoPtr &card) + : ColorIdentityWidget(parent, card->getColors()) +{ +} + void ColorIdentityWidget::populateManaSymbolWidgets() { // Define the full WUBRG set (White, Blue, Black, Red, Green) QString fullColorIdentity = "WUBRG"; - QStringList symbols = parseColorIdentity(manaCost); // Parse mana cost string + QStringList symbols = parseColorIdentity(colorIdentity); // Parse mana cost string + // clear old layout + QtUtils::clearLayoutRec(layout); + + // populate mana symbols if (SettingsCache::instance().getVisualDeckStorageDrawUnusedColorIdentities()) { for (const QString symbol : fullColorIdentity) { auto *manaSymbol = new ManaSymbolWidget(this, symbol, symbols.contains(symbol)); @@ -66,13 +55,6 @@ void ColorIdentityWidget::populateManaSymbolWidgets() void ColorIdentityWidget::toggleUnusedVisibility() { - if (layout != nullptr) { - QLayoutItem *item; - while ((item = layout->takeAt(0)) != nullptr) { - item->widget()->deleteLater(); // Delete the widget - delete item; // Delete the layout item - } - } populateManaSymbolWidgets(); } @@ -97,12 +79,12 @@ void ColorIdentityWidget::resizeEvent(QResizeEvent *event) } } -QStringList ColorIdentityWidget::parseColorIdentity(const QString &cmc) +QStringList ColorIdentityWidget::parseColorIdentity(const QString &manaString) { QStringList symbols; // Handle split costs (e.g., "3U // 4UU") - QStringList splitCosts = cmc.split(" // "); + QStringList splitCosts = manaString.split(" // "); for (const QString &part : splitCosts) { QRegularExpression regex(R"(\{([^}]+)\}|(\d+)|([WUBRGCSPX]))"); QRegularExpressionMatchIterator matches = regex.globalMatch(part); diff --git a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.h b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.h index 60d69af06..d9746b956 100644 --- a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.h +++ b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.h @@ -15,19 +15,19 @@ class ColorIdentityWidget : public QWidget { Q_OBJECT public: - explicit ColorIdentityWidget(QWidget *parent, CardInfoPtr card); - explicit ColorIdentityWidget(QWidget *parent, QString manaCost); + explicit ColorIdentityWidget(QWidget *parent, const QString &_colorIdentity = ""); + explicit ColorIdentityWidget(QWidget *parent, const CardInfoPtr &card); + void populateManaSymbolWidgets(); - QStringList parseColorIdentity(const QString &manaString); + static QStringList parseColorIdentity(const QString &manaString); public slots: void resizeEvent(QResizeEvent *event) override; void toggleUnusedVisibility(); private: - CardInfoPtr card; - QString manaCost; + QString colorIdentity; QHBoxLayout *layout; }; From a4eef648bca7d45a89d470eec459a3eb702e9945 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:56:09 +0100 Subject: [PATCH 140/325] [VDD] Move main type and format filter to quick settings (#6511) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [VDD] Reorder quick filters Took 1 hour 10 minutes Took 5 seconds Took 49 seconds * [VDD] Use Font Awesome Icons Took 49 minutes Took 5 seconds * [VDD] Shuffle some widgets around, label things. Took 31 minutes Took 5 seconds * Change buttons to be push rather than toggle. Took 17 minutes Took 9 seconds * Reduce margins, retranslate button texts. Took 15 minutes Took 9 seconds * Actually do it, don't commit the commented out testing version lol Took 3 minutes * Start sets in include, correct subtype include/exact match logic. Took 12 minutes * Block sync. Took 16 minutes Took 8 seconds --------- Co-authored-by: Lukas Brübach --- cockatrice/CMakeLists.txt | 2 + cockatrice/cockatrice.qrc | 7 + .../resources/icons/circle_half_stroke.svg | 1 + cockatrice/resources/icons/dragon.svg | 1 + cockatrice/resources/icons/floppy_disk.svg | 1 + cockatrice/resources/icons/gear.svg | 1 + cockatrice/resources/icons/pen_to_square.svg | 1 + cockatrice/resources/icons/scale_balanced.svg | 1 + cockatrice/resources/icons/scroll.svg | 1 + .../quick_settings/settings_button_widget.cpp | 16 ++- .../quick_settings/settings_button_widget.h | 1 + ...l_database_display_color_filter_widget.cpp | 56 ++++---- ...ual_database_display_color_filter_widget.h | 6 +- ...database_display_filter_toolbar_widget.cpp | 135 ++++++++++++++++++ ...l_database_display_filter_toolbar_widget.h | 48 +++++++ ..._display_format_legality_filter_widget.cpp | 40 ++++-- ...se_display_format_legality_filter_widget.h | 11 +- ...tabase_display_main_type_filter_widget.cpp | 36 +++-- ...database_display_main_type_filter_widget.h | 11 +- ...ual_database_display_set_filter_widget.cpp | 31 ++-- ...isual_database_display_set_filter_widget.h | 4 +- ...atabase_display_sub_type_filter_widget.cpp | 47 +++--- ..._database_display_sub_type_filter_widget.h | 9 +- .../visual_database_display_widget.cpp | 108 +------------- .../visual_database_display_widget.h | 41 +++--- 25 files changed, 392 insertions(+), 224 deletions(-) create mode 100644 cockatrice/resources/icons/circle_half_stroke.svg create mode 100644 cockatrice/resources/icons/dragon.svg create mode 100644 cockatrice/resources/icons/floppy_disk.svg create mode 100644 cockatrice/resources/icons/gear.svg create mode 100644 cockatrice/resources/icons/pen_to_square.svg create mode 100644 cockatrice/resources/icons/scale_balanced.svg create mode 100644 cockatrice/resources/icons/scroll.svg create mode 100644 cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp create mode 100644 cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 85e1cd00f..fb57bf7e0 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -317,6 +317,8 @@ set(cockatrice_SOURCES src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h + src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp + src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h ) add_subdirectory(sounds) diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc index 0f7bca2f1..6e90d342d 100644 --- a/cockatrice/cockatrice.qrc +++ b/cockatrice/cockatrice.qrc @@ -15,18 +15,23 @@ resources/icons/arrow_top_green.svg resources/icons/arrow_up_green.svg resources/icons/arrow_undo.svg + resources/icons/circle_half_stroke.svg resources/icons/clearsearch.svg resources/icons/cogwheel.svg resources/icons/conceded.svg resources/icons/decrement.svg resources/icons/delete.svg + resources/icons/dragon.svg resources/icons/dropdown_collapsed.svg resources/icons/dropdown_expanded.svg + resources/icons/floppy_disk.svg resources/icons/forgot_password.svg + resources/icons/gear.svg resources/icons/increment.svg resources/icons/info.svg resources/icons/lock.svg resources/icons/not_ready_start.svg + resources/icons/pen_to_square.svg resources/icons/pencil.svg resources/icons/pin.svg resources/icons/player.svg @@ -34,7 +39,9 @@ resources/icons/reload.svg resources/icons/remove_row.svg resources/icons/rename.svg + resources/icons/scale_balanced.svg resources/icons/scales.svg + resources/icons/scroll.svg resources/icons/search.svg resources/icons/settings.svg resources/icons/share.svg diff --git a/cockatrice/resources/icons/circle_half_stroke.svg b/cockatrice/resources/icons/circle_half_stroke.svg new file mode 100644 index 000000000..42e4dbca2 --- /dev/null +++ b/cockatrice/resources/icons/circle_half_stroke.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cockatrice/resources/icons/dragon.svg b/cockatrice/resources/icons/dragon.svg new file mode 100644 index 000000000..f45af39ef --- /dev/null +++ b/cockatrice/resources/icons/dragon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cockatrice/resources/icons/floppy_disk.svg b/cockatrice/resources/icons/floppy_disk.svg new file mode 100644 index 000000000..3c5b5054d --- /dev/null +++ b/cockatrice/resources/icons/floppy_disk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cockatrice/resources/icons/gear.svg b/cockatrice/resources/icons/gear.svg new file mode 100644 index 000000000..fdee3a297 --- /dev/null +++ b/cockatrice/resources/icons/gear.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cockatrice/resources/icons/pen_to_square.svg b/cockatrice/resources/icons/pen_to_square.svg new file mode 100644 index 000000000..60ff1d9c4 --- /dev/null +++ b/cockatrice/resources/icons/pen_to_square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cockatrice/resources/icons/scale_balanced.svg b/cockatrice/resources/icons/scale_balanced.svg new file mode 100644 index 000000000..fae468d38 --- /dev/null +++ b/cockatrice/resources/icons/scale_balanced.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cockatrice/resources/icons/scroll.svg b/cockatrice/resources/icons/scroll.svg new file mode 100644 index 000000000..f4828e679 --- /dev/null +++ b/cockatrice/resources/icons/scroll.svg @@ -0,0 +1 @@ + \ No newline at end of file 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 17087c8bc..57dfba800 100644 --- a/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp +++ b/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp @@ -8,7 +8,6 @@ SettingsButtonWidget::SettingsButtonWidget(QWidget *parent) : QWidget(parent), button(new QToolButton(this)), popup(new SettingsPopupWidget(nullptr)) { - button->setIcon(QPixmap("theme:icons/cogwheel")); button->setCheckable(true); button->setFixedSize(32, 32); @@ -36,6 +35,21 @@ void SettingsButtonWidget::setButtonIcon(QPixmap iconMap) button->setIcon(iconMap); } +void SettingsButtonWidget::setButtonText(const QString &buttonText) +{ + // 🔓 unlock size constraints + button->setMinimumSize(QSize()); + button->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); + + button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + button->setText(buttonText); + + button->setFixedHeight(32); + button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + + button->setMinimumWidth(button->sizeHint().width()); +} + void SettingsButtonWidget::togglePopup() { if (popup->isVisible()) { diff --git a/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.h b/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.h index d93078126..36f01ac38 100644 --- a/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.h +++ b/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.h @@ -22,6 +22,7 @@ public: void addSettingsWidget(QWidget *toAdd) const; void removeSettingsWidget(QWidget *toRemove) const; void setButtonIcon(QPixmap iconMap); + void setButtonText(const QString &buttonText); protected: void mousePressEvent(QMouseEvent *event) override; diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp index 3ca709071..57c6da762 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp @@ -31,12 +31,12 @@ VisualDatabaseDisplayColorFilterWidget::VisualDatabaseDisplayColorFilterWidget(Q &VisualDatabaseDisplayColorFilterWidget::handleColorToggled); } - toggleButton = new QPushButton(this); - toggleButton->setCheckable(true); - layout->addWidget(toggleButton); + modeComboBox = new QComboBox(this); + layout->addWidget(modeComboBox); + + connect(modeComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &VisualDatabaseDisplayColorFilterWidget::updateFilterMode); - // Connect the button's toggled signal - connect(toggleButton, &QPushButton::toggled, this, &VisualDatabaseDisplayColorFilterWidget::updateFilterMode); connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplayColorFilterWidget::syncWithFilterModel); }); @@ -46,19 +46,22 @@ VisualDatabaseDisplayColorFilterWidget::VisualDatabaseDisplayColorFilterWidget(Q void VisualDatabaseDisplayColorFilterWidget::retranslateUi() { - switch (currentMode) { - case FilterMode::ExactMatch: - toggleButton->setText(tr("Mode: Exact Match")); - break; - case FilterMode::Includes: - toggleButton->setText(tr("Mode: Includes")); - break; - case FilterMode::IncludeExclude: - toggleButton->setText(tr("Mode: Include/Exclude")); - break; + modeComboBox->blockSignals(true); + modeComboBox->clear(); + + modeComboBox->addItem(tr("Exact match"), QVariant::fromValue(FilterMode::ExactMatch)); + modeComboBox->addItem(tr("Includes"), QVariant::fromValue(FilterMode::Includes)); + modeComboBox->addItem(tr("Include / Exclude"), QVariant::fromValue(FilterMode::IncludeExclude)); + + modeComboBox->setToolTip(tr("How selected and unselected colors are combined in the filter")); + + // Restore current mode + const int index = modeComboBox->findData(QVariant::fromValue(currentMode)); + if (index >= 0) { + modeComboBox->setCurrentIndex(index); } - toggleButton->setToolTip(tr("Filter mode (AND/OR/NOT conjunctions of filters)")); + modeComboBox->blockSignals(false); } void VisualDatabaseDisplayColorFilterWidget::handleColorToggled(QChar color, bool active) @@ -145,24 +148,19 @@ void VisualDatabaseDisplayColorFilterWidget::removeFilter(QChar color) void VisualDatabaseDisplayColorFilterWidget::updateFilterMode() { - switch (currentMode) { - case FilterMode::ExactMatch: - currentMode = FilterMode::Includes; // Switch to Includes - break; - case FilterMode::Includes: - currentMode = FilterMode::IncludeExclude; // Switch to Include/Exclude - break; - case FilterMode::IncludeExclude: - currentMode = FilterMode::ExactMatch; // Switch to Exact Match - break; + const QVariant data = modeComboBox->currentData(); + if (!data.isValid()) { + return; } + currentMode = data.value(); + filterModel->blockSignals(true); filterModel->filterTree()->blockSignals(true); filterModel->clearFiltersOfType(CardFilter::Attr::AttrColor); - QList manaSymbolWidgets = findChildren(); + const QList manaSymbolWidgets = findChildren(); for (ManaSymbolWidget *manaSymbolWidget : manaSymbolWidgets) { handleColorToggled(manaSymbolWidget->getSymbolChar(), manaSymbolWidget->isColorActive()); @@ -173,9 +171,7 @@ void VisualDatabaseDisplayColorFilterWidget::updateFilterMode() emit filterModel->filterTree()->changed(); emit filterModel->layoutChanged(); - - retranslateUi(); // Update button text based on the mode - emit filterModeChanged(currentMode); // Signal mode change + emit filterModeChanged(currentMode); } void VisualDatabaseDisplayColorFilterWidget::setManaSymbolActive(QChar color, bool active) diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.h index 7b4c365c0..698ea9e97 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.h @@ -9,8 +9,8 @@ #include "../../../filters/filter_tree_model.h" +#include #include -#include #include class VisualDatabaseDisplayColorFilterCircleWidget : public QWidget @@ -39,6 +39,8 @@ enum class FilterMode IncludeExclude // Include selected colors (OR) and exclude unselected colors (AND NOT). }; +Q_DECLARE_METATYPE(FilterMode) + class VisualDatabaseDisplayColorFilterWidget : public QWidget { Q_OBJECT @@ -62,7 +64,7 @@ private slots: private: FilterTreeModel *filterModel; QHBoxLayout *layout; - QPushButton *toggleButton; + QComboBox *modeComboBox; FilterMode currentMode = FilterMode::Includes; // Default mode }; 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 new file mode 100644 index 000000000..324236ab7 --- /dev/null +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp @@ -0,0 +1,135 @@ +#include "visual_database_display_filter_toolbar_widget.h" + +#include "visual_database_display_widget.h" + +VisualDatabaseDisplayFilterToolbarWidget::VisualDatabaseDisplayFilterToolbarWidget(VisualDatabaseDisplayWidget *_parent) + : QWidget(_parent), visualDatabaseDisplay(_parent) +{ + filterContainerLayout = new QHBoxLayout(this); + filterContainerLayout->setContentsMargins(11, 0, 11, 0); + setLayout(filterContainerLayout); + filterContainerLayout->setAlignment(Qt::AlignLeft); + + setMaximumHeight(80); + + connect(this, &VisualDatabaseDisplayFilterToolbarWidget::searchModelChanged, visualDatabaseDisplay, + &VisualDatabaseDisplayWidget::onSearchModelChanged); + + filterByLabel = new QLabel(this); + + sortByLabel = new QLabel(this); + sortColumnCombo = new QComboBox(this); + sortColumnCombo->setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy::AdjustToContents); + sortOrderCombo = new QComboBox(this); + sortOrderCombo->setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy::AdjustToContents); + + sortOrderCombo->addItem("Ascending", Qt::AscendingOrder); + sortOrderCombo->addItem("Descending", Qt::DescendingOrder); + sortOrderCombo->view()->setMinimumWidth(sortOrderCombo->view()->sizeHintForColumn(0)); + sortOrderCombo->adjustSize(); + + // Populate columns dynamically from the model + for (int i = 0; i < visualDatabaseDisplay->getDatabaseDisplayModel()->columnCount(); ++i) { + QString header = visualDatabaseDisplay->getDatabaseDisplayModel()->headerData(i, Qt::Horizontal).toString(); + sortColumnCombo->addItem(header, i); + } + + sortColumnCombo->view()->setMinimumWidth(sortColumnCombo->view()->sizeHintForColumn(0)); + sortColumnCombo->adjustSize(); + + connect(sortColumnCombo, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { + int column = sortColumnCombo->currentData().toInt(); + Qt::SortOrder order = static_cast(sortOrderCombo->currentData().toInt()); + visualDatabaseDisplay->getDatabaseView()->sortByColumn(column, order); + + emit searchModelChanged(); + }); + + connect(sortOrderCombo, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { + int column = sortColumnCombo->currentData().toInt(); + Qt::SortOrder order = static_cast(sortOrderCombo->currentData().toInt()); + visualDatabaseDisplay->getDatabaseView()->sortByColumn(column, order); + + emit searchModelChanged(); + }); + + quickFilterSaveLoadWidget = new SettingsButtonWidget(this); + quickFilterSaveLoadWidget->setButtonIcon(QPixmap("theme:icons/floppy_disk")); + + quickFilterNameWidget = new SettingsButtonWidget(this); + quickFilterNameWidget->setButtonIcon(QPixmap("theme:icons/pen_to_square")); + + quickFilterMainTypeWidget = new SettingsButtonWidget(this); + quickFilterMainTypeWidget->setButtonIcon(QPixmap("theme:icons/circle_half_stroke")); + + quickFilterSubTypeWidget = new SettingsButtonWidget(this); + quickFilterSubTypeWidget->setButtonIcon(QPixmap("theme:icons/dragon")); + + quickFilterSetWidget = new SettingsButtonWidget(this); + quickFilterSetWidget->setButtonIcon(QPixmap("theme:icons/scroll")); + + quickFilterFormatLegalityWidget = new SettingsButtonWidget(this); + quickFilterFormatLegalityWidget->setButtonIcon(QPixmap("theme:icons/scale_balanced")); + + retranslateUi(); +} + +void VisualDatabaseDisplayFilterToolbarWidget::initialize() +{ + sortByLabel->setVisible(true); + filterByLabel->setVisible(true); + + quickFilterSaveLoadWidget->setVisible(true); + quickFilterNameWidget->setVisible(true); + quickFilterSubTypeWidget->setVisible(true); + quickFilterSetWidget->setVisible(true); + + auto filterModel = visualDatabaseDisplay->filterModel; + + saveLoadWidget = new VisualDatabaseDisplayFilterSaveLoadWidget(this, filterModel); + nameFilterWidget = + new VisualDatabaseDisplayNameFilterWidget(this, visualDatabaseDisplay->getDeckEditor(), filterModel); + mainTypeFilterWidget = new VisualDatabaseDisplayMainTypeFilterWidget(this, filterModel); + formatLegalityWidget = new VisualDatabaseDisplayFormatLegalityFilterWidget(this, filterModel); + subTypeFilterWidget = new VisualDatabaseDisplaySubTypeFilterWidget(this, filterModel); + setFilterWidget = new VisualDatabaseDisplaySetFilterWidget(this, filterModel); + + quickFilterSaveLoadWidget->addSettingsWidget(saveLoadWidget); + quickFilterNameWidget->addSettingsWidget(nameFilterWidget); + quickFilterMainTypeWidget->addSettingsWidget(mainTypeFilterWidget); + quickFilterSubTypeWidget->addSettingsWidget(subTypeFilterWidget); + quickFilterSetWidget->addSettingsWidget(setFilterWidget); + quickFilterFormatLegalityWidget->addSettingsWidget(formatLegalityWidget); + + filterContainerLayout->addWidget(sortByLabel); + filterContainerLayout->addWidget(sortColumnCombo); + filterContainerLayout->addWidget(sortOrderCombo); + filterContainerLayout->addWidget(filterByLabel); + filterContainerLayout->addWidget(quickFilterNameWidget); + filterContainerLayout->addWidget(quickFilterMainTypeWidget); + filterContainerLayout->addWidget(quickFilterSubTypeWidget); + filterContainerLayout->addWidget(quickFilterSetWidget); + filterContainerLayout->addWidget(quickFilterFormatLegalityWidget); + filterContainerLayout->addStretch(); + filterContainerLayout->addWidget(quickFilterSaveLoadWidget); +} + +void VisualDatabaseDisplayFilterToolbarWidget::retranslateUi() +{ + sortByLabel->setText(tr("Sort by:")); + filterByLabel->setText(tr("Filter by:")); + + quickFilterSaveLoadWidget->setToolTip(tr("Save and load filters")); + quickFilterNameWidget->setToolTip(tr("Filter by exact card name")); + quickFilterMainTypeWidget->setToolTip(tr("Filter by card main-type")); + quickFilterSubTypeWidget->setToolTip(tr("Filter by card sub-type")); + quickFilterSetWidget->setToolTip(tr("Filter by set")); + quickFilterFormatLegalityWidget->setToolTip(tr("Filter by format legality")); + + quickFilterSaveLoadWidget->setButtonText(tr("Save/Load")); + quickFilterNameWidget->setButtonText(tr("Name")); + quickFilterMainTypeWidget->setButtonText(tr("Main Type")); + quickFilterSubTypeWidget->setButtonText(tr("Sub Type")); + quickFilterSetWidget->setButtonText(tr("Sets")); + quickFilterFormatLegalityWidget->setButtonText(tr("Formats")); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h new file mode 100644 index 000000000..a6c614656 --- /dev/null +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h @@ -0,0 +1,48 @@ +#ifndef COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_TOOLBAR_WIDGET_H +#define COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_TOOLBAR_WIDGET_H + +#include "visual_database_display_filter_save_load_widget.h" +#include "visual_database_display_format_legality_filter_widget.h" +#include "visual_database_display_main_type_filter_widget.h" +#include "visual_database_display_name_filter_widget.h" +#include "visual_database_display_set_filter_widget.h" +#include "visual_database_display_sub_type_filter_widget.h" + +class VisualDatabaseDisplayWidget; + +class VisualDatabaseDisplayFilterToolbarWidget : public QWidget +{ + Q_OBJECT + +signals: + void searchModelChanged(); + +public: + explicit VisualDatabaseDisplayFilterToolbarWidget(VisualDatabaseDisplayWidget *parent); + void initialize(); + void retranslateUi(); + +private: + VisualDatabaseDisplayWidget *visualDatabaseDisplay; + + QLabel *sortByLabel; + QComboBox *sortColumnCombo, *sortOrderCombo; + + QLabel *filterByLabel; + + QHBoxLayout *filterContainerLayout; + SettingsButtonWidget *quickFilterSaveLoadWidget; + VisualDatabaseDisplayFilterSaveLoadWidget *saveLoadWidget; + SettingsButtonWidget *quickFilterNameWidget; + VisualDatabaseDisplayNameFilterWidget *nameFilterWidget; + SettingsButtonWidget *quickFilterMainTypeWidget; + VisualDatabaseDisplayMainTypeFilterWidget *mainTypeFilterWidget; + SettingsButtonWidget *quickFilterSubTypeWidget; + VisualDatabaseDisplaySubTypeFilterWidget *subTypeFilterWidget; + SettingsButtonWidget *quickFilterSetWidget; + VisualDatabaseDisplaySetFilterWidget *setFilterWidget; + SettingsButtonWidget *quickFilterFormatLegalityWidget; + VisualDatabaseDisplayFormatLegalityFilterWidget *formatLegalityWidget; +}; + +#endif // COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_TOOLBAR_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp index 1f1b7b94c..0df948016 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp @@ -2,6 +2,7 @@ #include "../../../filters/filter_tree_model.h" +#include #include #include #include @@ -14,11 +15,14 @@ VisualDatabaseDisplayFormatLegalityFilterWidget::VisualDatabaseDisplayFormatLega : QWidget(parent), filterModel(_filterModel) { allFormatsWithCount = CardDatabaseManager::query()->getAllFormatsWithCount(); + int maxValue = std::numeric_limits::min(); + for (int value : allFormatsWithCount) { + maxValue = std::max(maxValue, value); + } + setMinimumWidth(300); + setMaximumHeight(300); - setMaximumHeight(75); - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); - - layout = new QHBoxLayout(this); + layout = new QVBoxLayout(this); setLayout(layout); layout->setContentsMargins(0, 1, 0, 1); layout->setSpacing(1); @@ -27,33 +31,45 @@ VisualDatabaseDisplayFormatLegalityFilterWidget::VisualDatabaseDisplayFormatLega flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); layout->addWidget(flowWidget); + // Create a container for the threshold control + auto *thresholdLayout = new QHBoxLayout(); + thresholdLayout->setContentsMargins(0, 0, 0, 0); + + thresholdLabel = new QLabel(this); + thresholdLayout->addWidget(thresholdLabel); + // Create the spinbox spinBox = new QSpinBox(this); spinBox->setMinimum(1); - spinBox->setMaximum(getMaxMainTypeCount()); // Set the max value dynamically + spinBox->setMaximum(maxValue); // Set the max value dynamically spinBox->setValue(150); - layout->addWidget(spinBox); + thresholdLayout->addWidget(spinBox); + thresholdLayout->addStretch(); + + layout->addLayout(thresholdLayout); + connect(spinBox, qOverload(&QSpinBox::valueChanged), this, &VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatButtonsVisibility); // Create the toggle button for Exact Match/Includes mode toggleButton = new QPushButton(this); - toggleButton->setCheckable(true); layout->addWidget(toggleButton); - connect(toggleButton, &QPushButton::toggled, this, + connect(toggleButton, &QPushButton::clicked, this, &VisualDatabaseDisplayFormatLegalityFilterWidget::updateFilterMode); connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplayFormatLegalityFilterWidget::syncWithFilterModel); }); - createFormatButtons(); // Populate buttons initially - updateFilterMode(false); // Initialize toggle button text + createFormatButtons(); // Populate buttons initially + updateFilterMode(); // Initialize toggle button text retranslateUi(); } void VisualDatabaseDisplayFormatLegalityFilterWidget::retranslateUi() { + thresholdLabel->setText(tr("Show formats with at least:")); + spinBox->setSuffix(tr(" cards")); spinBox->setToolTip(tr("Do not display formats with less than this amount of cards in the database")); toggleButton->setToolTip(tr("Filter mode (AND/OR/NOT conjunctions of filters)")); } @@ -160,9 +176,9 @@ void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFormatFilter() emit filterModel->layoutChanged(); } -void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFilterMode(bool checked) +void VisualDatabaseDisplayFormatLegalityFilterWidget::updateFilterMode() { - exactMatchMode = checked; + exactMatchMode = !exactMatchMode; toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes")); updateFormatFilter(); } diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.h index a2a00b740..1b1a1382e 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.h @@ -4,6 +4,7 @@ #include "../../../filters/filter_tree_model.h" #include "../general/layout_containers/flow_widget.h" +#include #include #include #include @@ -23,21 +24,23 @@ public: void handleFormatToggled(const QString &format, bool active); void updateFormatFilter(); - void updateFilterMode(bool checked); + void updateFilterMode(); void syncWithFilterModel(); private: FilterTreeModel *filterModel; QMap allFormatsWithCount; - QSpinBox *spinBox; - QHBoxLayout *layout; + + QVBoxLayout *layout; FlowWidget *flowWidget; + QLabel *thresholdLabel; + QSpinBox *spinBox; QPushButton *toggleButton; // Mode switch button QMap activeFormats; // Track active filters QMap formatButtons; // Store toggle buttons - bool exactMatchMode = false; // Toggle between "Exact Match" and "Includes" + bool exactMatchMode = true; // Toggle between "Exact Match" and "Includes" }; #endif // COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp index 368ac8719..bc8e914bd 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp @@ -2,6 +2,7 @@ #include "../../../filters/filter_tree_model.h" +#include #include #include #include @@ -12,13 +13,12 @@ VisualDatabaseDisplayMainTypeFilterWidget::VisualDatabaseDisplayMainTypeFilterWi FilterTreeModel *_filterModel) : QWidget(parent), filterModel(_filterModel) { - allMainCardTypesWithCount = CardDatabaseManager::query()->getAllMainCardTypesWithCount(); // Get all main card types with their count + allMainCardTypesWithCount = CardDatabaseManager::query()->getAllMainCardTypesWithCount(); + setMinimumWidth(300); + setMaximumHeight(200); - setMaximumHeight(75); - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); - - layout = new QHBoxLayout(this); + layout = new QVBoxLayout(this); setLayout(layout); layout->setContentsMargins(0, 1, 0, 1); layout->setSpacing(1); @@ -27,32 +27,44 @@ VisualDatabaseDisplayMainTypeFilterWidget::VisualDatabaseDisplayMainTypeFilterWi flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); layout->addWidget(flowWidget); + // Create a container for the threshold control + auto *thresholdLayout = new QHBoxLayout(); + thresholdLayout->setContentsMargins(0, 0, 0, 0); + + thresholdLabel = new QLabel(this); + thresholdLayout->addWidget(thresholdLabel); + // Create the spinbox spinBox = new QSpinBox(this); spinBox->setMinimum(1); - spinBox->setMaximum(getMaxMainTypeCount()); // Set the max value dynamically + spinBox->setMaximum(getMaxMainTypeCount()); spinBox->setValue(150); - layout->addWidget(spinBox); + thresholdLayout->addWidget(spinBox); + thresholdLayout->addStretch(); + + layout->addLayout(thresholdLayout); + connect(spinBox, qOverload(&QSpinBox::valueChanged), this, &VisualDatabaseDisplayMainTypeFilterWidget::updateMainTypeButtonsVisibility); // Create the toggle button for Exact Match/Includes mode toggleButton = new QPushButton(this); - toggleButton->setCheckable(true); layout->addWidget(toggleButton); - connect(toggleButton, &QPushButton::toggled, this, &VisualDatabaseDisplayMainTypeFilterWidget::updateFilterMode); + connect(toggleButton, &QPushButton::clicked, this, &VisualDatabaseDisplayMainTypeFilterWidget::updateFilterMode); connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplayMainTypeFilterWidget::syncWithFilterModel); }); createMainTypeButtons(); // Populate buttons initially - updateFilterMode(false); // Initialize toggle button text + updateFilterMode(); // Initialize toggle button text retranslateUi(); } void VisualDatabaseDisplayMainTypeFilterWidget::retranslateUi() { + thresholdLabel->setText(tr("Show main types with at least:")); + spinBox->setSuffix(tr(" cards")); spinBox->setToolTip(tr("Do not display card main-types with less than this amount of cards in the database")); toggleButton->setToolTip(tr("Filter mode (AND/OR/NOT conjunctions of filters)")); } @@ -159,9 +171,9 @@ void VisualDatabaseDisplayMainTypeFilterWidget::updateMainTypeFilter() emit filterModel->layoutChanged(); } -void VisualDatabaseDisplayMainTypeFilterWidget::updateFilterMode(bool checked) +void VisualDatabaseDisplayMainTypeFilterWidget::updateFilterMode() { - exactMatchMode = checked; + exactMatchMode = !exactMatchMode; toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes")); updateMainTypeFilter(); } diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h index 27c61dffc..9145812a7 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h @@ -10,6 +10,7 @@ #include "../../../filters/filter_tree_model.h" #include "../general/layout_containers/flow_widget.h" +#include #include #include #include @@ -28,21 +29,23 @@ public: void handleMainTypeToggled(const QString &mainType, bool active); void updateMainTypeFilter(); - void updateFilterMode(bool checked); + void updateFilterMode(); void syncWithFilterModel(); private: FilterTreeModel *filterModel; QMap allMainCardTypesWithCount; - QSpinBox *spinBox; - QHBoxLayout *layout; + + QVBoxLayout *layout; FlowWidget *flowWidget; + QLabel *thresholdLabel; + QSpinBox *spinBox; QPushButton *toggleButton; // Mode switch button QMap activeMainTypes; // Track active filters QMap typeButtons; // Store toggle buttons - bool exactMatchMode = false; // Toggle between "Exact Match" and "Includes" + bool exactMatchMode = true; // Toggle between "Exact Match" and "Includes" }; #endif // VISUAL_DATABASE_DISPLAY_MAIN_TYPE_FILTER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp index 6e621e6d4..3339bc561 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp @@ -53,14 +53,6 @@ VisualDatabaseDisplaySetFilterWidget::VisualDatabaseDisplaySetFilterWidget(QWidg layout = new QVBoxLayout(this); setLayout(layout); - recentSetsSettingsWidget = new VisualDatabaseDisplayRecentSetFilterSettingsWidget(this); - layout->addWidget(recentSetsSettingsWidget); - - connect(&SettingsCache::instance(), &SettingsCache::visualDatabaseDisplayFilterToMostRecentSetsEnabledChanged, this, - &VisualDatabaseDisplaySetFilterWidget::filterToRecentSets); - connect(&SettingsCache::instance(), &SettingsCache::visualDatabaseDisplayFilterToMostRecentSetsAmountChanged, this, - &VisualDatabaseDisplaySetFilterWidget::filterToRecentSets); - searchBox = new QLineEdit(this); searchBox->setPlaceholderText(tr("Search sets...")); layout->addWidget(searchBox); @@ -70,15 +62,23 @@ VisualDatabaseDisplaySetFilterWidget::VisualDatabaseDisplaySetFilterWidget(QWidg flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); layout->addWidget(flowWidget); + recentSetsSettingsWidget = new VisualDatabaseDisplayRecentSetFilterSettingsWidget(this); + layout->addWidget(recentSetsSettingsWidget); + + connect(&SettingsCache::instance(), &SettingsCache::visualDatabaseDisplayFilterToMostRecentSetsEnabledChanged, this, + &VisualDatabaseDisplaySetFilterWidget::filterToRecentSets); + connect(&SettingsCache::instance(), &SettingsCache::visualDatabaseDisplayFilterToMostRecentSetsAmountChanged, this, + &VisualDatabaseDisplaySetFilterWidget::filterToRecentSets); + // Create the toggle button for Exact Match/Includes mode toggleButton = new QPushButton(this); - toggleButton->setCheckable(true); layout->addWidget(toggleButton); - connect(toggleButton, &QPushButton::toggled, this, &VisualDatabaseDisplaySetFilterWidget::updateFilterMode); + connect(toggleButton, &QPushButton::clicked, this, &VisualDatabaseDisplaySetFilterWidget::updateFilterMode); connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplaySetFilterWidget::syncWithFilterModel); }); createSetButtons(); // Populate buttons initially + updateFilterMode(); retranslateUi(); } @@ -266,9 +266,16 @@ void VisualDatabaseDisplaySetFilterWidget::syncWithFilterModel() } } -void VisualDatabaseDisplaySetFilterWidget::updateFilterMode(bool checked) +void VisualDatabaseDisplaySetFilterWidget::updateFilterMode() { - exactMatchMode = checked; + // Disconnect the layoutChanged -> sync lambda temporarily + disconnect(filterModel, &FilterTreeModel::layoutChanged, this, nullptr); + + exactMatchMode = !exactMatchMode; updateSetFilter(); retranslateUi(); + + // Reconnect the layoutChanged -> sync lambda + connect(filterModel, &FilterTreeModel::layoutChanged, this, + [this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplaySetFilterWidget::syncWithFilterModel); }); } diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h index b4362d1db..dc7fd0e92 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h @@ -44,7 +44,7 @@ public: void updateSetFilter(); void syncWithFilterModel(); - void updateFilterMode(bool checked); + void updateFilterMode(); private: FilterTreeModel *filterModel; @@ -60,7 +60,7 @@ private: QMap setButtons; // Store set filter buttons QMap activeSets; // Track active set filters - bool exactMatchMode = false; // Toggle between "Exact Match" and "Includes" + bool exactMatchMode = true; // Toggle between "Exact Match" and "Includes" }; #endif // VISUAL_DATABASE_DISPLAY_SET_FILTER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp index b34bb65d3..57559d12c 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp @@ -2,6 +2,7 @@ #include "../../../filters/filter_tree_model.h" +#include #include #include #include @@ -20,15 +21,6 @@ VisualDatabaseDisplaySubTypeFilterWidget::VisualDatabaseDisplaySubTypeFilterWidg layout = new QVBoxLayout(this); setLayout(layout); - // Create and setup the spinbox - spinBox = new QSpinBox(this); - spinBox->setMinimum(1); - spinBox->setMaximum(getMaxSubTypeCount()); - spinBox->setValue(150); - layout->addWidget(spinBox); - connect(spinBox, qOverload(&QSpinBox::valueChanged), this, - &VisualDatabaseDisplaySubTypeFilterWidget::updateSubTypeButtonsVisibility); - // Create search box searchBox = new QLineEdit(this); searchBox->setPlaceholderText(tr("Search subtypes...")); @@ -40,23 +32,44 @@ VisualDatabaseDisplaySubTypeFilterWidget::VisualDatabaseDisplaySubTypeFilterWidg flowWidget->setMaximumHeight(300); layout->addWidget(flowWidget); + // Create a container for the threshold control + auto *thresholdLayout = new QHBoxLayout(); + thresholdLayout->setContentsMargins(0, 0, 0, 0); + + thresholdLabel = new QLabel(this); + thresholdLayout->addWidget(thresholdLabel); + + // Create the spinbox + spinBox = new QSpinBox(this); + spinBox->setMinimum(1); + spinBox->setMaximum(getMaxSubTypeCount()); + spinBox->setValue(150); + thresholdLayout->addWidget(spinBox); + thresholdLayout->addStretch(); + + layout->addLayout(thresholdLayout); + + connect(spinBox, qOverload(&QSpinBox::valueChanged), this, + &VisualDatabaseDisplaySubTypeFilterWidget::updateSubTypeButtonsVisibility); + // Toggle button setup (Exact Match / Includes mode) toggleButton = new QPushButton(this); - toggleButton->setCheckable(true); layout->addWidget(toggleButton); - connect(toggleButton, &QPushButton::toggled, this, &VisualDatabaseDisplaySubTypeFilterWidget::updateFilterMode); + connect(toggleButton, &QPushButton::clicked, this, &VisualDatabaseDisplaySubTypeFilterWidget::updateFilterMode); connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplaySubTypeFilterWidget::syncWithFilterModel); }); - createSubTypeButtons(); // Populate buttons initially - updateFilterMode(false); // Initialize the toggle button text + createSubTypeButtons(); // Populate buttons initially + updateFilterMode(); // Initialize the toggle button text retranslateUi(); } void VisualDatabaseDisplaySubTypeFilterWidget::retranslateUi() { + thresholdLabel->setText(tr("Show sub types with at least:")); + spinBox->setSuffix(tr(" cards")); spinBox->setToolTip(tr("Do not display card sub-types with less than this amount of cards in the database")); toggleButton->setToolTip(tr("Filter mode (AND/OR/NOT conjunctions of filters)")); } @@ -155,7 +168,7 @@ void VisualDatabaseDisplaySubTypeFilterWidget::updateSubTypeFilter() if (activeSubTypes[type]) { QString typeString = type; filterModel->addFilter( - new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrSubType)); + new CardFilter(typeString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrSubType)); } } } @@ -166,9 +179,9 @@ void VisualDatabaseDisplaySubTypeFilterWidget::updateSubTypeFilter() emit filterModel->layoutChanged(); } -void VisualDatabaseDisplaySubTypeFilterWidget::updateFilterMode(bool checked) +void VisualDatabaseDisplaySubTypeFilterWidget::updateFilterMode() { - exactMatchMode = checked; + exactMatchMode = !exactMatchMode; toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes")); updateSubTypeFilter(); } @@ -188,7 +201,7 @@ void VisualDatabaseDisplaySubTypeFilterWidget::syncWithFilterModel() // Get active filters for sub types QSet activeTypes; for (const auto &filter : filterModel->getFiltersOfType(CardFilter::AttrSubType)) { - if (filter->type() == CardFilter::Type::TypeAnd) { + if (filter->type() == CardFilter::Type::TypeAnd || filter->type() == CardFilter::Type::TypeOr) { activeTypes.insert(filter->term()); } } diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h index c02db29be..ce5546fc8 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h @@ -10,6 +10,7 @@ #include "../../../filters/filter_tree_model.h" #include "../general/layout_containers/flow_widget.h" +#include #include #include #include @@ -27,22 +28,24 @@ public: void handleSubTypeToggled(const QString &mainType, bool active); void updateSubTypeFilter(); - void updateFilterMode(bool checked); + void updateFilterMode(); void syncWithFilterModel(); private: FilterTreeModel *filterModel; QMap allSubCardTypesWithCount; - QSpinBox *spinBox; + QVBoxLayout *layout; QLineEdit *searchBox; FlowWidget *flowWidget; + QLabel *thresholdLabel; + QSpinBox *spinBox; QPushButton *toggleButton; // Mode switch button QMap activeSubTypes; // Track active filters QMap typeButtons; // Store toggle buttons - bool exactMatchMode = false; // Toggle between "Exact Match" and "Includes" + bool exactMatchMode = true; // Toggle between "Exact Match" and "Includes" }; #endif // VISUAL_DATABASE_DISPLAY_SUB_TYPE_FILTER_WIDGET_H 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 6112faedf..44a9e98a0 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 @@ -101,49 +101,9 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, searchEdit->setTreeView(databaseView); - sortByLabel = new QLabel(this); - sortColumnCombo = new QComboBox(this); - sortColumnCombo->setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy::AdjustToContents); - sortOrderCombo = new QComboBox(this); - sortOrderCombo->setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy::AdjustToContents); - - sortOrderCombo->addItem("Ascending", Qt::AscendingOrder); - sortOrderCombo->addItem("Descending", Qt::DescendingOrder); - sortOrderCombo->view()->setMinimumWidth(sortOrderCombo->view()->sizeHintForColumn(0)); - sortOrderCombo->adjustSize(); - - // Populate columns dynamically from the model - for (int i = 0; i < databaseDisplayModel->columnCount(); ++i) { - QString header = databaseDisplayModel->headerData(i, Qt::Horizontal).toString(); - sortColumnCombo->addItem(header, i); - } - - sortColumnCombo->view()->setMinimumWidth(sortColumnCombo->view()->sizeHintForColumn(0)); - sortColumnCombo->adjustSize(); - - connect(sortColumnCombo, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { - int column = sortColumnCombo->currentData().toInt(); - Qt::SortOrder order = static_cast(sortOrderCombo->currentData().toInt()); - databaseView->sortByColumn(column, order); - - searchModelChanged(); - }); - - connect(sortOrderCombo, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { - int column = sortColumnCombo->currentData().toInt(); - Qt::SortOrder order = static_cast(sortOrderCombo->currentData().toInt()); - databaseView->sortByColumn(column, order); - - searchModelChanged(); - }); - colorFilterWidget = new VisualDatabaseDisplayColorFilterWidget(this, filterModel); - filterContainer = new QWidget(this); - filterContainerLayout = new QHBoxLayout(filterContainer); - filterContainer->setLayout(filterContainerLayout); - - filterByLabel = new QLabel(this); + filterContainer = new VisualDatabaseDisplayFilterToolbarWidget(this); clearFilterWidget = new QToolButton(); clearFilterWidget->setFixedSize(32, 32); @@ -158,20 +118,6 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, emit filterModel->layoutChanged(); }); - quickFilterSaveLoadWidget = new SettingsButtonWidget(this); - quickFilterSaveLoadWidget->setButtonIcon(QPixmap("theme:icons/lock")); - - quickFilterNameWidget = new SettingsButtonWidget(this); - quickFilterNameWidget->setButtonIcon(QPixmap("theme:icons/rename")); - - quickFilterSubTypeWidget = new SettingsButtonWidget(this); - quickFilterSubTypeWidget->setButtonIcon(QPixmap("theme:icons/player")); - - quickFilterSetWidget = new SettingsButtonWidget(this); - quickFilterSetWidget->setButtonIcon(QPixmap("theme:icons/scales")); - - filterContainer->setMaximumHeight(80); - databaseLoadIndicator = new QLabel(this); databaseLoadIndicator->setAlignment(Qt::AlignCenter); @@ -180,12 +126,7 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, if (CardDatabaseManager::getInstance()->getLoadStatus() != LoadStatus::Ok) { connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this, &VisualDatabaseDisplayWidget::initialize); - sortByLabel->setVisible(false); - filterByLabel->setVisible(false); - quickFilterSaveLoadWidget->setVisible(false); - quickFilterNameWidget->setVisible(false); - quickFilterSubTypeWidget->setVisible(false); - quickFilterSetWidget->setVisible(false); + filterContainer->setVisible(false); } else { initialize(); databaseLoadIndicator->setVisible(false); @@ -198,35 +139,7 @@ void VisualDatabaseDisplayWidget::initialize() { databaseLoadIndicator->setVisible(false); - sortByLabel->setVisible(true); - filterByLabel->setVisible(true); - quickFilterSaveLoadWidget->setVisible(true); - quickFilterNameWidget->setVisible(true); - quickFilterSubTypeWidget->setVisible(true); - quickFilterSetWidget->setVisible(true); - - saveLoadWidget = new VisualDatabaseDisplayFilterSaveLoadWidget(this, filterModel); - nameFilterWidget = new VisualDatabaseDisplayNameFilterWidget(this, deckEditor, filterModel); - mainTypeFilterWidget = new VisualDatabaseDisplayMainTypeFilterWidget(this, filterModel); - formatLegalityWidget = new VisualDatabaseDisplayFormatLegalityFilterWidget(this, filterModel); - subTypeFilterWidget = new VisualDatabaseDisplaySubTypeFilterWidget(this, filterModel); - setFilterWidget = new VisualDatabaseDisplaySetFilterWidget(this, filterModel); - - quickFilterSaveLoadWidget->addSettingsWidget(saveLoadWidget); - quickFilterNameWidget->addSettingsWidget(nameFilterWidget); - quickFilterSubTypeWidget->addSettingsWidget(subTypeFilterWidget); - quickFilterSetWidget->addSettingsWidget(setFilterWidget); - - filterContainerLayout->addWidget(sortByLabel); - filterContainerLayout->addWidget(sortColumnCombo); - filterContainerLayout->addWidget(sortOrderCombo); - filterContainerLayout->addWidget(filterByLabel); - filterContainerLayout->addWidget(quickFilterSaveLoadWidget); - filterContainerLayout->addWidget(quickFilterNameWidget); - filterContainerLayout->addWidget(quickFilterSubTypeWidget); - filterContainerLayout->addWidget(quickFilterSetWidget); - filterContainerLayout->addWidget(mainTypeFilterWidget); - filterContainerLayout->addWidget(formatLegalityWidget); + filterContainer->initialize(); searchLayout->addWidget(colorFilterWidget); searchLayout->addWidget(clearFilterWidget); @@ -246,11 +159,11 @@ void VisualDatabaseDisplayWidget::initialize() debounceTimer = new QTimer(this); debounceTimer->setSingleShot(true); // Ensure it only fires once after the timeout - connect(debounceTimer, &QTimer::timeout, this, &VisualDatabaseDisplayWidget::searchModelChanged); + connect(debounceTimer, &QTimer::timeout, this, &VisualDatabaseDisplayWidget::onSearchModelChanged); databaseDisplayModel->setFilterTree(filterModel->filterTree()); - connect(filterModel, &FilterTreeModel::layoutChanged, this, &VisualDatabaseDisplayWidget::searchModelChanged); + connect(filterModel, &FilterTreeModel::layoutChanged, this, &VisualDatabaseDisplayWidget::onSearchModelChanged); loadCardsTimer = new QTimer(this); loadCardsTimer->setSingleShot(true); // Ensure it only fires once after the timeout @@ -264,16 +177,7 @@ void VisualDatabaseDisplayWidget::initialize() void VisualDatabaseDisplayWidget::retranslateUi() { databaseLoadIndicator->setText(tr("Loading database ...")); - clearFilterWidget->setToolTip(tr("Clear all filters")); - - sortByLabel->setText(tr("Sort by:")); - filterByLabel->setText(tr("Filter by:")); - - quickFilterSaveLoadWidget->setToolTip(tr("Save and load filters")); - quickFilterNameWidget->setToolTip(tr("Filter by exact card name")); - quickFilterSubTypeWidget->setToolTip(tr("Filter by card sub-type")); - quickFilterSetWidget->setToolTip(tr("Filter by set")); } void VisualDatabaseDisplayWidget::resizeEvent(QResizeEvent *event) @@ -332,7 +236,7 @@ void VisualDatabaseDisplayWidget::updateSearch(const QString &search) const QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); } -void VisualDatabaseDisplayWidget::searchModelChanged() +void VisualDatabaseDisplayWidget::onSearchModelChanged() { if (flowWidget->isVisible()) { // Clear the current page and prepare for new data diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h index 24990f8e5..3aa8d7f8e 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h @@ -16,12 +16,7 @@ #include "../general/layout_containers/overlap_control_widget.h" #include "../utility/custom_line_edit.h" #include "visual_database_display_color_filter_widget.h" -#include "visual_database_display_filter_save_load_widget.h" -#include "visual_database_display_format_legality_filter_widget.h" -#include "visual_database_display_main_type_filter_widget.h" -#include "visual_database_display_name_filter_widget.h" -#include "visual_database_display_set_filter_widget.h" -#include "visual_database_display_sub_type_filter_widget.h" +#include "visual_database_display_filter_toolbar_widget.h" #include #include @@ -52,6 +47,21 @@ public: void sortCardList(const QStringList &properties, Qt::SortOrder order) const; void setDeckList(const DeckList &new_deck_list_model); + AbstractTabDeckEditor *getDeckEditor() + { + return deckEditor; + } + + CardDatabaseDisplayModel *getDatabaseDisplayModel() + { + return databaseDisplayModel; + } + + QTreeView *getDatabaseView() + { + return databaseView; + } + QWidget *searchContainer; QHBoxLayout *searchLayout; SearchLineEdit *searchEdit; @@ -60,7 +70,7 @@ public: VisualDatabaseDisplayColorFilterWidget *colorFilterWidget; public slots: - void searchModelChanged(); + void onSearchModelChanged(); signals: void cardClickedDatabaseDisplay(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance); @@ -80,23 +90,8 @@ protected slots: private: QLabel *databaseLoadIndicator; - QLabel *sortByLabel; - QComboBox *sortColumnCombo, *sortOrderCombo; - - QLabel *filterByLabel; QToolButton *clearFilterWidget; - QWidget *filterContainer; - QHBoxLayout *filterContainerLayout; - SettingsButtonWidget *quickFilterSaveLoadWidget; - VisualDatabaseDisplayFilterSaveLoadWidget *saveLoadWidget; - SettingsButtonWidget *quickFilterNameWidget; - VisualDatabaseDisplayNameFilterWidget *nameFilterWidget; - VisualDatabaseDisplayMainTypeFilterWidget *mainTypeFilterWidget; - VisualDatabaseDisplayFormatLegalityFilterWidget *formatLegalityWidget; - SettingsButtonWidget *quickFilterSubTypeWidget; - VisualDatabaseDisplaySubTypeFilterWidget *subTypeFilterWidget; - SettingsButtonWidget *quickFilterSetWidget; - VisualDatabaseDisplaySetFilterWidget *setFilterWidget; + VisualDatabaseDisplayFilterToolbarWidget *filterContainer; KeySignals searchKeySignals; AbstractTabDeckEditor *deckEditor; CardDatabaseModel *databaseModel; From c553e150369e5855aa9b75a77c9bbadc32b63d21 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 14 Jan 2026 05:20:12 -0800 Subject: [PATCH 141/325] [TabDeckEditor] Fix bug in #6499 causing view menu actions to sometimes not work (#6518) * remove a special case * fix --- cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp | 4 ---- .../tabs/visual_deck_editor/tab_deck_editor_visual.cpp | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index 3da078dda..9c7cc0a84 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -165,10 +165,6 @@ void TabDeckEditor::loadLayout() actions.aFloating->setChecked(dockWidget->isFloating()); } - // special case for cardDatabaseDock - auto &actions = dockToActions[cardDatabaseDockWidget]; - actions.aFloating->setChecked(actions.aVisible->isChecked()); - cardInfoDockWidget->setMinimumSize(layouts.getDeckEditorCardSize()); cardInfoDockWidget->setMaximumSize(layouts.getDeckEditorCardSize()); diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 8a92e1bc2..a3c18da7c 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -291,7 +291,7 @@ void TabDeckEditorVisual::loadLayout() QDockWidget *dockWidget = it.key(); const DockActions &actions = it.value(); - actions.aVisible->setCheckable(dockWidget->isHidden()); + actions.aVisible->setChecked(dockWidget->isHidden()); actions.aFloating->setEnabled(actions.aVisible->isChecked()); actions.aFloating->setChecked(dockWidget->isFloating()); } From 29f60c4a676d1bbd40895e56947eaef2c985fece Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Wed, 14 Jan 2026 14:41:54 +0100 Subject: [PATCH 142/325] [VDE] Placeholder image for deck view if deck is empty (#6516) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [VDE] A stab at things Took 14 minutes Took 10 minutes Took 5 minutes Took 4 minutes Took 41 seconds Took 10 minutes Took 3 minutes * [VDE] Use placeholder image for deck view if deck is empty. Took 15 minutes Took 9 seconds Took 5 seconds * Sort CMakeList correctly. Took 35 seconds Took 23 seconds * Visibility updates got lost in the rebase. Took 7 minutes * Same treatment for printing selector. Took 42 minutes * Actually add file. Took 4 minutes --------- Co-authored-by: Lukas Brübach --- cockatrice/CMakeLists.txt | 5 ++- cockatrice/cockatrice.qrc | 2 + .../resources/backgrounds/card_triplet.svg | 31 ++++++++++++++ .../placeholder_printing_selector.svg | 28 +++++++++++++ .../printing_selector/printing_selector.cpp | 23 +++++++++- .../printing_selector/printing_selector.h | 2 + .../printing_selector_placeholder_widget.cpp | 42 +++++++++++++++++++ .../printing_selector_placeholder_widget.h | 23 ++++++++++ .../visual_deck_editor_placeholder_widget.cpp | 41 ++++++++++++++++++ .../visual_deck_editor_placeholder_widget.h | 22 ++++++++++ .../visual_deck_editor_widget.cpp | 25 ++++++++++- .../visual_deck_editor_widget.h | 3 ++ 12 files changed, 242 insertions(+), 5 deletions(-) create mode 100644 cockatrice/resources/backgrounds/card_triplet.svg create mode 100644 cockatrice/resources/backgrounds/placeholder_printing_selector.svg create mode 100644 cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp create mode 100644 cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.h create mode 100644 cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp create mode 100644 cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index fb57bf7e0..c6d962ffb 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -203,6 +203,7 @@ set(cockatrice_SOURCES src/interface/widgets/printing_selector/printing_selector.cpp src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp + src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp src/interface/widgets/printing_selector/printing_selector_card_search_widget.cpp src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp @@ -228,6 +229,7 @@ set(cockatrice_SOURCES src/interface/widgets/utility/sequence_edit.cpp src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp + src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp @@ -236,6 +238,7 @@ set(cockatrice_SOURCES src/interface/widgets/visual_database_display/visual_database_display_widget.cpp src/interface/widgets/visual_database_display/visual_database_filter_display_widget.cpp src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp + src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp @@ -317,8 +320,6 @@ set(cockatrice_SOURCES src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h - src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp - src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h ) add_subdirectory(sounds) diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc index 6e90d342d..f087bfe66 100644 --- a/cockatrice/cockatrice.qrc +++ b/cockatrice/cockatrice.qrc @@ -60,6 +60,8 @@ resources/icons/mana/W.svg resources/backgrounds/home.png + resources/backgrounds/card_triplet.svg + resources/backgrounds/placeholder_printing_selector.svg resources/config/general.svg resources/config/appearance.svg diff --git a/cockatrice/resources/backgrounds/card_triplet.svg b/cockatrice/resources/backgrounds/card_triplet.svg new file mode 100644 index 000000000..3749c678b --- /dev/null +++ b/cockatrice/resources/backgrounds/card_triplet.svg @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/cockatrice/resources/backgrounds/placeholder_printing_selector.svg b/cockatrice/resources/backgrounds/placeholder_printing_selector.svg new file mode 100644 index 000000000..44f1e534c --- /dev/null +++ b/cockatrice/resources/backgrounds/placeholder_printing_selector.svg @@ -0,0 +1,28 @@ + + + diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp index 506395789..2f18c7116 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp @@ -8,6 +8,7 @@ #include "printing_selector_card_search_widget.h" #include "printing_selector_card_selection_widget.h" #include "printing_selector_card_sorting_widget.h" +#include "printing_selector_placeholder_widget.h" #include #include @@ -34,6 +35,8 @@ PrintingSelector::PrintingSelector(QWidget *parent, AbstractTabDeckEditor *_deck flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + placeholderWidget = new PrintingSelectorPlaceholderWidget(this); + sortToolBar = new PrintingSelectorCardSortingWidget(this); sortToolBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); @@ -70,8 +73,13 @@ PrintingSelector::PrintingSelector(QWidget *parent, AbstractTabDeckEditor *_deck layout->addWidget(sortAndOptionsContainer); + layout->addWidget(placeholderWidget); layout->addWidget(flowWidget); + // Initially show placeholder, hide flowWidget + placeholderWidget->setVisible(true); + flowWidget->setVisible(false); + cardSelectionBar = new PrintingSelectorCardSelectionWidget(this, deckStateManager); cardSelectionBar->setVisible(SettingsCache::instance().getPrintingSelectorNavigationButtonsVisible()); layout->addWidget(cardSelectionBar); @@ -139,7 +147,16 @@ void PrintingSelector::updateDisplay() widgetLoadingBufferTimer->deleteLater(); widgetLoadingBufferTimer = new QTimer(this); flowWidget->clearLayout(); - if (selectedCard != nullptr) { + + if (selectedCard.isNull()) { + // Show placeholder, hide flowWidget + placeholderWidget->setVisible(true); + flowWidget->setVisible(false); + setWindowTitle(tr("Printing Selector")); + } else { + // Hide placeholder, show flowWidget + placeholderWidget->setVisible(false); + flowWidget->setVisible(true); setWindowTitle(selectedCard->getName()); } getAllSetsForCurrentCard(); @@ -153,6 +170,8 @@ void PrintingSelector::updateDisplay() void PrintingSelector::setCard(const CardInfoPtr &newCard) { if (newCard.isNull()) { + selectedCard = newCard; + updateDisplay(); return; } @@ -229,4 +248,4 @@ void PrintingSelector::getAllSetsForCurrentCard() void PrintingSelector::toggleVisibilityNavigationButtons(bool _state) { cardSelectionBar->setVisible(_state); -} +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h index e48eb2f2c..f7844504d 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.h @@ -10,6 +10,7 @@ #include "../cards/card_size_widget.h" #include "../general/layout_containers/flow_widget.h" #include "../quick_settings/settings_button_widget.h" +#include "printing_selector_placeholder_widget.h" #include #include @@ -70,6 +71,7 @@ private: QCheckBox *navigationCheckBox; PrintingSelectorCardSortingWidget *sortToolBar; PrintingSelectorCardSearchWidget *searchBar; + PrintingSelectorPlaceholderWidget *placeholderWidget; FlowWidget *flowWidget; CardSizeWidget *cardSizeWidget; PrintingSelectorCardSelectionWidget *cardSelectionBar; diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp new file mode 100644 index 000000000..aee34bccd --- /dev/null +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp @@ -0,0 +1,42 @@ +#include "printing_selector_placeholder_widget.h" + +PrintingSelectorPlaceholderWidget::PrintingSelectorPlaceholderWidget(QWidget *parent) : QWidget(parent) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + mainLayout = new QVBoxLayout(this); + mainLayout->setAlignment(Qt::AlignCenter); + setLayout(mainLayout); + + // Image label with the background image + imageLabel = new QLabel(this); + imageLabel->setAlignment(Qt::AlignCenter); + imageLabel->setStyleSheet(R"( + QLabel { + background-image: url(theme:backgrounds/placeholder_printing_selector.svg); + background-repeat: no-repeat; + background-position: center; + } + )"); + imageLabel->setFixedSize(300, 300); + + textLabel = new QLabel(this); + textLabel->setAlignment(Qt::AlignCenter); + textLabel->setWordWrap(true); + textLabel->setStyleSheet(R"( + QLabel { + color: palette(mid); + font-size: 14px; + padding: 10px; + } + )"); + + mainLayout->addWidget(imageLabel); + mainLayout->addWidget(textLabel); + + retranslateUi(); +} + +void PrintingSelectorPlaceholderWidget::retranslateUi() +{ + textLabel->setText(tr("Select a card to view its available printings")); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.h new file mode 100644 index 000000000..abf030c69 --- /dev/null +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.h @@ -0,0 +1,23 @@ +#ifndef COCKATRICE_PRINTING_SELECTOR_PLACEHOLDER_WIDGET_H +#define COCKATRICE_PRINTING_SELECTOR_PLACEHOLDER_WIDGET_H + +#include +#include +#include + +class PrintingSelectorPlaceholderWidget : public QWidget +{ + Q_OBJECT + +public: + explicit PrintingSelectorPlaceholderWidget(QWidget *parent = nullptr); + +private: + QVBoxLayout *mainLayout; + QLabel *imageLabel; + QLabel *textLabel; + + void retranslateUi(); +}; + +#endif // COCKATRICE_PRINTING_SELECTOR_PLACEHOLDER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp new file mode 100644 index 000000000..da4fe09e5 --- /dev/null +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp @@ -0,0 +1,41 @@ +#include "visual_deck_editor_placeholder_widget.h" + +VisualDeckEditorPlaceholderWidget::VisualDeckEditorPlaceholderWidget(QWidget *parent) : QWidget(parent) +{ + mainLayout = new QVBoxLayout(this); + mainLayout->setAlignment(Qt::AlignCenter); + setLayout(mainLayout); + + // Image label with the background image + imageLabel = new QLabel(this); + imageLabel->setAlignment(Qt::AlignCenter); + imageLabel->setStyleSheet(R"( + QLabel { + background-image: url(theme:backgrounds/card_triplet.svg); + background-repeat: no-repeat; + background-position: center; + } + )"); + imageLabel->setFixedSize(300, 300); + + textLabel = new QLabel(this); + textLabel->setAlignment(Qt::AlignCenter); + textLabel->setWordWrap(true); + textLabel->setStyleSheet(R"( + QLabel { + color: palette(mid); + font-size: 14px; + padding: 10px; + } + )"); + + mainLayout->addWidget(imageLabel); + mainLayout->addWidget(textLabel); + + retranslateUi(); +} + +void VisualDeckEditorPlaceholderWidget::retranslateUi() +{ + textLabel->setText(tr("Add cards using the search bar or database tab to have them appear here")); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.h new file mode 100644 index 000000000..de2362a81 --- /dev/null +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.h @@ -0,0 +1,22 @@ +#ifndef COCKATRICE_VISUAL_DECK_EDITOR_PLACEHOLDER_WIDGET_H +#define COCKATRICE_VISUAL_DECK_EDITOR_PLACEHOLDER_WIDGET_H + +#include +#include +#include + +class VisualDeckEditorPlaceholderWidget : public QWidget +{ + Q_OBJECT + +public: + explicit VisualDeckEditorPlaceholderWidget(QWidget *parent = nullptr); + void retranslateUi(); + +private: + QVBoxLayout *mainLayout; + QLabel *imageLabel; + QLabel *textLabel; +}; + +#endif // COCKATRICE_VISUAL_DECK_EDITOR_PLACEHOLDER_WIDGET_H 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 9d2aadc63..d6afb5f22 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 @@ -59,6 +59,7 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, &VisualDeckEditorWidget::onSelectionChanged); } + updatePlaceholderVisibility(); retranslateUi(); } @@ -180,8 +181,14 @@ void VisualDeckEditorWidget::initializeScrollAreaAndZoneContainer() zoneContainer = new QWidget(scrollArea); zoneContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + zoneContainer->setObjectName("zoneContainer"); zoneContainerLayout = new QVBoxLayout(zoneContainer); zoneContainer->setLayout(zoneContainerLayout); + + // Create placeholder widget + placeholderWidget = new VisualDeckEditorPlaceholderWidget(zoneContainer); + zoneContainerLayout->addWidget(placeholderWidget); + scrollArea->addScrollBarWidget(zoneContainer, Qt::AlignHCenter); scrollArea->setWidget(zoneContainer); } @@ -200,6 +207,17 @@ void VisualDeckEditorWidget::retranslateUi() searchPushButton->setText(tr("Quick search and add card")); searchPushButton->setToolTip(tr("Search for closest match in the database (with auto-suggestions) and add " "preferred printing to the deck on pressing enter")); + + if (placeholderWidget) { + placeholderWidget->retranslateUi(); + } +} + +void VisualDeckEditorWidget::updatePlaceholderVisibility() +{ + if (placeholderWidget) { + placeholderWidget->setVisible(indexToWidgetMap.isEmpty()); + } } // ===================================================================================================================== @@ -250,6 +268,7 @@ void VisualDeckEditorWidget::constructZoneWidgetsFromDeckListModel() constructZoneWidgetForIndex(persistent); } + updatePlaceholderVisibility(); } void VisualDeckEditorWidget::updateZoneWidgets() @@ -264,6 +283,7 @@ void VisualDeckEditorWidget::clearAllDisplayWidgets() indexToWidgetMap.remove(idx); delete displayWidget; } + updatePlaceholderVisibility(); } void VisualDeckEditorWidget::cleanupInvalidZones(DeckCardZoneDisplayWidget *displayWidget) @@ -275,6 +295,7 @@ void VisualDeckEditorWidget::cleanupInvalidZones(DeckCardZoneDisplayWidget *disp } } delete displayWidget; + updatePlaceholderVisibility(); } // ===================================================================================================================== @@ -294,6 +315,7 @@ void VisualDeckEditorWidget::onCardAddition(const QModelIndex &parent, int first constructZoneWidgetForIndex(index); } } + updatePlaceholderVisibility(); } void VisualDeckEditorWidget::onCardRemoval(const QModelIndex &parent, int first, int last) @@ -308,6 +330,7 @@ void VisualDeckEditorWidget::onCardRemoval(const QModelIndex &parent, int first, indexToWidgetMap.remove(idx); } } + updatePlaceholderVisibility(); } void VisualDeckEditorWidget::decklistModelReset() @@ -390,4 +413,4 @@ void VisualDeckEditorWidget::onSelectionChanged(const QItemSelection &selected, } } } -} +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h index 5789155b8..13065d623 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.h @@ -11,6 +11,7 @@ #include "../cards/card_size_widget.h" #include "../general/layout_containers/overlap_control_widget.h" #include "../quick_settings/settings_button_widget.h" +#include "visual_deck_editor_placeholder_widget.h" #include #include @@ -44,6 +45,7 @@ public: void setSelectionModel(QItemSelectionModel *model); void onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + void updatePlaceholderVisibility(); QItemSelectionModel *getSelectionModel() const { return selectionModel; @@ -96,6 +98,7 @@ private: QScrollArea *scrollArea; QWidget *zoneContainer; QVBoxLayout *zoneContainerLayout; + VisualDeckEditorPlaceholderWidget *placeholderWidget; // OverlapControlWidget *overlapControlWidget; QHash indexToWidgetMap; }; From c075deeb2dd9aa547a43a87bc8df983662eff116 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Wed, 14 Jan 2026 14:47:27 +0100 Subject: [PATCH 143/325] [Placeholder images] Update color. (#6519) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 19 minutes Co-authored-by: Lukas Brübach --- cockatrice/resources/backgrounds/card_triplet.svg | 10 +++++----- .../backgrounds/placeholder_printing_selector.svg | 11 ++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/cockatrice/resources/backgrounds/card_triplet.svg b/cockatrice/resources/backgrounds/card_triplet.svg index 3749c678b..91114d578 100644 --- a/cockatrice/resources/backgrounds/card_triplet.svg +++ b/cockatrice/resources/backgrounds/card_triplet.svg @@ -1,10 +1,10 @@ @@ -13,19 +13,19 @@ id="g13"> diff --git a/cockatrice/resources/backgrounds/placeholder_printing_selector.svg b/cockatrice/resources/backgrounds/placeholder_printing_selector.svg index 44f1e534c..73a0082ab 100644 --- a/cockatrice/resources/backgrounds/placeholder_printing_selector.svg +++ b/cockatrice/resources/backgrounds/placeholder_printing_selector.svg @@ -1,28 +1,29 @@ + + style="fill:#989898;fill-opacity:1;stroke-width:0.198215" /> From 6213ccff48b922558acfaf02df042bd498111530 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 06:53:55 +0100 Subject: [PATCH 144/325] Update translation files (#6521) Co-authored-by: github-actions --- cockatrice/translations/cockatrice_cs.ts | 4674 +++++++++------- cockatrice/translations/cockatrice_de.ts | 4538 +++++++++------- cockatrice/translations/cockatrice_el.ts | 4672 +++++++++------- cockatrice/translations/cockatrice_en_US.ts | 4538 +++++++++------- cockatrice/translations/cockatrice_es.ts | 4658 +++++++++------- cockatrice/translations/cockatrice_fi.ts | 4680 +++++++++------- cockatrice/translations/cockatrice_fr.ts | 4538 +++++++++------- cockatrice/translations/cockatrice_it.ts | 4538 +++++++++------- cockatrice/translations/cockatrice_ja.ts | 4564 +++++++++------- cockatrice/translations/cockatrice_ko.ts | 4674 +++++++++------- cockatrice/translations/cockatrice_nl.ts | 4646 +++++++++------- cockatrice/translations/cockatrice_pl.ts | 4676 +++++++++------- cockatrice/translations/cockatrice_pt.ts | 4694 +++++++++------- cockatrice/translations/cockatrice_pt_BR.ts | 4728 ++++++++++------- cockatrice/translations/cockatrice_ru.ts | 4560 +++++++++------- cockatrice/translations/cockatrice_sr.ts | 4692 +++++++++------- cockatrice/translations/cockatrice_sv.ts | 4674 +++++++++------- cockatrice/translations/cockatrice_yue.ts | 4676 +++++++++------- cockatrice/translations/cockatrice_zh-Hans.ts | 4676 +++++++++------- cockatrice/translations/cockatrice_zh-Hant.ts | 4680 +++++++++------- oracle/translations/oracle_el.ts | 214 +- oracle/translations/oracle_en_US.ts | 214 +- oracle/translations/oracle_es.ts | 214 +- oracle/translations/oracle_et.ts | 214 +- oracle/translations/oracle_fi.ts | 214 +- oracle/translations/oracle_fr.ts | 214 +- oracle/translations/oracle_it.ts | 214 +- oracle/translations/oracle_ja.ts | 214 +- oracle/translations/oracle_ko.ts | 214 +- oracle/translations/oracle_nb.ts | 214 +- oracle/translations/oracle_nl.ts | 214 +- oracle/translations/oracle_pl.ts | 214 +- oracle/translations/oracle_pt.ts | 214 +- oracle/translations/oracle_pt_BR.ts | 214 +- oracle/translations/oracle_ru.ts | 214 +- oracle/translations/oracle_sr.ts | 214 +- oracle/translations/oracle_tr.ts | 214 +- oracle/translations/oracle_yue.ts | 214 +- oracle/translations/oracle_zh-Hans.ts | 214 +- 39 files changed, 56558 insertions(+), 40284 deletions(-) diff --git a/cockatrice/translations/cockatrice_cs.ts b/cockatrice/translations/cockatrice_cs.ts index 604ea1ce9..c13bfb2df 100644 --- a/cockatrice/translations/cockatrice_cs.ts +++ b/cockatrice/translations/cockatrice_cs.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Nastavit žeton... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter - + New value for counter '%1': Nová hodnota pro žeton '%1': @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,79 +36,81 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. + + Add Analytics Panel AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -116,12 +118,12 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard - + Sideboard @@ -129,22 +131,22 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error Chyba - + Could not create themes directory at '%1'. - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -155,7 +157,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -166,152 +168,165 @@ Are you sure you would like to disable this feature? - + Confirm Change - + Theme settings Nastavení vzhledu - + Current theme: Aktuální vzhled - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + + Show game filter toolbar above list in room tab + + + + Card rendering Vykreslování karet - + Display card names on cards having a picture Zobrazit jména karet na kartách s obrázky - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Zvětšovat karty při najetí myši - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Rozvržení ruky - + Display hand horizontally (wastes space) Zobrazit ruku horizontálně (plýtvá místem) - + Enable left justification Zapnout - + Table grid layout Rozložení herní mřížky - + Invert vertical coordinate Převrátit vertikální souřadnice - + Minimum player count for multi-column layout: Minimální počet hráčů pro víceřádkové rozvržení: - + Maximum font size for information displayed on cards: Maximální velikost písma pro informace na kartách + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -333,112 +348,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name Blokovat & Uživatelské jméno - + ban &IP address Blokovat & IP Adresu - + ban client I&D Banovat I&D klienta - + Ban type Typ banu - + &permanent ban &trvalý ban - + &temporary ban &dočasný ban - + &Days: &Dní: - + &Hours: &Hodin: - + &Minutes: &Minut: - + Duration of the ban Trvání banu - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Uveďte prosím důvod pro ban. Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. - + Please enter the reason for the ban that will be visible to the banned person. Uveďte prosám důvod pro ban, který uvidí banovaná osoba. - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Zrušit - + Ban user from server Zabanovat uživatele ze serveru - + + - - + Error Chyba - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. - + You must have a value in the name ban when selecting the name ban checkbox. - + You must have a value in the ip ban when selecting the ip ban checkbox. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. @@ -469,32 +484,32 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardDatabaseModel - + Name Jméno - + Sets Edice - + Mana cost Sesílací cena - + Card type Typ karty - + P/T S/O - + Color(s) Barva(y) @@ -502,96 +517,101 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardFilter - + AND Logical conjunction operator used in card filter A - + OR Logical disjunction operator used in card filter NEBO - + AND NOT Negated logical conjunction operator used in card filter A NE - + OR NOT Negated logical disjunction operator used in card filter NEBO NE - + Name Jméno - + + Name (Exact) + + + + Type Typ - + Color Barva - + Text Popis - + Set Edice - + Mana Cost Sesílací cena - + Mana Value Počet Many - + Rarity Vzácnost - + Power Síla - + Toughness Obrana - + Loyalty Oddanost - + Format Formát - + Main Type - + Sub Type @@ -599,22 +619,22 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardInfoFrameWidget - + Image - + Description - + Both - + View transformation @@ -622,22 +642,22 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -650,12 +670,22 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. - + + Set: + + + + + Collector Number: + + + + Related cards: - + Unknown card: @@ -663,124 +693,124 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -788,7 +818,7 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardSizeWidget - + Card Size @@ -796,133 +826,133 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardZoneLogic - + their hand nominative - + %1's hand nominative - - - their library - look at zone - - - - - %1's library - look at zone - - - - - of their library - top cards of zone, - - - - - of %1's library - top cards of zone - - their library - reveal zone + look at zone %1's library + look at zone + + + + + of their library + top cards of zone, + + + + + of %1's library + top cards of zone + + + + + their library reveal zone - + + %1's library + reveal zone + + + + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - - - their graveyard - nominative - - - - - %1's graveyard - nominative - - - - - their exile - nominative - - - - - %1's exile - nominative - - + their graveyard + nominative + + + + + %1's graveyard + nominative + + + + + their exile + nominative + + + + + %1's exile + nominative + + + + their sideboard look at zone - + %1's sideboard look at zone - - - their sideboard - nominative - - - - - %1's sideboard - nominative - - - their custom zone '%1' + their sideboard nominative + %1's sideboard + nominative + + + + + their custom zone '%1' + nominative + + + + %1's custom zone '%2' nominative @@ -931,7 +961,7 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CockatriceXml3Parser - + Parse error at line %1 col %2: @@ -939,7 +969,7 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CockatriceXml4Parser - + Parse error at line %1 col %2: @@ -958,6 +988,37 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -969,47 +1030,47 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add to Deck - + Add to Sideboard - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1017,87 +1078,97 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing - + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + + Format: + + + + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1105,17 +1176,17 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1123,114 +1194,114 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1238,7 +1309,7 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1246,194 +1317,227 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckEditorSettingsPage - - + + Update Spoilers Aktualizovat spoilery - - + + Success Úspěch - + Download URLs have been reset. - + Downloaded card pictures have been reset. Stáhnuté obrázky karet byly resetovány. - + Error Chyba - + One or more downloaded card pictures could not be cleared. Některé stáhnuté obrázky karet nemohli být vymazány. - + Add URL - - + + URL: URL: - - + + Edit URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL - + Remove URL - + Day(s) - + Updating... Aktualizuji... - + Choose path Vyberte cestu - + URL Download Priority - + Spoilers Spoilery - + Download Spoilers Automatically Stahovat spoilery automaticky - + Spoiler Location: - + Last Change Poslední změna - + Spoilers download automatically on launch Automaticky stahovat spoilery při spuštění - + Press the button to manually update without relaunching Manuálně aktualizovat bez restartování - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images Smazat stáhnuté obrázky - + Reset Download URLs - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count - + Set - + Number Počet - + Provider ID - + Card Karta @@ -1441,12 +1545,12 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1454,17 +1558,17 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1472,7 +1576,7 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -1480,62 +1584,62 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -1548,93 +1652,151 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1652,74 +1814,74 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckViewContainer - + Load deck... Načíst balíček... - + Load remote deck... Načíst balíček ze serveru... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start Připraven začít - + Force start - + Sideboard unlocked - + Sideboard locked - - + + Error Chyba - + The selected file could not be loaded. Vybraný soubor se nepodařilo načíst. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -1752,143 +1914,143 @@ This will kick all non-ready players from the game. Stahuji... - + Known Hosts Známí hostitelé - + Delete the currently selected saved server - + Refresh the server list with known public servers Znovu načíst seznam serverů se známými veřejnými servery - + New Host Nový hostitel - + Name: Jméno: - + &Host: &Hostitel: - + &Port: &Port: - + Player &name: Jméno &hráče: - + P&assword: H&eslo: - + &Save password &Uložit heslo - + A&uto connect A&utomatické připojení - + Automatically connect to the most recent login when Cockatrice opens Automaticky se připojit k poslednímu přihlášení při otevření Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! Jestli máte potíže se připájením nebo registrací, kontaktujte pracovníky servery pro pomoc! Pouze Anglicky - - + + Webpage Webová stránka - + Reset Password - + Forgot password? - + &Connect &Připojit - + Server Server - + Login Přihlášení - + Server Contact - + Connect to Server Připojit k serveru - + Server URL - + Communication Port Komunikační port - + Unique Server Name - + Connection Warning - + You need to name your new connection profile. - + Connect Warning Varování přihlášení - + The player name can't be empty. Jméno hráče nemůže být prázdné @@ -1896,117 +2058,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings Pamatovat si nastavení - + &Description: &Popis: - + P&layers: H&ráči: - + General Obecné - + Game type Typ hry - + &Password: &Heslo: - + Only &buddies can join Jen pro &přátele - + Only &registered users can join Jen pro &registrované - + Joining restrictions Omezení připojení - + &Spectators can watch &Diváci se mohou dívat - + Spectators &need a password to watch Diváci &potřebují ke sledování heslo - + Spectators can &chat Diváci mohou &chatovat - + Spectators can see &hands Diváci mohou vidět do &rukou - + Create game as spectator - + Spectators Diváci - + Starting life total: - + Open decklists in lobby - + + Create game as judge + + + + Game setup options - + &Clear &Smazat - + Create game Vytvořit hru - + Game information Informace o hře - + Error Chyba - + Server error. Chyba serveru. @@ -2014,97 +2181,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Jméno: - + Token Token - + C&olor: B&arva: - + white bílá - + blue modrá - + black černá - + red červená - + green zelená - + multicolor vícebarevný - + colorless bezbarvý - + &P/T: &S/O: - + &Annotation: &Poznámka: - + &Destroy token when it leaves the table &Token se při odchodu z bojiště zničí - + Create face-down (Only hides name) - + Token data Data tokenů - + Show &all tokens Ukázat &všechny tokeny - + Show tokens from this &deck Ukázat tokeny z tohto &balíčku - + Choose token from list Vybrat token ze seznamu. - + Create token Vytvořit token @@ -2112,53 +2279,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2166,40 +2333,40 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. Nebyl vybrán obrázek. - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. Pro změnu avatara vyberte nový obrázek. Pro odstranění současného avatara potvrďte bez vybrání nového obrázku. - + Browse... Prohlížet... - + Change avatar Změnit avatara - + Open Image Otevřít obrázek - + Image Files (*.png *.jpg *.bmp) Soubory obrázků (*.png *.jpg *.bmp) - + Invalid image chosen. Vybrán neplatný obrázek @@ -2207,17 +2374,17 @@ Pro odstranění současného avatara potvrďte bez vybrání nového obrázku.< DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2225,38 +2392,38 @@ Pro odstranění současného avatara potvrďte bez vybrání nového obrázku.< DlgEditPassword - + Old password: Staré heslo: - + New password: Nové heslo: - + Confirm new password: Potvrdit nové heslo: - + Change password Změnit heslo - - + + Error Chyba - + Your password is too short. - + The new passwords don't match. Nová hesla nejsou stejná. @@ -2264,93 +2431,93 @@ Pro odstranění současného avatara potvrďte bez vybrání nového obrázku.< DlgEditTokens - + &Name: &Jméno - + C&olor: B&arva - + white bílá - + blue modrá - + black černá - + red červená - + green zelená - + multicolor vícebarevná - + colorless bezbarvá - + &P/T: &S/O - + &Annotation: &Poznámka: - + Token data Data k tokenu - - + + Add token Přidat token - + Remove token Odstranit token - + Edit custom tokens Upravit vlastní tokeny - + Please enter the name of the token: Zadejte prosím jméno pro token: - + Error Chyba - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Vybrané jméno se nemůže shodovat s již existující kartou nebo tokenem. @@ -2444,7 +2611,8 @@ Pro správné zobrazení si aktivujte si edici 'Token' v "Nastavi - Hide games not created by buddy + Hide games not created by buddies + Hide games not created by buddy @@ -2531,52 +2699,52 @@ Pro správné zobrazení si aktivujte si edici 'Token' v "Nastavi DlgForgotPasswordChallenge - + Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: &Hostitel: - + &Port: &Port: - + Player &name: Jméno &hráče: - + Email: Email: - + Reset Password Challenge - + Reset Password Challenge Error - + The email address can't be empty. Emailová adresa musí být vyplněna. @@ -2584,37 +2752,37 @@ Pro správné zobrazení si aktivujte si edici 'Token' v "Nastavi DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: &Hostitel: - + &Port: &Port: - + Player &name: &Jméno hráče: - + Reset Password Request - + Reset Password Error - + The player name can't be empty. Jméno hráče musí být vyplněno. @@ -2622,86 +2790,86 @@ Pro správné zobrazení si aktivujte si edici 'Token' v "Nastavi DlgForgotPasswordReset - + Reset Password Warning - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: &Hostitel: - + &Port: &Port: - + Player &name: &Jméno hráče: - + Token: Token: - - + + New Password: Nové Heslo: - + Reset Password - + The player name can't be empty. Vyplňte jméno hráče. - - - - + + + + Reset Password Error - + The token can't be empty. Vyplňte token. - + The new password can't be empty. Vyplňte nové heslo. - + Error - + Your password is too short. - + The passwords do not match. Hesla se neshodují. @@ -2717,17 +2885,17 @@ Pro správné zobrazení si aktivujte si edici 'Token' v "Nastavi DlgLoadDeckFromClipboard - + Load deck from clipboard Nahrát balíček ze schránky - + Error Chyba - + Invalid deck list. Neplatný formát balíčku. @@ -2735,43 +2903,43 @@ Pro správné zobrazení si aktivujte si edici 'Token' v "Nastavi DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2785,7 +2953,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck Nahrát balíček @@ -2793,37 +2961,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -2831,91 +2999,91 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: &Hostitel: - + &Port: Port: - + Player &name: Jméno &hráče: - + P&assword: H&eslo: - + Password (again): Heslo (znovu): - + Email: Email: - + Email (again): Email (znovu): - + Country: Země: - + Undefined Neznámý - + Real name: Skutečné jméno: - + Register to server Registrovat se na server - - - - + + + + Registration Warning - + Your password is too short. - + Your passwords do not match, please try again. Vaše hesla se neshodují. - + Your email addresses do not match, please try again. Vaše emailové adresy se neshodují. - + The player name can't be empty. Vyplňte jméno hráče. @@ -2941,40 +3109,55 @@ Your email will be used to verify your account. DlgSelectSetForCards - + Unmodified Cards: - + Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database Neznámý error při nahrávání databáze karet. - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -2991,7 +3174,7 @@ Je možné že bude nutné znovu spustit Oracle pro obnovení databáze karet. Chtěl/a by jste změnit nastavení lokace databáze? - + Your card database version is too old. This can cause problems loading card information or images @@ -3008,7 +3191,7 @@ Je možné to spravit spuštěním Oracle pro aktualizaci vaší databáze karet Chcete změnit nastavení lokace databáze? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3017,7 +3200,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3026,7 +3209,7 @@ Would you like to change your database location setting? Chcete změnit nastavení lokace databáze? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3035,7 +3218,7 @@ Would you like to change your database location setting? Chcete změnit nastavení lokace databáze? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3044,59 +3227,59 @@ Would you like to change your database location setting? - - - + + + Error Chyba - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Cesta k adresáři s balíčky je neplatná. Chcete se vrátit a nastavit správnou? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Cesta k adresáři s obrázky je neplatná. Chcete se vrátit a nastavit správnou? - + Settings Nastavení - + General Obecné - + Appearance Vzhled - + User Interface Rozhraní - + Card Sources - + Chat Chat - + Sound Zvuk - + Shortcuts Skratky @@ -3104,39 +3287,39 @@ Would you like to change your database location setting? DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3144,17 +3327,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next Další - + Previous Předchozí - + Tip of the Day Tip @@ -3162,164 +3345,164 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel - + Reinstall Znovu instalovat - + Cancel Download Přerušit stahování - + Open Download Page - + Check for Client Updates - - - - + + + + Error Chyba - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - + Downloading update: %1 - + Checking for updates... Hledám aktualizace... - + Finished checking for updates Aktualizace byly skontrolovány - + No Update Available Žádná dostupná aktualizace - + Cockatrice is up to date! Cockatrice je v aktuální verzi! - + You are already running the latest version available in the chosen release channel. - + Current version Aktuální verze - + Selected release channel - - + + Update Available Dostupná aktualizace - - + + A new version of Cockatrice is available! Je dostupná nová verze Cockatrice! - - + + New version Nová verze - - + + Released - - + + Changelog - + Do you want to update now? Chcete aktualizovat nyní? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + + Update Error Chyba aktualizace - + An error occurred while checking for updates: Došlo k chybě při kontrole aktualizací: - + An error occurred while downloading an update: Došlo k chybě při stahování aktualizace: - + Installing... Instaluji... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -3327,21 +3510,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing - + Copy to clipboard - + Debug Log + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3411,33 +3726,39 @@ You may have to manually download the new version. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3453,22 +3774,22 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. @@ -3476,22 +3797,22 @@ You may have to manually download the new version. GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -3499,226 +3820,279 @@ You may have to manually download the new version. GameSelector - - - - - - + + + + + + Error Chyba - + Please join the appropriate room first. - + Wrong password. Špatné heslo. - + Spectators are not allowed in this game. Do této hry je divákům přístup zakázán. - + The game is already full. Hra je plná. - + The game does not exist any more. Hra již neexistuje. - + This game is only open to registered users. Hra je určena jen pro registrované. - + This game is only open to its creator's buddies. Hra je dostupná jen pro přátele zakládajícícho. - + You are being ignored by the creator of this game. Zakladatel hry vás ignoruje. - + Join Game - + Spectate Game - + Game Information - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game Připojit ke hře - + Password: Heslo: - + Please join the respective room first. - + &Filter games &Filtrovat hry: - + C&lear filter Z&rušit filtr: - + C&reate V&ytvořit - + &Join &Připojit se - + + Join as judge + + + + J&oin as spectator P&řipojit se jako divák - + + Join as judge spectator + + + + Games shown: %1 / %2 - + Games Hry + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password heslo - + buddies only jen pro přátele - + reg. users only jen pro registrované - + open decklists - - + + can chat může chatovat - + see hands vidět do rukou - + can see hands může vidět do rukou - + not allowed nepovolené - + Room Místnost - + Age Doba od otevření - + Description Popis - + Creator Zakladatel - + Type Typ - + Restrictions Omezení - + Players Hráči - + Spectators Diváci @@ -3726,143 +4100,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path Vyberte cestu - + Personal settings Osobní nastavení - + Language: Jazyk: - + Paths (editing disabled in portable mode) - + Paths Cesty - + How to help with translations - + Decks directory: Adresář s balíčky: - + Filters directory: - + Replays directory: Adresář se záznamy her - + Pictures directory: Adresář s obrázky: - + Card database: Databáze karet: - + Custom database directory: - + Token database: Databáze tokenů: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice Automaticky spustit Oracle při spouštění nové verze Cockatrice - + Show tips on startup Ukázat tip při spuštění - + Last update check on %1 (%2 days ago) @@ -3870,47 +4244,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -3918,111 +4292,141 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - - &Sort hand + + Sort hand by... - - Take &mulligan + + Name - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - Reveal r&andom card to... + + + All players - - - &All players + + Reveal r&andom card to... HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + + Browse Archidekt + + + + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4224,747 +4628,747 @@ You may have to manually download the new version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Na serveru je maximální počet uživatelů, skuste později. - + There are too many concurrent connections from your address. Z vaší adresy jde mnoho současných připojení. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. Neplatné uživatelské jméno. - + You have been logged out due to logging in at another location. - + Connection closed Připojení uzavřeno - + The server has terminated your connection. Reason: %1 Server přerušil spojení. Důvod: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Number of players Počet hráčů - + Please enter the number of players. Vložte počet hráčů. - - + + Player %1 Hráč %1 - + Load replay - + About Cockatrice O Cockatrice - + Version - + Cockatrice Webpage - + Project Manager: - + Past Project Managers: - + Developers: - + Our Developers - + Help Develop! - + Translators: Překlad: - + Our Translators - + Help Translate! - + Support: - + Report an Issue - + Troubleshooting - + F.A.Q. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error Chyba - + Server timeout Vypršel časový limit - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. S tímto uživatelským jménem jste již připojeni. Přerušte spojení a znovu se přihlašte. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - + + + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 Chyba socketu: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Snažíte se připojit na zastaralý server. Prosíme, stáhněte si nižší verzi Cockatrice, nebo se připojte k odpovídajícímu serveru. Lokální verze je %1, verze serveru je %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Váš klient je zastaralý. Prosíme, aktualizujte Cockatrice na vyšší verzi. Lokální verze je %1, verze serveru je %2. - + Connecting to %1... Připojování k %1... - + Registering to %1 as %2... - + Disconnected Odpojeno - + Connected, logging in at %1 + - Requesting forgotten password to %1 as %2... - + &Connect... &Připojit... - + &Disconnect &Odpojit - + Start &local game... Spustit &lokální hru... - + &Watch replay... - + &Full screen &Celá obrazovka - + &Register to server... - + &Restore password... - + &Settings... &Nastavení... - + &Exit &Konec - + A&ctions - + &Cockatrice &Cockatrice - + C&ard Database - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + Reload card database - + Tabs - + &Help &Nápověda - + &About Cockatrice &O Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information - + A card database update is already running. - + Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -4972,672 +5376,842 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play - + from their graveyard - + from exile z exilnutých karet - + from their hand - + the top card of %1's library - + the top card of their library - + from the top of %1's library - + from the top of their library - + the bottom card of %1's library - + the bottom card of their library - + from the bottom of %1's library - + from the bottom of their library - + from %1's library - + from their library - + from sideboard ze sideboardu - + from the stack ze stacku - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. - + a card kartu - + %1 gives %2 control over %3. %1 předává kontrolu hráči %2 karty %3. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 dává kartu %2 %3 do hry. - + %1 puts %2%3 into their graveyard. - + %1 exiles %2%3. %1 exiluje %2%3. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. %1 přesouvá %2%3 do sideboardu. - + %1 plays %2%3. %1 sesílá %2%3. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. Hra byla ukončena. - + The game has started. Hra začíná. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. - + %1 is now watching the game. %1 nyní sleduje hru. - + You have been kicked out of the game. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. - + %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -5645,110 +6219,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Přidat zprávu - - + + Message: Zpráva: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -5865,62 +6439,62 @@ Cockatrice will now reload the card database. Phase - + Unknown Phase - + Untap - + Upkeep - + Draw - + First Main - + Beginning of Combat - + Declare Attackers - + Declare Blockers - + Combat Damage - + End of Combat - + Second Main - + End/Cleanup @@ -5986,7 +6560,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -5995,134 +6569,134 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - + Move top cards to grave - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6130,17 +6704,17 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters - + S&ay @@ -6148,7 +6722,7 @@ Cockatrice will now reload the card database. PrintingSelector - + Display Navigation Buttons @@ -6156,22 +6730,22 @@ Cockatrice will now reload the card database. PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6187,17 +6761,17 @@ Cockatrice will now reload the card database. PrintingSelectorCardSelectionWidget - + Previous Card in Deck - + Bulk Selection - + Next Card in Deck @@ -6205,28 +6779,28 @@ Cockatrice will now reload the card database. PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -6292,37 +6866,37 @@ Cockatrice will now reload the card database. QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -6330,42 +6904,42 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - + All files (*.*) - + Cockatrice replays (*.cor) - + Maindeck - + Sideboard - + Tokens - + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? @@ -6373,92 +6947,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -6555,37 +7129,37 @@ Cockatrice will now reload the card database. RoomSelector - + Rooms Místnosti - + Joi&n &Připojit - + Room Místnost - + Description Popis - + Permissions - + Players Hráči - + Games Hry @@ -6626,27 +7200,27 @@ Cockatrice will now reload the card database. SetsModel - + Enabled - + Set type - + Set code - + Long name Dlouhý název - + Release date @@ -6654,53 +7228,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -6708,12 +7282,12 @@ Cockatrice will now reload the card database. ShortcutTreeView - + Action - + Shortcut @@ -6721,13 +7295,13 @@ Cockatrice will now reload the card database. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -6754,12 +7328,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -6767,27 +7341,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -6795,48 +7369,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -7000,56 +7574,123 @@ Please check your shortcut settings! + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info - + Deck - + Filters - + &View - + + Card Database + + + + Printing - - - - + + + + + Visible - - - - + + + + + Floating - + Reset layout - + Deck: %1 @@ -7057,61 +7698,61 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + + + + Visible - - - - + + + + Floating - + Reset layout @@ -7119,22 +7760,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7142,134 +7783,134 @@ Please check your shortcut settings! TabDeckStorage - + Local file system Lokální systém souborů - + Server deck storage Balíčky na serveru - - + + Open in deck editor Otevřít v editoru balíčků - + Rename deck or folder - + Upload deck Nahrát balíček - + Download deck Stáhnout balíček + - - - + + New folder Nová složka + - Delete Smazat - + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + Error - + Rename failed - - + + Invalid deck file - + Enter deck name Vložit jméno balíčku - + This decklist does not have a name. Please enter a name: Tentobalíček nemá jméno. Prosím vložte jméno: - + Unnamed deck Bezejmenný balíček - + Failed to upload deck to server - + Delete local file - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + Name of new folder: Název nové složky: @@ -7282,17 +7923,17 @@ Prosím vložte jméno: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 @@ -7333,7 +7974,7 @@ Prosím vložte jméno: - + EDHRec: @@ -7341,197 +7982,197 @@ Prosím vložte jméno: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases &Fáze - + &Game &Hra - + Next &phase Další &fáze - + Next phase with &action - + Next &turn Další &kolo - + Reverse turn order - + &Remove all local arrows &Odstranit všechny lokální šipky - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + Un&concede - - - + + + &Concede &Ukončit hru - + &Leave game &Opustit hru - + C&lose replay - + &Focus Chat - + &Say: &Chat: - + Selected cards - + &View - - + + + - Visible - - + + + - Floating - + Reset layout - + Concede Ukončit hru - + Are you sure you want to concede this game? Opravdu chcete ukončit tuto hru? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game Opustit hru - + Are you sure you want to leave this game? Opravdu chcete opustit tuto hru? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. @@ -7539,7 +8180,7 @@ Prosím vložte jméno: TabHome - + Home @@ -7547,157 +8188,157 @@ Prosím vložte jméno: TabLog - + Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error Chyba - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -7743,180 +8384,180 @@ The more information you put in, the more specific your results will be. TabReplays - + Local file system - + Server replay storage - - + + Watch replay - + Rename - - + + New folder - - + + Delete - + Open replays folder - + Download replay - + Toggle expiration lock - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay @@ -7982,30 +8623,30 @@ The more information you put in, the more specific your results will be. + - Error Chyba - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -8013,92 +8654,97 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - - - Home - - - - - &Visual Deck Storage - - - - - Visual Database Display - - - Server + Archidekt - Account + Home - Deck Storage + &Visual Deck Storage - Game Replays + Visual Database Display - Administration + Server + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + Logs - + Are you sure? - + There are still open games. Are you sure you want to quit? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8106,38 +8752,38 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -8146,7 +8792,12 @@ Please refrain from engaging in this activity or further actions may be taken ag TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display @@ -8226,7 +8877,7 @@ Please refrain from engaging in this activity or further actions may be taken ag UpdateDownloader - + Could not open the file for reading. @@ -8234,206 +8885,206 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details - + Private &chat - + Show this user's &games - + Add to &buddy list - + Remove from &buddy list - + Add to &ignore list - + Remove from &ignore list - + Kick from &game - + Warn user - + View user's war&n history - + Ban from &server - + View user's &ban history - + &Promote user to moderator - + Dem&ote user from moderator - + Promote user to &judge - + Demote user from judge - + View admin notes - - - + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - + Failed to get admin notes. - - + + Success - + Successfully promoted user. - + Successfully demoted user. - + + - Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -8615,137 +9266,142 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings Obecné - + &Double-click cards to play them (instead of single-click) &Pro zahraní karty je třeba dvojklik - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default - - - Close card view window when last card is removed - - - - - Auto focus search bar when card view window is opened - - - - - Annotate card text on tokens - - - - - Use tear-off menus, allowing right click menus to persist on screen - - - - - Notifications settings - - - - - Enable notifications in taskbar - - - - - Notify in the taskbar for game events while you are spectating - - - - - Notify in the taskbar when users in your buddy list connect - - - - - Animation settings - Animace - - - - &Tap/untap animation - &Animace tapnutí/odtapnutí - - Deck editor/storage settings + Do not delete &arrows inside of subphases - Open deck in new tab by default + Close card view window when last card is removed - Use visual deck storage in game lobby + Auto focus search bar when card view window is opened - Use selection animation for Visual Deck Storage + Annotate card text on tokens + + + + + Use tear-off menus, allowing right click menus to persist on screen - When adding a tag in the visual deck storage to a .txt deck: + Notifications settings + + + + + Enable notifications in taskbar - do nothing + Notify in the taskbar for game events while you are spectating + + + + + Notify in the taskbar when users in your buddy list connect - ask to convert to .cod - + Animation settings + Animace + + + + &Tap/untap animation + &Animace tapnutí/odtapnutí - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -8753,22 +9409,22 @@ Please refrain from engaging in this activity or further actions may be taken ag UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 @@ -8776,32 +9432,32 @@ Please refrain from engaging in this activity or further actions may be taken ag UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -8809,22 +9465,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match - + Mode: Includes - + Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -8832,21 +9488,49 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8872,28 +9556,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - Load from Deck + Filter by name... (Exact match) - Apply all card names in currently loaded deck as exact match name filters + Load from Deck - Load from Clipboard + Apply all card names in currently loaded deck as exact match name filters + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -8957,50 +9641,115 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - + + Sort by: + + + + + Filter by: + + + + Save and load filters - + Filter by exact card name - + Filter by card sub-type - + Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9008,46 +9757,25 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -9103,7 +9831,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9111,22 +9839,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -9134,17 +9862,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + Visual Deck Storage Settings @@ -9152,43 +9880,43 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Warn user for misconduct - - + + Error Chyba - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. @@ -9196,133 +9924,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -9330,72 +10058,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing zamíchat po zavření - + pile view @@ -9403,7 +10131,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English Česky (Czech) @@ -9411,12 +10139,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -9424,1005 +10152,1041 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window - - + + Deck Editor - + Game Lobby - + Card Counters - + Player Counters - + Power and Toughness Síla a obrana - + Game Phases Herní fáze - + Playing Area Herní oblast - + Move Selected Card - + View Pohled - + Move Top Card - + Move Bottom Card - + Gameplay Průběh hry - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - - + + Replays - + Tabs - + Check for Card Updates... - + Connect... - + Disconnect - + Exit - + Full screen - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters - + Clear Selected Filter - + Close Zavřít - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card - + Load Deck... - - + + Load Deck from Clipboard... - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck - + Open Custom Pictures Folder - + Print Deck... - + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... - + Load Remote Deck... - + Set Ready to Start - + Toggle Sideboard Lock - + Add Green Counter - + Remove Green Counter - + Set Green Counters... - + Add Red Counter - + Remove Red Counter - + Set Red Counters... - + Add Life Counter - + Show Status Bar - + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Increment all card counters - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap Odtapnutí - + Upkeep Upkeep - + Draw Lízání - + First Main Phase - + Start Combat - + Attack Útok - + Block Bránění - + Damage Zranění - + End Combat - + Second Main Phase - + End Konec - + Next Phase - + Next Phase Action - + Next Turn - + Hide Card in Reveal Window - + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library - - - - + + + + Exile Exil - - - - + + + + Graveyard Hřbitov - - + + + Hand Ruka - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - - Sort Hand - - - - + Library Knihovna - + Sideboard Sideboard - + Top Cards of Library - + Bottom Cards of Library - + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede Vzdát hru - + Roll Dice... - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan Mulligan - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_de.ts b/cockatrice/translations/cockatrice_de.ts index fbf984aea..b24a93992 100644 --- a/cockatrice/translations/cockatrice_de.ts +++ b/cockatrice/translations/cockatrice_de.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... Zähler &setzen... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Zähler setzen - + New value for counter '%1': Neuer Wert für den Zähler '%1': @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh &aktualisieren - + Parse Set Name and Number (if available) Setnamen und -Nummer parsen (falls verfügbar) @@ -36,81 +36,83 @@ AbstractTabDeckEditor - + Open in new tab In neuem Reiter öffnen - + Are you sure? Sind Sie sicher? - + The decklist has been modified. Do you want to save the changes? Die Deckliste wurde bearbeitet. Änderungen speichern? - - - - - - - + + + + + + Error Fehler - + Could not open deck at %1 Deck an Stelle %1 konnte nicht geöffnet werden - + Could not save remote deck Remote-Deck konnte nicht gespeichert werden - - + + The deck could not be saved. Please check that the directory is writable and try again. Das Deck konnte nicht abgespeichert werden. Bitte überprüfen Sie, ob das Verzeichnis bearbeitet werden kann und versuchen Sie es erneut. - + Save deck Deck speichern - + The deck could not be saved. Das Deck konnte nicht abgespeichert werden. - + There are no cards in your deck to be exported Es gibt keine Karten in Ihrem Deck zum Exportieren + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. - Es wurde kein Deck zum Exportieren ausgewählt. + + Add Analytics Panel + Analysepanel hinzufügen AdminNotesDialog - + Update Notes Aktualisierungsnotizen - + Admin Notes for %1 Administrator Notizen für %1 @@ -118,12 +120,12 @@ Bitte überprüfen Sie, ob das Verzeichnis bearbeitet werden kann und versuchen AllZonesCardAmountWidget - + Mainboard Hauptdeck - + Sideboard Nebendeck @@ -131,22 +133,22 @@ Bitte überprüfen Sie, ob das Verzeichnis bearbeitet werden kann und versuchen AppearanceSettingsPage - + seconds Sekunden - + Error Fehler - + Could not create themes directory at '%1'. Das Themeverzeichnis '%1' konnte nicht erstellt werden. - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -157,7 +159,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -168,152 +170,165 @@ Are you sure you would like to disable this feature? - + Confirm Change - + Theme settings Darstellungseinstellungen - + Current theme: Aktuelles Theme: - + Open themes folder Öffne Themeverzeichnis - + Home tab background source: Startseitenhintergrund-Quelle: - + Home tab background shuffle frequency: Startseitenhintergrund Wechselfrequenz: - + Disabled Deaktiviert - + Menu settings Einstellungsmenü - + Show keyboard shortcuts in right-click menus Tastatur-Kürzel anzeigen bei Rechtsklickmenü - + + Show game filter toolbar above list in room tab + + + + Card rendering Kartendarstellung - + Display card names on cards having a picture Kartennamen auch bei Karten mit Bildern darstellen - + Auto-Rotate cards with sideways layout Automatisches Drehen von Karten mit Seitwärts-Ausrichtung - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector Priorisieren von Sets, in welchen Karten des ausgewählten Decks vorhanden sind, nach oben in der Druckauswahl - + Scale cards on mouse over Karten beim Darüberfahren mit der Maus vergrößern - + Use rounded card corners Abgerundete Kartenecken verwenden - + Minimum overlap percentage of cards on the stack and in vertical hand Minimaler Überschneidungsanteil der Karten auf dem Stapel und in der vertikalen Hand - + Maximum initial height for card view window: Maximale Starthöhe für das Kartenansichtsfenster - - + + rows Reihen - + Maximum expanded height for card view window: Maximale ausgeklappte Höhe für das Kartenansichtsfenster: - + Card counters Kartenzähler - + Counter %1 Zähler %1 - + Hand layout Handdarstellung - + Display hand horizontally (wastes space) Hand horizontal anzeigen (verschwendet Platz) - + Enable left justification Linksbündige Ausrichtung aktivieren - + Table grid layout Spielfeldraster - + Invert vertical coordinate Vertikale Koordinate umkehren - + Minimum player count for multi-column layout: Mindestspieleranzahl für mehrspaltige Anordnung: - + Maximum font size for information displayed on cards: Maximale Schriftgröße für die Anzeige von Informationen auf Karten: + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -335,112 +350,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name &Benutzername - + ban &IP address &IP-Adresse - + ban client I&D Client I&D bannen - + Ban type Art des Banns - + &permanent ban &permanenter Bann - + &temporary ban &temporärer Bann - + &Days: &Tage: - + &Hours: &Stunden: - + &Minutes: &Minuten: - + Duration of the ban Länge des Banns - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Bitte geben Sie den Grund für den Bann ein. Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nicht gesehen werden. - + Please enter the reason for the ban that will be visible to the banned person. Bitte geben Sie den Grund ein, den die gebannte Person sehen kann. - + Redact all messages from this user in all rooms Entferne alle Nachrichten dieses Nutzers in allen Räumen - + &OK &OK - + &Cancel &Abbrechen - + Ban user from server Benutzer vom Server bannen - + + - - + Error Fehler - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Bitte wählen Sie einen Namens-, Adress- oder Client-ID-Bann oder eine Kombination aus diesen. - + You must have a value in the name ban when selecting the name ban checkbox. Wenn Sie das Namensbannkästchen auswählen, muss ein Wert für den Namen eingegeben werden. - + You must have a value in the ip ban when selecting the ip ban checkbox. Wenn Sie das Adressbannkästchen auswählen, muss ein Wert für die IP eingegeben werden. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Wenn Sie das Client-ID-Kästchen auswählen, muss ein Wert für die Client-ID eingegeben werden. @@ -471,32 +486,32 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardDatabaseModel - + Name Name - + Sets Editionen - + Mana cost Manakosten - + Card type Kartentyp - + P/T S/W - + Color(s) Farbe(n) @@ -504,96 +519,101 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardFilter - + AND Logical conjunction operator used in card filter UND - + OR Logical disjunction operator used in card filter ODER - + AND NOT Negated logical conjunction operator used in card filter UND NICHT - + OR NOT Negated logical disjunction operator used in card filter ODER NICHT - + Name Name - + + Name (Exact) + + + + Type Typ - + Color Farbe - + Text Text - + Set Edition - + Mana Cost Manakosten - + Mana Value Manawert - + Rarity Seltenheit - + Power Stärke - + Toughness Widerstandskraft - + Loyalty Loyalität - + Format Format - + Main Type Haupttyp - + Sub Type Untertyp @@ -601,22 +621,22 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardInfoFrameWidget - + Image Bild - + Description Beschreibung - + Both Beide - + View transformation Transformation anzeigen @@ -624,22 +644,22 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardInfoPictureWidget - + View related cards Zugehörige Karten ansehen - + Add card to deck Karte zum Deck hinzufügen - + Mainboard Mainboard - + Sideboard Sideboard @@ -652,12 +672,22 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Name: - + + Set: + + + + + Collector Number: + + + + Related cards: Zugehörige Karten: - + Unknown card: Unbekannte Karte: @@ -665,124 +695,124 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardMenu - + Re&veal to... Zeigen an.... - + &All players &Alle Spieler - + View related cards Verwandte Karten ansehen - + Token: Spielstein: - + All tokens Alle Spielsteine - + &Select All &Alles auswählen - + S&elect Row Reihe auswählen - + S&elect Column Spalte auswählen - + &Play &Spielen - + &Hide &Verstecken - + Play &Face Down Verdeckt ausspielen - + &Tap / Untap Turn sideways or back again &Tappen/ Enttappen - + Toggle &normal untapping Normales Enttapen umschalten - + T&urn Over Turn face up/face down Umdrehen - + &Peek at card face %Kartenvorderseite ansehen - + &Clone &Kopieren - + Attac&h to card... An Karte anhängen - + Unattac&h Lösen - + &Draw arrow... &Pfeil zeichnen - + &Set annotation... &Anmerkung setzen - + Ca&rd counters Kartenzähler - + &Add counter (%1) &Zähler hinzufügen (%1) - + &Remove counter (%1) &Zähler entfernen (%1) - + &Set counters (%1)... &Zähler setzen (%1)... @@ -790,7 +820,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardSizeWidget - + Card Size Kartengröße @@ -798,133 +828,133 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardZoneLogic - + their hand nominative ihrer Hand - + %1's hand nominative %1's Hand - - - their library - look at zone - ihrer Bibliothek - - - - %1's library - look at zone - %1's Bibliothek - - - - of their library - top cards of zone, - von ihrer Bibliothek - - - - of %1's library - top cards of zone - von %1's Bibliothek - their library - reveal zone + look at zone ihrer Bibliothek %1's library + look at zone + %1's Bibliothek + + + + of their library + top cards of zone, + von ihrer Bibliothek + + + + of %1's library + top cards of zone + von %1's Bibliothek + + + + their library + reveal zone + ihrer Bibliothek + + + + %1's library reveal zone %1's Bibliothek - + their library shuffle ihrer Bibliothek - + %1's library shuffle %1's Bibliothek - + their library nominative ihrer Bibliothek - + %1's library nominative %1's Bibliothek - + their graveyard nominative ihres Friedhofs - + %1's graveyard nominative %1's Friedhof - + their exile nominative ihres Exils - + %1's exile nominative %1's Exil - + their sideboard look at zone ihres Nebendecks - + %1's sideboard look at zone %1's Nebendeck - - - their sideboard - nominative - ihres Nebendecks - - - - %1's sideboard - nominative - %1's Nebendeck - + their sideboard + nominative + ihres Nebendecks + + + + %1's sideboard + nominative + %1's Nebendeck + + + their custom zone '%1' nominative ihrer individuellen Zone '%1' - + %1's custom zone '%2' nominative %1's individuelle Zone '%2' @@ -933,7 +963,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CockatriceXml3Parser - + Parse error at line %1 col %2: Parsingfehler in Zeile %1 Spalte %2: @@ -941,7 +971,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CockatriceXml4Parser - + Parse error at line %1 col %2: Parsingfehler in Zeile %1 Spalte %2: @@ -960,6 +990,37 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Individuelle Zone '%1' ansehen + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -971,47 +1032,47 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) Suche nach Kartennamen (oder Suchausdrücken) - + Add to Deck Zum Deck hinzufügen - + Add to Sideboard Zum Sideboard hinzufügen - + Select Printing Auswahl der Auflage - + Show on EDHRec (Commander) Auf EDHRec ansehen (Als Commander) - + Show on EDHRec (Card) Auf EDHRec ansehen (Als Karte) - + Show Related cards Zeige ähnliche Karten - + Add card to &maindeck Karte zum &Hauptdeck hinzufügen - + Add card to &sideboard Karte zum &Sideboard hinzufügen @@ -1019,87 +1080,97 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card Bannerkarte - + Main Type Haupttyp - + Mana Cost Manakosten - + Colors Farben - + Select Printing Auswahl der Auflage - + Deck Deck - + Deck &name: Deck&name: - + Banner Card/Tags Visibility Settings Bannerkarte/Sichtbarkeitseinstellungen von Tags - + Show banner card selection menu Bannerkartenauswahlmenü anzeigen - + Show tags selection menu Tagauswahlmenü anzeigen - + &Comments: &Kommentare: - + Group by: Gruppieren nach: - + + Format: + + + + Hash: Hash: - + &Increment number &Zahl erhöhen - + &Decrement number &Zahl verringern - + &Remove row &Reihe entfernen - + Swap card to/from sideboard Karte ins/aus dem Sideboard tauschen @@ -1107,17 +1178,17 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckEditorFilterDockWidget - + Filters Filter - + &Clear all filters &Alle Filter entfernen - + Delete selected Auswahl löschen @@ -1125,114 +1196,114 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckEditorMenu - + &Deck Editor &Deckeditor - + &New deck &Neues Deck - + &Load deck... &Deck laden... - + Load recent deck... Laden der letzten Decks... - + Clear Alles entfernen - + &Save deck &Deck speichern - + Save deck &as... &Deck speichern unter... - + Load deck from cl&ipboard... Deck aus &Zwischenablage laden... - + Edit deck in clipboard Deck in Zwischenablage bearbeiten - - + + Annotated Kommentiert - - + + Not Annotated Nicht kommentiert - + Save deck to clipboard Deck in Zwischenablage speichern - + Annotated (No set info) Komentiert (keine Setinformationen) - + Not Annotated (No set info) Nicht kommentiert (keine Setinformationen) - + &Print deck... &Deck drucken... - + Load deck from online service... Deck von Onlineservice laden.... - + &Send deck to online service &Deck an Onlineservice senden - + Create decklist (decklist.org) Deckliste erstellen (decklist.org) - + Create decklist (decklist.xyz) Deckliste erstellen (decklist.xyz) - + Analyze deck (deckstats.net) Deck analysieren (deckstats.net) - + Analyze deck (tappedout.net) Deck analysieren (tappedout.net) - + &Close &Schließen @@ -1240,7 +1311,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckEditorPrintingSelectorDockWidget - + Printing Selector Auflagenauswahl @@ -1248,194 +1319,227 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckEditorSettingsPage - - + + Update Spoilers Spoiler aktualisieren - - + + Success Erfolgreich - + Download URLs have been reset. Download-URLs wurden zurückgesetzt. - + Downloaded card pictures have been reset. Heruntergeladene Kartenbilder wurden zurückgesetzt. - + Error Fehler - + One or more downloaded card pictures could not be cleared. Eines oder mehrere Kartenbilder konnten nicht gelöscht werden. - + Add URL URL hinzufügen - - + + URL: URL - - + + Edit URL URL bearbeiten - + Network Cache Size: Größe des Netzwerkzwischenspeichers: - + Redirect Cache TTL: Umleitungszwischenspeicher TTL: - + How long cached redirects for urls are valid for. Wie lange gespeicherte Umleitungen für URLS gültig sind. - + Picture Cache Size: Größe des Bilderzwischenspeichers: - + Add New URL Neue URL hinzufügen - + Remove URL URL entfernen - + Day(s) Tag(e) - + Updating... Aktualisiere... - + Choose path Pfad auswählen - + URL Download Priority URL Downloadpriorität - + Spoilers Spoiler - + Download Spoilers Automatically Lade Spoiler automatisch herunter - + Spoiler Location: Spoiler Verzeichnis: - + Last Change Letzte Änderung - + Spoilers download automatically on launch Spoiler werden beim Start automatisch heruntergeladen - + Press the button to manually update without relaunching Drücke den Knopf um manuell zu aktualisieren ohne neu zu starten - + Do not close settings until manual update is complete Schließen Sie die Einstellungen nicht solange die manuelle Aktualisierung nicht abgeschlossen ist. - + Download card pictures on the fly Kartenbilder dynamisch herunterladen - + How to add a custom URL Wie eine benutzerdefinierte URL hinzugefügt wird - + Delete Downloaded Images Heruntergeladene Bilder löschen - + Reset Download URLs Download-URLs zurücksetzen - + On-disk cache for downloaded pictures Festplatten-Cache für heruntergeladene Bilder - + In-memory cache for pictures not currently on screen Arbeitsspeicher-Cache für Bilder die gerade nicht angezeigt werden + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count Anzahl - + Set Festlegen - + Number Nummer - + Provider ID ID des Anbieters - + Card Karte @@ -1443,12 +1547,12 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckLoader - + Common deck formats (%1) Standarddeckformate (%1) - + All files (*.*) Alle Dateien (*.*) @@ -1456,17 +1560,17 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match Modus: Exakte Übereinstimmung - + Mode: Includes Modus: Enthält - + Color identity filter mode (AND/OR/NOT conjunctions of filters) Farbidentitätsfiltermodus (UND/ODER/NICHT Verknüpfungen der Filter) @@ -1474,7 +1578,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckPreviewDeckTagsDisplayWidget - + Edit tags ... Tags bearbeiten ... @@ -1482,62 +1586,62 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckPreviewTagDialog - + Deck Tags Manager Decktagsmanager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: Verwalte Decktags. Wähle Tags nach Bedarf aus oder füge neue hinzu: - + Add a new tag (e.g., Aggro️) Neuen Tag hinzufügen (bspw. Aggro) - + Add Tag Tag hinzufügen - + Filter tags... Tags filtern... - + OK OK - + Edit default tags Standardtags bearbeiten - + Cancel Abbrechen - + Invalid Input Ungültige Eingabe - + Tag name cannot be empty! Tagname kann nicht leer sein! - + Duplicate Tag Tag duplizieren - + This tag already exists. Dieser Tag existiert bereits. @@ -1550,93 +1654,151 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Bannerkarte - + Open in deck editor Im Deckeditor öffnen - + Edit Tags Tags bearbeiten - + Rename Deck Deck umbenennen - + Save Deck to Clipboard Deck in die Zwischenablage speichern - + Annotated Kommentiert - + Annotated (No set info) Komentiert (keine Setinformationen) - + Not Annotated Nicht kommentiert - + Not Annotated (No set info) Nicht kommentiert (keine Setinformationen) - + Rename File Datei umbenennen - + Delete File Datei löschen - + Set Banner Card Bannerkarte setzen - - + + New name: Neuer Name: - - + + Error Fehler - + Rename failed Umbenennen fehlgeschlagen - + Delete file Datei löschen - + Are you sure you want to delete the selected file? Sind Sie sicher, dass die ausgewählte Datei gelöscht werden soll? - + Delete failed Löschen fehlgeschlagen + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1654,75 +1816,75 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckViewContainer - + Load deck... Deck laden... - + Load remote deck... Deck vom Server laden... - + Load from clipboard... Aus der Zwischenablage laden... - + Load from website... Von Internetseite laden.... - + Unload deck Deck entladen - + Ready to start Bereit zum Start - + Force start Zwangsstart - + Sideboard unlocked Sideboard entsperrt - + Sideboard locked Sideboard gesperrt - - + + Error Fehler - + The selected file could not be loaded. Die gewählte Datei konnte nicht geladen werden. - + Deck is greater than maximum file size. Deck übersteigt die maximale Dateigröße. - + Are you sure you want to force start? This will kick all non-ready players from the game. Sind Sie sicher, dass sie den Start erzwingen wollen? Dies wirft alle nicht bereiten Spieler aus dem Spiel. - + Cockatrice Cockatrice @@ -1757,143 +1919,143 @@ Möchten sie das Deck ins .cod Format konvertieren? Lade herunter... - + Known Hosts Bekannte Hosts - + Delete the currently selected saved server Den derzeit gespeicherten Server löschen - + Refresh the server list with known public servers Aktualisiere die Serverliste der bekannten öffentlichen Server - + New Host Neuer Host - + Name: Name: - + &Host: &Server: - + &Port: &Port: - + Player &name: Spieler&name: - + P&assword: P&asswort: - + &Save password Passwort &speichern - + A&uto connect a&utomatisch verbinden - + Automatically connect to the most recent login when Cockatrice opens Automatisch mit dem letzten Login verbinden, wenn sich Cockatrice öffnet - + If you have any trouble connecting or registering then contact the server staff for help! Falls Probleme mit der Verbindung oder Registrierung bestehen, kontaktieren Sie das Personal für Hilfe! - - + + Webpage Webseite - + Reset Password Passwort zurücksetzen - + Forgot password? Passwort vergessen? - + &Connect &Verbinden... - + Server Server - + Login Login - + Server Contact Serverkontakt - + Connect to Server Verbinde zum Server - + Server URL Server URL - + Communication Port Kommunikationsport - + Unique Server Name Einzigartiger Servername - + Connection Warning Verbindungswarnung - + You need to name your new connection profile. Sie müssen Ihr neues Verbindungsprofil benennen. - + Connect Warning Verbindungswarnung - + The player name can't be empty. Der Spielername kann nicht leer sein. @@ -1901,117 +2063,122 @@ Möchten sie das Deck ins .cod Format konvertieren? DlgCreateGame - + Re&member settings Einstellungen speichern - + &Description: &Beschreibung: - + P&layers: &Spieler: - + General Allgemeines - + Game type Spieltyp - + &Password: &Passwort: - + Only &buddies can join Nur &Freunde können teilnehmen - + Only &registered users can join Nur &registrierte Benutzer können teilnehmen - + Joining restrictions Teilnahmebedingungen - + &Spectators can watch Zuschauer erlauben - + Spectators &need a password to watch Zuschauer benötigen das Passwort - + Spectators can &chat Zuschauer können &chatten - + Spectators can see &hands Zuschauer sehen die Hände - + Create game as spectator Spiel als Zuschauer erstellen - + Spectators Zuschauer - + Starting life total: Anfangslebenspunktzahl - + Open decklists in lobby Offene Decklisten in Lobby - + + Create game as judge + + + + Game setup options Spielaufsetzungsoptionen - + &Clear Löschen - + Create game Spiel erstellen - + Game information &Spielinformationen - + Error Fehler - + Server error. Serverfehler. @@ -2019,97 +2186,97 @@ Möchten sie das Deck ins .cod Format konvertieren? DlgCreateToken - + &Name: &Name: - + Token Spielstein - + C&olor: &Farbe: - + white weiß - + blue blau - + black schwarz - + red rot - + green grün - + multicolor mehrfarbig - + colorless farblos - + &P/T: &Kampfwerte: - + &Annotation: &Hinweis: - + &Destroy token when it leaves the table Spielstein &zerstören, wenn er das Spielfeld verlässt - + Create face-down (Only hides name) Verdeckt erstellen (versteckt nur den Namen) - + Token data Spielstein-Daten - + Show &all tokens &Alle möglichen Spielsteine zeigen - + Show tokens from this &deck Spielsteine dieses &Decks zeigen - + Choose token from list Spielstein aus Liste auswählen - + Create token Spielstein erstellen @@ -2117,53 +2284,53 @@ Möchten sie das Deck ins .cod Format konvertieren? DlgDefaultTagsEditor - + Edit Tags Tags bearbeiten - + Add Hinzufügen - + Confirm Bestätigen - + Cancel Abbrechen - + Enter a tag and press Enter Tag eingeben und Eingabetaste drücken - - + + - + Invalid Input Ungültige Eingabe - + Tag name cannot be empty! Tagname kann nicht leer sein! - + Duplicate Tag Tag duplizieren - + This tag already exists. Dieser Tag existiert bereits @@ -2171,40 +2338,40 @@ Möchten sie das Deck ins .cod Format konvertieren? DlgEditAvatar - - + + No image chosen. Kein Bild gewählt. - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. Um Ihren Avatar zu wechseln, wählen Sie ein neues Bild. Um Ihren derzeitigen Avatar zu entfernen, bestätigen Sie, ohne ein neues Bild zu wählen. - + Browse... Durchsuchen... - + Change avatar Avatar wechseln - + Open Image Bild öffnen - + Image Files (*.png *.jpg *.bmp) Bild-Dateien (*.png *.jpg *.bmp) - + Invalid image chosen. Ungültiges Bild gewählt. @@ -2212,17 +2379,17 @@ Um Ihren derzeitigen Avatar zu entfernen, bestätigen Sie, ohne ein neues Bild z DlgEditDeckInClipboard - + Edit deck in clipboard Deck in Zwischenablage bearbeiten - + Error Fehler - + Invalid deck list. Ungültige Deckliste. @@ -2230,38 +2397,38 @@ Um Ihren derzeitigen Avatar zu entfernen, bestätigen Sie, ohne ein neues Bild z DlgEditPassword - + Old password: Altes Passwort: - + New password: Neues Passwort: - + Confirm new password: Bestätige neues Passwort: - + Change password Passwort ändern - - + + Error Fehler - + Your password is too short. Das Passwort ist zu kurz. - + The new passwords don't match. Die neuen Passwörter stimmen nicht überein. @@ -2269,93 +2436,93 @@ Um Ihren derzeitigen Avatar zu entfernen, bestätigen Sie, ohne ein neues Bild z DlgEditTokens - + &Name: &Name: - + C&olor: &Farbe: - + white weiß - + blue blau - + black schwarz - + red rot - + green grün - + multicolor mehrfarbig - + colorless farblos - + &P/T: &Kampfwerte: - + &Annotation: &Hinweis: - + Token data Spielstein-Daten - - + + Add token Spielstein hinzufügen - + Remove token Spielstein entfernen - + Edit custom tokens Benutzerdefinierte Spielsteine bearbeiten - + Please enter the name of the token: Bitte geben Sie den Namen des Spielsteins ein: - + Error Fehler - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Es existiert bereits eine Karte oder ein Spielstein mit dem gewählten Namen. @@ -2449,8 +2616,9 @@ Aktivieren Sie die Edition „Token" im „Editionen verwalten...“ Menü - Hide games not created by buddy - Nicht von Freunden erstellte Spiele verstecken + Hide games not created by buddies + Hide games not created by buddy + @@ -2536,52 +2704,52 @@ Aktivieren Sie die Edition „Token" im „Editionen verwalten...“ Menü DlgForgotPasswordChallenge - + Reset Password Challenge Warning Setze Passwortherausforderungswarnung zurück - + A problem has occurred. Please try to request a new password again. Ein Problem ist aufgetreten. Bitte erneut ein neues Passwort anfordern. - + Enter the information of the server and the account you'd like to request a new password for. Gebe die Informationen des Servers und Kontos, für das ein neues Passwort angefordert werden soll, an. - + &Host: &Host: - + &Port: &Port: - + Player &name: Spieler&name: - + Email: E-Mail: - + Reset Password Challenge Setze Passwortherausforderung zurück - + Reset Password Challenge Error Setze Passwortherausforderungsfehler zurück - + The email address can't be empty. Die E-Mail-Adresse kann nicht leer sein. @@ -2589,37 +2757,37 @@ Aktivieren Sie die Edition „Token" im „Editionen verwalten...“ Menü DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Gebe die Informationen des Servers, für den ein neues Passwort angefordert werden soll, an. - + &Host: &Host: - + &Port: &Port: - + Player &name: Spieler&name: - + Reset Password Request Passwortrücksetzungsanfrage - + Reset Password Error Passwortrücksetzungsfehler - + The player name can't be empty. Der Spielername kann nicht leer sein. @@ -2627,86 +2795,86 @@ Aktivieren Sie die Edition „Token" im „Editionen verwalten...“ Menü DlgForgotPasswordReset - + Reset Password Warning Passwortrücksetzungswarnung - + A problem has occurred. Please try to request a new password again. Ein Problem ist aufgetreten. Bitte erneut ein neues Passwort anfordern. - + Enter the received token and the new password in order to set your new password. Gebe den erhaltenen Token und das neue Passwort ein, um das neue Passwort zu setzen. - + &Host: &Host: - + &Port: &Port: - + Player &name: Spieler&name: - + Token: Spielstein: - - + + New Password: Neues Passwort: - + Reset Password Passwort zurücksetzen - + The player name can't be empty. Der Spielername kann nicht leer sein. - - - - + + + + Reset Password Error Passwortrücksetzungsfehler - + The token can't be empty. Der Spielstein kann nicht leer sein. - + The new password can't be empty. Das neue Passwort kann nicht leer sein. - + Error Fehler - + Your password is too short. Das Passwort ist zu kurz. - + The passwords do not match. Die Passwörter stimmen nicht überein. @@ -2722,17 +2890,17 @@ Aktivieren Sie die Edition „Token" im „Editionen verwalten...“ Menü DlgLoadDeckFromClipboard - + Load deck from clipboard Deck aus der Zwischenablage laden - + Error Fehler - + Invalid deck list. Ungültige Deckliste. @@ -2740,45 +2908,45 @@ Aktivieren Sie die Edition „Token" im „Editionen verwalten...“ Menü DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Hier einen Link einer Decklistenseite einfügen um ihn zu importieren. (Archidekt, Deckstats, Moxfield und TappedOut sind unterstützt.) - - - - - + + + + + Load Deck from Website Deck von Internetseite laden - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Kein Parset verfügbar für diesen Deckanbieter. (Archidekt, Deckstats, Moxfield und TappedOut sind unterstützt.) - + Network error: %1 Netzwerkfehler: %1 - + Received empty deck data. Leere Deckdaten erhalten. - + Failed to parse deck data: %1 Fehler beim parsen der Deckdaten: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2798,7 +2966,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck Deck laden @@ -2806,37 +2974,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): Kartenname (oder Suchbezeichnung) - + Number of hits: Anzahl der Treffer: - + Auto play hits Automatisches Ausspielen von Treffern - + Put top cards on stack until... Oberste Karten auf den Stapel legen bis... - + No cards matching the search expression exists in the card database. Proceed anyways? Keine Karten, die den Suchbezeichnungen entsprechen, existieren in der Kartendatenbank. Trotzdem fortfahren? - + Cockatrice Basilisk - + Invalid filter Ungültiger Filter @@ -2844,92 +3012,92 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Geben Sie Ihre Informationen und die Informationen des Servers, bei dem Sie sich registrieren wollen, an. Die E-Mail-Adresse wird zur Accountverifikation genutzt. - + &Host: &Host: - + &Port: &Port: - + Player &name: Spieler&name: - + P&assword: P&asswort - + Password (again): Passwort (Wiederholung) - + Email: E-Mail: - + Email (again): E-Mail (Wiederholung) - + Country: Land: - + Undefined Undefiniert - + Real name: Echter Name: - + Register to server Auf Server registrieren - - - - + + + + Registration Warning Registrierungswarnung - + Your password is too short. Das Passwort ist zu kurz. - + Your passwords do not match, please try again. Ihre Passwörter stimmen nicht überein, bitte versuchen Sie es erneut. - + Your email addresses do not match, please try again. Ihre Mailadressen stimmen nicht überein, bitte versuchen Sie es erneut. - + The player name can't be empty. Der Spielername kann nicht leer sein. @@ -2955,40 +3123,55 @@ Die E-Mail-Adresse wird zur Accountverifikation genutzt. DlgSelectSetForCards - + Unmodified Cards: Ungeänderte Karten: - + Modified Cards: Geänderte Karten: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. Sets auswählen, um sie zu aktivieren. Per Drag-and-Drop kann die Reihenfolge und damit die Priorität geändert werden. Karten verwenden die Druckversion des aktivierten Sets mit der höchsten Priorität. - + Clear all set information Alle Setinformationen löschen - + Set all to preferred Alle auf bevorzugt setzen + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database Unbekannter Fehler beim Laden der Kartendatenbank - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3005,7 +3188,7 @@ Sie müssen Oracle unter Umständen nochmals ausführen um Ihre Kartendatenbank Möchten Sie Ihren Speicherort der Datenbank aktualisieren?? - + Your card database version is too old. This can cause problems loading card information or images @@ -3022,7 +3205,7 @@ Normalerweise kann dies durch einen erneuten Start von Oracle, um die Kartendate Möchten Sie Ihren Speicherort der Datenbank aktualisieren? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3035,7 +3218,7 @@ Bitte erstellen Sie ein Ticket auf https://github.com/Cockatrice/Cockatrice/issu Möchten Sie die Einstellung des Datenbankspeicherorts ändern? - + File Error loading your card database. Would you like to change your database location setting? @@ -3044,7 +3227,7 @@ Would you like to change your database location setting? Möchten Sie Ihren Speicherort der Datenbank aktualisieren? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3053,7 +3236,7 @@ Would you like to change your database location setting? Möchten Sie Ihren Speicherort der Datenbank aktualisieren? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3066,59 +3249,59 @@ Bitte erstellen Sie ein Ticket auf https://github.com/Cockatrice/Cockatrice/issu Möchten Sie die Einstellung des Datenbankspeicherorts ändern? - - - + + + Error Fehler - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Der Pfad zu Ihrem Deckordner ist ungültig. Möchten Sie zurückgehen und den korrekten Pfad einstellen? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Der Pfad zu Ihrem Kartenbilderordner ist ungültig. Möchten Sie zurückgehen und den korrekten Pfad einstellen? - + Settings Einstellungen - + General Allgemeines - + Appearance Erscheinungsbild - + User Interface Benutzeroberfläche - + Card Sources Kartenquellen - + Chat Chat - + Sound Töne - + Shortcuts Tastaturkürzel @@ -3126,12 +3309,12 @@ Möchten Sie die Einstellung des Datenbankspeicherorts ändern? DlgStartupCardCheck - + Card Update Check Kartenaktualisierungsüberprüfung - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. @@ -3140,27 +3323,27 @@ Bitte wählen Sie aus, wie der Kartendatenbankaktualisierer ausgeführt werden s Sie können das Verhalten jederzeit im "Allgemein"-Tab der Einstellungen ändern. - + Run in foreground Im Vordergrund ausführen - + Run in background Im Hintergrund ausführen - + Run in background and always from now on Immer im Hintergrund ausführen - + Don't prompt again and don't run Nicht erneut fragen und nicht ausführen - + Don't run this time Dieses Mal nicht ausführen @@ -3168,17 +3351,17 @@ Sie können das Verhalten jederzeit im "Allgemein"-Tab der Einstellung DlgTipOfTheDay - + Next Vor - + Previous Zurück - + Tip of the Day Tipp des Tages @@ -3186,166 +3369,166 @@ Sie können das Verhalten jederzeit im "Allgemein"-Tab der Einstellung DlgUpdate - + Current release channel Momentaner Veröffentlichungskanal - + Reinstall Neu installieren - + Cancel Download Download abbrechen - + Open Download Page Downloadseite öffnen - + Check for Client Updates Auf Clientaktualisierungen prüfen - - - - + + + + Error Fehler - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Ihre Version von Cockatrice hat keine SSL-Unterstützung, dadurch können Sie keine Aktualisierungen automatisch herunterladen! Bitte besuchen Sie die Downloadseite, um Cockatrice manuell zu aktualisieren. - + Downloading update: %1 Lade Aktualisierung herunter: %1 - + Checking for updates... Suche nach Aktualisierungen... - + Finished checking for updates Suche nach Aktualisierungen abgeschlossen - + No Update Available Keine Aktualisierung verfügbar - + Cockatrice is up to date! Cockatrice ist aktuell! - + You are already running the latest version available in the chosen release channel. Sie verwenden bereits die aktuellste Version des gewählten Veröffentlichungskanals. - + Current version Momentane Version - + Selected release channel Ausgewählter Veröffentlichungskanal - - + + Update Available Aktualisierung verfügbar - - + + A new version of Cockatrice is available! Eine neue Cockatrice-Version ist verfügbar! - - + + New version Neue Version - - + + Released Veröffentlicht - - + + Changelog Änderungsprotokoll - + Do you want to update now? Möchten Sie jetzt aktualisieren? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. Leider hat der automatische Updater keinen kompatiblen Download gefunden. Eventuell müssen sie manuell eine neue Version herunterladen. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. Bitte prüfen Sie die <a href='%1'>Veröffentlichungsseite</a> auf unserer Github-Seite und laden Sie die Version für Ihr Betriebssystem herunter. - - - + + + Update Error Aktualisierungsfehler - + An error occurred while checking for updates: Während der Suche nach Aktualisierungen ist ein Fehler aufgetreten: - + An error occurred while downloading an update: Während des Herunterladens der Aktualisierung ist ein Fehler aufgetreten: - + Installing... Installiere... - + Cockatrice is unable to open the installer. Cockatrice kann das Installationsprogramm nicht öffnen. - + Try to update manually by closing Cockatrice and running the installer. Versuchen Sie manuell zu aktualisieren, indem Sie Cockatrice beenden und das Installationsprogramm aufrufen. - + Download location Downloadverzeichnis @@ -3353,21 +3536,153 @@ Eventuell müssen sie manuell eine neue Version herunterladen. DlgViewLog - + Clear log when closing Lösche den Log beim Schließen - + Copy to clipboard In die Zwischenablage kopieren - + Debug Log Fehlerprotokoll + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3437,33 +3752,39 @@ Eventuell müssen sie manuell eine neue Version herunterladen. %1% Synergie + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos Kombos - + Average Deck Durchschnittliches Deck - - - Game Changers - Game Changer - - - - Budget - Budget - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: Salz: @@ -3479,22 +3800,22 @@ Eventuell müssen sie manuell eine neue Version herunterladen. FilterDisplayWidget - + Confirm Delete Löschen bestätigen - + Are you sure you want to delete the filter '%1'? Sind Sie sicher, dass Sie den Filter '%1' löschen wollen? - + Delete Failed Löschen fehlgeschlagen - + Failed to delete filter '%1'. Löschen des Filters '%1' fehlgeschlagen. @@ -3502,22 +3823,22 @@ Eventuell müssen sie manuell eine neue Version herunterladen. GameEventHandler - + kicked by game host or moderator Von Spielhost oder Moderator gekickt - + player left the game Spieler hat das Spiel verlassen - + player disconnected from server Spieler hat die Verbindung zum Server getrennt - + reason unknown Grund unbekannt @@ -3525,226 +3846,279 @@ Eventuell müssen sie manuell eine neue Version herunterladen. GameSelector - - - - - - + + + + + + Error Fehler - + Please join the appropriate room first. Bitte betreten Sie erst den entsprechenden Raum. - + Wrong password. Falsches Passwort. - + Spectators are not allowed in this game. In diesem Spiel sind keine Zuschauer zugelassen. - + The game is already full. Das Spiel ist bereits voll. - + The game does not exist any more. Dieses Spiel gibt es nicht mehr. - + This game is only open to registered users. Dieses Spiel kann nur von registrierten Benutzern betreten werden. - + This game is only open to its creator's buddies. Dieses Spiel kann nur von Freunden des Erstellers betreten werden. - + You are being ignored by the creator of this game. Der Ersteller dieses Spiels ignoriert Sie. - + Join Game Spiel beitreten - + Spectate Game Spiel beobachten - + Game Information Spielinformationen - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game Spiel beitreten - + Password: Passwort: - + Please join the respective room first. Bitte betreten Sie zuerst den entsprechenden Raum. - + &Filter games Spiele &filtern - + C&lear filter Filter &zurücksetzen - + C&reate E&rstellen - + &Join &Beitreten - + + Join as judge + + + + J&oin as spectator Als Zuschauer beitreten - + + Join as judge spectator + + + + Games shown: %1 / %2 Angezeigte Spiele: %1 / %2 - + Games Spiele + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day >1 Tag - + %1%2 hr short age in hours %1%2 h%1%2 h - + new neu - + %1%2 min short age in minutes %1%2 min%1%2 min - + password Passwort - + buddies only nur Freunde - + reg. users only nur reg. Benutzer - + open decklists Offene Decklisten - - + + can chat können chatten - + see hands sehen Hände - + can see hands können Hände sehen - + not allowed nicht erlaubt - + Room Raum - + Age Alter - + Description Beschreibung - + Creator Ersteller - + Type Typ - + Restrictions Bedingungen - + Players Spieler - + Spectators Zuschauer @@ -3752,143 +4126,143 @@ Eventuell müssen sie manuell eine neue Version herunterladen. GeneralSettingsPage - + Reset all paths Alle Pfade zurücksetzen - + All paths have been reset Alle Pfade wurden zurückgesetzt - - - - - - - + + + + + + + Choose path Pfad auswählen - + Personal settings Persönliche Einstellungen - + Language: Sprache: - + Paths (editing disabled in portable mode) Pfade (Anpassungen im portablen Modus deaktiviert) - + Paths Pfade - + How to help with translations Wie man beim Übersetzen helfen kann - + Decks directory: Verzeichnis mit Decklisten: - + Filters directory: Filterverzeichnis: - + Replays directory: Verzeichnis mit aufgezeichneten Spielen: - + Pictures directory: Verzeichnis mit Bilddateien: - + Card database: Kartendatenbank: - + Custom database directory: Verzeichnis für benutzerdefinierte Datenbank: - + Token database: Spielsteindatenbank: - + Update channel Aktualisierungskanal - + Check for client updates on startup Nach Aktualisierungen des Klienten beim Start der Anwendung suchen - + Check for card database updates on startup Auf Kartendatenbankaktualisierungen beim Anwendungsstart prüfen - + Don't check Nicht überprüfen - + Prompt for update Zu Aktualisierungen auffordern - + Always update in the background Immer im Hintergrund aktualisieren - + Check for card database updates every Auf Kartendatenbankaktualisierung prüfen alle - + days Tage - + Notify if a feature supported by the server is missing in my client Benachrichtigung wenn ein vom Server unterstütze Funktion in meinem Client fehlt - + Automatically run Oracle when running a new version of Cockatrice Starte Oracle automatisch, wenn eine neue Version von Cockatrice gestartet wird - + Show tips on startup Zeige Tipps beim Start - + Last update check on %1 (%2 days ago) Letzte Aktualisierungsprüfung am %1 (vor %2 Tagen) @@ -3896,47 +4270,47 @@ Eventuell müssen sie manuell eine neue Version herunterladen. GraveyardMenu - + &Graveyard &Friedhof - + &View graveyard &Friedhof ansehen - + &Move graveyard to... & Friedhof nach ... bewegen - + &Top of library &Oben in der Bibliothek - + &Bottom of library &Unten in der Bibliothek - + &All players - + &Hand &Hand - + &Exile &Exil - + Reveal random card to... Zufällige Karte vorzeigen für ... @@ -3944,111 +4318,141 @@ Eventuell müssen sie manuell eine neue Version herunterladen. HandMenu - + &Hand &Hand - + &View hand &Hand ansehen - - &Sort hand - &Hand sortieren + + Sort hand by... + - - Take &mulligan - Mulligan nehmen + + Name + - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... &Hand nach ... bewegen - + &Top of library &Oben in der Bibliothek - + &Bottom of library &Unten in der Bibliothek - + &Graveyard &Friedhof - + &Exile &Exil - + &Reveal hand to... &Hand an ... vorzeigen - - Reveal r&andom card to... - Zufällige Karten an ... vorzeigen + + + All players + - - - &All players - + + Reveal r&andom card to... + Zufällige Karten an ... vorzeigen HomeWidget - + Create New Deck Neues Deck erstellen - + Browse Decks Decks durchsuchen - + Browse Card Database Kartendatenbank durchsuchen - + Browse EDHRec EDHRec durchsuchen - + + Browse Archidekt + + + + View Replays Wiederholung ansehen - + Quit Verlassen - + Connecting... Verbinden... - + Connect Verbinden - + Play Spielen @@ -4250,61 +4654,61 @@ Eventuell müssen sie manuell eine neue Version herunterladen. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Der Server hat seine maximale Benutzeranzahl erreicht. Bitte versuchen Sie es später noch einmal. - + There are too many concurrent connections from your address. Es gibt zu viele gleichzeitige Verbindungen von Ihrer Adresse. - + Banned by moderator Gebannt von einem Moderator - + Expected end time: %1 Voraussichtliches Ende: %1 - + This ban lasts indefinitely. Dieser Bann ist unbefristet. - + Scheduled server shutdown. Planmäßige Serverabschaltung. - - + + Invalid username. Ungültiger Benutzername. - + You have been logged out due to logging in at another location. Sie wurden abgemeldet, da Sie sich an einer anderen Stelle angemeldet haben. - + Connection closed Verbindung geschlossen - + The server has terminated your connection. Reason: %1 Der Server hat Ihre Verbindung beendet. Grund: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -4315,584 +4719,584 @@ Alle laufenden Spiele werden beendet. Grund für die Abschaltung: %1 - + Scheduled server shutdown Planmäßige Serverabschaltung - - + + Success Erfolgreich - + Registration accepted. Will now login. Registrierung angenommen. Login läuft. - + Account activation accepted. Will now login. Accountaktivierung angenommen. Login läuft. - + Number of players Spieleranzahl - + Please enter the number of players. Bitte die Spieleranzahl eingeben: - - + + Player %1 Spieler %1 - + Load replay Aufgezeichnetes Spiel laden - + About Cockatrice Über Cockatrice - + Version Version - + Cockatrice Webpage Cockatrice Webseite - + Project Manager: Projektmanager: - + Past Project Managers: frühere Projektmanager: - + Developers: Entwickler: - + Our Developers unsere Entwickler - + Help Develop! Hilf bei der Entwicklung! - + Translators: Übersetzer: - + Our Translators Unsere Übersetzer - + Help Translate! Hilf bei der Übersetzung! - + Support: Unterstützung: - + Report an Issue Problem melden - + Troubleshooting Fehlersuche - + F.A.Q. F.A.Q. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error Fehler - + Server timeout Server Zeitüberschreitung - + Failed Login Anmeldung fehlgeschlagen - + Your client seems to be missing features this server requires for connection. Ihrem Client scheinen Funktionen, die der Server für die Verbindung benötigt, zu fehlen. - + To update your client, go to 'Help -> Check for Client Updates'. Um Ihren Client zu aktualisieren, gehen Sie auf 'Hilfe -> Auf Clientaktualisierungen prüfen'. - + Incorrect username or password. Please check your authentication information and try again. Falscher Benutzername oder Passwort. Bitte überprüfe deine Login Informationen und versuche es erneut. - + There is already an active session using this user name. Please close that session first and re-login. Es gibt bereits eine aktive Verbindung mit diesem Benutzernamen. Bitte schließen Sie diese Verbindung zuerst und versuchen Sie es dann erneut. - - + + You are banned until %1. Sie sind gebannt bis: %1. - - + + You are banned indefinitely. Sie sind auf unbestimmte Zeit gebannt. - + This server requires user registration. Do you want to register now? Diese Server benötigt eine Benutzerregistrierung. Möchten Sie sich jetzt registrieren? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. Dieser Server verlangt Client-IDs. Entweder das Erzeugen der ID schlägt fehl oder Sie verwenden einen modifizierten Client. Bitte starten Sie ihren Client neu, um es erneut zu versuchen. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. Ein interner Fehler ist aufgetreten, bitte starten Sie Cockatrice neu, bevor Sie es erneut versuchen. Falls der Fehler erneut auftritt, versichern Sie sich, dass Sie die neuste Softwareversion verwenden, und kontaktieren Sie falls nötig die Softwareentwickler. - + Account activation Accountaktivierung - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Ihr Account wurde noch nicht aktiviert. Sie müssen den Aktivierungstoken aus der Aktivierungsemail verwenden - + Server Full Server voll - + Unknown login error: %1 Unbekannter Login-Fehler: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Dies bedeutet normalerweise, dass Ihre Clientversion veraltet ist und der Server eine Antwort geschickt hat, die Ihr Client nicht versteht. - + Your username must respect these rules: Ihr Benutzername muss diesen Regeln entsprechen: - + is %1 - %2 characters long %1 - %2 Zeichen lang - + can %1 contain lowercase characters kann %1 Kleinbuchstaben beinhalten - - - - + + + + NOT NICHT - + can %1 contain uppercase characters kann %1 Großbuchstaben beinhalten - + can %1 contain numeric characters kann %1 Zahlen beinhalten - + can contain the following punctuation: %1 kann folgende Sonderzeichen beinhalten: %1 - + first character can %1 be a punctuation mark Das erste Zeichen kann %1 ein Sonderzeichen sein - + no unacceptable language as specified by these server rules: note that the following lines will not be translated Keine inakzeptable Sprache wie durch folgende Serverregeln festgelegt: - + can not contain any of the following words: %1 kann keines der folgenden Wörter enthalten: %1 - + can not match any of the following expressions: %1 kann keinem der folgenden Ausdrücke entsprechen: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Sie können nur A-Z, a-z, 0-9, _, ., und - in Ihrem Benutzernamen verwenden. - - - - - - + + + + + + Registration denied Registrierung verweigert - + Registration is currently disabled on this server Momentan ist die Registrierung auf diesem Server deaktiviert - + There is already an existing account with the same user name. Es existiert bereits ein Account mit dem gleichen Benutzernamen. - + It's mandatory to specify a valid email address when registering. Es ist erforderlich, eine korrekte Emailadresse während der Registrierung anzugeben. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. Scheinbar versuchen Sie, ein neues Benutzerkonto auf diesem Server zu registrieren, obwohl Sie bereits ein Konto mit der verwendeten E-Mailadresse haben. Dieser Server beschränkt die Anzahl an Benutzerkonten, die ein Nutzer pro E-Mailadresse registrieren kann. Bitte kontaktieren Sie den Server-Betreiber für weitere Hilfe oder um Ihre Zugangsinformationen zu erhalten. - + Password too short. Passwort zu kurz. - + Registration failed for a technical problem on the server. Registrierung aufgrund eines technischen Problems des Servers fehlgeschlagen. - + The connection to the server has been lost. Verbindung zum Server verloren. - + Unknown registration error: %1 Unbekannter Registrierungsfehler: %1 - + Account activation failed Accountaktivierung fehlgeschlagen - + Socket error: %1 Netzwerkfehler: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Sie versuchen sich an einem veralteten Server anzumelden. Bitte verwenden Sie eine ältere Cockatrice-Version oder melden Sie sich an einem aktuellen Server an. Lokale Version ist %1, Serverversion ist %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Ihr Cockatrice-Client ist veraltet. Bitte laden Sie sich die neueste Version herunter. Lokale Version ist %1, Serverversion ist %2. - + Connecting to %1... Verbinde zu %1... - + Registering to %1 as %2... Registrierung bei %1 als %2... - + Disconnected nicht verbunden - + Connected, logging in at %1 Verbunden, Anmeldung bei %1 + - Requesting forgotten password to %1 as %2... Fordere vergessenes Password von %1 als %2 an... - + &Connect... &Verbinden... - + &Disconnect Verbindung &trennen - + Start &local game... &Lokales Spiel starten... - + &Watch replay... &Aufgezeichnetes Spiel abspielen... - + &Full screen &Vollbild - + &Register to server... Auf Server &registrieren... - + &Restore password... Passwort wiede&rherstellen... - + &Settings... &Einstellungen... - + &Exit &Beenden - + A&ctions Aktionen - + &Cockatrice &Cockatrice - + C&ard Database K&artendatenbank - + &Manage sets... Editionen &verwalten... - + Edit custom &tokens... Bearbeite benutzerdefinierte Spiels&teine... - + Open custom image folder Öffne den Ordner für benutzerdefinierte Kartenbilder - + Open custom sets folder Öffne den Ordner für benutzerdefinierte Sets - + Add custom sets/cards Benutzerdefinierte Karten/Sets hinzufügen - + Reload card database Neu laden der Kartendatenbank - + Tabs Reiter - + &Help &Hilfe - + &About Cockatrice &Über Cockatrice - + &Tip of the Day &Tipp des Tages - + Check for Client Updates Auf Clientaktualisierungen prüfen - + Check for Card Updates... Nach Kartenaktualisierungen suchen... - + Check for Card Updates (Automatic) Prüfe auf Kartenaktualisierungen (automatisch) - + Show Status Bar Statuszeile anzeigen - + View &Debug Log Fehlerprotokoll ansehen - + Open Settings Folder Einstellungsordner öffnen - + Show/Hide Ein-/Ausblenden - + New Version Neue Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Herzlichen Glückwunsch zur Aktualisierung auf Cockatrice %1! Oracle wird gestartet um ihre Kartendatenbank zu aktualisieren. - + Cockatrice installed Cockatrice wurde installiert - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Herzlichen Glückwunsch, dass Sie Cockatrice %1 installiert haben! Oracle wird jetzt gestartet und installiert die initiale Kartendatenbank. - + Card database Kartendatenbank - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -4901,29 +5305,29 @@ Möchten Sie Ihre Kartendatenbank jetzt aktualisieren? Falls Sie unsicher sind oder Cockatrice das erste Mal nutzen, wählen Sie „Ja“ - - + + Yes Ja - - + + No Nein - + Open settings Einstellungen öffnen - + New sets found Neue Editionen gefunden - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -4934,17 +5338,17 @@ Setcode(s): %1 Möchten Sie es/sie aktivieren? - + View sets Editionen ansehen - + Welcome Willkommen - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -4953,65 +5357,65 @@ Alle Editionen der Kartendatenbank wurden aktiviert. Lesen Sie mehr über das Ändern der Editionsreihenfolge oder die Deaktivierung bestimmter Editionen im „Editionen verwalten...“ Fenster. - - + + Information Information - + A card database update is already running. Eine Datenbankaktualisierung wird bereits durchgeführt. - + Unable to run the card database updater: Kartendatenbankaktualisierung nicht ausführbar: - + Card database update running. Kartendatenbankaktualisierung läuft. - + Failed to start. The file might be missing, or permissions might be incorrect. Starten fehlgeschlagen. Die Datei könnte fehlen oder die Berechtigung fehlen. - + The process crashed some time after starting successfully. Der Prozess ist einige Zeit nach erfolgreichem Start abgestürzt. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. Zeitüberschreitung. Der Prozess hat zu lange nicht geantwortet. Zeitüberschreitung bei letzter waitFor...() Funktion. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. Ein Fehler ist beim Schreiben an den Prozess aufgetreten. Beispielsweise könnte der Prozess derzeit nicht ausgeführt werden oder er hat seinen Eingabekanal geschlossen. - + An error occurred when attempting to read from the process. For example, the process may not be running. Ein Fehler ist beim Lesen vom Prozess aufgetreten. Beispielsweise könnte der Prozess derzeit nicht ausgeführt werden. - + Unknown error occurred. Unbekannter Fehler aufgetreten. - + The card database updater exited with an error: %1 Die Kartendatenbankaktualisierung brach mit einem Fehler ab: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5022,673 +5426,843 @@ Dies stellt wahrscheinlich kein Problem dar, allerdings könnte dies bedeuten, d Um Ihren Client zu aktualisieren, navigieren Sie zu Hilfe -> Auf Aktualisierungen prüfen. - - - - - + + + + + Load sets/cards Lade Sets/Karten - + Selected file cannot be found. Die ausgewählte Datei wurde nicht gefunden. - + You can only import XML databases at this time. Im Moment ist es nur möglich XML Datenbanken zu importieren. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Die neuen Sets/Karten wurden erfolgreich hinzugefügt. Cockatrice wird jetzt die Kartendatenbank neu laden. - + Sets/cards failed to import. Set-/Kartenimport fehlgeschlagen. - - - + + + Reset Password Passwort zurücksetzen - + Your password has been reset successfully, you can now log in using the new credentials. Ihr Passwort wurde erfolgreich zurückgesetzt, Sie können sich jetzt mit den neuen Anmeldedaten anmelden. - + Failed to reset user account password, please contact the server operator to reset your password. Passwortzurücksetzung fehlgeschlagen. Bitte kontaktieren Sie den Serverbetreiber, um Ihr Passwort zurücksetzen zu lassen. - + Activation request received, please check your email for an activation token. Aktivierungsanfrage erhalten. Bitte prüfen Sie Ihre E-Mails nach einem Aktivierungstoken. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base Manabasis + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve Manakurve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion Manahingabe + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play vom Spielfeld - + from their graveyard von ihrem Friedhof - + from exile aus dem Exil - + from their hand von ihrer Hand - + the top card of %1's library die oberste Karte von %1s Bibliothek - + the top card of their library die oberste Karte ihrer Bibliothek - + from the top of %1's library oben von %1s Bibliothek - + from the top of their library von ihrer Bibliothek von oben - + the bottom card of %1's library die unterste Karte von %1s Bibliothek - + the bottom card of their library die unterste Karte ihrer Bibliothek - + from the bottom of %1's library von der Unterseite von %1s Bibliothek - + from the bottom of their library von ihrer Bibliothek von unten - + from %1's library aus %1s Bibliothek - + from their library von ihrer Bibliothek - + from sideboard aus dem Sideboard - + from the stack vom Stapel - + from custom zone '%1' von der benutzerdefinierten Zone '%1' - + %1 is now keeping the top card %2 revealed. %1 lässt nun die oberste Karte %2 aufgedeckt. - + %1 is not revealing the top card %2 any longer. %1 lässt die oberste Karte %2 nicht mehr aufgedeckt. - + %1 can now look at top card %2 at any time. %1 kann nun die oberste Karte %2 jederzeit betrachten. - + %1 no longer can look at top card %2 at any time. %1 kann die oberste Karte %2 nicht mehr jederzeit betrachten. - + %1 attaches %2 to %3's %4. %1 legt %2 an %3s %4 an. - + %1 has conceded the game. %1 hat das Spiel aufgegeben. - + %1 has unconceded the game. %1 hat das Spiel doch nicht aufgegeben. - + %1 has restored connection to the game. %1 ist wieder mit dem Spiel verbunden. - + %1 has lost connection to the game. %1 hat die Verbindung zum Spiel verloren. - + %1 points from their %2 to themselves. %1 zeigt von ihrem %2 auf sich selbst. - + %1 points from their %2 to %3. %1 zeigt von ihrem %2 auf %3. - + %1 points from %2's %3 to themselves. %1 zeigt von %2s %3 auf sich selbst. - + %1 points from %2's %3 to %4. %1 zeigt von %2s %3 auf %4. - + %1 points from their %2 to their %3. %1 zeigt von ihrem %2 auf ihren %3. - + %1 points from their %2 to %3's %4. %1 zeigt von ihrem %2 auf %3s %4. - + %1 points from %2's %3 to their own %4. %1 zeigt von %2s %3 auf ihr %4. - + %1 points from %2's %3 to %4's %5. %1 zeigt von %2s %3 auf %4s %5. - + %1 creates a face down token. %1 erzeugt einen umgedrehten Spielstein. - + %1 creates token: %2%3. %1 erstellt Spielstein: %2%3. - + %1 has loaded a deck (%2). %1 hat ein Deck geladen (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 hat ein Deck mit %2 Sideboardkarten geladen (%3). - + %1 destroys %2. %1 zerstört %2. - + a card eine Karte - + %1 gives %2 control over %3. %1 überlässt %2 die Kontrolle über %3. - + %1 puts %2 into play%3 face down. %1 bringt %2%3 verdeckt ins Spiel. - + %1 puts %2 into play%3. %1 bringt %2%3 ins Spiel. - + %1 puts %2%3 into their graveyard. %1 legt %2%3 in ihren Friedhof. - + %1 exiles %2%3. %1 schickt %2%3 ins Exil. - + %1 moves %2%3 to their hand. %1 bewegt %2%3 in ihre Hand. - + %1 puts %2%3 into their library. %1 legt %2%3 in ihre Bibliothek. - + %1 puts %2%3 onto the bottom of their library. %1 legt %2%3 unter die Bibliothek. - + %1 puts %2%3 on top of their library. %1 legt %2%3 auf ihre Bibliothek. - + %1 puts %2%3 into their library %4 cards from the top. %1 legt %2%3 in die Bibliothek an %4 Stelle von oben. - + %1 moves %2%3 to sideboard. %1 legt %2%3 in sein Sideboard. - + %1 plays %2%3. %1 spielt %2%3 aus. - + %1 moves %2%3 to custom zone '%4'. %1 bewegt %2%3 zur benutzerdefinierten Zone '%4'. - + %1 tries to draw from an empty library %1 versucht von einer leeren Bibliothek zu ziehen - + %1 draws %2 card(s). %1 zieht %2 Karte(n).%1 zieht %2 Karte(n). - + %1 is looking at %2. %1 sieht sich %2 an. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 sieht sich die %4 %3 Karte(n) %2 an.%1 sieht sich die %4 %3 Karte(n) %2 an. - + bottom untersten - + top obersten - + %1 turns %2 face-down. %1 wendet %2 auf die Rückseite. - + %1 turns %2 face-up. %1 wendet %2 auf die Vorderseite. - + The game has been closed. Das Spiel wurde geschlossen. - + The game has started. Das Spiel hat begonnen. - + You are flooding the game. Please wait a couple of seconds. Du überschwemmst das Spiel. Warte bitte einige Sekunden. - + %1 has joined the game. %1 ist dem Spiel beigetreten. - + %1 is now watching the game. %1 schaut nun dem Spiel zu. - + You have been kicked out of the game. Sie wurden aus dem Spiel geworfen. - + %1 has left the game (%2). %1 hat das Spiel verlassen (%2). - + %1 is not watching the game any more (%2). %1 schaut dem Spiel nicht mehr zu (%2). - + %1 is not ready to start the game any more. %1 ist nicht mehr bereit, das Spiel zu starten. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 mischt sein Deck und zieht eine Hand mit %2 Karte(n).%1 mischt sein Deck und zieht eine neue Hand mit %2 Karte(n). - + %1 shuffles their deck and draws a new hand. %1 mischt sein Deck und zieht eine neue Hand. - + You are watching a replay of game #%1. Sie sehen eine Aufzeichnung des Spiels #%1. - + %1 is ready to start the game. %1 ist bereit, das Spiel zu starten. - + cards an unknown amount of cards Karten - + %1 card(s) a card for singular, %1 cards for plural %1 Karte(n)%1 Karte(n) - + %1 lends %2 to %3. %1 verleiht %2 an %3. - + %1 reveals %2 to %3. %1 zeigt %3 %2. - + %1 reveals %2. %1 zeigt %2 offen vor. - + %1 randomly reveals %2%3 to %4. %1 zeigt %4 zufällig %2%3 vor. - + %1 randomly reveals %2%3. %1 zeigt zufällig %2%3 offen vor. - + %1 peeks at face down card #%2. %1 schaut sich die umgedrehte Karte #%2 an. - + %1 peeks at face down card #%2: %3. %1 schaut sich die umgedrehte Karte #%2 an: %3. - + %1 reveals %2%3 to %4. %1 zeigt %4 %2%3 vor. - + %1 reveals %2%3. %1 zeigt %2%3 offen vor. - + %1 reversed turn order, now it's %2. %1 kehrte die Zugreihenfolge um, nun ist es %2. - + reversed umgekehrt - + normal normal - + Heads Kopf - + Tails Zahl - + %1 flipped a coin. It landed as %2. %1 warf eine Münze. Es fiel %2. - + %1 rolls a %2 with a %3-sided die. %1 würfelt eine %2 mit einem %3-seitigen Würfel. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 wirft %2 Münzen. Es ergab %3 mal Kopf und %4 mal Zahl. - + %1 rolls a %2-sided dice %3 times: %4. %1 wirft einen %2-seitigen Würfel %3 mal: %4. - + %1's turn. %1 ist am Zug. - + %1 sets annotation of %2 to %3. %1 versieht %2 mit dem Hinweis %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). %1 platziert %2 "%3" Zähler auf %4 (jetzt %5).%1 platziert %2 "%3" Zähler auf %4 (jetzt %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). %1 entfernt %2 "%3" Marke(n) von %4 (jetzt %5).%1 entfernt %2 "%3" Zähler von %4 (jetzt %5). - + %1 sets counter %2 to %3 (%4%5). %1 setzt Zähler %2 auf %3 (%4%5). - + %1 sets %2 to not untap normally. %1 setzt %2 auf explizites Enttappen. - + %1 sets %2 to untap normally. %1 setzt %2 auf normales Enttappen. - + %1 removes the PT of %2. %1 entfernt die Kampfwerte von %2. - + %1 changes the PT of %2 from nothing to %4. %1 ändert die Kampfwerte von %2 von nichts auf %4. - + %1 changes the PT of %2 from %3 to %4. %1 ändert die Kampfwerte von %2 von %3 auf %4. - + %1 has locked their sideboard. %1 hat ihr Sideboard gesperrt. - + %1 has unlocked their sideboard. %1 hat ihr Sideboard entsperrt. - + %1 taps their permanents. %1 tappt ihre bleibenden Karten. - + %1 untaps their permanents. %1 enttappt ihre bleibenden Karten. - + %1 taps %2. %1 tappt %2. - + %1 untaps %2. %1 enttappt %2. - + %1 shuffles %2. %1 mischt %2. - + %1 shuffles the bottom %3 cards of %2. %1 mischt die untersten %3 Karten von %2. - + %1 shuffles the top %3 cards of %2. %1 mischt die obersten %3 Karten von %2. - + %1 shuffles cards %3 - %4 of %2. %1 mischt die Karten %3 - %4 von %2. - + %1 unattaches %2. %1 löst %2 ab. - + %1 undoes their last draw. %1 legt die zuletzt gezogene Karte zurück. - + %1 undoes their last draw (%2). %1 legt die zuletzt gezogene Karte zurück (%2) @@ -5696,110 +6270,110 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. MessagesSettingsPage - + Word1 Word2 Word3 Wort1 Wort2 Wort3 - + Add New Message Neue Nachricht hinzufügen - + Edit Message Nachricht bearbeiten - + Remove Message Nachricht entfernen - + Add message Nachricht hinzufügen - - + + Message: Nachricht: - + Edit message Nachricht bearbeiten - + Chat settings Chat Einstellungen - + Custom alert words Benutzerdefinierte Benachrichtigungswörter - + Enable chat mentions Chat Erwähnungen aktivieren - + Enable mention completer Autovervollständigung aktivieren - + In-game message macros Makros für Nachrichten in Spielen - + How to use in-game message macros Anleitung zum Verwenden der Makros für Nachrichten in Spielen - + Ignore chat room messages sent by unregistered users Nachrichten von unregistrierten Benutzern im Chatroom ignorieren - + Ignore private messages sent by unregistered users Private Nachrichten von unregistrierten Benutzern ignorieren - - + + Invert text color Textfarbe invertieren - + Enable desktop notifications for private messages Desktop Benachrichtigungen für private Nachrichten aktivieren - + Enable desktop notification for mentions Desktop Benachrichtigungen für Erwähnungen aktivieren - + Enable room message history on join Nachrichtenverlauf beim Betreten eines Raumes aktivieren - - + + (Color is hexadecimal) (Farbcode in hexadezimal) - + Separate words with a space, alphanumeric characters only Wörter durch Leerzeichen trennen, nur alphanumerische Zeichen @@ -5916,62 +6490,62 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. Phase - + Unknown Phase Unbekannte Phase - + Untap Enttappen - + Upkeep Versorgung - + Draw Ziehen - + First Main Erste Hauptphase - + Beginning of Combat Anfangssegment des Kampfes - + Declare Attackers Angreifer deklarieren - + Declare Blockers Blocker deklarieren - + Combat Damage Kampfschaden - + End of Combat Ende des Kampfes - + Second Main Zweite Hauptphase - + End/Cleanup Ende/Aufräumen @@ -6037,7 +6611,7 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. PictureLoader - + en code for scryfall's language property, not available for all languages de @@ -6046,134 +6620,134 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. PlayerActions - + View top cards of library Oberste Karten der Bibliothek ansehen - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) Anzahl der Karten: (max. %1) - + View bottom cards of library Unterste Karten der Bibliothek ansehen - + Shuffle top cards of library Oberste Karten der Bibliothek mischen - + Shuffle bottom cards of library Unterste Karten der Bibliothek mischen - + Draw hand Hand ziehen - + 0 and lower are in comparison to current hand size 0 und kleiner sind im Vergleich zur jetzigen Handgröße - + Draw cards Karten ziehen - + Move top cards to grave Oberste Karten in den Friedhof bewegen - + Move top cards to exile Oberste Karten ins Exil bewegen - + Move bottom cards to grave Unterste Karten in den Friedhof bewegen - + Move bottom cards to exile Unterste Karten ins Exil bewegen - + Draw bottom cards Unterste Karten ziehen - - + + C&reate another %1 token Einen weiteren %1 Spielstein erzeugen - + Create tokens Spielsteine erzeugen - - + + Number: Anzahl: - + Place card X cards from top of library Karte X Karten von oben in der Bibliothek plazieren - + Which position should this card be placed: In welcher Position sollte diese Karte platziert werden: - + (max. %1) (max. %1) - + Change power/toughness Stärke/Widerstandskraft ändern - + Change stats to: Ändere Werte auf: - + Set annotation Anmerkung setzen - + Please enter the new annotation: Bitte die neue Anmerkung eingeben: - + Set counters Zähler setzen @@ -6181,17 +6755,17 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. PlayerMenu - + Player "%1" Spieler "%1" - + &Counters &Zähler - + S&ay S&agen @@ -6199,7 +6773,7 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. PrintingSelector - + Display Navigation Buttons Navigationstasten anzeigen @@ -6207,22 +6781,22 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. PrintingSelectorCardOverlayWidget - + Preference Preferenz - + Pin Printing Version festsetzen - + Unpin Printing Version lösen - + Show Related cards Zugehörige Karten anzeigen @@ -6238,17 +6812,17 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. PrintingSelectorCardSelectionWidget - + Previous Card in Deck Vorherige Karte im Deck - + Bulk Selection Massenauswahl - + Next Card in Deck Nächste Karte im Deck @@ -6256,28 +6830,28 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. PrintingSelectorCardSortingWidget - + Alphabetical Alphabetisch - + Preference Präferenz - + Release Date Erscheinungsdatum - - + + Descending Absteigend - + Ascending Aufsteigend @@ -6343,37 +6917,37 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. QMenuBar - + Services Dienste - + Hide %1 %1 ausblenden - + Hide Others Andere ausblenden - + Show All Alle anzeigen - + Preferences... Einstellungen... - + Quit %1 Schließe %1 - + About %1 Über %1 @@ -6381,42 +6955,42 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. QObject - + Cockatrice card database (*.xml) Cockatrice Kartendatenbank (*.xml) - + All files (*.*) Alle Dateien (*.*) - + Cockatrice replays (*.cor) Aufgezeichnete Cockatrice-Spiele (*.cor) - + Maindeck Hauptdeck - + Sideboard Sideboard - + Tokens Spielsteine - + Overwrite Existing File? Existierende Datei überschreiben? - + A .cod version of this deck already exists. Overwrite it? Eine .cod Version dieses Decks existiert bereits. Überschreiben? @@ -6424,92 +6998,92 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. QPlatformTheme - + OK OK - + Save Speichern - + Save All Alle speichern - + Open Öffnen - + &Yes Ja - + Yes to &All Ja zu &Allem - + &No &Nein - + N&o to All Nein zu Allem - + Abort Abbrechen - + Retry Erneut versuchen - + Ignore Ignorieren - + Close Schließen - + Cancel Abbrechen - + Discard Verwerfen - + Help Hilfe - + Apply Anwenden - + Reset Zurücksetzen - + Restore Defaults Standard wiederherstellen @@ -6606,37 +7180,37 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. RoomSelector - + Rooms Räume - + Joi&n Teil&nehmen - + Room Raum - + Description Beschreibung - + Permissions Berechtigungen - + Players Spieler - + Games Spiele @@ -6677,27 +7251,27 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. SetsModel - + Enabled Aktiviert - + Set type Editionsart - + Set code Editionscode - + Long name Langer Name - + Release date Veröffentlichung @@ -6705,53 +7279,53 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. ShortcutSettingsPage - - + + Restore all default shortcuts Alle Standard-Tastaturkürzel wiederherstellen - + Do you really want to restore all default shortcuts? Möchten Sie wirklich alle Standard-Tastaturkürzel wiederherstellen? - + Clear all default shortcuts Alle Standard-Tastaturkürzel entfernen - + Do you really want to clear all shortcuts? Möchten Sie wirklich alle Tastaturkürzel entfernen? - + Section: Abschnitt: - + Action: Aktion: - + Shortcut: Tastaturkürzel: - + How to set custom shortcuts Wie werden benutzerdefinierte Tastaturkürzel gesetzt - + Clear all shortcuts Alle Tastaturkürzel entfernen - + Search by shortcut name Suchen nach Name des Tastenkürzels @@ -6759,12 +7333,12 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. ShortcutTreeView - + Action Aktion - + Shortcut Abkürzung @@ -6772,14 +7346,14 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! Ihre Konfigurationsdatei enthielt ungültige Verknüpfungen. Bitte überprüfen Sie die Verknüpfungseinstellungen! - + The following shortcuts have been set to default: Die folgenden Verknüpfungen wurden auf den Standard gesetzt: @@ -6807,12 +7381,12 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! SideboardMenu - + &Sideboard &Nebendeck - + &View sideboard &Nebendeck ansehen @@ -6820,27 +7394,27 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! SoundSettingsPage - + Enable &sounds Töne aktivieren - + Current sounds theme: Aktuelles Ton-Theme: - + Test system sound engine Systemsound testen - + Sound settings Toneinstellungen - + Master volume Masterlautstärke @@ -6848,48 +7422,48 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! SpoilerBackgroundUpdater - + Spoilers season has ended Spoiler Saison ist beendet - + Deleting spoiler.xml. Please run Oracle spoiler.xml gelöscht. Bitte führe Oracle aus - - + + Spoilers download failed Spoiler Download fehlgeschlagen - + No internet connection Keine Internetverbindung - + Error Fehler - + Spoilers already up to date Spoiler bereits aktuell - + No new spoilers added Keine neuen Spoiler hinzugefügt - + Spoilers have been updated! Spoiler wurden aktualisiert! - + Last change: Letzte Änderung: @@ -7053,56 +7627,123 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! Benutzer nicht freischaltbar. Interner Fehler + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info Karteninformationen - + Deck Deck - + Filters Filter - + &View Ansicht - + + Card Database + + + + Printing Auflage - - - - + + + + + Visible Sichtbar - - - - + + + + + Floating Schwebend - + Reset layout Darstellung zurücksetzen - + Deck: %1 Deck: %1 @@ -7110,61 +7751,61 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! TabDeckEditorVisual - + Visual Deck: %1 Visuelles Deck: %1 - + &Visual Deck Editor &Visueller Deckeditor - - + + Card Info Karteninformation - - + + Deck Deck - - + + Filters Filter - + &View &Ansehen - + Printing Auflage - - - - + + + + Visible Sichtbar - - - - + + + + Floating Schwebend - + Reset layout Anordnung zurücksetzen @@ -7172,22 +7813,22 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! TabDeckEditorVisualTabWidget - + Visual Deck View Visuelle Deckansicht - + Visual Database Display Visuelle Datenbankanzeige - + Deck Analytics Deckanalysen - + Sample Hand Probehand @@ -7195,134 +7836,134 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! TabDeckStorage - + Local file system Lokales Dateisystem - + Server deck storage Deckablage auf dem Server - - + + Open in deck editor Im Deckeditor öffnen - + Rename deck or folder Deck oder Ordner umbenennen - + Upload deck Deck hochladen - + Download deck Deck herunterladen + - - - + + New folder Neuer Ordner + - Delete Löschen - + Open decks folder Deckordner öffnen - + Rename local folder Lokalen Ordner umbenennen - + Rename local file Lokale Datei umbenennen - + New name: Neuer Name: - - - - + + + + Error Fehler - + Rename failed Umbenennen fehlgeschlagen - - + + Invalid deck file Ungültige Deckdatei - + Enter deck name Decknamen eingeben - + This decklist does not have a name. Please enter a name: Diese Deckliste hat keinen Namen. Bitte geben Sie einen Namen ein: - + Unnamed deck Unbenanntes Deck - + Failed to upload deck to server Hochladen des Decks zum Server fehlgeschlagen - + Delete local file Lokale Datei löschen - + Are you sure you want to delete the selected files? Sind sie sicher, dass die ausgewählten Dateien gelöscht werden sollen? - + Delete remote decks Löschen der online gespeicherten Decks - + Are you sure you want to delete the selected decks? Sind sie sicher, dass sie die ausgewählten Decks löschen wollen? - - + + Name of new folder: Name für den neuen Ordner: @@ -7335,17 +7976,17 @@ Bitte geben Sie einen Namen ein: TabDeckStorageVisual - + Visual Deck Storage Visuelle Deckablage - + Error Fehler - + Could not open deck at %1 Deck an Stelle %1 konnte nicht geöffnet werden @@ -7386,7 +8027,7 @@ Bitte geben Sie einen Namen ein: Suche - + EDHRec: EDHRec: @@ -7394,197 +8035,197 @@ Bitte geben Sie einen Namen ein: TabGame - - - + + + Replay Wiederholung - - + + Game Spiel - - + + Player List Spielerliste - - + + Card Info Karteninformationen - - + + Messages Nachrichten - - + + Replay Timeline Zeitleiste der Aufzeichnung - + &Phases &Phasen - + &Game Spi&el - + Next &phase Nächste &Phase - + Next phase with &action Nächste Phase mit &Aktion - + Next &turn Nächster &Zug - + Reverse turn order Kehre Zugreihenfolge um - + &Remove all local arrows &Lokale Pfeile entfernen - + Rotate View Cl&ockwise Ansicht im Uhrzeigesinn drehen - + Rotate View Co&unterclockwise Ansicht gegen den Uhrzeigersinn drehen - + Game &information &Spielinformationen - + Un&concede Aufgabe zurückziehen - - - + + + &Concede &Aufgeben - + &Leave game Spiel ver&lassen - + C&lose replay Wiederholung sch&ließen - + &Focus Chat Chat &fokussieren - + &Say: &Sagen: - + Selected cards Ausgewählte Karten - + &View Ansicht - - + + + - Visible Sichtbar - - + + + - Floating Schwebend - + Reset layout Darstellung zurücksetzen - + Concede Aufgeben - + Are you sure you want to concede this game? Sind Sie sicher, dass Sie das Spiel aufgeben möchten? - + Unconcede Doch nicht aufgeben - + You have already conceded. Do you want to return to this game? Sie haben bereits aufgegeben. Möchten Sie zu diesem Spiel zurückkehren? - + Leave game Spiel verlassen - + Are you sure you want to leave this game? Sind Sie sicher, dass Sie das Spiel verlassen möchten? - + A player has joined game #%1 Ein Spieler ist Spiel #%1 beigetreten - + %1 has joined the game %1 ist dem Spiel beigetreten - + You have been kicked out of the game. Sie wurden aus dem Spiel geworfen. @@ -7592,7 +8233,7 @@ Bitte geben Sie einen Namen ein: TabHome - + Home Zuhause @@ -7600,158 +8241,158 @@ Bitte geben Sie einen Namen ein: TabLog - + Logs Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Zeit;SenderName;SenderIP;Nachricht;ZielID;ZielName - + Room Logs Raumlogs - + Game Logs Spielelogs - + Chat Logs Chatlogs - - + + Error Fehler - + You must select at least one filter. Sie müssen mindestens einen Filter auswählen. - + You have to select a valid number of days to locate. Sie müssen eine gültige Anzahl an zu lokalisierenden Tagen auswählen. - + Username: Benutzername: - + IP Address: IP-Adresse: - + Game Name: Spielname: - + GameID: Spiel-ID: - + Message: Nachricht: - + Main Room Hauptraum - + Game Room Spielraum - + Private Chat Privater Chat - + Past X Days: Die letzten X Tage: - + Today Heute - + Last Hour Letzte Stunde - + Maximum Results: Maximale Ergebnisse: - + At least one filter is required. The more information you put in, the more specific your results will be. Mindestens ein Filter ist notwendig. Je mehr Informationen Sie angeben, desto spezifischer fallen die Ergebnisse aus. - + Get User Logs Benutzerlogs anzeigen - + Clear Filters Filter entfernen - + Filters Filter - + Log Locations Logstandorte - + Date Range Datumsbereich - + Maximum Results Maximale Ergebnisse - - + + Message History Nachrichtenverlauf - + Failed to collect message history information. Das Sammeln von Nachrichtenverlaufsinformationen ist fehlgeschlagen. - + There are no messages for the selected filters. Keine Nachrichten für die gewählten Filter. @@ -7797,181 +8438,181 @@ Je mehr Informationen Sie angeben, desto spezifischer fallen die Ergebnisse aus. TabReplays - + Local file system Lokales Dateisystem - + Server replay storage Wiederholungsablage auf dem Server - - + + Watch replay Wiederholung abspielen - + Rename Umbenennen - - + + New folder Neuer Ordner - - + + Delete Löschen - + Open replays folder Wiederholungsorder öffnen - + Download replay Wiederholung herunterladen - + Toggle expiration lock automatische Löschung umschalten - + Get replay share code Wiederholungscode zum Teilen erhalten - - + + Look up replay by share code Wiederholung durch einen Code zum Teilen nachschauen - + Rename local folder Umbenennen des lokalen Ordners - + Rename local file Umbenennen der lokalen Datei - + New name: Neuer Name: - + Error Fehler - + Rename failed Umbenennen fehlgeschlagen - + Name of new folder: Name des neuen Ordners: - + Delete local file Lokale Datei löschen - + Are you sure you want to delete the selected files? Sind sie sicher, dass die ausgewählten Dateien gelöscht werden sollen? - + Are you sure you want to delete the selected replays? Sind sie sicher, dass die ausgewählten Wiederholungen gelöscht werden sollen? - + Failed to get code Erhalten des Codes fehlgeschlagen - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. Entweder unterstützt dieser Server das Teilen von Wiederholungen nicht, oder erlaubt dir das Teilen von Wiederholungen nicht. - - - + + + Failed Fehlgeschlagen - + Could not get replay code Wiederholungs-Code konnte nicht erhalten werden - + Replay Share Code Wiederholungscode zum Teilen - + Others can use this code to add the replay to their list of remote replays: %1 Andere können diesen Code benutzen um diese Wiederholung zu ihrer Liste von Fremd-Wiederholungen hinzuzufügen: %1 - + Copy to clipboard In die Zwischenablage kopieren - + Replay share code Wiederholungscode zum Teilen - + Replay code found Wiederholungscode gefunden - + Replay was added, or you already had access to it. Wiederholung wurde hinzugefügt, oder du hattest bereits Zugang dazu. - + Replay code not found Wiederholungscode nicht gefunden - + Failed to submit code Einreichen des Codes fehlgeschlagen - + Unexpected error Unerwarteter Fehler - + Delete remote replay Wiederholung vom Server löschen @@ -8037,30 +8678,30 @@ Je mehr Informationen Sie angeben, desto spezifischer fallen die Ergebnisse aus. Server + - Error Fehler - + Failed to join the server room: it doesn't exist on the server. Serverraumbeitritt fehlgeschlagen: der Raum existiert auf dem Server nicht. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. Der Server denkt Sie seien im Serverraum, allerdings kann Ihr Client diesen nicht anzeigen. Versuchen Sie, Ihren Client neu zu starten. - + You do not have the required permission to join this server room. Sie haben nicht die erforderlichen Berechtigungen, um diesem Serverraum beizutreten. - + Failed to join the server room due to an unknown error: %1. Beitritt des Serverraums aufgrund eines unbekannten Fehlers fehlgeschlagen: %1. @@ -8068,92 +8709,97 @@ Je mehr Informationen Sie angeben, desto spezifischer fallen die Ergebnisse aus. TabSupervisor - + Deck Editor Deckeditor - + Visual Deck Editor Visueller Deckeditor - + EDHRec EDHRec - + + Archidekt + + + + Home Zuhause - + &Visual Deck Storage &Visuelle Deckablage - + Visual Database Display Visuelle Datenbankanzeige - + Server Server - + Account Benutzerkonto - + Deck Storage Deckablage - + Game Replays Aufgezeichnete Spiele - + Administration Administration - + Logs Logs - + Are you sure? Sind Sie sicher? - + There are still open games. Are you sure you want to quit? Es sind noch Spiele offen. Wollen Sie das Programm trotzdem beenden? - + Click to view Zum Anzeigen klicken - + Your buddy %1 has signed on! Dein Freund %1 hat sich eingeloggt! - + Unknown Event Unbekanntes Ereignis - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8164,39 +8810,39 @@ Dies könnte bedeuten, dass eine neue Version von Cockatrice verfügbar ist oder Um Ihren Client zu aktualisieren, navigieren Sie zu Hilfe -> Auf Aktualisierungen prüfen. - + Idle Timeout Inaktivitätszeitüberschreitung - + You are about to be logged out due to inactivity. Sie werden wegen Inaktivität abgemeldet. - + Promotion Beförderung - + You have been promoted. Please log out and back in for changes to take effect. Sie wurden befördert. Bitte loggen Sie sich aus und wieder ein, damit die Änderungen in Kraft treten. - + Warned Gewarnt - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Sie haben eine Warnung erhalten aufgrund von %1. Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie eingeleitet. Sollten Sie Fragen haben, stellen Sie diese bitte über eine private Nachricht an einen Moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) Sie haben die folgende Nachricht vom Server erhalten. @@ -8206,7 +8852,12 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display Visuelle Datenbankanzeige @@ -8288,7 +8939,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie UpdateDownloader - + Could not open the file for reading. Datei konnte nicht zum Lesen geöffnet werden. @@ -8296,206 +8947,206 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie UserContextMenu - + User &details Benutzer&details - + Private &chat Privater &Chat - + Show this user's &games Spiele dieses &Benutzers anzeigen - + Add to &buddy list Zur &Freundesliste hinzufügen - + Remove from &buddy list Von &Freundesliste entfernen - + Add to &ignore list &Ignorieren - + Remove from &ignore list Nicht mehr &ignorieren - + Kick from &game Aus dem &Spiel werfen - + Warn user Nutzer warnen - + View user's war&n history Verwar&nungsverlauf des Nutzers ansehen - + Ban from &server Vom &Server bannen - + View user's &ban history &Bannverlauf des Nutzers ansehen - + &Promote user to moderator Nutzer zum Moderator befördern - + Dem&ote user from moderator Moderator degradieren - + Promote user to &judge Nutzer zum Richter befördern - + Demote user from judge Richter degradieren - + View admin notes Administratornotizen ansehen - - - + + + Error Fehler - + This user does not exist. Dieser Benutzer existiert nicht. - + You are being ignored by %1 and can't see their games. Du wirst ignoriert von %1 und kannst ihre Spiele nicht sehen. - + Could not get %1's games. Konnte Spiele von %1 nicht erhalten. - + %1's games %1s Spiele - - - + + + Ban History Bannverlauf - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Bannzeit;Moderator;Banndauer;Banngrund;Sichtbarer Grund - + User has never been banned. Nutzer wurde bisher nie gebannt. - + Failed to collect ban information. Sammeln von Informationen über Bans fehlgeschlagen. - - - + + + Warning History Warnungsverlauf - + Warning Time;Moderator;User Name;Reason Warnungszeit;Moderator;Nutzername;Grund - + User has never been warned. Nutzer wurde bisher nie gewarnt. - + Failed to collect warning information. Sammeln von Informationen über Verwarnungen fehlgeschlagen. - + Failed to get admin notes. Administratornotizen konnten nicht abgerufen werden. - - + + Success Erfolgreich - + Successfully promoted user. Nutzer erfolgreich befördert. - + Successfully demoted user. Nutzer erfolgreich degradiert. - + + - Failed Fehlgeschlagen - + Failed to promote user. Beförderung des Nutzers fehlgeschlagen. - + Failed to demote user. Degradierung des Nutzers fehlgeschlagen. - + Copy hash to clipboard Kopiere Hash in die Zwischenablage - + Remove this user's messages Nachrichten dieses Nutzers entfernen @@ -8677,137 +9328,142 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie UserInterfaceSettingsPage - + General interface settings Allgemeine Bedienung - + &Double-click cards to play them (instead of single-click) Karten durch &Doppelklick ausspielen (statt Einzelklick) - + &Clicking plays all selected cards (instead of just the clicked card) &Anklicken spielt alle ausgewählten Karten aus (anstatt nur der angeklickten Karte) - + &Play all nonlands onto the stack (not the battlefield) by default Alle Nichtländer standardmäßig über den Stapel spielen (anstatt direkt auf das Spielfeld) - + + Do not delete &arrows inside of subphases + + + + Close card view window when last card is removed Schließe Kartenanzeigefenster, wenn die letzte Karte entfernt wird - + Auto focus search bar when card view window is opened Sucheingabe automatisch in den Fokus nehmen, wenn das Kartenansichtsfenster geöffnet wird - + Annotate card text on tokens Kartentext auf Spielsteinen anzeigen - + Use tear-off menus, allowing right click menus to persist on screen Benutze Abreißmenüs, erlaubt Kontextmenüs auf dem Bildschirm zu verbleiben - + Notifications settings Benachrichtigungseinstellungen - + Enable notifications in taskbar Benachrichtigungen in der Taskleiste aktivieren - + Notify in the taskbar for game events while you are spectating Benachrichtigungen für Spielereignisse auch beim Zuschauen anderer Spiele in der Taskbar anzeigen - + Notify in the taskbar when users in your buddy list connect Benachrichtige in der Taskleiste wenn sich Benutzer aus der Freundesliste anmelden - + Animation settings Animationseinstellungen - + &Tap/untap animation Animiertes &Tappen/Enttappen - + Deck editor/storage settings Deckeditor/-ablage Einstellungen - + Open deck in new tab by default Decks in neuem Tab öffnen als Standard - + Use visual deck storage in game lobby Visuelle Deckablage in der Spielelobby verwenden - + Use selection animation for Visual Deck Storage Auswahlanimation für Visuellen Deckspeicher verwenden - + When adding a tag in the visual deck storage to a .txt deck: Wenn ein Tag im Visuellen Deckspeicher zu einem .txt Deck hinzugefügt wird: - + do nothing tue nichts - + ask to convert to .cod frage, ob zu .cod konvertiert werden soll - + always convert to .cod immer nach .cod konvertieren - + Default deck editor type Standarddeckeditortyp - + Classic Deck Editor Klassischer Deckeditor - + Visual Deck Editor Visueller Deckeditor - + Replay settings Wiederholungsoptionen - + Buffer time for backwards skip via shortcut: Pufferzeit für Rückwärtsüberspringen durch Tastenkürzel @@ -8815,22 +9471,22 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie UserListWidget - + Users connected to server: %1 Mit dem Server verbundene Nutzer: %1 - + Users in this room: %1 Benutzer in diesem Raum: %1 - + Buddies online: %1 / %2 Freunde online: %1 / %2 - + Ignored users online: %1 / %2 Ignorierte Benutzer online: %1 / %2 @@ -8838,32 +9494,32 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie UtilityMenu - + Increment all card counters Alle Kartenzähler erhöhen - + &Untap all permanents &Enttappe alle bleibenden Karten - + R&oll die... &Würfeln... - + &Create token... &Spielstein erzeugen... - + C&reate another token Einen weiteren Spielstein erzeugen - + Cr&eate predefined token &Vordefinierten Spielstein erzeugen @@ -8871,22 +9527,22 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match Modus: Exakte Übereinstimmung - + Mode: Includes Modus: Enthält - + Mode: Include/Exclude Modus: Inkludieren/Exkludieren - + Filter mode (AND/OR/NOT conjunctions of filters) Filtermodus (UND/ODER/NICHT Verknüpfungen der Filter) @@ -8894,21 +9550,49 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter Filter speichern - + Save all currently applied filters to a file Alle derzeit aktiven Filter in einer Datei speichern - + Enter filename... Dateinamen eingeben... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8935,27 +9619,27 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDatabaseDisplayNameFilterWidget - - Filter by name... - Nach Namen filtern + + Filter by name... (Exact match) + - + Load from Deck Aus Deck laden - + Apply all card names in currently loaded deck as exact match name filters Alle Kartennamen im momentan geladenen Deck als Filter mit exakter Übereinstimmung anwenden - + Load from Clipboard Aus der Zwischenablage laden - + Apply all card names in clipboard as exact match name filters Alle Kartennamen aus der Zwischenablage als Filter mit exakter Übereinstimmung anwenden @@ -9019,50 +9703,115 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDatabaseDisplayWidget - + Search by card name (or search expressions) Suche nach Kartennamen (oder Suchausdrücken) - + + + Visual + + + + Loading database ... Datenbank wird geladen... - + Clear all filters Alle Filter entfernen - + + Sort by: + + + + + Filter by: + + + + Save and load filters Filter speichern und laden - + Filter by exact card name Nach exaktem Kartennamen filtern - + Filter by card sub-type Nach Kartenuntertyp filtern - + Filter by set Nach Set filtern + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand Eine neue Probehand ziehen - + Sample hand size Probehandgröße @@ -9070,46 +9819,25 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDeckEditorWidget - - Click and drag to change the sort order within the groups - Klicken und ziehen, um die Sortierungsreihenfolge innerhalb von Gruppen zu ändern + + Type a card name here for suggestions from the database... + - + Quick search and add card Schnellsuche und Karte hinzufügen - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter Nach bester Entsprechung in der Datenbank suchen (mit automatischen Vorschlägen) und die bevorzugte Auflage beim Betätigen der Eingabetaste dem Deck hinzufügen - - - Configure how cards are sorted within their groups - Wie Karten innerhalb ihrer Gruppierung sortiert werden einstellen - - - - - Overlap Layout - Überlappungsdarstellung - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - Wie Karten innerhalb von Zonen angezeigt werden ändern (überlappend oder vollständig sichtbar) - - - - Flat Layout - Flache Darstellung - VisualDeckStorageFolderDisplayWidget - + Deck Storage Deckablage @@ -9165,7 +9893,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDeckStorageSearchWidget - + Search by filename (or search expression) Suche nach Dateinamen (oder Suchausdruck) @@ -9173,22 +9901,22 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) Alphabetisch sortieren (Deckname) - + Sort Alphabetically (Filename) Alphabetisch sortieren (Dateiname) - + Sort by Last Modified Nach Änderungsdatum sortieren - + Sort by Last Loaded Nach letztem Laden sortieren @@ -9196,17 +9924,17 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDeckStorageWidget - + Loading database ... Datenbank wird geladen ... - + Refresh loaded files Geladene Dateien aktualisieren - + Visual Deck Storage Settings Einstellungen des Visuellen Deckeditors @@ -9214,43 +9942,43 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie WarningDialog - + Which warning would you like to send? Welche Warnung möchten Sie versenden? - + Redact all messages from this user in all rooms Entferne alle Nachrichten dieses Nutzers in allen Räumen - + &OK &OK - + &Cancel Abbre&chen - + Warn user for misconduct Nutzer aufgrund von Fehlverhalten warnen - - + + Error Fehler - + User name to send a warning to can not be blank, please specify a user to warn. Der zu warnende Nutzername kann nicht leer gelassen werden, bitte geben Sie einen Nutzernamen an. - + Warning to use can not be blank, please select a valid warning to send. Warnungsart kann nicht leer gelassen werden, bitte wählen Sie die zu sendende Warnungsart. @@ -9258,133 +9986,133 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie WndSets - + Move selected set to the top Ausgewählte Edition an die Spitze verschieben - + Move selected set up nach oben verschieben - + Move selected set down nach unten verschieben - + Move selected set to the bottom Ausgewählte Edition ans Ende verschieben - + Search by set name, code, or type Suche nach Setname, Code oder Typ - + Default order Standardreihenfolge - + Restore original art priority order Stelle die ursprüngliche Reihenfolge für Bilderprioritäten wieder her - + Enable all sets Alle Editionen aktivieren - + Disable all sets Alle Editionen deaktivieren - + Enable selected set(s) Ausgewählte Edition(en) aktivieren - + Disable selected set(s) Ausgewählte Edition(en) deaktivieren - + Deck Editor Deckeditor - + Use CTRL+A to select all sets in the view. Benutzen Sie Strg+A, um alle Sets in der Ansicht auszuwählen. - + Only cards in enabled sets will appear in the card list of the deck editor. Nur Karten aus aktivierten Sets werden in der Kartenliste des Deckeditors angezeigt. - + Image priority is decided in the following order: Bildpriorität wird auf folgende Reihenfolge festgelegt: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki zuerst der BENUTZERDEFINIERTE Ordner (%1), dann die aktivierten Sets aus diesem Dialog (von oben nach unten) - + Include cards rebalanced for Alchemy [requires restart] Karten, die für Alchemy augeglichen wurden, einschließen [erfordert Neustart] - + Card Art Kartenzeichnung - + How to use custom card art Wie benutzerdefinierte Kartenbilder verwendet werden - + Hints Tipps - + Note Notiz - + Sorting by column allows you to find a set while not changing set priority. Nach einer Spalte sortieren erlaubt, eine Edition zu finden, ohne die Editionspriorität zu verändern. - + To enable ordering again, click the column header until this message disappears. Um die Sortierung erneut zu aktivieren, klicken Sie auf den Spaltenkopf bis diese Nachricht verschwindet. - + Use the current sorting as the set priority instead Benutze die momentane Sortierung als Editionspriorität. - + Sorts the set priority using the same column Sortiert die Editionspriorität mit der selben Spalte - + Manage sets Editionen verwalten @@ -9392,72 +10120,72 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie ZoneViewWidget - + Search by card name (or search expressions) Suche nach Kartennamen (oder Suchausdrücken) - + Ungrouped Ungruppiert - + Group by Type Gruppieren nach Kartentyp - + Group by Mana Value Gruppieren nach Manabetrag - + Group by Color Gruppieren nach Farbe - + Unsorted Unsortiert - + Sort by Name Sortieren nach Name - + Sort by Type Sortieren nach Kartentyp - + Sort by Mana Cost Sortieren nach Manakosten - + Sort by Colors Sortieren nach Farben - + Sort by P/T Sortieren nach P/T - + Sort by Set Sortieren nach Auflage - + shuffle when closing beim Schließen mischen - + pile view Stapelansicht @@ -9465,7 +10193,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie i18n - + English Deutsch (German) @@ -9473,12 +10201,12 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie main - + Connect on startup Verbinde beim Start - + Debug to file Debugge in Datei @@ -9486,1005 +10214,1041 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie shortcutsTab - + Main Window Hauptfenster - - + + Deck Editor Deckeditor - + Game Lobby Spielelobby - + Card Counters Kartenmarken - + Player Counters Spielermarken - + Power and Toughness Stärke und Widerstandskraft - + Game Phases Spielphasen - + Playing Area Spielebereich - + Move Selected Card Ausgewählte Karte verschieben - + View Ansehen - + Move Top Card Oberste Karte verschieben - + Move Bottom Card Unterste Karte verschieben - + Gameplay Spielmechanik - + Drawing Ziehen - + Chat Room Chatraum - + Game Window Spielfenster - + Load Deck from Clipboard Deck aus der Zwischenablage laden - - + + Replays Wiederholungen - + Tabs Reiter - + Check for Card Updates... Nach Kartenaktualisierungen suchen... - + Connect... Verbinden... - + Disconnect Getrennt - + Exit Beenden - + Full screen Vollbild - + Register... Registrieren... - + Settings... Einstellungen... - + Start a Local Game... Lokales Spiel starten... - + Watch Replay... Aufgezeichnetes Spiel ansehen... - + Analyze Deck (deckstats.net) Deck analysieren (deckstats.net) - + Analyze Deck (tappedout.net) Deck analysieren (tappedout.net) - + Clear All Filters Alle Filter entfernen - + Clear Selected Filter Ausgewählten Filter entfernen - + Close Schließen - + Remove Card Karte entfernen - + Manage Sets... Editionen verwalten... - + Edit Custom Tokens... Benutzerdefinierte Spielsteine bearbeiten... - + Export Deck (decklist.org) Deck exportieren (decklist.org) - + Export Deck (decklist.xyz) Deck exportieren (decklist.xyz) - + Add Card Karte hinzufügen - + Load Deck... Deck laden... - - + + Load Deck from Clipboard... Deck aus der Zwischenablage laden... - + Edit Deck in Clipboard, Annotated Deck in Zwischenablage bearbeiten, kommentiert - + Edit Deck in Clipboard Deck in Zwischenablage bearbeiten - + New Deck Neues Deck - + Open Custom Pictures Folder Benutzerdefinierten Bilderordner öffnen - + Print Deck... Deck drucken... - + Delete Card Karte löschen - - + + Reset Layout Darstellung zurücksetzen - + Save Deck Deck speichern - + Save Deck as... Deck speichern unter... - + Save Deck to Clipboard, Annotated Deck kommentiert in die Zwischenablage speichern - + Save Deck to Clipboard, Annotated (No Set Info) Deck in die Zwischenablage speichern, kommentiert (keine Setinformationen) - + Save Deck to Clipboard Deck in die Zwischenablage speichern - + Save Deck to Clipboard (No Set Info) Deck in die Zwischenablage speichern (keine Setinformationen) - + Load Local Deck... Lokales Deck laden... - + Load Remote Deck... Deck vom Server laden... - + Set Ready to Start Bereit zum Start setzen - + Toggle Sideboard Lock Sideboardsperre umschalten - + Add Green Counter Grüne Marke hinzufügen - + Remove Green Counter Grüne Marke entfernen - + Set Green Counters... Grüne Marken setzen... - + Add Red Counter Rote Marke hinzufügen - + Remove Red Counter Rote Marke entfernen - + Set Red Counters... Rote Marken setzen... - + Add Life Counter Lebensmarke hinzufügen - + Show Status Bar Statuszeile anzeigen - + Unload Deck Deck entladen - + Force Start Zwangsstart - + Add Card Counter (F) Kartenmarke hinzufügen (F) - + Remove Card Counter (F) Kartenmarke entfernen (F) - + Set Card Counters (F)... Kartenmarke setzen (F)... - + Add Card Counter (E) Kartenmarke hinzufügen (E) - + Remove Card Counter (E) Kartenmarke entfernen (E) - + Set Card Counters (E)... Kartenmarke setzen (E)... - + Add Card Counter(D) Kartenmarke hinzufügen (D) - + Remove Card Counter (D) Kartenmarke entfernen (D) - + Set Card Counters (D)... Kartenmarke setzen (D)... - + Add Card Counter (C) Kartenmarke hinzufügen (C) - + Remove Card Counter (C) Kartenmarke entfernen (C) - + Set Card Counters (C)... Kartenmarke setzen (C)... - + Add Card Counter (B) Kartenmarke hinzufügen (B) - + Remove Card Counter (B) Kartenmarke entfernen (B) - + Set Card Counters (B)... Kartenmarke setzen (B)... - + Add Card Counter (A) Kartenmarke hinzufügen (A) - + Remove Card Counter (A) Kartenmarke entfernen (A) - + Set Card Counters (A)... Kartenmarke setzen (A)... - + Remove Life Counter Lebensmarke entfernen - + Set Life Counters... Lebensmarken setzen... - + Add White Counter Weiße Marke hinzufügen - + Remove White Counter Weiße Marke entfernen - + Set White Counters... Weiße Marken setzen... - + Add Blue Counter Blaue Marke hinzufügen - + Remove Blue Counter Blaue Marke entfernen - + Set Blue Counters... Blaue Marken setzen... - + Add Black Counter Schwarze Marke hinzufügen - + Remove Black Counter Schwarze Marke entfernen - + Set Black Counters... Schwarze Marken setzen... - + Add Colorless Counter Farblose Marke hinzufügen - + Remove Colorless Counter Farblose Marke entfernen - + Set Colorless Counters... Farblose Marken setzen... - + Add Other Counter Andere Marke hinzufügen - + Remove Other Counter Andere Marke entfernen - + Set Other Counters... Andere Marke setzen... - + Increment all card counters Alle Kartenzähler erhöhen - + Add Power (+1/+0) Stärke hinzufügen (+1/+0) - + Remove Power (-1/-0) Stärke entfernen (-1/-0) - + Move Toughness to Power (+1/-1) Widerstandskraft nach Stärke verschieben (+1/-1) - + Add Toughness (+0/+1) Widerstandskraft hinzufügen (+0/+1) - + Remove Toughness (-0/-1) Widerstandskraft entfernen (-0/-1) - + Move Power to Toughness (-1/+1) Stärke nach Widerstandskraft verschieben (-1/+1) - + Add Power and Toughness (+1/+1) Stärke und Widerstandskraft hinzufügen (+1/+1) - + Remove Power and Toughness (-1/-1) Stärke und Widerstandskraft entfernen (-1/-1) - + Set Power and Toughness... Stärke und Widerstandskraft setzen... - + Reset Power and Toughness Stärke und Widerstandskraft zurücksetzen - + Untap Enttappen - + Upkeep Versorgung - + Draw Ziehen - + First Main Phase Erste Hauptphase - + Start Combat Beginn des Kampfes - + Attack Angriff - + Block Blocken - + Damage Schaden - + End Combat Ende des Kampfes - + Second Main Phase Zweite Hauptphase - + End Endsegment - + Next Phase Nächste Phase - + Next Phase Action Nächste Phasenaktion - + Next Turn Nächster Zug - + Hide Card in Reveal Window Karte im Enthüllungsfenster verstecken - + Tap / Untap Card Tappe / Enttappe Karte - + Untap All Alle enttappen - + Toggle Untap Enttappen umschalten - + Turn Card Over Karte herumdrehen - + Peek Card Karte ansehen - + Play Card Karte spielen - + Attach Card... Karte anlegen... - + Unattach Card Karte lösen - + Clone Card Karte klonen - + Create Token... Spielstein erstellen... - + Create All Related Tokens Alle zugehörigen Spielsteine erstellen - + Create Another Token Einen weiter Spielstein erstellen - + Set Annotation... Notiz setzen... - + Select All Cards in Zone Alle Karten in der Zone auswählen - + Select All Cards in Row Alle Karten in der Reihe auswählen - + Select All Cards in Column Alle Karten in der Spalte auswählen - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library Untere Karte der Bibliothek - - - - + + + + Exile Exil - - - - + + + + Graveyard Friedhof - - + + + Hand Hand - - + + Top of Library Obere Karte der Bibliothek - - - + + + Battlefield, Face Down Spielfeld, verdeckt - + Battlefield Spielfeld - - Sort Hand - Hand sortieren - - - + Library Bibliothek - + Sideboard Sideboard - + Top Cards of Library Oberste Karten der Bibliothek - + Bottom Cards of Library Unterste Karten der Bibliothek - + Close Recent View Schließe letzte Ansicht - - + + Stack Stapel - - + + Graveyard (Multiple) Friedhof (mehrere) - - + + Exile (Multiple) Exil (mehrere) - + Stack Until Found Stapel bis gefunden - + Draw Bottom Card Unterste Karte ziehen - + Draw Multiple Cards from Bottom... Mehrere Karten von unten ziehen... - + Draw Arrow... Pfeil zeichnen... - + Remove Local Arrows Lokale Pfeile entfernen - + Leave Game Spiel verlassen - + Concede Aufgeben - + Roll Dice... Würfeln... - + Shuffle Library Bibliothek mischen - + Shuffle Top Cards of Library Oberste Karten der Bibliothek mischen - + Shuffle Bottom Cards of Library Unterste Karten der Bibliothek mischen - + Mulligan Mulligan - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card Eine Karte ziehen - + Draw Multiple Cards... Mehrere Karten ziehen... - + Undo Draw Ziehen rückgängig machen - + Always Reveal Top Card Oberste Karte aufgedeckt lassen - + Always Look At Top Card Oberste Karte immer betrachten - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise Ansicht im Uhrzeigersinn drehen - + Rotate View Counterclockwise Ansicht gegen den Uhrzeigersinn drehen - + Unfocus Text Box Textboxfokus aufheben - + Focus Chat Chat fokussieren - + Clear Chat Chat leeren - + Refresh Aktualisieren - + Skip Forward Vorspulen - + Skip Backward Zurückspulen - + Skip Forward by a lot Sehr weit vorspulen - + Skip Backward by a lot Sehr weit zurückspulen - + Play/Pause Abspielen/Pausieren - + Toggle Fast Forward Schnelldurchlauf umschalten - + Home Zuhause - + Visual Deck Storage Visuelle Deckablage - + Deck Storage Deckablage - + Server Server - + Account Benutzerkonto - + Administration Administration - + Logs Logs diff --git a/cockatrice/translations/cockatrice_el.ts b/cockatrice/translations/cockatrice_el.ts index 8755f6502..dac77fe01 100644 --- a/cockatrice/translations/cockatrice_el.ts +++ b/cockatrice/translations/cockatrice_el.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Ρύθμιση μετρητή... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Ρύθμιση μετρητή - + New value for counter '%1': Νέα τιμή για τον μετρητή '%1' @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,79 +36,81 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. + + Add Analytics Panel AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -116,12 +118,12 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard - + Sideboard @@ -129,22 +131,22 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error Σφάλμα - + Could not create themes directory at '%1'. - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -155,7 +157,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -166,152 +168,165 @@ Are you sure you would like to disable this feature? - + Confirm Change - + Theme settings Ρυθμίσεις θέματος - + Current theme: Τρέχον θέμα: - + Open themes folder Άνοιγμα φακέλου θεμάτων - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + + Show game filter toolbar above list in room tab + + + + Card rendering Απόδοση κάρτας - + Display card names on cards having a picture Εμφανίστε τα ονόματα καρτών σε κάρτες με εικόνα - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Αύξηση της κλίμακας των καρτών με την επιλογή του ποντικιού - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Διάταξη χειρός - + Display hand horizontally (wastes space) Εμφάνιση χεριού οριζόντια (σπατάλη χώρου) - + Enable left justification Ενεργοποιήστε την αριστερή αιτιολόγηση - + Table grid layout Διάταξη πλέγματος πίνακα - + Invert vertical coordinate Ανατροπή κάθετης συντεταγμένης - + Minimum player count for multi-column layout: Ελάχιστος αριθμός παικτών για διάταξη πολλαπλών στηλών: - + Maximum font size for information displayed on cards: Μέγιστο μέγεθος γραμματοσειράς για πληροφορίες που εμφανίζονται στις κάρτες: + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -333,112 +348,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name απαγόρευση και όνομα χρήστη - + ban &IP address απαγόρευση & διεύθυνση IP - + ban client I&D απαγόρευση εξυπηρετητή I&D - + Ban type Τύπος απαγόρευσης - + &permanent ban &μόνιμη απαγόρευση - + &temporary ban &προσωρινή απαγόρευση - + &Days: &Μέρες: - + &Hours: &Ώρες: - + &Minutes: &Λεπτά: - + Duration of the ban Διάρκεια της απαγόρευσης - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Παρακαλώ καταχωρίστε τον λόγο της απαγόρευσης. Αυτό αποθηκεύεται μόνο για τους επόπτες και δεν μπορεί να το δει το αποκλεισμένο άτομο. - + Please enter the reason for the ban that will be visible to the banned person. Παρακαλώ καταχωρίστε τον λόγο της απαγόρευσης που θα είναι ορατή στο αποκλεισμένο άτομο. - + Redact all messages from this user in all rooms - + &OK &Εντάξει - + &Cancel &Άκυρο - + Ban user from server Απαγόρευση χρήστη από τον διακομιστή - + + - - + Error Σφάλμα - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Για την απαγόρευση πρέπει να επιλέξετε ένα όνομα, μία IP, ένα clientId, ή κάποιο συνδυασμό των τριών. - + You must have a value in the name ban when selecting the name ban checkbox. Πρέπει να έχετε μια τιμή στην απαγόρευση ονόματος όταν επιλέγετε το πλαίσιο ελέγχου απαγόρευσης ονόματος. - + You must have a value in the ip ban when selecting the ip ban checkbox. Πρέπει να έχετε μια τιμή στην απαγόρευση ip όταν επιλέγετε το πλαίσιο ελέγχου απαγόρευσης ip. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Πρέπει να έχετε μια τιμή στην απαγόρευση clientid όταν επιλέγετε το πλαίσιο ελέγχου απαγόρευσης clientid. @@ -469,32 +484,32 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name Όνομα - + Sets Σετς - + Mana cost Το κόστος του Μάνα - + Card type Είδος κάρτας - + P/T P/T - + Color(s) Χρώμα(τα) @@ -502,96 +517,101 @@ This is only saved for moderators and cannot be seen by the banned person. CardFilter - + AND Logical conjunction operator used in card filter - + OR Logical disjunction operator used in card filter - + AND NOT Negated logical conjunction operator used in card filter - + OR NOT Negated logical disjunction operator used in card filter - + Name Όνομα - + + Name (Exact) + + + + Type Τύπος - + Color Χρώμα - + Text Κείμενο - + Set - + Mana Cost - + Mana Value - + Rarity Σπανιότητα - + Power Δύναμη - + Toughness Σκληρότητα - + Loyalty - + Format - + Main Type - + Sub Type @@ -599,22 +619,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoFrameWidget - + Image - + Description - + Both - + View transformation @@ -622,22 +642,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -650,12 +670,22 @@ This is only saved for moderators and cannot be seen by the banned person. - + + Set: + + + + + Collector Number: + + + + Related cards: - + Unknown card: @@ -663,124 +693,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -788,7 +818,7 @@ This is only saved for moderators and cannot be seen by the banned person. CardSizeWidget - + Card Size @@ -796,133 +826,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative - + %1's hand nominative - - - their library - look at zone - - - - - %1's library - look at zone - - - - - of their library - top cards of zone, - - - - - of %1's library - top cards of zone - - their library - reveal zone + look at zone %1's library + look at zone + + + + + of their library + top cards of zone, + + + + + of %1's library + top cards of zone + + + + + their library reveal zone - + + %1's library + reveal zone + + + + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - - - their graveyard - nominative - - - - - %1's graveyard - nominative - - - - - their exile - nominative - - - - - %1's exile - nominative - - + their graveyard + nominative + + + + + %1's graveyard + nominative + + + + + their exile + nominative + + + + + %1's exile + nominative + + + + their sideboard look at zone - + %1's sideboard look at zone - - - their sideboard - nominative - - - - - %1's sideboard - nominative - - - their custom zone '%1' + their sideboard nominative + %1's sideboard + nominative + + + + + their custom zone '%1' + nominative + + + + %1's custom zone '%2' nominative @@ -931,7 +961,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml3Parser - + Parse error at line %1 col %2: @@ -939,7 +969,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml4Parser - + Parse error at line %1 col %2: @@ -958,6 +988,37 @@ This is only saved for moderators and cannot be seen by the banned person. + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -969,47 +1030,47 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add to Deck - + Add to Sideboard - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1017,87 +1078,97 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing - + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + + Format: + + + + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1105,17 +1176,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1123,114 +1194,114 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1238,7 +1309,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1246,194 +1317,227 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers - - + + Success Επιτυχία - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error Σφάλμα - + One or more downloaded card pictures could not be cleared. - + Add URL - - + + URL: - - + + Edit URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL - + Remove URL - + Day(s) - + Updating... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count - + Set - + Number Αριθμός - + Provider ID - + Card Κάρτα @@ -1441,12 +1545,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1454,17 +1558,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1472,7 +1576,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -1480,62 +1584,62 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -1548,93 +1652,151 @@ This is only saved for moderators and cannot be seen by the banned person. - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1652,74 +1814,74 @@ This is only saved for moderators and cannot be seen by the banned person. DeckViewContainer - + Load deck... Φόρτωση deck... - + Load remote deck... Φόρτωση απομακρυσμένου deck... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start - + Force start - + Sideboard unlocked - + Sideboard locked - - + + Error Σφάλμα - + The selected file could not be loaded. Δεν ήταν δυνατή η φόρτωση του επιλεγμένου αρχείου. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -1752,143 +1914,143 @@ This will kick all non-ready players from the game. - + Known Hosts Γνωστοί Hosts - + Delete the currently selected saved server - + Refresh the server list with known public servers - + New Host Νέος Host - + Name: Όνομα: - + &Host: &Host: - + &Port: &Θύρα: - + Player &name: Όνομα &παίχτη: - + P&assword: Κωδικός: - + &Save password &Αποθήκευση Κωδικού - + A&uto connect Α&υτόματη σύνδεση - + Automatically connect to the most recent login when Cockatrice opens Συνδεθείτε αυτόματα στην πιο πρόσφατη σύνδεση όταν ανοίγει το Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + Forgot password? - + &Connect - + Server Διακομιστής - + Login Σύνδεση - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning Προειδοποίηση σύνδεσης - + You need to name your new connection profile. Πρέπει να ονομάσετε το νέο προφίλ σύνδεσης. - + Connect Warning Προειδοποίηση σύνδεσης - + The player name can't be empty. Το όνομα παίκτη δεν μπορεί να είναι άδειο. @@ -1896,117 +2058,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings Θυ&μηθείτε τις ρυθμίσεις - + &Description: &Περιγραφή: - + P&layers: Π&αίχτες: - + General - + Game type Είδος παιχνιδιού - + &Password: &Κωδικός: - + Only &buddies can join Μόνο &φίλοι μπορούν να συμμετάσχουν - + Only &registered users can join Μόνο &εγγεγραμμένοι χρήστες μπορούν να συμμετάσχουν - + Joining restrictions Περιορισμοί συμμετοχής - + &Spectators can watch Οι &θεατές μπορούν να παρακολουθήσουν - + Spectators &need a password to watch Οι θεατές &χρειάζονται κωδικό για να παρακολουθήσουν - + Spectators can &chat Οι θεατές μπορούν να &συζητήσουν - + Spectators can see &hands Οι θεατές μπορούν να δουν τα &χέρια - + Create game as spectator - + Spectators Θεατές - + Starting life total: - + Open decklists in lobby - + + Create game as judge + + + + Game setup options - + &Clear &Καθάρισε - + Create game Δημιουργία παιχνιδιού - + Game information Πληροφορίες παιχνιδιού - + Error Σφάλμα - + Server error. Σφάλμα Διακομιστή. @@ -2014,97 +2181,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Όνομα: - + Token Token - + C&olor: Χ&ρώμα: - + white άσπρο - + blue μπλε - + black μαύρο - + red κόκκινο - + green πράσινο - + multicolor πολύχρωμο - + colorless άχρωμο - + &P/T: &P/T: - + &Annotation: &Σχόλιο: - + &Destroy token when it leaves the table &κατέστρεψε το token όταν φύγει από το τραπέζι - + Create face-down (Only hides name) - + Token data Στοιχεία token - + Show &all tokens Εμφάνισε &όλα τα tokens - + Show tokens from this &deck Εμφάνιση tokens από αυτό το &deck - + Choose token from list Επέλεξε token από τη λίστα - + Create token Δημιουργία token @@ -2112,53 +2279,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2166,40 +2333,40 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. Δεν έχει επιλεγεί εικόνα. - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. Για να αλλάξετε το avatar σας, επιλέξτε μια νέα εικόνα Για να καταργήσετε το τρέχον avatar, επιβεβαιώστε χωρίς να επιλέξετε μια νέα εικόνα. - + Browse... Εξέτασε... - + Change avatar Αλλαγή avatar - + Open Image Άνοιγμα εικόνας - + Image Files (*.png *.jpg *.bmp) Αρχεία εικόνας (*.png *.jpg *.bmp) - + Invalid image chosen. Επιλέχθηκε μη έγκυρη εικόνα. @@ -2207,17 +2374,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2225,38 +2392,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: Παλιός κωδικός: - + New password: Νέος κωδικός: - + Confirm new password: Επιβεβαιώστε τον καινούριο σας κωδικό: - + Change password Αλλαγή κωδικού - - + + Error Σφάλμα - + Your password is too short. Ο κωδικός σας είναι πολύ μικρός. - + The new passwords don't match. Οι νέοι κωδικοί πρόσβασης δεν ταιριάζουν. @@ -2264,93 +2431,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: &Όνομα: - + C&olor: Χ&ρώμα: - + white άσπρο - + blue μπλε - + black μαύρο - + red κόκκινο - + green πράσινο - + multicolor πολύχρωμο - + colorless άχρωμο - + &P/T: &P/T: - + &Annotation: &Σχόλιο: - + Token data Στοιχεία token - - + + Add token Προσθήκη token - + Remove token Αφαίρεση token - + Edit custom tokens - + Please enter the name of the token: Παρακαλώ εισάγετε το όνομα του token: - + Error Σφάλμα - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. @@ -2443,7 +2610,8 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games not created by buddy + Hide games not created by buddies + Hide games not created by buddy @@ -2530,52 +2698,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: &Host: - + &Port: &Θύρα: - + Player &name: Όνομα &παίχτη: - + Email: Email: - + Reset Password Challenge - + Reset Password Challenge Error - + The email address can't be empty. Η διεύθυνση e-mail δεν μπορεί να είναι άδεια @@ -2583,37 +2751,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: &Host: - + &Port: &Θύρα: - + Player &name: Όνομα &παίχτη: - + Reset Password Request - + Reset Password Error - + The player name can't be empty. Το όνομα παίκτη δεν μπορεί να είναι άδειο. @@ -2621,86 +2789,86 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: &Host: - + &Port: &Θύρα: - + Player &name: Όνομα &παίχτη: - + Token: Token: - - + + New Password: Νέος κωδικός: - + Reset Password - + The player name can't be empty. Το όνομα παίκτη δεν μπορεί να είναι άδειο. - - - - + + + + Reset Password Error - + The token can't be empty. - + The new password can't be empty. - + Error Σφάλμα - + Your password is too short. - + The passwords do not match. Οι κωδικοί πρόσβασης δεν ταιριάζουν. @@ -2716,17 +2884,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard Φόρτωση deck από το clipboard - + Error Σφάλμα - + Invalid deck list. Μη έγκυρο deck list @@ -2734,43 +2902,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2784,7 +2952,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck Φόρτωση deck @@ -2792,37 +2960,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -2830,91 +2998,91 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: &Host: - + &Port: &Θύρα: - + Player &name: Όνομα &παίχτη: - + P&assword: - + Password (again): Κωδικός (ξανά): - + Email: Email: - + Email (again): Διεύθυνση e-mail (ξανά): - + Country: Χώρα: - + Undefined Απροσδιόριστο - + Real name: Πραγματικό όνομα: - + Register to server Εγγραφή σε διακομιστή - - - - + + + + Registration Warning Προειδοποίηση Εγγραφής - + Your password is too short. Ο κωδικός σας είναι πολύ μικρός. - + Your passwords do not match, please try again. Οι κωδικοί δεν ταιριάζουν, παρακαλώ προσπαθήστε ξανά - + Your email addresses do not match, please try again. - + The player name can't be empty. Το όνομα παίκτη δεν μπορεί να είναι άδειο. @@ -2940,40 +3108,55 @@ Your email will be used to verify your account. DlgSelectSetForCards - + Unmodified Cards: - + Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -2984,7 +3167,7 @@ Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -3001,7 +3184,7 @@ Would you like to change your database location setting? Θα θέλατε να αλλάξετε τη ρυθμισμένη θέση της βάσης δεδομένων σας; - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3010,21 +3193,21 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3033,59 +3216,59 @@ Would you like to change your database location setting? - - - + + + Error Σφάλμα - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings Ρυθμίσεις - + General Γενικά - + Appearance Εμφάνιση - + User Interface Διεπαφή Χρήστη - + Card Sources - + Chat - + Sound Ήχος - + Shortcuts Συντομεύσεις @@ -3093,39 +3276,39 @@ Would you like to change your database location setting? DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3133,17 +3316,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next - + Previous - + Tip of the Day @@ -3151,164 +3334,164 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel Τρέχον κανάλι έκδοσης - + Reinstall Επανεγκατάσταση - + Cancel Download Ακύρωση Κατεβάσματος - + Open Download Page Άνοιγμα Σελίδας Κατεβάσματος - + Check for Client Updates - - - - + + + + Error Σφάλμα - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - + Downloading update: %1 - + Checking for updates... - + Finished checking for updates Ο έλεγχος για ενημερώσεις ολοκληρώθηκε - + No Update Available Καμία Διαθέσιμη Ενημέρωση - + Cockatrice is up to date! Το Cockatrice είναι πλήρως ενημερωμένο! - + You are already running the latest version available in the chosen release channel. Ήδη τρέχετε την πιό πρόσφατη έκδοση διαθέσιμη σε αυτό το κανάλι. - + Current version Τρέχουσα έκδοση - + Selected release channel Επιλεγμένο κανάλι έκδοσης - - + + Update Available Υπάρχει Διαθέσιμη Ενημέρωση - - + + A new version of Cockatrice is available! Μία νέα έκδοση του Cockatrice είναι διαθέσιμη! - - + + New version Νέα έκδοση - - + + Released - - + + Changelog Αρχείο Αλλαγών - + Do you want to update now? Θέλετε να ενημερώσετε τώρα; - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + + Update Error - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -3316,21 +3499,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing - + Copy to clipboard - + Debug Log + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3400,33 +3715,39 @@ You may have to manually download the new version. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3442,22 +3763,22 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. @@ -3465,22 +3786,22 @@ You may have to manually download the new version. GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -3488,226 +3809,279 @@ You may have to manually download the new version. GameSelector - - - - - - + + + + + + Error Σφάλμα - + Please join the appropriate room first. - + Wrong password. Λάθος κωδικός. - + Spectators are not allowed in this game. - + The game is already full. - + The game does not exist any more. - + This game is only open to registered users. - + This game is only open to its creator's buddies. - + You are being ignored by the creator of this game. - + Join Game - + Spectate Game - + Game Information - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game - + Password: Κωδικός: - + Please join the respective room first. - + &Filter games - + C&lear filter Καθάρισμα Φίλτρου - + C&reate Δημιουργία - + &Join Είσοδος - + + Join as judge + + + + J&oin as spectator Είσοδος ως θεατής - + + Join as judge spectator + + + + Games shown: %1 / %2 - + Games + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password κωδικός - + buddies only μόνο φίλοι - + reg. users only μόνο εγγεγραμμένοι χρήστες - + open decklists - - + + can chat μπορούν να συνομιλήσουν - + see hands βλέπουν τα χέρια - + can see hands μπορούν να δούν τα χέρια - + not allowed δεν επιτρέπεται - + Room Δωμάτιο - + Age Ηλικία - + Description Περιγραφή - + Creator Δημιουργός - + Type Τύπος - + Restrictions Περιορισμοί - + Players Παίκτες - + Spectators Θεατές @@ -3715,143 +4089,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path Επιλέξτε θέση - + Personal settings - + Language: Γλώσσα: - + Paths (editing disabled in portable mode) - + Paths - + How to help with translations - + Decks directory: Φάκελος deck: - + Filters directory: - + Replays directory: Φάκελος replay: - + Pictures directory: Φάκελος εικόνων: - + Card database: Βάση δεδομένων καρτών - + Custom database directory: - + Token database: Βάση δεδομένων token: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup - + Last update check on %1 (%2 days ago) @@ -3859,47 +4233,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -3907,111 +4281,141 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - - &Sort hand + + Sort hand by... - - Take &mulligan + + Name - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - Reveal r&andom card to... + + + All players - - - &All players + + Reveal r&andom card to... HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + + Browse Archidekt + + + + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4213,743 +4617,743 @@ You may have to manually download the new version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Number of players - + Please enter the number of players. - - + + Player %1 - + Load replay - + About Cockatrice - + Version Έκδοση - + Cockatrice Webpage - + Project Manager: - + Past Project Managers: - + Developers: - + Our Developers - + Help Develop! - + Translators: - + Our Translators Οι μεταφραστές μας - + Help Translate! - + Support: Υποστήριξη: - + Report an Issue Αναφορά Προβλήματος - + Troubleshooting Αντιμετώπιση Προβλημάτων - + F.A.Q. Συχνές Ερωτήσεις - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error Σφάλμα - + Server timeout - + Failed Login Αποτυχημένη Σύνδεση - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. Είστε αποκλεισμένος μέχρι %1. - - + + You are banned indefinitely. Είστε αποκλεισμένος επ' αόριστον. - + This server requires user registration. Do you want to register now? Ο σέρβερ απαιτεί εγγραφή. Θέλετε να εγγραφείτε τώρα; - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation Ενεργοποίηση λογαριασμού - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full Σέρβερ Πλήρης - + Unknown login error: %1 Άγνωστο σφάλμα σύνδεσης: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - + + + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. Ο κωδικός είναι πολύ μικρός. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 + - Requesting forgotten password to %1 as %2... - + &Connect... Σύνδεση... - + &Disconnect Αποσύνδεση - + Start &local game... Έναρξη τοπικού παιχνιδιού... - + &Watch replay... - + &Full screen Πλήρης οθόνη - + &Register to server... Εγγραφή σε σέρβερ... - + &Restore password... - + &Settings... Ρυθμίσεις... - + &Exit Έξοδος - + A&ctions - + &Cockatrice &Cockatrice - + C&ard Database Βάση δεδομένων καρτών - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + Reload card database - + Tabs - + &Help Βοήθεια - + &About Cockatrice Σχετικά με το Cockatrice - + &Tip of the Day - + Check for Client Updates Έλεγχος για ενημερώσεις - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database Βάση δεδομένων καρτών - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes Ναι - - + + No Όχι - + Open settings Άνοιγμα ρυθμίσεων - + New sets found Βρέθηκαν νέα σετ - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets Εμφάνιση σετ - + Welcome Καλωσήρθατε - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information Πληροφορίες - + A card database update is already running. - + Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -4957,672 +5361,842 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards Φόρτωση σετ/καρτών - + Selected file cannot be found. Το επιλεγμένο αρχείο δε βρέθηκε. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play από το παιχνίδι - + from their graveyard - + from exile από την εξορία - + from their hand - + the top card of %1's library την πρώτη κάρτα της βιβλιοθήκης του/της %1 - + the top card of their library - + from the top of %1's library από την κορυφή της βιβλιοθήκης του/της %1 - + from the top of their library - + the bottom card of %1's library την τελευταία κάρτα της βιβλιοθήκης του/της %1 - + the bottom card of their library - + from the bottom of %1's library από το τέλος της βιβλιοθήκης του/της %1 - + from the bottom of their library - + from %1's library από τη βιβλιοθήκη του/της %1 - + from their library - + from sideboard από το sideboard - + from the stack από το stack - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). Ο/Η %1 φόρτωσε ένα deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). Ο/Η %1 φόρτωσε ένα deck με %2 κάτρες στο sideboard (%3). - + %1 destroys %2. - + a card μία κάρτα - + %1 gives %2 control over %3. ο/η %1 δίνει στον/στην %2 τον έλεγχο του %3 . - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. ο/η %1 βάζει στο παιχνίδι το %2%3. - + %1 puts %2%3 into their graveyard. - + %1 exiles %2%3. ο/η %1 εξορίζει το %2%3. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. - + %1 plays %2%3. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. Το παιχνίδι έχει κλείσει - + The game has started. Το παιχνίδι ξεκίνησε. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. - + %1 is now watching the game. Ο/Η %1 παρακολουθεί το παιχνίδι. - + You have been kicked out of the game. Σας απέβαλαν από το παιχνίδι. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. - + %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -5630,110 +6204,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message - - + + Message: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -5850,62 +6424,62 @@ Cockatrice will now reload the card database. Phase - + Unknown Phase - + Untap - + Upkeep - + Draw - + First Main - + Beginning of Combat - + Declare Attackers - + Declare Blockers - + Combat Damage - + End of Combat - + Second Main - + End/Cleanup @@ -5971,7 +6545,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -5980,134 +6554,134 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - + Move top cards to grave - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6115,17 +6689,17 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters - + S&ay @@ -6133,7 +6707,7 @@ Cockatrice will now reload the card database. PrintingSelector - + Display Navigation Buttons @@ -6141,22 +6715,22 @@ Cockatrice will now reload the card database. PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6172,17 +6746,17 @@ Cockatrice will now reload the card database. PrintingSelectorCardSelectionWidget - + Previous Card in Deck - + Bulk Selection - + Next Card in Deck @@ -6190,28 +6764,28 @@ Cockatrice will now reload the card database. PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -6277,37 +6851,37 @@ Cockatrice will now reload the card database. QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -6315,42 +6889,42 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - + All files (*.*) - + Cockatrice replays (*.cor) - + Maindeck - + Sideboard - + Tokens - + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? @@ -6358,92 +6932,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -6540,37 +7114,37 @@ Cockatrice will now reload the card database. RoomSelector - + Rooms - + Joi&n - + Room - + Description - + Permissions - + Players - + Games @@ -6611,27 +7185,27 @@ Cockatrice will now reload the card database. SetsModel - + Enabled - + Set type - + Set code - + Long name - + Release date @@ -6639,53 +7213,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -6693,12 +7267,12 @@ Cockatrice will now reload the card database. ShortcutTreeView - + Action - + Shortcut @@ -6706,13 +7280,13 @@ Cockatrice will now reload the card database. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -6739,12 +7313,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -6752,27 +7326,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -6780,48 +7354,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error Σφάλμα - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -6985,56 +7559,123 @@ Please check your shortcut settings! + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info - + Deck - + Filters - + &View - + + Card Database + + + + Printing - - - - + + + + + Visible - - - - + + + + + Floating - + Reset layout - + Deck: %1 @@ -7042,61 +7683,61 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + + + + Visible - - - - + + + + Floating - + Reset layout @@ -7104,22 +7745,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7127,133 +7768,133 @@ Please check your shortcut settings! TabDeckStorage - + Local file system - + Server deck storage - - + + Open in deck editor - + Rename deck or folder - + Upload deck - + Download deck + - - - + + New folder + - Delete - + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + Error Σφάλμα - + Rename failed - - + + Invalid deck file - + Enter deck name - + This decklist does not have a name. Please enter a name: - + Unnamed deck - + Failed to upload deck to server - + Delete local file - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + Name of new folder: @@ -7266,17 +7907,17 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 @@ -7317,7 +7958,7 @@ Please enter a name: - + EDHRec: @@ -7325,197 +7966,197 @@ Please enter a name: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + Un&concede - - - + + + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + Selected cards - + &View - - + + + - Visible - - + + + - Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game - + Are you sure you want to leave this game? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. @@ -7523,7 +8164,7 @@ Please enter a name: TabHome - + Home @@ -7531,157 +8172,157 @@ Please enter a name: TabLog - + Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error Σφάλμα - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -7727,180 +8368,180 @@ The more information you put in, the more specific your results will be. TabReplays - + Local file system - + Server replay storage - - + + Watch replay - + Rename - - + + New folder - - + + Delete - + Open replays folder - + Download replay - + Toggle expiration lock - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay @@ -7966,30 +8607,30 @@ The more information you put in, the more specific your results will be. + - Error Σφάλμα - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -7997,92 +8638,97 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - - - Home - - - - - &Visual Deck Storage - - - - - Visual Database Display - - - Server + Archidekt - Account + Home - Deck Storage + &Visual Deck Storage - Game Replays + Visual Database Display - Administration + Server + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + Logs - + Are you sure? - + There are still open games. Are you sure you want to quit? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8090,38 +8736,38 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -8130,7 +8776,12 @@ Please refrain from engaging in this activity or further actions may be taken ag TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display @@ -8210,7 +8861,7 @@ Please refrain from engaging in this activity or further actions may be taken ag UpdateDownloader - + Could not open the file for reading. @@ -8218,206 +8869,206 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details - + Private &chat - + Show this user's &games - + Add to &buddy list - + Remove from &buddy list - + Add to &ignore list - + Remove from &ignore list - + Kick from &game - + Warn user - + View user's war&n history - + Ban from &server - + View user's &ban history - + &Promote user to moderator - + Dem&ote user from moderator - + Promote user to &judge - + Demote user from judge - + View admin notes - - - + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - + Failed to get admin notes. - - + + Success - + Successfully promoted user. - + Successfully demoted user. - + + - Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -8599,137 +9250,142 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default - - - Close card view window when last card is removed - - - - - Auto focus search bar when card view window is opened - - - - - Annotate card text on tokens - - - - - Use tear-off menus, allowing right click menus to persist on screen - - - - - Notifications settings - - - - - Enable notifications in taskbar - - - - - Notify in the taskbar for game events while you are spectating - - - - - Notify in the taskbar when users in your buddy list connect - - - - - Animation settings - - - - - &Tap/untap animation - - - Deck editor/storage settings + Do not delete &arrows inside of subphases - Open deck in new tab by default + Close card view window when last card is removed - Use visual deck storage in game lobby + Auto focus search bar when card view window is opened - Use selection animation for Visual Deck Storage + Annotate card text on tokens + + + + + Use tear-off menus, allowing right click menus to persist on screen - When adding a tag in the visual deck storage to a .txt deck: + Notifications settings + + + + + Enable notifications in taskbar - do nothing + Notify in the taskbar for game events while you are spectating + + + + + Notify in the taskbar when users in your buddy list connect - ask to convert to .cod + Animation settings + + + + + &Tap/untap animation - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -8737,22 +9393,22 @@ Please refrain from engaging in this activity or further actions may be taken ag UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 @@ -8760,32 +9416,32 @@ Please refrain from engaging in this activity or further actions may be taken ag UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -8793,22 +9449,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match - + Mode: Includes - + Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -8816,21 +9472,49 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8856,28 +9540,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - Load from Deck + Filter by name... (Exact match) - Apply all card names in currently loaded deck as exact match name filters + Load from Deck - Load from Clipboard + Apply all card names in currently loaded deck as exact match name filters + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -8941,50 +9625,115 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - + + Sort by: + + + + + Filter by: + + + + Save and load filters - + Filter by exact card name - + Filter by card sub-type - + Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -8992,46 +9741,25 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -9087,7 +9815,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9095,22 +9823,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -9118,17 +9846,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + Visual Deck Storage Settings @@ -9136,43 +9864,43 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Warn user for misconduct - - + + Error Σφάλμα - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. @@ -9180,133 +9908,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -9314,72 +10042,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing - + pile view @@ -9387,7 +10115,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English Ελληνικά (Greek) @@ -9395,12 +10123,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -9408,1005 +10136,1041 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window - - + + Deck Editor - + Game Lobby - + Card Counters - + Player Counters - + Power and Toughness - + Game Phases - + Playing Area - + Move Selected Card - + View - + Move Top Card - + Move Bottom Card - + Gameplay - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - - + + Replays - + Tabs - + Check for Card Updates... - + Connect... - + Disconnect - + Exit - + Full screen - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters - + Clear Selected Filter - + Close - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card - + Load Deck... - - + + Load Deck from Clipboard... - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck - + Open Custom Pictures Folder - + Print Deck... - + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... - + Load Remote Deck... - + Set Ready to Start - + Toggle Sideboard Lock - + Add Green Counter - + Remove Green Counter - + Set Green Counters... - + Add Red Counter - + Remove Red Counter - + Set Red Counters... - + Add Life Counter - + Show Status Bar - + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Increment all card counters - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap - + Upkeep - + Draw - + First Main Phase - + Start Combat - + Attack - + Block - + Damage - + End Combat - + Second Main Phase - + End - + Next Phase - + Next Phase Action - + Next Turn - + Hide Card in Reveal Window - + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library - - - - + + + + Exile - - - - + + + + Graveyard - - + + + Hand - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - - Sort Hand - - - - + Library - + Sideboard - + Top Cards of Library - + Bottom Cards of Library - + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede - + Roll Dice... - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_en_US.ts b/cockatrice/translations/cockatrice_en_US.ts index be8a7818b..58ca883c8 100644 --- a/cockatrice/translations/cockatrice_en_US.ts +++ b/cockatrice/translations/cockatrice_en_US.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Set counter... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Set counter - + New value for counter '%1': New value for counter '%1': @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh &Refresh - + Parse Set Name and Number (if available) Parse Set Name and Number (if available) @@ -36,81 +36,83 @@ AbstractTabDeckEditor - + Open in new tab Open in new tab - + Are you sure? Are you sure? - + The decklist has been modified. Do you want to save the changes? The decklist has been modified. Do you want to save the changes? - - - - - - - + + + + + + Error Error - + Could not open deck at %1 Could not open deck at %1 - + Could not save remote deck Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. The deck could not be saved. Please check that the directory is writable and try again. - + Save deck Save deck - + The deck could not be saved. The deck could not be saved. - + There are no cards in your deck to be exported There are no cards in your deck to be exported + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. - No deck was selected to be exported. + + Add Analytics Panel + AdminNotesDialog - + Update Notes Update Notes - + Admin Notes for %1 Admin Notes for %1 @@ -118,12 +120,12 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard Mainboard - + Sideboard Sideboard @@ -131,22 +133,22 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds seconds - + Error Error - + Could not create themes directory at '%1'. Could not create themes directory at '%1'. - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -163,7 +165,7 @@ You will have to use the Set Manager, available through Card Database -> Mana Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -180,152 +182,165 @@ You can also use the Set Manager to adjust custom sort order for printings in th Are you sure you would like to disable this feature? - + Confirm Change Confirm Change - + Theme settings Theme settings - + Current theme: Current theme: - + Open themes folder Open themes folder - + Home tab background source: Home tab background source: - + Home tab background shuffle frequency: Home tab background shuffle frequency: - + Disabled Disabled - + Menu settings Menu settings - + Show keyboard shortcuts in right-click menus Show keyboard shortcuts in right-click menus - + + Show game filter toolbar above list in room tab + + + + Card rendering Card rendering - + Display card names on cards having a picture Display card names on cards having a picture - + Auto-Rotate cards with sideways layout Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Scale cards on mouse over - + Use rounded card corners Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: Maximum initial height for card view window: - - + + rows rows - + Maximum expanded height for card view window: Maximum expanded height for card view window: - + Card counters Card counters - + Counter %1 Counter %1 - + Hand layout Hand layout - + Display hand horizontally (wastes space) Display hand horizontally (wastes space) - + Enable left justification Enable left justification - + Table grid layout Table grid layout - + Invert vertical coordinate Invert vertical coordinate - + Minimum player count for multi-column layout: Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: Maximum font size for information displayed on cards: + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -347,112 +362,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name ban &user name - + ban &IP address ban &IP address - + ban client I&D ban client I&D - + Ban type Ban type - + &permanent ban &permanent ban - + &temporary ban &temporary ban - + &Days: &Days: - + &Hours: &Hours: - + &Minutes: &Minutes: - + Duration of the ban Duration of the ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. - + Please enter the reason for the ban that will be visible to the banned person. Please enter the reason for the ban that will be visible to the banned person. - + Redact all messages from this user in all rooms Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Cancel - + Ban user from server Ban user from server - + + - - + Error Error - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. - + You must have a value in the name ban when selecting the name ban checkbox. You must have a value in the name ban when selecting the name ban checkbox. - + You must have a value in the ip ban when selecting the ip ban checkbox. You must have a value in the ip ban when selecting the ip ban checkbox. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. You must have a value in the clientid ban when selecting the clientid ban checkbox. @@ -483,32 +498,32 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name Name - + Sets Sets - + Mana cost Mana cost - + Card type Card type - + P/T P/T - + Color(s) Color(s) @@ -516,96 +531,101 @@ This is only saved for moderators and cannot be seen by the banned person. CardFilter - + AND Logical conjunction operator used in card filter AND - + OR Logical disjunction operator used in card filter OR - + AND NOT Negated logical conjunction operator used in card filter AND NOT - + OR NOT Negated logical disjunction operator used in card filter OR NOT - + Name Name - + + Name (Exact) + + + + Type Type - + Color Color - + Text Text - + Set Set - + Mana Cost Mana Cost - + Mana Value Mana Value - + Rarity Rarity - + Power Power - + Toughness Toughness - + Loyalty Loyalty - + Format Format - + Main Type Main Type - + Sub Type Sub Type @@ -613,22 +633,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoFrameWidget - + Image Image - + Description Description - + Both Both - + View transformation View transformation @@ -636,22 +656,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards View related cards - + Add card to deck Add card to deck - + Mainboard Mainboard - + Sideboard Sideboard @@ -664,12 +684,22 @@ This is only saved for moderators and cannot be seen by the banned person.Name: - + + Set: + + + + + Collector Number: + + + + Related cards: Related cards: - + Unknown card: Unknown card: @@ -677,124 +707,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... Re&veal to... - + &All players &All players - + View related cards View related cards - + Token: Token: - + All tokens All tokens - + &Select All &Select All - + S&elect Row S&elect Row - + S&elect Column S&elect Column - + &Play &Play - + &Hide &Hide - + Play &Face Down Play &Face Down - + &Tap / Untap Turn sideways or back again &Tap / Untap - + Toggle &normal untapping Toggle &normal untapping - + T&urn Over Turn face up/face down T&urn Over - + &Peek at card face &Peek at card face - + &Clone &Clone - + Attac&h to card... Attac&h to card... - + Unattac&h Unattac&h - + &Draw arrow... &Draw arrow... - + &Set annotation... &Set annotation... - + Ca&rd counters Ca&rd counters - + &Add counter (%1) &Add counter (%1) - + &Remove counter (%1) &Remove counter (%1) - + &Set counters (%1)... &Set counters (%1)... @@ -802,7 +832,7 @@ This is only saved for moderators and cannot be seen by the banned person. CardSizeWidget - + Card Size Card Size @@ -810,133 +840,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative their hand - + %1's hand nominative %1's hand - - - their library - look at zone - their library - - - - %1's library - look at zone - %1's library - - - - of their library - top cards of zone, - of their library - - - - of %1's library - top cards of zone - of %1's library - their library - reveal zone + look at zone their library %1's library + look at zone + %1's library + + + + of their library + top cards of zone, + of their library + + + + of %1's library + top cards of zone + of %1's library + + + + their library + reveal zone + their library + + + + %1's library reveal zone %1's library - + their library shuffle their library - + %1's library shuffle %1's library - + their library nominative their library - + %1's library nominative %1's library - + their graveyard nominative their graveyard - + %1's graveyard nominative %1's graveyard - + their exile nominative their exile - + %1's exile nominative %1's exile - + their sideboard look at zone their sideboard - + %1's sideboard look at zone %1's sideboard - - - their sideboard - nominative - their sideboard - - - - %1's sideboard - nominative - %1's sideboard - + their sideboard + nominative + their sideboard + + + + %1's sideboard + nominative + %1's sideboard + + + their custom zone '%1' nominative their custom zone '%1' - + %1's custom zone '%2' nominative %1's custom zone '%2' @@ -945,7 +975,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml3Parser - + Parse error at line %1 col %2: Parse error at line %1 col %2: @@ -953,7 +983,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml4Parser - + Parse error at line %1 col %2: Parse error at line %1 col %2: @@ -972,6 +1002,37 @@ This is only saved for moderators and cannot be seen by the banned person.View custom zone '%1' + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -983,47 +1044,47 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) Search by card name (or search expressions) - + Add to Deck Add to Deck - + Add to Sideboard Add to Sideboard - + Select Printing Select Printing - + Show on EDHRec (Commander) Show on EDHRec (Commander) - + Show on EDHRec (Card) Show on EDHRec (Card) - + Show Related cards Show Related cards - + Add card to &maindeck Add card to &maindeck - + Add card to &sideboard Add card to &sideboard @@ -1031,87 +1092,97 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card Banner Card - + Main Type Main Type - + Mana Cost Mana Cost - + Colors Colors - + Select Printing Select Printing - + Deck Deck - + Deck &name: Deck &name: - + Banner Card/Tags Visibility Settings Banner Card/Tags Visibility Settings - + Show banner card selection menu Show banner card selection menu - + Show tags selection menu Show tags selection menu - + &Comments: &Comments: - + Group by: Group by: - + + Format: + + + + Hash: Hash: - + &Increment number &Increment number - + &Decrement number &Decrement number - + &Remove row &Remove row - + Swap card to/from sideboard Swap card to/from sideboard @@ -1119,17 +1190,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters Filters - + &Clear all filters &Clear all filters - + Delete selected Delete selected @@ -1137,114 +1208,114 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorMenu - + &Deck Editor &Deck Editor - + &New deck &New deck - + &Load deck... &Load deck... - + Load recent deck... Load recent deck... - + Clear Clear - + &Save deck &Save deck - + Save deck &as... Save deck &as... - + Load deck from cl&ipboard... Load deck from cl&ipboard... - + Edit deck in clipboard Edit deck in clipboard - - + + Annotated Annotated - - + + Not Annotated Not Annotated - + Save deck to clipboard Save deck to clipboard - + Annotated (No set info) Annotated (No set info) - + Not Annotated (No set info) Not Annotated (No set info) - + &Print deck... &Print deck... - + Load deck from online service... Load deck from online service... - + &Send deck to online service &Send deck to online service - + Create decklist (decklist.org) Create decklist (decklist.org) - + Create decklist (decklist.xyz) Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) Analyze deck (tappedout.net) - + &Close &Close @@ -1252,7 +1323,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector Printing Selector @@ -1260,194 +1331,227 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers Update Spoilers - - + + Success Success - + Download URLs have been reset. Download URLs have been reset. - + Downloaded card pictures have been reset. Downloaded card pictures have been reset. - + Error Error - + One or more downloaded card pictures could not be cleared. One or more downloaded card pictures could not be cleared. - + Add URL Add URL - - + + URL: URL: - - + + Edit URL Edit URL - + Network Cache Size: Network Cache Size: - + Redirect Cache TTL: Redirect Cache TTL: - + How long cached redirects for urls are valid for. How long cached redirects for urls are valid for. - + Picture Cache Size: Picture Cache Size: - + Add New URL Add New URL - + Remove URL Remove URL - + Day(s) Day(s) - + Updating... Updating... - + Choose path Choose path - + URL Download Priority URL Download Priority - + Spoilers Spoilers - + Download Spoilers Automatically Download Spoilers Automatically - + Spoiler Location: Spoiler Location: - + Last Change Last Change - + Spoilers download automatically on launch Spoilers download automatically on launch - + Press the button to manually update without relaunching Press the button to manually update without relaunching - + Do not close settings until manual update is complete Do not close settings until manual update is complete - + Download card pictures on the fly Download card pictures on the fly - + How to add a custom URL How to add a custom URL - + Delete Downloaded Images Delete Downloaded Images - + Reset Download URLs Reset Download URLs - + On-disk cache for downloaded pictures On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen In-memory cache for pictures not currently on screen + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count Count - + Set Set - + Number Number - + Provider ID Provider ID - + Card Card @@ -1455,12 +1559,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) Common deck formats (%1) - + All files (*.*) All files (*.*) @@ -1468,17 +1572,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match Mode: Exact Match - + Mode: Includes Mode: Includes - + Color identity filter mode (AND/OR/NOT conjunctions of filters) Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1486,7 +1590,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... Edit tags ... @@ -1494,62 +1598,62 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewTagDialog - + Deck Tags Manager Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) Add a new tag (e.g., Aggro️) - + Add Tag Add Tag - + Filter tags... Filter tags... - + OK OK - + Edit default tags Edit default tags - + Cancel Cancel - + Invalid Input Invalid Input - + Tag name cannot be empty! Tag name cannot be empty! - + Duplicate Tag Duplicate Tag - + This tag already exists. This tag already exists. @@ -1562,93 +1666,151 @@ This is only saved for moderators and cannot be seen by the banned person.Banner Card - + Open in deck editor Open in deck editor - + Edit Tags Edit Tags - + Rename Deck Rename Deck - + Save Deck to Clipboard Save Deck to Clipboard - + Annotated Annotated - + Annotated (No set info) Annotated (No set info) - + Not Annotated Not Annotated - + Not Annotated (No set info) Not Annotated (No set info) - + Rename File Rename File - + Delete File Delete File - + Set Banner Card Set Banner Card - - + + New name: New name: - - + + Error Error - + Rename failed Rename failed - + Delete file Delete file - + Are you sure you want to delete the selected file? Are you sure you want to delete the selected file? - + Delete failed Delete failed + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1666,75 +1828,75 @@ This is only saved for moderators and cannot be seen by the banned person. DeckViewContainer - + Load deck... Load deck... - + Load remote deck... Load remote deck... - + Load from clipboard... Load from clipboard... - + Load from website... Load from website... - + Unload deck Unload deck - + Ready to start Ready to start - + Force start Force start - + Sideboard unlocked Sideboard unlocked - + Sideboard locked Sideboard locked - - + + Error Error - + The selected file could not be loaded. The selected file could not be loaded. - + Deck is greater than maximum file size. Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice Cockatrice @@ -1769,143 +1931,143 @@ This will kick all non-ready players from the game. Downloading... - + Known Hosts Known Hosts - + Delete the currently selected saved server Delete the currently selected saved server - + Refresh the server list with known public servers Refresh the server list with known public servers - + New Host New Host - + Name: Name: - + &Host: &Host: - + &Port: &Port: - + Player &name: Player &name: - + P&assword: P&assword: - + &Save password &Save password - + A&uto connect A&uto connect - + Automatically connect to the most recent login when Cockatrice opens Automatically connect to the most recent login when Cockatrice opens - + If you have any trouble connecting or registering then contact the server staff for help! If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage Webpage - + Reset Password Reset Password - + Forgot password? Forgot password? - + &Connect &Connect - + Server Server - + Login Login - + Server Contact Server Contact - + Connect to Server Connect to Server - + Server URL Server URL - + Communication Port Communication Port - + Unique Server Name Unique Server Name - + Connection Warning Connection Warning - + You need to name your new connection profile. You need to name your new connection profile. - + Connect Warning Connect Warning - + The player name can't be empty. The player name can't be empty. @@ -1913,117 +2075,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings Re&member settings - + &Description: &Description: - + P&layers: P&layers: - + General General - + Game type Game type - + &Password: &Password: - + Only &buddies can join Only &buddies can join - + Only &registered users can join Only &registered users can join - + Joining restrictions Joining restrictions - + &Spectators can watch &Spectators can watch - + Spectators &need a password to watch Spectators &need a password to watch - + Spectators can &chat Spectators can &chat - + Spectators can see &hands Spectators can see &hands - + Create game as spectator Create game as spectator - + Spectators Spectators - + Starting life total: Starting life total: - + Open decklists in lobby Open decklists in lobby - + + Create game as judge + + + + Game setup options Game setup options - + &Clear &Clear - + Create game Create game - + Game information Game information - + Error Error - + Server error. Server error. @@ -2031,97 +2198,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Name: - + Token Token - + C&olor: C&olor: - + white white - + blue blue - + black black - + red red - + green green - + multicolor multicolor - + colorless colorless - + &P/T: &P/T: - + &Annotation: &Annotation: - + &Destroy token when it leaves the table &Destroy token when it leaves the table - + Create face-down (Only hides name) Create face-down (Only hides name) - + Token data Token data - + Show &all tokens Show &all tokens - + Show tokens from this &deck Show tokens from this &deck - + Choose token from list Choose token from list - + Create token Create token @@ -2129,53 +2296,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags Edit Tags - + Add Add - + Confirm Confirm - + Cancel Cancel - + Enter a tag and press Enter Enter a tag and press Enter - - + + - + Invalid Input Invalid Input - + Tag name cannot be empty! Tag name cannot be empty! - + Duplicate Tag Duplicate Tag - + This tag already exists. This tag already exists. @@ -2183,40 +2350,40 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. No image chosen. - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. - + Browse... Browse... - + Change avatar Change avatar - + Open Image Open Image - + Image Files (*.png *.jpg *.bmp) Image Files (*.png *.jpg *.bmp) - + Invalid image chosen. Invalid image chosen. @@ -2224,17 +2391,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard Edit deck in clipboard - + Error Error - + Invalid deck list. Invalid deck list. @@ -2242,38 +2409,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: Old password: - + New password: New password: - + Confirm new password: Confirm new password: - + Change password Change password - - + + Error Error - + Your password is too short. Your password is too short. - + The new passwords don't match. The new passwords don't match. @@ -2281,93 +2448,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: &Name: - + C&olor: C&olor: - + white white - + blue blue - + black black - + red red - + green green - + multicolor multicolor - + colorless colorless - + &P/T: &P/T: - + &Annotation: &Annotation: - + Token data Token data - - + + Add token Add token - + Remove token Remove token - + Edit custom tokens Edit custom tokens - + Please enter the name of the token: Please enter the name of the token: - + Error Error - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. The chosen name conflicts with an existing card or token. @@ -2461,8 +2628,9 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games not created by buddy - Hide games not created by buddy + Hide games not created by buddies + Hide games not created by buddy + @@ -2548,52 +2716,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. Enter the information of the server and the account you'd like to request a new password for. - + &Host: &Host: - + &Port: &Port: - + Player &name: Player &name: - + Email: Email: - + Reset Password Challenge Reset Password Challenge - + Reset Password Challenge Error Reset Password Challenge Error - + The email address can't be empty. The email address can't be empty. @@ -2601,37 +2769,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Enter the information of the server you'd like to request a new password for. - + &Host: &Host: - + &Port: &Port: - + Player &name: Player &name: - + Reset Password Request Reset Password Request - + Reset Password Error Reset Password Error - + The player name can't be empty. The player name can't be empty. @@ -2639,86 +2807,86 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning Reset Password Warning - + A problem has occurred. Please try to request a new password again. A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. Enter the received token and the new password in order to set your new password. - + &Host: &Host: - + &Port: &Port: - + Player &name: Player &name: - + Token: Token: - - + + New Password: New Password: - + Reset Password Reset Password - + The player name can't be empty. The player name can't be empty. - - - - + + + + Reset Password Error Reset Password Error - + The token can't be empty. The token can't be empty. - + The new password can't be empty. The new password can't be empty. - + Error Error - + Your password is too short. Your password is too short. - + The passwords do not match. The passwords do not match. @@ -2734,17 +2902,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard Load deck from clipboard - + Error Error - + Invalid deck list. Invalid deck list. @@ -2752,45 +2920,45 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 Network error: %1 - + Received empty deck data. Received empty deck data. - + Failed to parse deck data: %1 Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2810,7 +2978,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck Load deck @@ -2818,37 +2986,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): Card name (or search expressions): - + Number of hits: Number of hits: - + Auto play hits Auto play hits - + Put top cards on stack until... Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice Cockatrice - + Invalid filter Invalid filter @@ -2856,92 +3024,92 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: &Host: - + &Port: &Port: - + Player &name: Player &name: - + P&assword: P&assword: - + Password (again): Password (again): - + Email: Email: - + Email (again): Email (again): - + Country: Country: - + Undefined Undefined - + Real name: Real name: - + Register to server Register to server - - - - + + + + Registration Warning Registration Warning - + Your password is too short. Your password is too short. - + Your passwords do not match, please try again. Your passwords do not match, please try again. - + Your email addresses do not match, please try again. Your email addresses do not match, please try again. - + The player name can't be empty. The player name can't be empty. @@ -2967,40 +3135,55 @@ Your email will be used to verify your account. DlgSelectSetForCards - + Unmodified Cards: Unmodified Cards: - + Modified Cards: Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information Clear all set information - + Set all to preferred Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database Unknown Error loading card database - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3017,7 +3200,7 @@ You may need to rerun oracle to update your card database. Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -3034,7 +3217,7 @@ Usually this can be fixed by rerunning oracle to to update your card database. Would you like to change your database location setting? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3047,7 +3230,7 @@ Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with you Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3056,7 +3239,7 @@ Would you like to change your database location setting? Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3065,7 +3248,7 @@ Would you like to change your database location setting? Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3078,59 +3261,59 @@ Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues Would you like to change your database location setting? - - - + + + Error Error - + The path to your deck directory is invalid. Would you like to go back and set the correct path? The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings Settings - + General General - + Appearance Appearance - + User Interface User Interface - + Card Sources Card Sources - + Chat Chat - + Sound Sound - + Shortcuts Shortcuts @@ -3138,12 +3321,12 @@ Would you like to change your database location setting? DlgStartupCardCheck - + Card Update Check Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. @@ -3152,27 +3335,27 @@ Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground Run in foreground - + Run in background Run in background - + Run in background and always from now on Run in background and always from now on - + Don't prompt again and don't run Don't prompt again and don't run - + Don't run this time Don't run this time @@ -3180,17 +3363,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next Next - + Previous Previous - + Tip of the Day Tip of the Day @@ -3198,166 +3381,166 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel Current release channel - + Reinstall Reinstall - + Cancel Download Cancel Download - + Open Download Page Open Download Page - + Check for Client Updates Check for Client Updates - - - - + + + + Error Error - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - + Downloading update: %1 Downloading update: %1 - + Checking for updates... Checking for updates... - + Finished checking for updates Finished checking for updates - + No Update Available No Update Available - + Cockatrice is up to date! Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. You are already running the latest version available in the chosen release channel. - + Current version Current version - + Selected release channel Selected release channel - - + + Update Available Update Available - - + + A new version of Cockatrice is available! A new version of Cockatrice is available! - - + + New version New version - - + + Released Released - - + + Changelog Changelog - + Do you want to update now? Do you want to update now? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + + Update Error Update Error - + An error occurred while checking for updates: An error occurred while checking for updates: - + An error occurred while downloading an update: An error occurred while downloading an update: - + Installing... Installing... - + Cockatrice is unable to open the installer. Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. Try to update manually by closing Cockatrice and running the installer. - + Download location Download location @@ -3365,21 +3548,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing Clear log when closing - + Copy to clipboard Copy to clipboard - + Debug Log Debug Log + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3449,33 +3764,39 @@ You may have to manually download the new version. %1% Synergy + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos Combos - + Average Deck Average Deck - - - Game Changers - Game Changers - - - - Budget - Budget - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: Salt: @@ -3491,22 +3812,22 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete Confirm Delete - + Are you sure you want to delete the filter '%1'? Are you sure you want to delete the filter '%1'? - + Delete Failed Delete Failed - + Failed to delete filter '%1'. Failed to delete filter '%1'. @@ -3514,22 +3835,22 @@ You may have to manually download the new version. GameEventHandler - + kicked by game host or moderator kicked by game host or moderator - + player left the game player left the game - + player disconnected from server player disconnected from server - + reason unknown reason unknown @@ -3537,226 +3858,279 @@ You may have to manually download the new version. GameSelector - - - - - - + + + + + + Error Error - + Please join the appropriate room first. Please join the appropriate room first. - + Wrong password. Wrong password. - + Spectators are not allowed in this game. Spectators are not allowed in this game. - + The game is already full. The game is already full. - + The game does not exist any more. The game does not exist any more. - + This game is only open to registered users. This game is only open to registered users. - + This game is only open to its creator's buddies. This game is only open to its creator's buddies. - + You are being ignored by the creator of this game. You are being ignored by the creator of this game. - + Join Game Join Game - + Spectate Game Spectate Game - + Game Information Game Information - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game Join game - + Password: Password: - + Please join the respective room first. Please join the respective room first. - + &Filter games &Filter games - + C&lear filter C&lear filter - + C&reate C&reate - + &Join &Join - + + Join as judge + + + + J&oin as spectator J&oin as spectator - + + Join as judge spectator + + + + Games shown: %1 / %2 Games shown: %1 / %2 - + Games Games + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day >1 day - + %1%2 hr short age in hours %1%2 hr%1%2 hrs - + new new - + %1%2 min short age in minutes %1%2 min%1%2 mins - + password password - + buddies only buddies only - + reg. users only reg. users only - + open decklists open decklists - - + + can chat can chat - + see hands see hands - + can see hands can see hands - + not allowed not allowed - + Room Room - + Age Age - + Description Description - + Creator Creator - + Type Type - + Restrictions Restrictions - + Players Players - + Spectators Spectators @@ -3764,143 +4138,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths Reset all paths - + All paths have been reset All paths have been reset - - - - - - - + + + + + + + Choose path Choose path - + Personal settings Personal settings - + Language: Language: - + Paths (editing disabled in portable mode) Paths (editing disabled in portable mode) - + Paths Paths - + How to help with translations How to help with translations - + Decks directory: Decks directory: - + Filters directory: Filters directory: - + Replays directory: Replays directory: - + Pictures directory: Pictures directory: - + Card database: Card database: - + Custom database directory: Custom database directory: - + Token database: Token database: - + Update channel Update channel - + Check for client updates on startup Check for client updates on startup - + Check for card database updates on startup Check for card database updates on startup - + Don't check Don't check - + Prompt for update Prompt for update - + Always update in the background Always update in the background - + Check for card database updates every Check for card database updates every - + days days - + Notify if a feature supported by the server is missing in my client Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup Show tips on startup - + Last update check on %1 (%2 days ago) Last update check on %1 (%2 days ago) @@ -3908,47 +4282,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard &Graveyard - + &View graveyard &View graveyard - + &Move graveyard to... &Move graveyard to... - + &Top of library &Top of library - + &Bottom of library &Bottom of library - + &All players &All players - + &Hand &Hand - + &Exile &Exile - + Reveal random card to... Reveal random card to... @@ -3956,111 +4330,141 @@ You may have to manually download the new version. HandMenu - + &Hand &Hand - + &View hand &View hand - - &Sort hand - &Sort hand + + Sort hand by... + - - Take &mulligan - Take &mulligan + + Name + - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... &Move hand to... - + &Top of library &Top of library - + &Bottom of library &Bottom of library - + &Graveyard &Graveyard - + &Exile &Exile - + &Reveal hand to... &Reveal hand to... - - Reveal r&andom card to... - Reveal r&andom card to... + + + All players + - - - &All players - &All players + + Reveal r&andom card to... + Reveal r&andom card to... HomeWidget - + Create New Deck Create New Deck - + Browse Decks Browse Decks - + Browse Card Database Browse Card Database - + Browse EDHRec Browse EDHRec - + + Browse Archidekt + + + + View Replays View Replays - + Quit Quit - + Connecting... Connecting... - + Connect Connect - + Play Play @@ -4262,61 +4666,61 @@ You may have to manually download the new version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. There are too many concurrent connections from your address. - + Banned by moderator Banned by moderator - + Expected end time: %1 Expected end time: %1 - + This ban lasts indefinitely. This ban lasts indefinitely. - + Scheduled server shutdown. Scheduled server shutdown. - - + + Invalid username. Invalid username. - + You have been logged out due to logging in at another location. You have been logged out due to logging in at another location. - + Connection closed Connection closed - + The server has terminated your connection. Reason: %1 The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -4327,584 +4731,584 @@ All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown Scheduled server shutdown - - + + Success Success - + Registration accepted. Will now login. Registration accepted. Will now login. - + Account activation accepted. Will now login. Account activation accepted. Will now login. - + Number of players Number of players - + Please enter the number of players. Please enter the number of players. - - + + Player %1 Player %1 - + Load replay Load replay - + About Cockatrice About Cockatrice - + Version Version - + Cockatrice Webpage Cockatrice Webpage - + Project Manager: Project Manager: - + Past Project Managers: Past Project Managers: - + Developers: Developers: - + Our Developers Our Developers - + Help Develop! Help Develop! - + Translators: Translators: - + Our Translators Our Translators - + Help Translate! Help Translate! - + Support: Support: - + Report an Issue Report an Issue - + Troubleshooting Troubleshooting - + F.A.Q. F.A.Q. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error Error - + Server timeout Server timeout - + Failed Login Failed Login - + Your client seems to be missing features this server requires for connection. Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. You are banned until %1. - - + + You are banned indefinitely. You are banned indefinitely. - + This server requires user registration. Do you want to register now? This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full Server Full - + Unknown login error: %1 Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: Your username must respect these rules: - + is %1 - %2 characters long is %1 - %2 characters long - + can %1 contain lowercase characters can %1 contain lowercase characters - - - - + + + + NOT NOT - + can %1 contain uppercase characters can %1 contain uppercase characters - + can %1 contain numeric characters can %1 contain numeric characters - + can contain the following punctuation: %1 can contain the following punctuation: %1 - + first character can %1 be a punctuation mark first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated no unacceptable language as specified by these server rules: - + can not contain any of the following words: %1 can not contain any of the following words: %1 - + can not match any of the following expressions: %1 can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - + + + + + + Registration denied Registration denied - + Registration is currently disabled on this server Registration is currently disabled on this server - + There is already an existing account with the same user name. There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. Password too short. - + Registration failed for a technical problem on the server. Registration failed for a technical problem on the server. - + The connection to the server has been lost. The connection to the server has been lost. - + Unknown registration error: %1 Unknown registration error: %1 - + Account activation failed Account activation failed - + Socket error: %1 Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - + Connecting to %1... Connecting to %1... - + Registering to %1 as %2... Registering to %1 as %2... - + Disconnected Disconnected - + Connected, logging in at %1 Connected, logging in at %1 + - Requesting forgotten password to %1 as %2... Requesting forgotten password to %1 as %2... - + &Connect... &Connect... - + &Disconnect &Disconnect - + Start &local game... Start &local game... - + &Watch replay... &Watch replay... - + &Full screen &Full screen - + &Register to server... &Register to server... - + &Restore password... &Restore password... - + &Settings... &Settings... - + &Exit &Exit - + A&ctions A&ctions - + &Cockatrice &Cockatrice - + C&ard Database C&ard Database - + &Manage sets... &Manage sets... - + Edit custom &tokens... Edit custom &tokens... - + Open custom image folder Open custom image folder - + Open custom sets folder Open custom sets folder - + Add custom sets/cards Add custom sets/cards - + Reload card database Reload card database - + Tabs Tabs - + &Help &Help - + &About Cockatrice &About Cockatrice - + &Tip of the Day &Tip of the Day - + Check for Client Updates Check for Client Updates - + Check for Card Updates... Check for Card Updates... - + Check for Card Updates (Automatic) Check for Card Updates (Automatic) - + Show Status Bar Show Status Bar - + View &Debug Log View &Debug Log - + Open Settings Folder Open Settings Folder - + Show/Hide Show/Hide - + New Version New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -4913,29 +5317,29 @@ Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes Yes - - + + No No - + Open settings Open settings - + New sets found New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -4946,17 +5350,17 @@ Set codes: %1 Do you want to enable them? - + View sets View sets - + Welcome Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -4965,65 +5369,65 @@ All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information Information - + A card database update is already running. A card database update is already running. - + Unable to run the card database updater: Unable to run the card database updater: - + Card database update running. Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. Unknown error occurred. - + The card database updater exited with an error: %1 The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5034,673 +5438,843 @@ This is most likely not a problem, but this message might mean there is a new ve To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards Load sets/cards - + Selected file cannot be found. Selected file cannot be found. - + You can only import XML databases at this time. You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. Sets/cards failed to import. - - - + + + Reset Password Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. Activation request received, please check your email for an activation token. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play from play - + from their graveyard from their graveyard - + from exile from exile - + from their hand from their hand - + the top card of %1's library the top card of %1's library - + the top card of their library the top card of their library - + from the top of %1's library from the top of %1's library - + from the top of their library from the top of their library - + the bottom card of %1's library the bottom card of %1's library - + the bottom card of their library the bottom card of their library - + from the bottom of %1's library from the bottom of %1's library - + from the bottom of their library from the bottom of their library - + from %1's library from %1's library - + from their library from their library - + from sideboard from sideboard - + from the stack from the stack - + from custom zone '%1' from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 attaches %2 to %3's %4. - + %1 has conceded the game. %1 has conceded the game. - + %1 has unconceded the game. %1 has unconceded the game. - + %1 has restored connection to the game. %1 has restored connection to the game. - + %1 has lost connection to the game. %1 has lost connection to the game. - + %1 points from their %2 to themselves. %1 points from their %2 to themselves. - + %1 points from their %2 to %3. %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. %1 creates a face down token. - + %1 creates token: %2%3. %1 creates token: %2%3. - + %1 has loaded a deck (%2). %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. %1 destroys %2. - + a card a card - + %1 gives %2 control over %3. %1 gives %2 control over %3. - + %1 puts %2 into play%3 face down. %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 puts %2 into play%3. - + %1 puts %2%3 into their graveyard. %1 puts %2%3 into their graveyard. - + %1 exiles %2%3. %1 exiles %2%3. - + %1 moves %2%3 to their hand. %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. %1 moves %2%3 to sideboard. - + %1 plays %2%3. %1 plays %2%3. - + %1 moves %2%3 to custom zone '%4'. %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1 tries to draw from an empty library - + %1 draws %2 card(s). %1 draws %2 card.%1 draws %2 cards. - + %1 is looking at %2. %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 is looking at the %4 %3 card %2.%1 is looking at the %4 %3 cards %2. - + bottom bottom - + top top - + %1 turns %2 face-down. %1 turns %2 face-down. - + %1 turns %2 face-up. %1 turns %2 face-up. - + The game has been closed. The game has been closed. - + The game has started. The game has started. - + You are flooding the game. Please wait a couple of seconds. You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 has joined the game. - + %1 is now watching the game. %1 is now watching the game. - + You have been kicked out of the game. You have been kicked out of the game. - + %1 has left the game (%2). %1 has left the game (%2). - + %1 is not watching the game any more (%2). %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 shuffles their deck and draws a card.%1 shuffles their deck and draws a new hand of %2 cards. - + %1 shuffles their deck and draws a new hand. %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. You are watching a replay of game #%1. - + %1 is ready to start the game. %1 is ready to start the game. - + cards an unknown amount of cards cards - + %1 card(s) a card for singular, %1 cards for plural one card%1 cards - + %1 lends %2 to %3. %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 reveals %2 to %3. - + %1 reveals %2. %1 reveals %2. - + %1 randomly reveals %2%3 to %4. %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. %1 reveals %2%3 to %4. - + %1 reveals %2%3. %1 reveals %2%3. - + %1 reversed turn order, now it's %2. %1 reversed turn order, now it's %2. - + reversed reversed - + normal normal - + Heads Heads - + Tails Tails - + %1 flipped a coin. It landed as %2. %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. %1's turn. - + %1 sets annotation of %2 to %3. %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). %1 places %2 "%3" counter on %4 (now %5).%1 places %2 "%3" counters on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). %1 removes %2 "%3" counter from %4 (now %5).%1 removes %2 "%3" counters from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. %1 sets %2 to untap normally. - + %1 removes the PT of %2. %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. %1 has locked their sideboard. - + %1 has unlocked their sideboard. %1 has unlocked their sideboard. - + %1 taps their permanents. %1 taps their permanents. - + %1 untaps their permanents. %1 untaps their permanents. - + %1 taps %2. %1 taps %2. - + %1 untaps %2. %1 untaps %2. - + %1 shuffles %2. %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. %1 unattaches %2. - + %1 undoes their last draw. %1 undoes their last draw. - + %1 undoes their last draw (%2). %1 undoes their last draw (%2). @@ -5708,110 +6282,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 Word1 Word2 Word3 - + Add New Message Add New Message - + Edit Message Edit Message - + Remove Message Remove Message - + Add message Add message - - + + Message: Message: - + Edit message Edit message - + Chat settings Chat settings - + Custom alert words Custom alert words - + Enable chat mentions Enable chat mentions - + Enable mention completer Enable mention completer - + In-game message macros In-game message macros - + How to use in-game message macros How to use in-game message macros - + Ignore chat room messages sent by unregistered users Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users Ignore private messages sent by unregistered users - - + + Invert text color Invert text color - + Enable desktop notifications for private messages Enable desktop notifications for private messages - + Enable desktop notification for mentions Enable desktop notification for mentions - + Enable room message history on join Enable room message history on join - - + + (Color is hexadecimal) (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only Separate words with a space, alphanumeric characters only @@ -5928,62 +6502,62 @@ Cockatrice will now reload the card database. Phase - + Unknown Phase Unknown Phase - + Untap Untap - + Upkeep Upkeep - + Draw Draw - + First Main First Main - + Beginning of Combat Beginning of Combat - + Declare Attackers Declare Attackers - + Declare Blockers Declare Blockers - + Combat Damage Combat Damage - + End of Combat End of Combat - + Second Main Second Main - + End/Cleanup End/Cleanup @@ -6049,7 +6623,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages en @@ -6058,134 +6632,134 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library View top cards of library - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) Number of cards: (max. %1) - + View bottom cards of library View bottom cards of library - + Shuffle top cards of library Shuffle top cards of library - + Shuffle bottom cards of library Shuffle bottom cards of library - + Draw hand Draw hand - + 0 and lower are in comparison to current hand size 0 and lower are in comparison to current hand size - + Draw cards Draw cards - + Move top cards to grave Move top cards to grave - + Move top cards to exile Move top cards to exile - + Move bottom cards to grave Move bottom cards to grave - + Move bottom cards to exile Move bottom cards to exile - + Draw bottom cards Draw bottom cards - - + + C&reate another %1 token C&reate another %1 token - + Create tokens Create tokens - - + + Number: Number: - + Place card X cards from top of library Place card X cards from top of library - + Which position should this card be placed: Which position should this card be placed: - + (max. %1) (max. %1) - + Change power/toughness Change power/toughness - + Change stats to: Change stats to: - + Set annotation Set annotation - + Please enter the new annotation: Please enter the new annotation: - + Set counters Set counters @@ -6193,17 +6767,17 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" Player "%1" - + &Counters &Counters - + S&ay S&ay @@ -6211,7 +6785,7 @@ Cockatrice will now reload the card database. PrintingSelector - + Display Navigation Buttons Display Navigation Buttons @@ -6219,22 +6793,22 @@ Cockatrice will now reload the card database. PrintingSelectorCardOverlayWidget - + Preference Preference - + Pin Printing Pin Printing - + Unpin Printing Unpin Printing - + Show Related cards Show Related cards @@ -6250,17 +6824,17 @@ Cockatrice will now reload the card database. PrintingSelectorCardSelectionWidget - + Previous Card in Deck Previous Card in Deck - + Bulk Selection Bulk Selection - + Next Card in Deck Next Card in Deck @@ -6268,28 +6842,28 @@ Cockatrice will now reload the card database. PrintingSelectorCardSortingWidget - + Alphabetical Alphabetical - + Preference Preference - + Release Date Release Date - - + + Descending Descending - + Ascending Ascending @@ -6355,37 +6929,37 @@ Cockatrice will now reload the card database. QMenuBar - + Services Services - + Hide %1 Hide %1 - + Hide Others Hide Others - + Show All Show All - + Preferences... Preferences... - + Quit %1 Quit %1 - + About %1 About %1 @@ -6393,42 +6967,42 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) Cockatrice card database (*.xml) - + All files (*.*) All files (*.*) - + Cockatrice replays (*.cor) Cockatrice replays (*.cor) - + Maindeck Maindeck - + Sideboard Sideboard - + Tokens Tokens - + Overwrite Existing File? Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? A .cod version of this deck already exists. Overwrite it? @@ -6436,92 +7010,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK OK - + Save Save - + Save All Save All - + Open Open - + &Yes &Yes - + Yes to &All Yes to &All - + &No &No - + N&o to All N&o to All - + Abort Abort - + Retry Retry - + Ignore Ignore - + Close Close - + Cancel Cancel - + Discard Discard - + Help Help - + Apply Apply - + Reset Reset - + Restore Defaults Restore Defaults @@ -6618,37 +7192,37 @@ Cockatrice will now reload the card database. RoomSelector - + Rooms Rooms - + Joi&n Joi&n - + Room Room - + Description Description - + Permissions Permissions - + Players Players - + Games Games @@ -6689,27 +7263,27 @@ Cockatrice will now reload the card database. SetsModel - + Enabled Enabled - + Set type Set type - + Set code Set code - + Long name Long name - + Release date Release date @@ -6717,53 +7291,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts Restore all default shortcuts - + Do you really want to restore all default shortcuts? Do you really want to restore all default shortcuts? - + Clear all default shortcuts Clear all default shortcuts - + Do you really want to clear all shortcuts? Do you really want to clear all shortcuts? - + Section: Section: - + Action: Action: - + Shortcut: Shortcut: - + How to set custom shortcuts How to set custom shortcuts - + Clear all shortcuts Clear all shortcuts - + Search by shortcut name Search by shortcut name @@ -6771,12 +7345,12 @@ Cockatrice will now reload the card database. ShortcutTreeView - + Action Action - + Shortcut Shortcut @@ -6784,14 +7358,14 @@ Cockatrice will now reload the card database. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: The following shortcuts have been set to default: @@ -6819,12 +7393,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard &Sideboard - + &View sideboard &View sideboard @@ -6832,27 +7406,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Enable &sounds - + Current sounds theme: Current sounds theme: - + Test system sound engine Test system sound engine - + Sound settings Sound settings - + Master volume Master volume @@ -6860,48 +7434,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed Spoilers download failed - + No internet connection No internet connection - + Error Error - + Spoilers already up to date Spoilers already up to date - + No new spoilers added No new spoilers added - + Spoilers have been updated! Spoilers have been updated! - + Last change: Last change: @@ -7065,56 +7639,123 @@ Please check your shortcut settings! Unable to activate user. Internal error + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info Card Info - + Deck Deck - + Filters Filters - + &View &View - + + Card Database + + + + Printing Printing - - - - + + + + + Visible Visible - - - - + + + + + Floating Floating - + Reset layout Reset layout - + Deck: %1 Deck: %1 @@ -7122,61 +7763,61 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 Visual Deck: %1 - + &Visual Deck Editor &Visual Deck Editor - - + + Card Info Card Info - - + + Deck Deck - - + + Filters Filters - + &View &View - + Printing Printing - - - - + + + + Visible Visible - - - - + + + + Floating Floating - + Reset layout Reset layout @@ -7184,22 +7825,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View Visual Deck View - + Visual Database Display Visual Database Display - + Deck Analytics Deck Analytics - + Sample Hand Sample Hand @@ -7207,134 +7848,134 @@ Please check your shortcut settings! TabDeckStorage - + Local file system Local file system - + Server deck storage Server deck storage - - + + Open in deck editor Open in deck editor - + Rename deck or folder Rename deck or folder - + Upload deck Upload deck - + Download deck Download deck + - - - + + New folder New folder + - Delete Delete - + Open decks folder Open decks folder - + Rename local folder Rename local folder - + Rename local file Rename local file - + New name: New name: - - - - + + + + Error Error - + Rename failed Rename failed - - + + Invalid deck file Invalid deck file - + Enter deck name Enter deck name - + This decklist does not have a name. Please enter a name: This decklist does not have a name. Please enter a name: - + Unnamed deck Unnamed deck - + Failed to upload deck to server Failed to upload deck to server - + Delete local file Delete local file - + Are you sure you want to delete the selected files? Are you sure you want to delete the selected files? - + Delete remote decks Delete remote decks - + Are you sure you want to delete the selected decks? Are you sure you want to delete the selected decks? - - + + Name of new folder: Name of new folder: @@ -7347,17 +7988,17 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage Visual Deck Storage - + Error Error - + Could not open deck at %1 Could not open deck at %1 @@ -7398,7 +8039,7 @@ Please enter a name: Search - + EDHRec: EDHRec: @@ -7406,197 +8047,197 @@ Please enter a name: TabGame - - - + + + Replay Replay - - + + Game Game - - + + Player List Player List - - + + Card Info Card Info - - + + Messages Messages - - + + Replay Timeline Replay Timeline - + &Phases &Phases - + &Game &Game - + Next &phase Next &phase - + Next phase with &action Next phase with &action - + Next &turn Next &turn - + Reverse turn order Reverse turn order - + &Remove all local arrows &Remove all local arrows - + Rotate View Cl&ockwise Rotate View Cl&ockwise - + Rotate View Co&unterclockwise Rotate View Co&unterclockwise - + Game &information Game &information - + Un&concede Un&concede - - - + + + &Concede &Concede - + &Leave game &Leave game - + C&lose replay C&lose replay - + &Focus Chat &Focus Chat - + &Say: &Say: - + Selected cards Selected cards - + &View &View - - + + + - Visible Visible - - + + + - Floating Floating - + Reset layout Reset layout - + Concede Concede - + Are you sure you want to concede this game? Are you sure you want to concede this game? - + Unconcede Unconcede - + You have already conceded. Do you want to return to this game? You have already conceded. Do you want to return to this game? - + Leave game Leave game - + Are you sure you want to leave this game? Are you sure you want to leave this game? - + A player has joined game #%1 A player has joined game #%1 - + %1 has joined the game %1 has joined the game - + You have been kicked out of the game. You have been kicked out of the game. @@ -7604,7 +8245,7 @@ Please enter a name: TabHome - + Home Home @@ -7612,158 +8253,158 @@ Please enter a name: TabLog - + Logs Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs Room Logs - + Game Logs Game Logs - + Chat Logs Chat Logs - - + + Error Error - + You must select at least one filter. You must select at least one filter. - + You have to select a valid number of days to locate. You have to select a valid number of days to locate. - + Username: Username: - + IP Address: IP Address: - + Game Name: Game Name: - + GameID: GameID: - + Message: Message: - + Main Room Main Room - + Game Room Game Room - + Private Chat Private Chat - + Past X Days: Past X Days: - + Today Today - + Last Hour Last Hour - + Maximum Results: Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs Get User Logs - + Clear Filters Clear Filters - + Filters Filters - + Log Locations Log Locations - + Date Range Date Range - + Maximum Results Maximum Results - - + + Message History Message History - + Failed to collect message history information. Failed to collect message history information. - + There are no messages for the selected filters. There are no messages for the selected filters. @@ -7809,181 +8450,181 @@ The more information you put in, the more specific your results will be. TabReplays - + Local file system Local file system - + Server replay storage Server replay storage - - + + Watch replay Watch replay - + Rename Rename - - + + New folder New folder - - + + Delete Delete - + Open replays folder Open replays folder - + Download replay Download replay - + Toggle expiration lock Toggle expiration lock - + Get replay share code Get replay share code - - + + Look up replay by share code Look up replay by share code - + Rename local folder Rename local folder - + Rename local file Rename local file - + New name: New name: - + Error Error - + Rename failed Rename failed - + Name of new folder: Name of new folder: - + Delete local file Delete local file - + Are you sure you want to delete the selected files? Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? Are you sure you want to delete the selected replays? - + Failed to get code Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed Failed - + Could not get replay code Could not get replay code - + Replay Share Code Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard Copy to clipboard - + Replay share code Replay share code - + Replay code found Replay code found - + Replay was added, or you already had access to it. Replay was added, or you already had access to it. - + Replay code not found Replay code not found - + Failed to submit code Failed to submit code - + Unexpected error Unexpected error - + Delete remote replay Delete remote replay @@ -8049,30 +8690,30 @@ The more information you put in, the more specific your results will be.Server + - Error Error - + Failed to join the server room: it doesn't exist on the server. Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. Failed to join the server room due to an unknown error: %1. @@ -8080,92 +8721,97 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor Deck Editor - + Visual Deck Editor Visual Deck Editor - + EDHRec EDHRec - + + Archidekt + + + + Home Home - + &Visual Deck Storage &Visual Deck Storage - + Visual Database Display Visual Database Display - + Server Server - + Account Account - + Deck Storage Deck Storage - + Game Replays Game Replays - + Administration Administration - + Logs Logs - + Are you sure? Are you sure? - + There are still open games. Are you sure you want to quit? There are still open games. Are you sure you want to quit? - + Click to view Click to view - + Your buddy %1 has signed on! Your buddy %1 has signed on! - + Unknown Event Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8176,39 +8822,39 @@ This message might mean there is a new version of Cockatrice available or this s To update your client, go to Help -> Check for Updates. - + Idle Timeout Idle Timeout - + You are about to be logged out due to inactivity. You are about to be logged out due to inactivity. - + Promotion Promotion - + You have been promoted. Please log out and back in for changes to take effect. You have been promoted. Please log out and back in for changes to take effect. - + Warned Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) You have received the following message from the server. @@ -8218,7 +8864,12 @@ Please refrain from engaging in this activity or further actions may be taken ag TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display Visual Database Display @@ -8300,7 +8951,7 @@ Please refrain from engaging in this activity or further actions may be taken ag UpdateDownloader - + Could not open the file for reading. Could not open the file for reading. @@ -8308,206 +8959,206 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details User &details - + Private &chat Private &chat - + Show this user's &games Show this user's &games - + Add to &buddy list Add to &buddy list - + Remove from &buddy list Remove from &buddy list - + Add to &ignore list Add to &ignore list - + Remove from &ignore list Remove from &ignore list - + Kick from &game Kick from &game - + Warn user Warn user - + View user's war&n history View user's war&n history - + Ban from &server Ban from &server - + View user's &ban history View user's &ban history - + &Promote user to moderator &Promote user to moderator - + Dem&ote user from moderator Dem&ote user from moderator - + Promote user to &judge Promote user to &judge - + Demote user from judge Demote user from judge - + View admin notes View admin notes - - - + + + Error Error - + This user does not exist. This user does not exist. - + You are being ignored by %1 and can't see their games. You are being ignored by %1 and can't see their games. - + Could not get %1's games. Could not get %1's games. - + %1's games %1's games - - - + + + Ban History Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. User has never been banned. - + Failed to collect ban information. Failed to collect ban information. - - - + + + Warning History Warning History - + Warning Time;Moderator;User Name;Reason Warning Time;Moderator;User Name;Reason - + User has never been warned. User has never been warned. - + Failed to collect warning information. Failed to collect warning information. - + Failed to get admin notes. Failed to get admin notes. - - + + Success Success - + Successfully promoted user. Successfully promoted user. - + Successfully demoted user. Successfully demoted user. - + + - Failed Failed - + Failed to promote user. Failed to promote user. - + Failed to demote user. Failed to demote user. - + Copy hash to clipboard Copy hash to clipboard - + Remove this user's messages Remove this user's messages @@ -8689,137 +9340,142 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings General interface settings - + &Double-click cards to play them (instead of single-click) &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default &Play all nonlands onto the stack (not the battlefield) by default - + + Do not delete &arrows inside of subphases + + + + Close card view window when last card is removed Close card view window when last card is removed - + Auto focus search bar when card view window is opened Auto focus search bar when card view window is opened - + Annotate card text on tokens Annotate card text on tokens - + Use tear-off menus, allowing right click menus to persist on screen Use tear-off menus, allowing right click menus to persist on screen - + Notifications settings Notifications settings - + Enable notifications in taskbar Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect Notify in the taskbar when users in your buddy list connect - + Animation settings Animation settings - + &Tap/untap animation &Tap/untap animation - + Deck editor/storage settings Deck editor/storage settings - + Open deck in new tab by default Open deck in new tab by default - + Use visual deck storage in game lobby Use visual deck storage in game lobby - + Use selection animation for Visual Deck Storage Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: When adding a tag in the visual deck storage to a .txt deck: - + do nothing do nothing - + ask to convert to .cod ask to convert to .cod - + always convert to .cod always convert to .cod - + Default deck editor type Default deck editor type - + Classic Deck Editor Classic Deck Editor - + Visual Deck Editor Visual Deck Editor - + Replay settings Replay settings - + Buffer time for backwards skip via shortcut: Buffer time for backwards skip via shortcut: @@ -8827,22 +9483,22 @@ Please refrain from engaging in this activity or further actions may be taken ag UserListWidget - + Users connected to server: %1 Users connected to server: %1 - + Users in this room: %1 Users in this room: %1 - + Buddies online: %1 / %2 Buddies online: %1 / %2 - + Ignored users online: %1 / %2 Ignored users online: %1 / %2 @@ -8850,32 +9506,32 @@ Please refrain from engaging in this activity or further actions may be taken ag UtilityMenu - + Increment all card counters Increment all card counters - + &Untap all permanents &Untap all permanents - + R&oll die... R&oll die... - + &Create token... &Create token... - + C&reate another token C&reate another token - + Cr&eate predefined token Cr&eate predefined token @@ -8883,22 +9539,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match Mode: Exact Match - + Mode: Includes Mode: Includes - + Mode: Include/Exclude Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) Filter mode (AND/OR/NOT conjunctions of filters) @@ -8906,21 +9562,49 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter Save Filter - + Save all currently applied filters to a file Save all currently applied filters to a file - + Enter filename... Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8947,27 +9631,27 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayNameFilterWidget - - Filter by name... - Filter by name... + + Filter by name... (Exact match) + - + Load from Deck Load from Deck - + Apply all card names in currently loaded deck as exact match name filters Apply all card names in currently loaded deck as exact match name filters - + Load from Clipboard Load from Clipboard - + Apply all card names in clipboard as exact match name filters Apply all card names in clipboard as exact match name filters @@ -9031,50 +9715,115 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) Search by card name (or search expressions) - + + + Visual + + + + Loading database ... Loading database ... - + Clear all filters Clear all filters - + + Sort by: + + + + + Filter by: + + + + Save and load filters Save and load filters - + Filter by exact card name Filter by exact card name - + Filter by card sub-type Filter by card sub-type - + Filter by set Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand Draw a new sample hand - + Sample hand size Sample hand size @@ -9082,46 +9831,25 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - - Click and drag to change the sort order within the groups - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... + - + Quick search and add card Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - Configure how cards are sorted within their groups - - - - - Overlap Layout - Overlap Layout - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - Flat Layout - Flat Layout - VisualDeckStorageFolderDisplayWidget - + Deck Storage Deck Storage @@ -9177,7 +9905,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) Search by filename (or search expression) @@ -9185,22 +9913,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) Sort Alphabetically (Filename) - + Sort by Last Modified Sort by Last Modified - + Sort by Last Loaded Sort by Last Loaded @@ -9208,17 +9936,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... Loading database ... - + Refresh loaded files Refresh loaded files - + Visual Deck Storage Settings Visual Deck Storage Settings @@ -9226,43 +9954,43 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? Which warning would you like to send? - + Redact all messages from this user in all rooms Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Cancel - + Warn user for misconduct Warn user for misconduct - - + + Error Error - + User name to send a warning to can not be blank, please specify a user to warn. User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. Warning to use can not be blank, please select a valid warning to send. @@ -9270,133 +9998,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top Move selected set to the top - + Move selected set up Move selected set up - + Move selected set down Move selected set down - + Move selected set to the bottom Move selected set to the bottom - + Search by set name, code, or type Search by set name, code, or type - + Default order Default order - + Restore original art priority order Restore original art priority order - + Enable all sets Enable all sets - + Disable all sets Disable all sets - + Enable selected set(s) Enable selected set(s) - + Disable selected set(s) Disable selected set(s) - + Deck Editor Deck Editor - + Use CTRL+A to select all sets in the view. Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) - + Include cards rebalanced for Alchemy [requires restart] Include cards rebalanced for Alchemy [requires restart] - + Card Art Card Art - + How to use custom card art How to use custom card art - + Hints Hints - + Note Note - + Sorting by column allows you to find a set while not changing set priority. Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead Use the current sorting as the set priority instead - + Sorts the set priority using the same column Sorts the set priority using the same column - + Manage sets Manage sets @@ -9404,72 +10132,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) Search by card name (or search expressions) - + Ungrouped Ungrouped - + Group by Type Group by Type - + Group by Mana Value Group by Mana Value - + Group by Color Group by Color - + Unsorted Unsorted - + Sort by Name Sort by Name - + Sort by Type Sort by Type - + Sort by Mana Cost Sort by Mana Cost - + Sort by Colors Sort by Colors - + Sort by P/T Sort by P/T - + Sort by Set Sort by Set - + shuffle when closing shuffle when closing - + pile view pile view @@ -9477,7 +10205,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English English @@ -9485,12 +10213,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup Connect on startup - + Debug to file Debug to file @@ -9498,1005 +10226,1041 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window Main Window - - + + Deck Editor Deck Editor - + Game Lobby Game Lobby - + Card Counters Card Counters - + Player Counters Player Counters - + Power and Toughness Power and Toughness - + Game Phases Game Phases - + Playing Area Playing Area - + Move Selected Card Move Selected Card - + View View - + Move Top Card Move Top Card - + Move Bottom Card Move Bottom Card - + Gameplay Gameplay - + Drawing Drawing - + Chat Room Chat Room - + Game Window Game Window - + Load Deck from Clipboard Load Deck from Clipboard - - + + Replays Replays - + Tabs Tabs - + Check for Card Updates... Check for Card Updates... - + Connect... Connect... - + Disconnect Disconnect - + Exit Exit - + Full screen Full screen - + Register... Register... - + Settings... Settings... - + Start a Local Game... Start a Local Game... - + Watch Replay... Watch Replay... - + Analyze Deck (deckstats.net) Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) Analyze Deck (tappedout.net) - + Clear All Filters Clear All Filters - + Clear Selected Filter Clear Selected Filter - + Close Close - + Remove Card Remove Card - + Manage Sets... Manage Sets... - + Edit Custom Tokens... Edit Custom Tokens... - + Export Deck (decklist.org) Export Deck (decklist.org) - + Export Deck (decklist.xyz) Export Deck (decklist.xyz) - + Add Card Add Card - + Load Deck... Load Deck... - - + + Load Deck from Clipboard... Load Deck from Clipboard... - + Edit Deck in Clipboard, Annotated Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard Edit Deck in Clipboard - + New Deck New Deck - + Open Custom Pictures Folder Open Custom Pictures Folder - + Print Deck... Print Deck... - + Delete Card Delete Card - - + + Reset Layout Reset Layout - + Save Deck Save Deck - + Save Deck as... Save Deck as... - + Save Deck to Clipboard, Annotated Save Deck to Clipboard, Annotated - + Save Deck to Clipboard, Annotated (No Set Info) Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard Save Deck to Clipboard - + Save Deck to Clipboard (No Set Info) Save Deck to Clipboard (No Set Info) - + Load Local Deck... Load Local Deck... - + Load Remote Deck... Load Remote Deck... - + Set Ready to Start Set Ready to Start - + Toggle Sideboard Lock Toggle Sideboard Lock - + Add Green Counter Add Green Counter - + Remove Green Counter Remove Green Counter - + Set Green Counters... Set Green Counters... - + Add Red Counter Add Red Counter - + Remove Red Counter Remove Red Counter - + Set Red Counters... Set Red Counters... - + Add Life Counter Add Life Counter - + Show Status Bar Show Status Bar - + Unload Deck Unload Deck - + Force Start Force Start - + Add Card Counter (F) Add Card Counter (F) - + Remove Card Counter (F) Remove Card Counter (F) - + Set Card Counters (F)... Set Card Counters (F)... - + Add Card Counter (E) Add Card Counter (E) - + Remove Card Counter (E) Remove Card Counter (E) - + Set Card Counters (E)... Set Card Counters (E)... - + Add Card Counter(D) Add Card Counter(D) - + Remove Card Counter (D) Remove Card Counter (D) - + Set Card Counters (D)... Set Card Counters (D)... - + Add Card Counter (C) Add Card Counter (C) - + Remove Card Counter (C) Remove Card Counter (C) - + Set Card Counters (C)... Set Card Counters (C)... - + Add Card Counter (B) Add Card Counter (B) - + Remove Card Counter (B) Remove Card Counter (B) - + Set Card Counters (B)... Set Card Counters (B)... - + Add Card Counter (A) Add Card Counter (A) - + Remove Card Counter (A) Remove Card Counter (A) - + Set Card Counters (A)... Set Card Counters (A)... - + Remove Life Counter Remove Life Counter - + Set Life Counters... Set Life Counters... - + Add White Counter Add White Counter - + Remove White Counter Remove White Counter - + Set White Counters... Set White Counters... - + Add Blue Counter Add Blue Counter - + Remove Blue Counter Remove Blue Counter - + Set Blue Counters... Set Blue Counters... - + Add Black Counter Add Black Counter - + Remove Black Counter Remove Black Counter - + Set Black Counters... Set Black Counters... - + Add Colorless Counter Add Colorless Counter - + Remove Colorless Counter Remove Colorless Counter - + Set Colorless Counters... Set Colorless Counters... - + Add Other Counter Add Other Counter - + Remove Other Counter Remove Other Counter - + Set Other Counters... Set Other Counters... - + Increment all card counters Increment all card counters - + Add Power (+1/+0) Add Power (+1/+0) - + Remove Power (-1/-0) Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) Add Toughness (+0/+1) - + Remove Toughness (-0/-1) Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) Remove Power and Toughness (-1/-1) - + Set Power and Toughness... Set Power and Toughness... - + Reset Power and Toughness Reset Power and Toughness - + Untap Untap - + Upkeep Upkeep - + Draw Draw - + First Main Phase First Main Phase - + Start Combat Start Combat - + Attack Attack - + Block Block - + Damage Damage - + End Combat End Combat - + Second Main Phase Second Main Phase - + End End - + Next Phase Next Phase - + Next Phase Action Next Phase Action - + Next Turn Next Turn - + Hide Card in Reveal Window Hide Card in Reveal Window - + Tap / Untap Card Tap / Untap Card - + Untap All Untap All - + Toggle Untap Toggle Untap - + Turn Card Over Turn Card Over - + Peek Card Peek Card - + Play Card Play Card - + Attach Card... Attach Card... - + Unattach Card Unattach Card - + Clone Card Clone Card - + Create Token... Create Token... - + Create All Related Tokens Create All Related Tokens - + Create Another Token Create Another Token - + Set Annotation... Set Annotation... - + Select All Cards in Zone Select All Cards in Zone - + Select All Cards in Row Select All Cards in Row - + Select All Cards in Column Select All Cards in Column - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library Bottom of Library - - - - + + + + Exile Exile - - - - + + + + Graveyard Graveyard - - + + + Hand Hand - - + + Top of Library Top of Library - - - + + + Battlefield, Face Down Battlefield, Face Down - + Battlefield Battlefield - - Sort Hand - Sort Hand - - - + Library Library - + Sideboard Sideboard - + Top Cards of Library Top Cards of Library - + Bottom Cards of Library Bottom Cards of Library - + Close Recent View Close Recent View - - + + Stack Stack - - + + Graveyard (Multiple) Graveyard (Multiple) - - + + Exile (Multiple) Exile (Multiple) - + Stack Until Found Stack Until Found - + Draw Bottom Card Draw Bottom Card - + Draw Multiple Cards from Bottom... Draw Multiple Cards from Bottom... - + Draw Arrow... Draw Arrow... - + Remove Local Arrows Remove Local Arrows - + Leave Game Leave Game - + Concede Concede - + Roll Dice... Roll Dice... - + Shuffle Library Shuffle Library - + Shuffle Top Cards of Library Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library Shuffle Bottom Cards of Library - + Mulligan Mulligan - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card Draw a Card - + Draw Multiple Cards... Draw Multiple Cards... - + Undo Draw Undo Draw - + Always Reveal Top Card Always Reveal Top Card - + Always Look At Top Card Always Look At Top Card - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise Rotate View Clockwise - + Rotate View Counterclockwise Rotate View Counterclockwise - + Unfocus Text Box Unfocus Text Box - + Focus Chat Focus Chat - + Clear Chat Clear Chat - + Refresh Refresh - + Skip Forward Skip Forward - + Skip Backward Skip Backward - + Skip Forward by a lot Skip Forward by a lot - + Skip Backward by a lot Skip Backward by a lot - + Play/Pause Play/Pause - + Toggle Fast Forward Toggle Fast Forward - + Home Home - + Visual Deck Storage Visual Deck Storage - + Deck Storage Deck Storage - + Server Server - + Account Account - + Administration Administration - + Logs Logs diff --git a/cockatrice/translations/cockatrice_es.ts b/cockatrice/translations/cockatrice_es.ts index bd9f01d5d..e80e28b52 100644 --- a/cockatrice/translations/cockatrice_es.ts +++ b/cockatrice/translations/cockatrice_es.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Establecer contador... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Establecer contador - + New value for counter '%1': Nuevo valor para el contador '%1': @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh &Actualizar - + Parse Set Name and Number (if available) @@ -36,79 +36,81 @@ AbstractTabDeckEditor - + Open in new tab Abrir en nueva pestaña - + Are you sure? ¿Estás seguro? - + The decklist has been modified. Do you want to save the changes? La lista del mazo ha sido modificada. ¿Deseas guardar los cambios? - - - - - - - + + + + + + Error Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck Guardar mazo - + The deck could not be saved. El mazo no puede ser guardado. - + There are no cards in your deck to be exported No hay cartas en tu mazo para exportar + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. - No se ha seleccionado ningún mazo para ser exportado. + + Add Analytics Panel + AdminNotesDialog - + Update Notes Notas de actualización - + Admin Notes for %1 @@ -116,12 +118,12 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard Mazo principal - + Sideboard Banquillo @@ -129,22 +131,22 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds segundos - + Error Error - + Could not create themes directory at '%1'. No se pudo crear directorio de temas en '%1'. - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -155,7 +157,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -166,152 +168,165 @@ Are you sure you would like to disable this feature? - + Confirm Change - + Theme settings Preferencias del tema - + Current theme: Tema actual: - + Open themes folder Abrir carpeta de temas - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled Deshabilitado - + Menu settings - + Show keyboard shortcuts in right-click menus - + + Show game filter toolbar above list in room tab + + + + Card rendering Representación de las cartas - + Display card names on cards having a picture Mostrar nombre de las cartas en aquellas que tengan imagen - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Cambiar tamaño de las cartas al pasar el ratón por encima - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows filas - + Maximum expanded height for card view window: - + Card counters Contadores de carta - + Counter %1 - + Hand layout Disposición de la mano - + Display hand horizontally (wastes space) Mostrar la mano horizontalmente (desperdicia espacio) - + Enable left justification Habilitar justificación a la izquierda - + Table grid layout Diseño de la cuadrícula de la mesa - + Invert vertical coordinate Invertir coordenada vertical - + Minimum player count for multi-column layout: Número minimo de jugadores para usar la cuadrícula multicolumna: - + Maximum font size for information displayed on cards: Tamaño de fuente máxima para la información presentada en las cartas: + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -333,112 +348,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name banear &usuario - + ban &IP address banear dirección IP &IP - + ban client I&D banear por I&D de cliente - + Ban type Tipo de ban - + &permanent ban acceso prohibido &permanentemente - + &temporary ban acceso prohibido &temporalmente - + &Days: &Días: - + &Hours: &Horas: - + &Minutes: &Minutos: - + Duration of the ban Duración del ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Por favor, introduce el motivo de la restricción de acceso Se almacenará unicamente para moderadores y no podrá ser visto por la persona restringida. - + Please enter the reason for the ban that will be visible to the banned person. Por favor, introduce el motivo de la restricción que será visible para la persona restringida. - + Redact all messages from this user in all rooms Redactar todos los mensajes de este usuario en todas las salas - + &OK Aceptar (&O) - + &Cancel &Cancelar - + Ban user from server Banear usuario del servidor - + + - - + Error Error - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Debes seleccionar baneo por nombre, por IP, por id de cliente, o cualquier combinación de los tres. - + You must have a value in the name ban when selecting the name ban checkbox. Debes introducir un nombre cuando la casilla de baneo por nombre está activada. - + You must have a value in the ip ban when selecting the ip ban checkbox. Debes introducir una IP cuando la casilla de baneo por IP está activada. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Debes introducir un id de cliente cuando la casilla de baneo por id de cliente está activada. @@ -469,32 +484,32 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CardDatabaseModel - + Name Nombre - + Sets Ediciones - + Mana cost Coste de maná - + Card type Tipo de carta - + P/T F/R - + Color(s) Color(es) @@ -502,96 +517,101 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CardFilter - + AND Logical conjunction operator used in card filter Y - + OR Logical disjunction operator used in card filter O - + AND NOT Negated logical conjunction operator used in card filter Y NO - + OR NOT Negated logical disjunction operator used in card filter O NO - + Name Nombre - + + Name (Exact) + + + + Type Tipo - + Color Color - + Text Texto - + Set Expansión - + Mana Cost Coste de maná - + Mana Value Valor de maná - + Rarity Rareza - + Power Poder - + Toughness Resistencia - + Loyalty Lealtad - + Format Formato - + Main Type Tipo principal de carta - + Sub Type Subtipo de carta @@ -599,22 +619,22 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CardInfoFrameWidget - + Image Imagen - + Description Descripción - + Both Ambos - + View transformation @@ -622,22 +642,22 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CardInfoPictureWidget - + View related cards Ver cartas relacionadas - + Add card to deck Añadir carta al mazo - + Mainboard Mazo principal - + Sideboard Banquillo @@ -650,12 +670,22 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona Nombre: - + + Set: + + + + + Collector Number: + + + + Related cards: Cartas relacionadas: - + Unknown card: Carta desconocida: @@ -663,124 +693,124 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CardMenu - + Re&veal to... - + &All players &Todos los jugadores - + View related cards Ver cartas relacionadas - + Token: Ficha: - + All tokens Todas las fichas - + &Select All &Seleccionar todo - + S&elect Row &Seleccionar fila - + S&elect Column &Seleccionar columna - + &Play &Jugar - + &Hide &Ocultar - + Play &Face Down Jugar boca abajo - + &Tap / Untap Turn sideways or back again &Girar / Enderezar - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone &Clonar - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) &Añadir contador (%1) - + &Remove counter (%1) &Quitar contador (%1) - + &Set counters (%1)... &Establecer contadores (%1) @@ -788,7 +818,7 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CardSizeWidget - + Card Size Tamaño de carta @@ -796,133 +826,133 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CardZoneLogic - + their hand nominative su mano - + %1's hand nominative - - - their library - look at zone - su biblioteca - - - - %1's library - look at zone - - - - - of their library - top cards of zone, - de su biblioteca - - - - of %1's library - top cards of zone - - their library - reveal zone + look at zone su biblioteca %1's library + look at zone + + + + + of their library + top cards of zone, + de su biblioteca + + + + of %1's library + top cards of zone + + + + + their library + reveal zone + su biblioteca + + + + %1's library reveal zone - + their library shuffle su biblioteca - + %1's library shuffle - + their library nominative su biblioteca - + %1's library nominative - - - their graveyard - nominative - su cementerio - - - - %1's graveyard - nominative - - - - - their exile - nominative - su zona de exilio - - - - %1's exile - nominative - - - their sideboard - look at zone - su banquillo + their graveyard + nominative + su cementerio - - %1's sideboard - look at zone + + %1's graveyard + nominative - - their sideboard + + their exile nominative + su zona de exilio + + + + %1's exile + nominative + + + + + their sideboard + look at zone su banquillo - + %1's sideboard - nominative + look at zone + their sideboard + nominative + su banquillo + + + + %1's sideboard + nominative + + + + their custom zone '%1' nominative - + %1's custom zone '%2' nominative @@ -931,7 +961,7 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CockatriceXml3Parser - + Parse error at line %1 col %2: @@ -939,7 +969,7 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CockatriceXml4Parser - + Parse error at line %1 col %2: @@ -958,6 +988,37 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -969,47 +1030,47 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) Buscar por nombre de carta (o buscar expresiones) - + Add to Deck Añadir al Mazo - + Add to Sideboard Añadir al Banquillo - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards Mostrar cartas Relacionadas - + Add card to &maindeck Añadir carta al &mazo principal - + Add card to &sideboard Añadir carta al &banquillo @@ -1017,87 +1078,97 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card - + Main Type Tipo principal de carta - + Mana Cost Coste de maná - + Colors Colores - + Select Printing - + Deck Mazo - + Deck &name: &Nombre del mazo: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: &Comentarios: - + Group by: Agrupar por: - + + Format: + + + + Hash: - + &Increment number &Incrementar número - + &Decrement number &Reducir número - + &Remove row &Quitar fila - + Swap card to/from sideboard @@ -1105,17 +1176,17 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckEditorFilterDockWidget - + Filters Filtros - + &Clear all filters &Limpiar todos los filtros - + Delete selected Borrar seleccionado @@ -1123,114 +1194,114 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckEditorMenu - + &Deck Editor &Editor de mazos - + &New deck &Nuevo mazo - + &Load deck... &Cargar mazo... - + Load recent deck... Cargar mazo reciente... - + Clear Borrar - + &Save deck &Guardar mazo - + Save deck &as... Guardar mazo &como... - + Load deck from cl&ipboard... Cargar mazo desde el &portapapeles... - + Edit deck in clipboard Editar mazo en el portapapeles - - + + Annotated Anotado - - + + Not Annotated No Anotado - + Save deck to clipboard Guardar mazo al portapapeles - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close &Cerrar @@ -1238,7 +1309,7 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1246,194 +1317,227 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckEditorSettingsPage - - + + Update Spoilers Actualizar Spoilers - - + + Success Éxito - + Download URLs have been reset. Las URL de descarga se han reiniciado. - + Downloaded card pictures have been reset. Las imágenes de las cartas descargadas se han reiniciado. - + Error Error - + One or more downloaded card pictures could not be cleared. Una o más imágenes descargadas pueden no ser claras - + Add URL Añadir URL - - + + URL: URL: - - + + Edit URL Editar URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL Añadir URL - + Remove URL Borrar URL - + Day(s) Día(s) - + Updating... Actualizando... - + Choose path Elegir camino - + URL Download Priority Prioridad de descarga de URL - + Spoilers Spoilers - + Download Spoilers Automatically Descargar Spoilers automáticamente - + Spoiler Location: Localización del Spoiler: - + Last Change Último cambio - + Spoilers download automatically on launch Spoilers se descargan automáticamente en lanzamiento - + Press the button to manually update without relaunching Presione el botón para actualizar manualmente sin reiniciar - + Do not close settings until manual update is complete No cierre la ventana de configuración hasta que la actualización manual este completa. - + Download card pictures on the fly Descargar imagenes de las cartas rapidamente - + How to add a custom URL Cómo agregar una URL personalizada - + Delete Downloaded Images Eliminar imágenes descargadas - + Reset Download URLs Reiniciar descarga de URLs - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count - + Set - + Number Número - + Provider ID - + Card Carta @@ -1441,12 +1545,12 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1454,17 +1558,17 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1472,7 +1576,7 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckPreviewDeckTagsDisplayWidget - + Edit tags ... Editar etiquetas @@ -1480,62 +1584,62 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckPreviewTagDialog - + Deck Tags Manager Gestor de etiquetas de mazos - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) Añadir una nueva etiqueta (p.e., Aggro) - + Add Tag Añadir etiqueta - + Filter tags... Filtrar etiquetas... - + OK OK - + Edit default tags - + Cancel Cancelar - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. Esta etiqueta ya existe. @@ -1548,93 +1652,151 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona - + Open in deck editor - + Edit Tags Editar etiquetas - + Rename Deck Renombrar mazo - + Save Deck to Clipboard - + Annotated Anotado - + Annotated (No set info) - + Not Annotated No Anotado - + Not Annotated (No set info) - + Rename File Renombrar archivo - + Delete File Eliminar archivo - + Set Banner Card - - + + New name: Nuevo nombre: - - + + Error Error - + Rename failed - + Delete file Eliminar archivo - + Are you sure you want to delete the selected file? ¿Estás seguro de que quieres eliminar el archivo seleccionado? - + Delete failed Borrado fallido + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1652,74 +1814,74 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckViewContainer - + Load deck... Cargar mazo... - + Load remote deck... Cargar mazo remoto... - + Load from clipboard... Cargar desde el portapapeles... - + Load from website... - + Unload deck - + Ready to start Listo para empezar - + Force start Forzar inicio - + Sideboard unlocked Banquillo desbloqueado - + Sideboard locked Banquillo bloqueado - - + + Error Error - + The selected file could not be loaded. El fichero seleccionado no pudo cargarse. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice Cockatrice @@ -1752,143 +1914,143 @@ This will kick all non-ready players from the game. Descargando... - + Known Hosts Hospedajes Conocidos - + Delete the currently selected saved server - + Refresh the server list with known public servers Actualizar la lista con servidores públicos conocidos - + New Host Nueva dirección - + Name: Nombre: - + &Host: &Dirección: - + &Port: &Puerto: - + Player &name: &Nombre del jugador: - + P&assword: &Contraseña: - + &Save password &Guardar contraseña - + A&uto connect Conectarse a&utomáticamente - + Automatically connect to the most recent login when Cockatrice opens Conectar automáticamente al servidor más reciente cuando se inicie Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! ¡Si tiene algún problema en conectarse o registrarse puede contactar a nuestro equipo! - - + + Webpage Página web - + Reset Password Reiniciar Contraseña - + Forgot password? ¿Olvidó la contraseña? - + &Connect &Conectar - + Server Servidor - + Login Iniciar sesión - + Server Contact Contacto del Servidor - + Connect to Server Conectar al Servidor - + Server URL dirección URL del servidor - + Communication Port Puerto de comunicación - + Unique Server Name Nombre único para el servidor - + Connection Warning Advertencia de Conexión - + You need to name your new connection profile. Necesita dar nombre a su nuevo perfil de conexión. - + Connect Warning Advertencia de conexión - + The player name can't be empty. El nombre del jugador no puede estar vacío. @@ -1896,117 +2058,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings Recordar preferencias (&M) - + &Description: &Descripción: - + P&layers: &Jugadores: - + General General - + Game type Tipo de partida - + &Password: &Contraseña: - + Only &buddies can join Sólo los &amigos pueden participar - + Only &registered users can join Sólo los usuarios &registrados pueden participar - + Joining restrictions Restricciones de participación - + &Spectators can watch Los e&spectadores pueden observar - + Spectators &need a password to watch Los espectadores &necesitan contraseña para unirse - + Spectators can &chat Los espectadores pueden &chatear - + Spectators can see &hands Los espectadores pueden ver las &manos - + Create game as spectator Crear una partida como espectador - + Spectators Espectadores - + Starting life total: Vida inicial total: - + Open decklists in lobby - + + Create game as judge + + + + Game setup options - + &Clear Limpiar (&C) - + Create game Crear partida - + Game information Información de la partida - + Error Error - + Server error. Error del servidor. @@ -2014,97 +2181,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Nombre: - + Token Ficha - + C&olor: &Color: - + white blanco - + blue azul - + black negro - + red rojo - + green verde - + multicolor multicolor - + colorless incoloro - + &P/T: &F/R: - + &Annotation: &Anotación: - + &Destroy token when it leaves the table &Destruir la ficha cuando deje la mesa - + Create face-down (Only hides name) - + Token data Datos de la ficha - + Show &all tokens Mostrar &todas las fichas - + Show tokens from this &deck Mostrar todas las fichas de este &mazo - + Choose token from list Elegir ficha de la lista - + Create token Crear ficha @@ -2112,53 +2279,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags Editar etiquetas - + Add Añadir - + Confirm Confirmar - + Cancel Cancelar - + Enter a tag and press Enter Introduce una etiqueta y pulsa Enter - - + + - + Invalid Input - + Tag name cannot be empty! El nombre de la etiqueta no puede estar vacío! - + Duplicate Tag Duplicar etiqueta - + This tag already exists. Esta etiqueta ya existe. @@ -2166,40 +2333,40 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. Ninguna imagen seleccionada - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. Para cambiar tu avatar, elige una nueva imagen. Para eliminar tu avatar actual, confirma sin elegir una nueva imagen. - + Browse... Buscar - + Change avatar Cambiar avatar - + Open Image Abrir imagen - + Image Files (*.png *.jpg *.bmp) Archivos de imagen (*.png *.jpg *.bmp) - + Invalid image chosen. La imagen seleccionada no es válida. @@ -2207,17 +2374,17 @@ Para eliminar tu avatar actual, confirma sin elegir una nueva imagen. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error Error - + Invalid deck list. Lista de mazo inválida. @@ -2225,38 +2392,38 @@ Para eliminar tu avatar actual, confirma sin elegir una nueva imagen. DlgEditPassword - + Old password: Contraseña anterior: - + New password: Nueva contraseña: - + Confirm new password: Confirmar la nueva contraseña: - + Change password Cambiar contraseña - - + + Error Error - + Your password is too short. Su contraseña es muy corta - + The new passwords don't match. Las nuevas contraseñas no coinciden. @@ -2264,93 +2431,93 @@ Para eliminar tu avatar actual, confirma sin elegir una nueva imagen. DlgEditTokens - + &Name: &Nombre: - + C&olor: &Color: - + white blanco - + blue azul - + black negro - + red rojo - + green verde - + multicolor multicolor - + colorless incoloro - + &P/T: &F/R: - + &Annotation: &Anotación: - + Token data Información de la ficha - - + + Add token Añadir ficha - + Remove token Eliminar ficha - + Edit custom tokens Editar fichas personalizadas - + Please enter the name of the token: Por favor, introduzca el nombre de la ficha: - + Error Error - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. El nombre elegido ya existe y está siendo usado por una carta o ficha. @@ -2444,7 +2611,8 @@ Asegúrese de habilitar la edición 'Fichas' en la opción "Mante - Hide games not created by buddy + Hide games not created by buddies + Hide games not created by buddy @@ -2531,52 +2699,52 @@ Asegúrese de habilitar la edición 'Fichas' en la opción "Mante DlgForgotPasswordChallenge - + Reset Password Challenge Warning Reiniciar contraseña de desafíos con advertencia - + A problem has occurred. Please try to request a new password again. Ha ocurrido un problema. Por favor intenta solicitar una nueva contraseña. - + Enter the information of the server and the account you'd like to request a new password for. Introduce la información del servidor y de la cuenta para la que deseas solicitar una nueva contraseña. - + &Host: &Dirección: - + &Port: &Puerto: - + Player &name: &Nombre del jugador: - + Email: Correo electrónico: - + Reset Password Challenge Reiniciar contraseña de desafío - + Reset Password Challenge Error Error al reiniciar contraseña de desafío - + The email address can't be empty. La dirección de correo electrónico no puede estar vacía. @@ -2584,37 +2752,37 @@ Asegúrese de habilitar la edición 'Fichas' en la opción "Mante DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Introduce la información del servidor para la que deseas solicitar una nueva contraseña. - + &Host: &Dirección: - + &Port: &Puerto: - + Player &name: &Nombre del jugador: - + Reset Password Request Error al reiniciar la petición para una contraseña - + Reset Password Error Error al reiniciar la contraseña - + The player name can't be empty. El nombre del jugador no puede estar vacío. @@ -2622,86 +2790,86 @@ Asegúrese de habilitar la edición 'Fichas' en la opción "Mante DlgForgotPasswordReset - + Reset Password Warning Reinicia la contraseña de advertencia - + A problem has occurred. Please try to request a new password again. Ha ocurrido un problema. Por favor intenta solicitar de nuevo una nueva contraseña. - + Enter the received token and the new password in order to set your new password. Introduce el token recibido y la nueva contraseña para establecer tu nueva contraseña. - + &Host: &Dirección: - + &Port: &Puerto: - + Player &name: &Nombre del jugador: - + Token: Ficha: - - + + New Password: Nueva contraseña: - + Reset Password Reiniciar contraseña - + The player name can't be empty. El nombre del jugador no puede estar vacío. - - - - + + + + Reset Password Error Error al reiniciar contraseña - + The token can't be empty. La ficha no puede estar vacía. - + The new password can't be empty. La nueva contraseña no puede estar vacía. - + Error Error - + Your password is too short. Tu contraseña es demasiado corta. - + The passwords do not match. Las contraseñas no coinciden. @@ -2717,17 +2885,17 @@ Asegúrese de habilitar la edición 'Fichas' en la opción "Mante DlgLoadDeckFromClipboard - + Load deck from clipboard Cargar mazo del portapapeles - + Error Error - + Invalid deck list. Lista de mazo inválida. @@ -2735,43 +2903,43 @@ Asegúrese de habilitar la edición 'Fichas' en la opción "Mante DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2785,7 +2953,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck Cargar mazo @@ -2793,37 +2961,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice Cockatrice - + Invalid filter Filtro no válido @@ -2831,92 +2999,92 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Introduce tu información y la información del servidor en el que deseas registrarte. Tu correo electrónico se utilizará para verificar tu cuenta. - + &Host: &Dirección: - + &Port: &Puerto: - + Player &name: &Nombre del jugador: - + P&assword: &Contraseña: - + Password (again): Contraseña (de nuevo): - + Email: Correo electrónico: - + Email (again): Correo electrónico (de nuevo): - + Country: País: - + Undefined Indefinido - + Real name: Nombre real: - + Register to server Registrarse en el servidor - - - - + + + + Registration Warning Advertencia durante el registro - + Your password is too short. Tu contraseña es demasiado corta. - + Your passwords do not match, please try again. Tus contraseñas no coinciden, por favor inténtalo de nuevo. - + Your email addresses do not match, please try again. Tus direcciones de correo electrónico no coinciden, por favor inténtalo de nuevo. - + The player name can't be empty. El nombre del jugador no puede estar vacío. @@ -2942,40 +3110,55 @@ Tu correo electrónico se utilizará para verificar tu cuenta. DlgSelectSetForCards - + Unmodified Cards: Cartas no modificadas: - + Modified Cards: Cartas modificadas: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database Error desconocido al cargar la base de datos de cartas. - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -2992,7 +3175,7 @@ Podrías necesitar volver a ejecutar oracle para actualizar tu base de datos de ¿Quieres cambiar la ubicación de tu base de datos? - + Your card database version is too old. This can cause problems loading card information or images @@ -3009,7 +3192,7 @@ Normalmente esto se soluciona volviendo a ejecutar oracle para actualizar tu bas ¿Quieres cambiar la ubicación de tu base de datos? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3022,7 +3205,7 @@ Por favor, envía un ticket en https://github.com/Cockatrice/Cockatrice/issues c ¿Te gustaría cambiar la configuración de la ubicación de tu base de datos? - + File Error loading your card database. Would you like to change your database location setting? @@ -3031,7 +3214,7 @@ Would you like to change your database location setting? ¿Quieres cambiar la ubicación de tu base de datos? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3040,7 +3223,7 @@ Would you like to change your database location setting? ¿Quieres cambiar la ubicación de tu base de datos? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3053,59 +3236,59 @@ Por favor, envía un ticket en https://github.com/Cockatrice/Cockatrice/issues ¿Te gustaría cambiar la configuración de la ubicación de tu base de datos? - - - + + + Error Error - + The path to your deck directory is invalid. Would you like to go back and set the correct path? La ruta a tu directorio de mazos no es válida. ¿Deseas volver y seleccionar la ruta correcta? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? La ruta a tu directorio de imágenes de las cartas no es válida. ¿Deseas volver y seleccionar la ruta correcta? - + Settings Preferencias - + General General - + Appearance Apariencia - + User Interface Interfaz de usuario - + Card Sources Origen de la carta - + Chat Chat - + Sound Sonido - + Shortcuts Atajos de teclado @@ -3113,39 +3296,39 @@ Por favor, envía un ticket en https://github.com/Cockatrice/Cockatrice/issues DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground Ejecutar en primer plano - + Run in background Ejecutar en segundo plano - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time No ejecutar esta vez @@ -3153,17 +3336,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next Siguiente - + Previous Anterior - + Tip of the Day Tip del día @@ -3171,165 +3354,165 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel Canal de lanzamiento actual - + Reinstall Reinstalar - + Cancel Download Cancelar Descarga - + Open Download Page Abrir página de descarga - + Check for Client Updates Comprobar actualizaciones del cliente - - - - + + + + Error Error - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice no fue creado con soporte para SSL, ¡por lo que no se pueden descargar actualizaciones automáticamente! Por favor, visita la página de descargas para actualizarlo manualmente. - + Downloading update: %1 Descargando actualización: %1 - + Checking for updates... Buscando actualizaciones... - + Finished checking for updates Comprobación de actualizaciones finalizada - + No Update Available Actualización no disponible - + Cockatrice is up to date! ¡Cockatrice se ha actualizado! - + You are already running the latest version available in the chosen release channel. Actualmente está ejecutando la última versión disponible en el canal de distribución. - + Current version Versión actual - + Selected release channel Seleccionado canal de lanzamiento - - + + Update Available Actualización disponible - - + + A new version of Cockatrice is available! ¡Una nueva version de Cockatrice está disponible! - - + + New version Nueva versión - - + + Released Lanzado - - + + Changelog Registro de cambios - + Do you want to update now? ¿Quieres actualizar ahora? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + + Update Error Error en la actualización - + An error occurred while checking for updates: Ha ocurrido un error mientras se buscaban actualizaciones: - + An error occurred while downloading an update: Ha ocurrido un error mientras se descargaba una actualización: - + Installing... Instalando... - + Cockatrice is unable to open the installer. Cockatrice es incapaz de abrir el instalador. - + Try to update manually by closing Cockatrice and running the installer. Intente actualizar manualmente cerrando Cockatrice y ejecutando el instalador. - + Download location Ubicación de descarga @@ -3337,21 +3520,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing Limpiar el historial al cerrar - + Copy to clipboard - + Debug Log Registro de depuración + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3421,33 +3736,39 @@ You may have to manually download the new version. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - Presupuesto - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3463,22 +3784,22 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed Borrado fallido - + Failed to delete filter '%1'. @@ -3486,22 +3807,22 @@ You may have to manually download the new version. GameEventHandler - + kicked by game host or moderator expulsado por el anfitrión del juego o moderador - + player left the game jugador ha dejado la partida - + player disconnected from server jugador se ha desconectado del servidor - + reason unknown razón desconocida @@ -3509,226 +3830,279 @@ You may have to manually download the new version. GameSelector - - - - - - + + + + + + Error Error - + Please join the appropriate room first. Por favor, entre a la sala adecuada primero. - + Wrong password. Contraseña incorrecta. - + Spectators are not allowed in this game. No se permiten espectadores en esta partida. - + The game is already full. La partida no tiene plazas libres. - + The game does not exist any more. La partida ya no existe. - + This game is only open to registered users. Esta partida está abierta sólo a usuarios registrados. - + This game is only open to its creator's buddies. Esta partida está abierta sólo a los amigos del creador. - + You are being ignored by the creator of this game. Estás siendo ignorado por el creador de la partida. - + Join Game Unirse a partida - + Spectate Game Espectar partida - + Game Information Información de la partida - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game Entrar en la partida - + Password: Contraseña: - + Please join the respective room first. Por favor, entre primero en la sala respectiva. - + &Filter games &Filtrar partidas - + C&lear filter &Limpiar filtro - + C&reate C&rear - + &Join Unirse (&J) - + + Join as judge + + + + J&oin as spectator Entrar como e&spectador - + + Join as judge spectator + + + + Games shown: %1 / %2 Juegos mostrados: %1 / %2 - + Games Partidas + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day >1 day - + %1%2 hr short age in hours %1%2 hora%1%2 horas%1%2 horas - + new nuevo - + %1%2 min short age in minutes %1%2 min%1%2 min%1%2 min - + password contraseña - + buddies only solo amigos - + reg. users only solo usuarios registrados - + open decklists - - + + can chat pueden usar el chat - + see hands ver manos - + can see hands puede ver manos - + not allowed no permitidos - + Room Sala - + Age Edad - + Description Descripción - + Creator Creador - + Type Tipo - + Restrictions Restricciones - + Players Jugadores - + Spectators Espectadores @@ -3736,143 +4110,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths Reiniciar todos los caminos - + All paths have been reset Todos los caminos se han reiniciado - - - - - - - + + + + + + + Choose path Elija ruta - + Personal settings Preferencias personales - + Language: Idioma: - + Paths (editing disabled in portable mode) Rutas (edición deshabilitada en modo portatil) - + Paths Rutas - + How to help with translations Cómo ayudar con las traducciones - + Decks directory: Directorio de mazos: - + Filters directory: Directorio de filtros: - + Replays directory: Directorio de repeticiones: - + Pictures directory: Directorio de imágenes: - + Card database: Base de datos de cartas: - + Custom database directory: Directorio de base de datos personalizado: - + Token database: Base de datos de tokens: - + Update channel Actualizar canal - + Check for client updates on startup Comprobar actualizaciones del cliente al arrancar - + Check for card database updates on startup Comprobar actualizaciones de la base de datos de cartas al arrancar - + Don't check No comprobar - + Prompt for update - + Always update in the background Siempre actualizar en segundo plano - + Check for card database updates every Comprobar actualizaciones de la base de datos de cartas cada - + days días - + Notify if a feature supported by the server is missing in my client Notificar si hace falta en mi cliente alguna característica soportada por el servidor - + Automatically run Oracle when running a new version of Cockatrice Oracle se ejecutará automáticamente cuando se ejecuté una nueva versión de Cockatrice - + Show tips on startup Mostrar tips al inicio - + Last update check on %1 (%2 days ago) @@ -3880,47 +4254,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard &Cementerio - + &View graveyard Ver &cementerio - + &Move graveyard to... &Mover cementerio a... - + &Top of library &Parte superior de la biblioteca - + &Bottom of library &Parte inferior de la biblioteca - + &All players - + &Hand &Mano - + &Exile &Exilio - + Reveal random card to... Mostrar una carta al azar a... @@ -3928,111 +4302,141 @@ You may have to manually download the new version. HandMenu - + &Hand &Mano - + &View hand &Ver mano - - &Sort hand - &Ordenar mano + + Sort hand by... + - - Take &mulligan - Hacer &mulligan + + Name + - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... &Mover mano a... - + &Top of library &Parte superior de la biblioteca - + &Bottom of library &Parte inferior de la biblioteca - + &Graveyard &Cementerio - + &Exile &Exilio - + &Reveal hand to... &Mostrar mano a... - - Reveal r&andom card to... - Mostrar una carta al azar a... + + + All players + - - - &All players - + + Reveal r&andom card to... + Mostrar una carta al azar a... HomeWidget - + Create New Deck Crear nuevo mazo - + Browse Decks Buscar mazos - + Browse Card Database Buscar base de datos de cartas - + Browse EDHRec - + + Browse Archidekt + + + + View Replays Ver repeticiones - + Quit Quitar - + Connecting... Conectando... - + Connect Conectar - + Play Jugar @@ -4234,61 +4638,61 @@ You may have to manually download the new version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Este servidor ha alcanzado su capacidad máxima de usuarios, por favor inténtalo más tarde. - + There are too many concurrent connections from your address. Hay demasiadas conexiones simultaneas desde tu dirección. - + Banned by moderator Baneado por el moderador - + Expected end time: %1 Tiempo de finalización estimado: %1 - + This ban lasts indefinitely. Este ban permanecerá indefinidamente. - + Scheduled server shutdown. Apagado programado del servidor. - - + + Invalid username. El nombre de usuario no es válido. - + You have been logged out due to logging in at another location. Se ha cerrado tu sesión debido a que se ha iniciado en otra ubicación. - + Connection closed Conexión cerrada - + The server has terminated your connection. Reason: %1 El servidor ha finalizado tu conexión. Motivo: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -4301,583 +4705,583 @@ Todas las partidas en curso se cerraran. Motivo para el apagado: %1 - + Scheduled server shutdown Apagado programado del servidor - - + + Success Éxito - + Registration accepted. Will now login. Registro aceptado. Iniciando sesión. - + Account activation accepted. Will now login. Activación de la cuenta aceptada. Iniciando sesión. - + Number of players Número de jugadores - + Please enter the number of players. Por favor, introduzca el número de jugadores. - - + + Player %1 Jugador %1 - + Load replay Cargar repetición - + About Cockatrice Acerca de Cockatrice - + Version Versión - + Cockatrice Webpage Página de Cockatrice - + Project Manager: Jefe de Proyecto: - + Past Project Managers: Antiguos Jefes de Proyecto: - + Developers: Desarrolladores: - + Our Developers Nuestros desarrolladores - + Help Develop! ¡Ayuda a desarrollar! - + Translators: Traductores: - + Our Translators Nuestros Traductores - + Help Translate! ¡Ayuda a traducir! - + Support: Soporte: - + Report an Issue Informa de un problema - + Troubleshooting Problemas frecuentes - + F.A.Q. Preguntas frecuentes - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error Error - + Server timeout Tiempo de espera del servidor agotado - + Failed Login Inicio de sesión fallido - + Your client seems to be missing features this server requires for connection. Tu cliente parece no tener las características requeridas para la conexión de este servidor. - + To update your client, go to 'Help -> Check for Client Updates'. Para actualizar tu cliente, ir a 'Ayuda->Marcar para Actualizaciones del Cliente' - + Incorrect username or password. Please check your authentication information and try again. Nombre de usuario o contraseña incorrecto. Por favor, verifica tu información de autentificación y vuelve a intentarlo. - + There is already an active session using this user name. Please close that session first and re-login. Ya existe una sesión activa usando ese nombre de usuario. Por favor, cierra esa sesión primero y reintentalo. - - + + You are banned until %1. Estás baneado hasta %1. - - + + You are banned indefinitely. Estás baneado indefinidamente. - + This server requires user registration. Do you want to register now? Este servidor requiere el registro de usuarios. ¿Quieres registrarte ahora? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. Este servidor exige IDs de cliente. Tu cliente no está generando un ID o estás ejecutando un cliente modificado. Cierra y vuelve a abrir el cliente para intentarlo de nuevo. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. Ha ocurrido un error interno. Cierra y vuelve a abrir Cockatrice antes de intentar de nuevo. Si el error persiste, asegúrate de que estás ejecutando la versión más reciente del software y contacta a los desarrolladores del software si es necesario. - + Account activation Activación de cuenta - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Tu cuenta aún no ha sido activada. Tienes que proporcionar el código recibido en el email de activación - + Server Full Servidor Lleno - + Unknown login error: %1 Error de inicio de sesión desconocido: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Esto suele ser debido a que tu versión del cliente está desfasada y el servidor ha enviado una respuesta que tu cliente no comprende. - + Your username must respect these rules: Tu nombre de usuario debe: - + is %1 - %2 characters long Tener de %1 a %2 caracteres de longitud. - + can %1 contain lowercase characters %1 Puede tener minúsculas. - - - - + + + + NOT NO - + can %1 contain uppercase characters %1 Puede tener mayúsculas. - + can %1 contain numeric characters %1 Puede tener caracteres númericos. - + can contain the following punctuation: %1 Puede tener los siguientes signos de puntuación: %1 - + first character can %1 be a punctuation mark El primer caracter %1 puede ser un signo de puntuación. - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 No puede contener ninguna de las siguientes palabras: %1 - + can not match any of the following expressions: %1 No puede coincidir ninguna de las siguientes expresiones: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Sólo puedes usar A-Z, a-z, 0-9, _, ., y - en tu nombre de usuario. - - - - - - + + + + + + Registration denied Registro denegado - + Registration is currently disabled on this server El registro está actualmente deshabilitado en este servidor - + There is already an existing account with the same user name. Ya existe una cuenta con ese nombre. - + It's mandatory to specify a valid email address when registering. Es obligatorio especificar una dirección de correo válida al registrarse. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. Parece ser que usted está intentando registrar una cuenta nueva en este servidor; sin embargo usted ya tiene una cuetna registrada con el correo provisto. Este servidor restringe el número de cuentas que un usuario puede registrar por correo electrónico. Por favor contacte al operador del servidor para asistencia o para obtener la información de sus credenciales. - + Password too short. La contraseña es demasiado corta. - + Registration failed for a technical problem on the server. El registro falló debido a un problema técnico en el servidor. - + The connection to the server has been lost. Se ha perdido la conexión con el servidor. - + Unknown registration error: %1 Error de registro desconocido: %1 - + Account activation failed La activación de cuenta falló. - + Socket error: %1 Error del Socket: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Estás intentando conectar a un servidor obsoleto. Por favor, usa una versión anterior de Cockatrice o conecta a un servidor apropiado. La versión local es %1, la versión remota es %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Tu cliente de Cockatrice está obsoleto. Por favor, actualiza tu versión de Cockatrice. La versión local es %1, la versión remota es %2. - + Connecting to %1... Conectando a %1... - + Registering to %1 as %2... Registrando en %1 como %2... - + Disconnected Desconectado - + Connected, logging in at %1 Conectado, hora de inicio de sesión: %1 + - Requesting forgotten password to %1 as %2... Solicitando contraseña olvidada de %1 como %2... - + &Connect... &Conectar... - + &Disconnect &Desconectar - + Start &local game... Empezar partida &local... - + &Watch replay... &Ver repetición... - + &Full screen &Pantalla completa - + &Register to server... &Registrarse en el servidor... - + &Restore password... &Restaurar contraseña... - + &Settings... &Preferencias... - + &Exit &Salir - + A&ctions A&cciones - + &Cockatrice &Cockatrice - + C&ard Database Base de datos de c&artas - + &Manage sets... &Mantenedor de ediciones... - + Edit custom &tokens... Editar &fichas personalizadas... - + Open custom image folder Abrir carpeta de imágenes personalizadas - + Open custom sets folder Abrir carpeta de ediciones personalizadas - + Add custom sets/cards Agregar cartas/ediciones personalizadas - + Reload card database Recargar la base de datos de cartas - + Tabs Pestañas - + &Help A&yuda - + &About Cockatrice &Acerca de Cockatrice - + &Tip of the Day &Tip del Día - + Check for Client Updates Buscar Actualizaciones de Cliente - + Check for Card Updates... Comprobar actualizaciones de cartas... - + Check for Card Updates (Automatic) Comprobar actualizaciones de cartas (Automático) - + Show Status Bar Mostrar barra de estatus - + View &Debug Log Ver &registro de debugs - + Open Settings Folder Abrir carpeta de preferencias - + Show/Hide Mostrar/Ocultar - + New Version Nueva Versión - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Felicitaciones se ha actualizado Cockatrice %1! Oracle actualizará tu base de datos de cartas. - + Cockatrice installed Cockatrice está instalado. - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. ¡Felicitaciones! Se ha instalado Cockatrice %1 Ahora se ejecutará Oracle para instalar la base de datos de cartas inicial. - + Card database Base de datos de cartas - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -4886,29 +5290,29 @@ If unsure or first time user, choose "Yes" Si no está seguro o es su primera vez, seleccione "Sí" - - + + Yes Si - - + + No No - + Open settings Abrir preferencias - + New sets found Nuevas ediciones encontradas - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -4921,17 +5325,17 @@ Códigos de sets: %1 ¿Quieres habilitarlos? - + View sets Ver ediciones - + Welcome Bienvenido/a - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -4940,64 +5344,64 @@ Todas las ediciones en la base de datos de cartas han sido habilitadas. Aprenda más sobre cambiar el orden de la edición o deshabilitando ediciones especificas y otras preferencias en la opción "Mantenedor de ediciones". - - + + Information Información - + A card database update is already running. La actualización de la base de datos de cartas ya está en marcha. - + Unable to run the card database updater: Imposible iniciar el actualizador de la base de datos de cartas: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. Ocurrió un error desconocido. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5007,674 +5411,844 @@ Esto no debería ser un problema, pero este mensaje podría significar que hay u Para actualizar tu cliente, ve a Ayuda -> Buscar Actualizaciones. - - - - - + + + + + Load sets/cards Cargar ediciones/cartas - + Selected file cannot be found. El archivo seleccionado no fue encontrado. - + You can only import XML databases at this time. Solo puedes importar bases de datos en XML en este momento. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Las nuevas ediciones/cartas se han agregado correctamente. Cockatrice está recargando la base de datos de cartas. - + Sets/cards failed to import. Las ediciones/cartas fallaron al ser importadas. - - - + + + Reset Password Reiniciar contraseña - + Your password has been reset successfully, you can now log in using the new credentials. Tu contraseña se ha reiniciado con éxito, ahora puedes conectarte con las nuevas credenciales. - + Failed to reset user account password, please contact the server operator to reset your password. No se pudo reiniciar la contraseña, por favor contacte con el operador del servidor para reiniciar su contraseña. - + Activation request received, please check your email for an activation token. Petición de activación recibida, por favor comprueba tu email para el código de activación. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base Base de maná + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve Curva de maná + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion Devoción de maná + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play del juego - + from their graveyard de su cementerio - + from exile del exilio - + from their hand de su mano - + the top card of %1's library la carta superior de la biblioteca de %1 - + the top card of their library la parte superior de su biblioteca - + from the top of %1's library desde la parte superior de la biblioteca de %1 - + from the top of their library Desde la parte superior de su biblioteca - + the bottom card of %1's library la carta de la parte inferior de la biblioteca de %1 - + the bottom card of their library La carta de la parte inferior de su biblioteca - + from the bottom of %1's library desde la parte inferior de la biblioteca de %1 - + from the bottom of their library Desde la parte inferior de su biblioteca - + from %1's library desde la biblioteca de %1 - + from their library desde su biblioteca - + from sideboard desde el banquillo - + from the stack desde la pila - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 está manteniendo la carta superior %2 revelada. - + %1 is not revealing the top card %2 any longer. %1 ya no mantiene revelada la carta superior %2. - + %1 can now look at top card %2 at any time. %1 ahora puede ver la carta superior de %2 en cualquier momento. - + %1 no longer can look at top card %2 at any time. %1 ya no puede ver la carta superior de %2 en cualquier momento. - + %1 attaches %2 to %3's %4. %1 anexa %2 a %3's %4. - + %1 has conceded the game. %1 ha concedido la partida. - + %1 has unconceded the game. %1 ha concedido la partida - + %1 has restored connection to the game. %1 ha recuperado la conexión a la partida. - + %1 has lost connection to the game. %1 ha perdido la conexión a la partida. - + %1 points from their %2 to themselves. %1 apunta desde su %2 a si mismo. - + %1 points from their %2 to %3. %1 apunta desde su %2 a %3. - + %1 points from %2's %3 to themselves. %1 apunta desde el %3 de %2 a si mismo. - + %1 points from %2's %3 to %4. %1 apunta desde el %3 de %2 a %4. - + %1 points from their %2 to their %3. %1 apunta de su %2 a su %3. - + %1 points from their %2 to %3's %4. %1 apunta desde su %2 al %4 de %3. - + %1 points from %2's %3 to their own %4. %1 apunta desde el %3 de %2 a su %4. - + %1 points from %2's %3 to %4's %5. %1 apunta desde el %3 de %2 al %5 de %4. - + %1 creates a face down token. - + %1 creates token: %2%3. %1 crea una ficha: %2%3. - + %1 has loaded a deck (%2). %1 ha cargado un mazo (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 ha cargado un mazo con %2 cartas en el banquillo (%3). - + %1 destroys %2. %1 destruye %2. - + a card una carta - + %1 gives %2 control over %3. %1 entrega a %2 el control sobre %3. - + %1 puts %2 into play%3 face down. %1 pone %2 en juego %3 bocabajo. - + %1 puts %2 into play%3. %1 pone %2 en juego %3. - + %1 puts %2%3 into their graveyard. %1 pone %2%3 en su cementerio. - + %1 exiles %2%3. %1 exilia %2%3. - + %1 moves %2%3 to their hand. %1 mueve %2%3 a su mano. - + %1 puts %2%3 into their library. %1 pone %2%3 en su biblioteca. - + %1 puts %2%3 onto the bottom of their library. %1 pone %2%3 al fondo de su biblioteca. - + %1 puts %2%3 on top of their library. %1 pone %2%3 en la parte superior de su biblioteca. - + %1 puts %2%3 into their library %4 cards from the top. %1 pone %2%3 en la biblioteca de su propietario en el %4 lugar desde la parte superior - + %1 moves %2%3 to sideboard. %1 mueve %2%3 al banquillo. - + %1 plays %2%3. %1 juega %2%3. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). %1 roba %2 carta(s).%1 roba %2 c%1 roba %2 c - + %1 is looking at %2. %1 está mirando a %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom parte inferior - + top parte superior - + %1 turns %2 face-down. %1 pone %2 boca abajo. - + %1 turns %2 face-up. %1 pone %2 boca arriba. - + The game has been closed. La partida ha sido cerrada. - + The game has started. La partida ha comenzado. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 se ha unido a la partida. - + %1 is now watching the game. %1 está ahora observando la partida. - + You have been kicked out of the game. Has sido expulsado de la partida. - + %1 has left the game (%2). %1 ha dejado la partida (%2). - + %1 is not watching the game any more (%2). %1 ya no está viendo el juego (%2). - + %1 is not ready to start the game any more. %1 está preparado/a para empezar la partida. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 barajea su mazo y roba una nueva mano de %2 carta(s).%1 barajea su mazo y roba una nueva mano de %2 carta(s)%1 barajea su mazo y roba una nueva mano de %2 carta(s) - + %1 shuffles their deck and draws a new hand. %1 Baraja su maso y robas una nueva mano. - + You are watching a replay of game #%1. Estás viendo una repetición de la partida #%1. - + %1 is ready to start the game. %1 está preparado/a para empezar la partida. - + cards an unknown amount of cards cartas - + %1 card(s) a card for singular, %1 cards for plural %1 carta(s)%1 cars%1 cars - + %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 revela %2 a %3. - + %1 reveals %2. %1 revela %2. - + %1 randomly reveals %2%3 to %4. %1 revela al azar %2%3 a %4. - + %1 randomly reveals %2%3. %1 revela al azar %2%3. - + %1 peeks at face down card #%2. %1 está mirando la carta #%2 boca abajo. - + %1 peeks at face down card #%2: %3. %1 mira la carta boca abajo #%2: %3. - + %1 reveals %2%3 to %4. %1 revela %2%3 a %4. - + %1 reveals %2%3. %1 revela %2%3. - + %1 reversed turn order, now it's %2. %1 Orden del turno invertido, ahora es %2. - + reversed Invertido. - + normal Normal. - + Heads Cara - + Tails Cruz - + %1 flipped a coin. It landed as %2. %1 tira una moneda. Ha salido %2. - + %1 rolls a %2 with a %3-sided die. %1 sacó un %2 con un dado de %3 caras. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. Turno %1. - + %1 sets annotation of %2 to %3. %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 establece el contador %2 a %3 (%4%5). - + %1 sets %2 to not untap normally. %1 establece que %2 no se endereze normalmente. - + %1 sets %2 to untap normally. %1 establece que %2 se endereze normalmente. - + %1 removes the PT of %2. %1 elimina la F/R de %2 - + %1 changes the PT of %2 from nothing to %4. %1 cambia la F/R de %2 de nada a %4. - + %1 changes the PT of %2 from %3 to %4. %1 cambia la F/R de %2 de %3 a %4. - + %1 has locked their sideboard. %1 ha bloqueado su banquillo. - + %1 has unlocked their sideboard. %1 ha desbloqueado su banquillo. - + %1 taps their permanents. %1 gira sus permanentes. - + %1 untaps their permanents. %1 ha enderezado sus permanentes. - + %1 taps %2. %1 gira %2. - + %1 untaps %2. %1 endereza %2. - + %1 shuffles %2. %1 baraja %2. - + %1 shuffles the bottom %3 cards of %2. %1 barajea las últimas %3 cartas de %2. - + %1 shuffles the top %3 cards of %2. %1 barajea las primeras %3 cartas de %2. - + %1 shuffles cards %3 - %4 of %2. %1 barajea las cartas %3 - %4 de %2. - + %1 unattaches %2. %1 desanexa %2. - + %1 undoes their last draw. %1 deshace su último robo. - + %1 undoes their last draw (%2). %1 deshace su último robo (%2). @@ -5682,110 +6256,110 @@ Cockatrice está recargando la base de datos de cartas. MessagesSettingsPage - + Word1 Word2 Word3 Palabra1 Palabra2 Palabra3 - + Add New Message Añadir nuevo mensaje - + Edit Message Editar mensaje - + Remove Message Borrar mensaje - + Add message Añadir mensaje - - + + Message: Mensaje: - + Edit message Editar mensaje - + Chat settings Preferencias de Chat - + Custom alert words Palabras de alerta personalizadas - + Enable chat mentions Habilitar las menciones en el chat - + Enable mention completer Habilitar completado de menciones - + In-game message macros Macros para mensajes durante la partida - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users Ignorar mensajes de sala de usuarios sin registrar - + Ignore private messages sent by unregistered users Ignorar mensajes privados de usuarios sin registrar - - + + Invert text color Invertir el color del texto - + Enable desktop notifications for private messages Habilitar notificaciones de escritorio para los mensajes privados - + Enable desktop notification for mentions Habilitar notificaciones de escritorio para menciones - + Enable room message history on join Habilitar historial de mensajes de sala al unirse - - + + (Color is hexadecimal) (El color es hexadecimal) - + Separate words with a space, alphanumeric characters only Separa las palabras con un espacio, solo caracteres alfanuméricos @@ -5902,62 +6476,62 @@ Cockatrice está recargando la base de datos de cartas. Phase - + Unknown Phase Fase Desconocida - + Untap Endereza - + Upkeep Mantenimiento - + Draw Dibuja - + First Main Primero Principal - + Beginning of Combat Comienzo del Combate - + Declare Attackers Declarar atacantes - + Declare Blockers Declarar bloqueadores - + Combat Damage Daño de combate - + End of Combat Fin del Combate - + Second Main Segundo Principal - + End/Cleanup Paso Final/Limpieza @@ -6023,7 +6597,7 @@ Cockatrice está recargando la base de datos de cartas. PictureLoader - + en code for scryfall's language property, not available for all languages es @@ -6032,134 +6606,134 @@ Cockatrice está recargando la base de datos de cartas. PlayerActions - + View top cards of library Ver cartas de la parte superior de la biblioteca - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) Número de cartas: (máx. %1) - + View bottom cards of library Ver cartas de la parte inferior de la biblioteca - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand Robar mano - + 0 and lower are in comparison to current hand size - + Draw cards Robar cartas - + Move top cards to grave - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens Crear fichas - - + + Number: Número: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness Cambiar fuerza/resistencia - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters Establecer contadores @@ -6167,17 +6741,17 @@ Cockatrice está recargando la base de datos de cartas. PlayerMenu - + Player "%1" - + &Counters - + S&ay @@ -6185,7 +6759,7 @@ Cockatrice está recargando la base de datos de cartas. PrintingSelector - + Display Navigation Buttons @@ -6193,22 +6767,22 @@ Cockatrice está recargando la base de datos de cartas. PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6224,17 +6798,17 @@ Cockatrice está recargando la base de datos de cartas. PrintingSelectorCardSelectionWidget - + Previous Card in Deck - + Bulk Selection - + Next Card in Deck Siguiente carta en el mazo @@ -6242,28 +6816,28 @@ Cockatrice está recargando la base de datos de cartas. PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date Fecha de lanzamiento - - + + Descending Descendente - + Ascending Ascendente @@ -6329,37 +6903,37 @@ Cockatrice está recargando la base de datos de cartas. QMenuBar - + Services Servicios - + Hide %1 Ocultar %1 - + Hide Others Ocultar otros - + Show All Mostrar todo - + Preferences... Preferencias... - + Quit %1 Salir %1 - + About %1 Sobre %1 @@ -6367,42 +6941,42 @@ Cockatrice está recargando la base de datos de cartas. QObject - + Cockatrice card database (*.xml) Base de datos de cartas de Cockatrice (*.xml) - + All files (*.*) Todos los archivos (*.*) - + Cockatrice replays (*.cor) Repeticiones de Cockatrice (*.cor) - + Maindeck Mazo principal - + Sideboard Banquillo - + Tokens Fichas - + Overwrite Existing File? Sobrescribir archivo existente? - + A .cod version of this deck already exists. Overwrite it? Ya existe una versión .cod de este mazo. Sobrescribirla? @@ -6410,92 +6984,92 @@ Cockatrice está recargando la base de datos de cartas. QPlatformTheme - + OK OK - + Save Guardar - + Save All Guardar todo - + Open Abrir - + &Yes &Sí - + Yes to &All Sí a &Todo - + &No &No - + N&o to All N&o a todo - + Abort Abortar - + Retry Volver a intentar - + Ignore Ignorar - + Close Cerrar - + Cancel Cancelar - + Discard Descartar - + Help Ayuda - + Apply Aplicar - + Reset Reiniciar - + Restore Defaults Restaurar predeterminados @@ -6592,37 +7166,37 @@ Cockatrice está recargando la base de datos de cartas. RoomSelector - + Rooms Salas - + Joi&n E&ntrar - + Room Sala - + Description Descripción - + Permissions Permisos - + Players Jugadores - + Games Partidas @@ -6663,27 +7237,27 @@ Cockatrice está recargando la base de datos de cartas. SetsModel - + Enabled Activado - + Set type Tipo de set - + Set code Código de set - + Long name Nombre largo - + Release date Fecha de lanzamiento @@ -6691,53 +7265,53 @@ Cockatrice está recargando la base de datos de cartas. ShortcutSettingsPage - - + + Restore all default shortcuts Restaurar atajos por defecto - + Do you really want to restore all default shortcuts? ¿Realmente quieres restablecer los atajos predeterminados? - + Clear all default shortcuts Limpiar todos los atajos predeterminados - + Do you really want to clear all shortcuts? ¿Realmente quieres limpiar los atajos predeterminados? - + Section: Sección: - + Action: Acción: - + Shortcut: Atajo: - + How to set custom shortcuts Cómo fijar atajos personalizados - + Clear all shortcuts - + Search by shortcut name @@ -6745,12 +7319,12 @@ Cockatrice está recargando la base de datos de cartas. ShortcutTreeView - + Action Acción - + Shortcut Atajo @@ -6758,14 +7332,14 @@ Cockatrice está recargando la base de datos de cartas. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! Su archivo de configuración contiene atajos inválidos. Por favor, revise su configuración de atajos. - + The following shortcuts have been set to default: Los siguientes atajos han sido ajustados a como eran por defecto: @@ -6792,12 +7366,12 @@ Por favor, revise su configuración de atajos. SideboardMenu - + &Sideboard &Banquillo - + &View sideboard @@ -6805,27 +7379,27 @@ Por favor, revise su configuración de atajos. SoundSettingsPage - + Enable &sounds Activar &sonidos - + Current sounds theme: Tema de sonidos actual: - + Test system sound engine Probar el audio del sistema - + Sound settings Preferencias de sonido - + Master volume Volumen maestro @@ -6833,48 +7407,48 @@ Por favor, revise su configuración de atajos. SpoilerBackgroundUpdater - + Spoilers season has ended La temporada de Spoilers ha terminado - + Deleting spoiler.xml. Please run Oracle Eliminando spoiler.xml Por favor, ejecute Oracle - - + + Spoilers download failed Error al descargar Spoilers - + No internet connection No hay conexión a internet - + Error Error - + Spoilers already up to date Los spoilers ya están actualizados - + No new spoilers added No se han añadido nuevos spoilers - + Spoilers have been updated! Los Spoilers han sido actualizados! - + Last change: Último cambio: @@ -7038,56 +7612,123 @@ Por favor, revise su configuración de atajos. + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info Información de la carta - + Deck Mazo - + Filters Filtros - + &View &Ver - + + Card Database + + + + Printing - - - - + + + + + Visible Visible - - - - + + + + + Floating Flotante - + Reset layout Reinicializar la disposición - + Deck: %1 Mazo: %1 @@ -7095,61 +7736,61 @@ Por favor, revise su configuración de atajos. TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info Información de la carta - - + + Deck Mazo - - + + Filters - + &View - + Printing - - - - + + + + Visible Visible - - - - + + + + Floating - + Reset layout @@ -7157,22 +7798,22 @@ Por favor, revise su configuración de atajos. TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7180,134 +7821,134 @@ Por favor, revise su configuración de atajos. TabDeckStorage - + Local file system Sistema de archivos local - + Server deck storage Almacen de mazos del servidor - - + + Open in deck editor Abrir en el editor de mazos - + Rename deck or folder - + Upload deck Subir mazo - + Download deck Descargar mazo + - - - + + New folder Nueva carpeta + - Delete Borrar - + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + Error Error - + Rename failed - - + + Invalid deck file Archivo de baraja inválido - + Enter deck name Introduzca el nombre del mazo - + This decklist does not have a name. Please enter a name: Esta lista de mazo no tiene un nombre. Por favor, introduce un nombre: - + Unnamed deck Mazo sin nombre - + Failed to upload deck to server Fallo al subir baraja al servidor - + Delete local file Borrar fichero local - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + Name of new folder: Nombre de la nueva carpeta: @@ -7320,17 +7961,17 @@ Por favor, introduce un nombre: TabDeckStorageVisual - + Visual Deck Storage - + Error Error - + Could not open deck at %1 @@ -7371,7 +8012,7 @@ Por favor, introduce un nombre: Buscar - + EDHRec: @@ -7379,197 +8020,197 @@ Por favor, introduce un nombre: TabGame - - - + + + Replay Repetir - - + + Game Partida - - + + Player List Lista de jugadores - - + + Card Info Información de la carta - - + + Messages Mensajes - - + + Replay Timeline Historial de partidas - + &Phases &Fases - + &Game &Partida - + Next &phase Próxima &fase - + Next phase with &action Próxima fase con &acción - + Next &turn Próximo &turno - + Reverse turn order Orden del turno invertido. - + &Remove all local arrows &Retirar todas las flechas locales - + Rotate View Cl&ockwise Girar en sentido horario - + Rotate View Co&unterclockwise Girar en sentido antih&orario (&U) - + Game &information &Información de la partida - + Un&concede - - - + + + &Concede &Conceder - + &Leave game &Abandonar la partida - + C&lose replay &Cerrar repetición - + &Focus Chat &Resaltar chat - + &Say: &Decir: - + Selected cards Cartas seleccionadas - + &View &Ver - - + + + - Visible Visible - - + + + - Floating Flotante - + Reset layout Resetear la distribución - + Concede Conceder - + Are you sure you want to concede this game? ¿Estás seguro de que quieres conceder esta partida? - + Unconcede Revertir rendición - + You have already conceded. Do you want to return to this game? Ya te rendiste. ¿Quieres regresar a este juego? - + Leave game Abandonar la partida - + Are you sure you want to leave this game? ¿Estás seguro de que quieres abandonar la partida? - + A player has joined game #%1 Un jugador se ha unido a la partida #%1 - + %1 has joined the game %1 se ha unido a la partida - + You have been kicked out of the game. Has sido expulsado de la partida. @@ -7577,7 +8218,7 @@ Por favor, introduce un nombre: TabHome - + Home Inicio @@ -7585,158 +8226,158 @@ Por favor, introduce un nombre: TabLog - + Logs Historial - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs Registros de sala - + Game Logs Registros de partidas - + Chat Logs Registros de chat - - + + Error Error - + You must select at least one filter. Debes seleccionar como mínimo un filtro. - + You have to select a valid number of days to locate. Debes seleccionar un número de días válido para buscar. - + Username: Nombre de usuario: - + IP Address: Dirección IP: - + Game Name: Nombre de partida: - + GameID: ID de partida: - + Message: Mensaje: - + Main Room Sala Principal - + Game Room Sala de Partida - + Private Chat Chat privado - + Past X Days: Pasados X días: - + Today Hoy - + Last Hour Última hora - + Maximum Results: Resultados máximos: - + At least one filter is required. The more information you put in, the more specific your results will be. Se requiere un filtro como mínimo. Cuando más información introduzcas, más específicos serán los resultados. - + Get User Logs Ver registros de usuario - + Clear Filters Borrar filtros - + Filters Filtros - + Log Locations Localización de registro. - + Date Range Alcance de fecha - + Maximum Results Resultados máximos - - + + Message History Historial de mensajes - + Failed to collect message history information. Error al recopilar información del historial de mensajes. - + There are no messages for the selected filters. No hay mensajes con los filtros seleccionados @@ -7782,180 +8423,180 @@ Cuando más información introduzcas, más específicos serán los resultados. TabReplays - + Local file system Sistema de archivos local - + Server replay storage Almacén de repeticiones del servidor - - + + Watch replay Ver repetición - + Rename Renombrar - - + + New folder Nueva carpeta - - + + Delete Borrar - + Open replays folder - + Download replay Descargar repetición - + Toggle expiration lock Alternar expiración del bloqueo - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error Error - + Rename failed - + Name of new folder: - + Delete local file Borrar fichero local - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed Fallido - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay Borrar repetición remota @@ -8021,30 +8662,30 @@ Cuando más información introduzcas, más específicos serán los resultados.Servidor + - Error Error - + Failed to join the server room: it doesn't exist on the server. Fallo al unirse a la sala del servidor: no existe en el servidor. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. El servidor cree que estás en la sala del servidor pero tu cliente es incapaz de mostrarlo. Intenta reiniciar tu cliente. - + You do not have the required permission to join this server room. No tienes los permisos requeridos para unirte a esta sala del servidor. - + Failed to join the server room due to an unknown error: %1. Fallo al unirse a la sala del servidor debido a un error desconocido: %1. @@ -8052,92 +8693,97 @@ Cuando más información introduzcas, más específicos serán los resultados. TabSupervisor - + Deck Editor Editor de mazos - + Visual Deck Editor - + EDHRec - + + Archidekt + + + + Home Inicio - + &Visual Deck Storage - + Visual Database Display - + Server Servidor - + Account Cuenta - + Deck Storage - + Game Replays - + Administration Administración - + Logs - + Are you sure? ¿Estás seguro? - + There are still open games. Are you sure you want to quit? Todavía hay partidas abiertas. ¿Estás seguro/a de que quieres salir? - + Click to view Click para ver - + Your buddy %1 has signed on! Tu amigo %1 se ha conectado! - + Unknown Event Evento desconocido - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8147,39 +8793,39 @@ Este mensaje podría significar que hay una nueva versión de Cockatrice disponi Para actualizar tu cliente, ve a Ayuda -> Buscar Actualizaciones. - + Idle Timeout Tiempo de ausencia agotado - + You are about to be logged out due to inactivity. Va a ser desconectado por inactividad. - + Promotion Promoción - + You have been promoted. Please log out and back in for changes to take effect. Has sido promocionado. Por favor cierra la sesión y vuelve a iniciarla para que los cambios surtan efecto. - + Warned Advertido - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Has recibido una advertencia debido a %1 Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas contra usted. Si tiene alguna pregunta, por favor envíe un mensaje privado a un moderador. - + You have received the following message from the server. (custom messages like these could be untranslated) Ha recibido los siguientes mensajes del servidor. @@ -8189,7 +8835,12 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display @@ -8269,7 +8920,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas UpdateDownloader - + Could not open the file for reading. No se pudo abrir el archivo para su lectura. @@ -8277,206 +8928,206 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas UserContextMenu - + User &details &Detalles del usuario - + Private &chat &Chat privado - + Show this user's &games Mostrar &partidas de este usuario - + Add to &buddy list Añadir a la lista de &amigos - + Remove from &buddy list Quitar de la lista de &amigos - + Add to &ignore list Añadir a la lista de &ignorados - + Remove from &ignore list Quitar de la lista de &ignorados - + Kick from &game Expulsar de la &partida - + Warn user Usuario advertido - + View user's war&n history Ver historial de advertencias del usuario - + Ban from &server Banear del &servidor - + View user's &ban history Ver historial historial de baneo del usuario. - + &Promote user to moderator &Promocionar usuario a moderador - + Dem&ote user from moderator Degradar usuario de m&oderador - + Promote user to &judge Usuario promovido a &juez - + Demote user from judge Degradar usuario de juez - + View admin notes - - - + + + Error Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games Partidas de %1 - - - + + + Ban History Historial de baneo - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. El usuario nunca ha sido baneado - + Failed to collect ban information. Error al recopilar informacion de baneos. - - - + + + Warning History Historial de advertencias - + Warning Time;Moderator;User Name;Reason Warning Time;Moderator;User Name;Reason - + User has never been warned. El usuario nunca ha sido advertido - + Failed to collect warning information. Error al recopilar información de advertencias. - + Failed to get admin notes. - - + + Success Éxito - + Successfully promoted user. Usuario promocionado con éxito. - + Successfully demoted user. Usuario degradado con éxito. - + + - Failed Fallo - + Failed to promote user. Fallo al promocionar usuario. - + Failed to demote user. Fallo al degradar usuario. - + Copy hash to clipboard Copiar al portapapeles - + Remove this user's messages Borrar mensajes de este usuario @@ -8658,137 +9309,142 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas UserInterfaceSettingsPage - + General interface settings Preferencias generales de la interfaz - + &Double-click cards to play them (instead of single-click) &Doble click en las cartas para jugarlas (en lugar de un solo click) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default &Jugar todas las cartas que no sean tierras en la pila (en lugar de en el campo de batalla) por defecto. - - - Close card view window when last card is removed - - - - - Auto focus search bar when card view window is opened - - - - - Annotate card text on tokens - Anotar en las fichas. - - - - Use tear-off menus, allowing right click menus to persist on screen - Usar menus flotantes, permitir click derecho para anclar menus en la pantalla - - - - Notifications settings - Ajustes de notificaciones - - - - Enable notifications in taskbar - Habilitar notificaciones en la barra de tareas - - - - Notify in the taskbar for game events while you are spectating - Notificar en la barra de tareas los eventos de la partida mientras estás como espectador - - - - Notify in the taskbar when users in your buddy list connect - Recibir una notificación en la barra de tareas cuando alguien de tu lista de amigos se conecte - - - - Animation settings - Preferencias de animación - - - - &Tap/untap animation - Animación de &girar/enderezar - - Deck editor/storage settings + Do not delete &arrows inside of subphases - Open deck in new tab by default + Close card view window when last card is removed - Use visual deck storage in game lobby + Auto focus search bar when card view window is opened - Use selection animation for Visual Deck Storage - + Annotate card text on tokens + Anotar en las fichas. + + + + Use tear-off menus, allowing right click menus to persist on screen + Usar menus flotantes, permitir click derecho para anclar menus en la pantalla - When adding a tag in the visual deck storage to a .txt deck: - + Notifications settings + Ajustes de notificaciones + + + + Enable notifications in taskbar + Habilitar notificaciones en la barra de tareas - do nothing - no hacer nada + Notify in the taskbar for game events while you are spectating + Notificar en la barra de tareas los eventos de la partida mientras estás como espectador + + + + Notify in the taskbar when users in your buddy list connect + Recibir una notificación en la barra de tareas cuando alguien de tu lista de amigos se conecte - ask to convert to .cod - + Animation settings + Preferencias de animación + + + + &Tap/untap animation + Animación de &girar/enderezar - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + no hacer nada + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -8796,22 +9452,22 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 @@ -8819,32 +9475,32 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... &Lanzar dado... - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -8852,22 +9508,22 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match - + Mode: Includes - + Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -8875,21 +9531,49 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter Guardar filtro - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8916,27 +9600,27 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDatabaseDisplayNameFilterWidget - - Filter by name... + + Filter by name... (Exact match) - + Load from Deck Cargar desde mazo - + Apply all card names in currently loaded deck as exact match name filters - + Load from Clipboard - + Apply all card names in clipboard as exact match name filters @@ -9000,50 +9684,115 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters Limpiar todos los filtros - + + Sort by: + + + + + Filter by: + + + + Save and load filters - + Filter by exact card name - + Filter by card sub-type - + Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand Robar una nueva mano de muestra - + Sample hand size @@ -9051,46 +9800,25 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -9146,7 +9874,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9154,22 +9882,22 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -9177,17 +9905,17 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + Visual Deck Storage Settings @@ -9195,43 +9923,43 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas WarningDialog - + Which warning would you like to send? ¿Qué advertencia quieres enviar? - + Redact all messages from this user in all rooms Redactar todos los mensajes de este usuario en todas las salas - + &OK &OK - + &Cancel &Cancelar - + Warn user for misconduct Usuario advertido por conducta inadecuada - - + + Error Error - + User name to send a warning to can not be blank, please specify a user to warn. El nombre de usuario no puede quedarse en blanco, por favor especifique un usuario al que advertir. - + Warning to use can not be blank, please select a valid warning to send. La advertencia no puede quedarse en blanco, por favor seleccione una advertencia válida. @@ -9239,133 +9967,133 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas WndSets - + Move selected set to the top Mover el set seleccionado al principio - + Move selected set up Mover arriba el set seleccionado - + Move selected set down Mover abajo el set seleccionado - + Move selected set to the bottom Mover el set seleccionado al final - + Search by set name, code, or type Buscar por nombre de edición, código, o tipo - + Default order Orden por defecto - + Restore original art priority order Restaurar orden predeterminado de arte original - + Enable all sets Seleccionar todos los sets - + Disable all sets Deseleccionar todos los sets - + Enable selected set(s) Habilitar set(s) seleccionados - + Disable selected set(s) Deshabilitar set(s) seleccionados - + Deck Editor Editor de mazo - + Use CTRL+A to select all sets in the view. Usa CTRL+A para seleccionar todas las colecciones aquí mostradas. - + Only cards in enabled sets will appear in the card list of the deck editor. Solo las cartas en las colecciones permitidas aparecerán en la lista de cartas del editor de mazos. - + Image priority is decided in the following order: La prioridad de las imágenes es decidida en el siguiente orden: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki primero la Carpeta PERSONALIZADA (%1), luego las Ediciones Habilitadas en este mensaje (de Arriba a Abajo) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art Arte de carta - + How to use custom card art Cómo usar arte de carta personalizada. - + Hints Consejos - + Note Nota - + Sorting by column allows you to find a set while not changing set priority. Ordenar por columna te permite encontrar una edición sin cambiar la prioridad de edición. - + To enable ordering again, click the column header until this message disappears. Para habilitar el orden de nuevo, haga click en el encabezado de la columna hasta que este mensaje deje de aparecer. - + Use the current sorting as the set priority instead Usa el filtro actual como la prioridad de edición - + Sorts the set priority using the same column Ordena la prioridad de edición usando la misma columna - + Manage sets Mantenedor de ediciones @@ -9373,72 +10101,72 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped Desagrupado - + Group by Type Agrupar por tipo - + Group by Mana Value Agrupar por valor de maná - + Group by Color Agrupar por color - + Unsorted Desordenado - + Sort by Name Ordenar por nombre - + Sort by Type Ordenar por tipo - + Sort by Mana Cost Ordenar por coste de maná - + Sort by Colors Ordenar por colores - + Sort by P/T - + Sort by Set - + shuffle when closing barajar al cerrar - + pile view vista de la pila @@ -9446,7 +10174,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas i18n - + English Español (Spanish) @@ -9454,12 +10182,12 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas main - + Connect on startup Conectar al inicio - + Debug to file Depurar a archivo @@ -9467,1005 +10195,1041 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas shortcutsTab - + Main Window Ventana principal - - + + Deck Editor Editor de mazos - + Game Lobby Vestíbulo de juego - + Card Counters Contadores de carta - + Player Counters Contadores de jugador - + Power and Toughness Fuerza y resistencia - + Game Phases Fases del juego - + Playing Area Area de juego - + Move Selected Card Mover la carta seleccionada - + View Ver - + Move Top Card Mover la carta superior - + Move Bottom Card Mover la carta inferior - + Gameplay Gameplay - + Drawing Robar - + Chat Room Sala de chat - + Game Window Ventana de juego - + Load Deck from Clipboard Cargar deck desde el portapapeles - - + + Replays Repeticiones - + Tabs Pestañas - + Check for Card Updates... Buscar actualizaciones de cartas ... - + Connect... Conectar... - + Disconnect Desconectar - + Exit Salir - + Full screen Pantalla completa - + Register... Registrar... - + Settings... Ajustes... - + Start a Local Game... Empezar una partida local... - + Watch Replay... Ver repetición... - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters Eliminar filtros - + Clear Selected Filter Eleminar filtro seleccionado - + Close Cerrar - + Remove Card Quitar carta - + Manage Sets... Administrar ediciones - + Edit Custom Tokens... Editar fichas personalizadas - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card Añadir carta - + Load Deck... Cargar mazo... - - + + Load Deck from Clipboard... Cargar mazo desde el portapapeles - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck Nuevo mazo - + Open Custom Pictures Folder Abrir carpeta de imágenes personalizadas - + Print Deck... Imprimir mazo... - + Delete Card Borrar carta - - + + Reset Layout Restablecer disposición - + Save Deck Guardar mazo - + Save Deck as... Guarda mazo como... - + Save Deck to Clipboard, Annotated Guarda mazo al portapapeles, Comentado - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard Guardar mazo al portapapeles - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... Cargar mazo local... - + Load Remote Deck... Carga mazo remoto... - + Set Ready to Start Marcar listo para empezar - + Toggle Sideboard Lock Permitir bloqueo de banquillo - + Add Green Counter Añadir contador verde - + Remove Green Counter Retirar un contador verde - + Set Green Counters... Establecer contadores verdes... - + Add Red Counter Agregar contador rojo - + Remove Red Counter Retirar un contador rojo - + Set Red Counters... Establecer contadores rojos... - + Add Life Counter Añadir contador de vida - + Show Status Bar - + Unload Deck - + Force Start Forzar inicio - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter Retirar un contador de vida - + Set Life Counters... Establecer contadores de vida... - + Add White Counter Agregar contador blanco - + Remove White Counter Retirar un contador blanco... - + Set White Counters... Establecer contadores blancos... - + Add Blue Counter Agregar contador azul - + Remove Blue Counter Retirar un contador azul... - + Set Blue Counters... Establecer contadores azules... - + Add Black Counter Agregar contador negro - + Remove Black Counter Quitar contador negro - + Set Black Counters... Establecer contadores negros... - + Add Colorless Counter Agregar contador incoloro - + Remove Colorless Counter Retirar contador incoloro - + Set Colorless Counters... Establecer contadores incoloros... - + Add Other Counter Añadir otro contador - + Remove Other Counter Borrar otro contador - + Set Other Counters... Colocar otros contadores... - + Increment all card counters - + Add Power (+1/+0) Agregar fuerza (+1/+0) - + Remove Power (-1/-0) Reducir fuerza (-1/-0) - + Move Toughness to Power (+1/-1) Pasar de resistencia a fuerza (+1/-1) - + Add Toughness (+0/+1) Agregar resistencia (+0/+1) - + Remove Toughness (-0/-1) Reducir resistencia (-0/-1) - + Move Power to Toughness (-1/+1) Pasar de fuerza a resistencia (-1/+1) - + Add Power and Toughness (+1/+1) Agregar fuerza y resistencia (+1/+1) - + Remove Power and Toughness (-1/-1) Reducir fuerza y resistencia (-1/-1) - + Set Power and Toughness... Establecer fuerza y resistencia... - + Reset Power and Toughness Restablecer fuerza y resistencia - + Untap Enderezar - + Upkeep Mantenimiento - + Draw Robo - + First Main Phase Primera fase principal - + Start Combat Comenzar combate - + Attack Ataque - + Block Bloqueo - + Damage Daño - + End Combat Terminar combate - + Second Main Phase Segunda fase principal - + End Final - + Next Phase Próxima fase - + Next Phase Action Próxima fase de acción - + Next Turn Próximo turno - + Hide Card in Reveal Window - + Tap / Untap Card Girar / Enderezar Carta - + Untap All Enderezar todas - + Toggle Untap Alternar enderezamiento - + Turn Card Over Girar carta - + Peek Card Mirar carta - + Play Card Jugar carta - + Attach Card... Anexar carta... - + Unattach Card Desanexar carta... - + Clone Card Clonar carta - + Create Token... Crear ficha... - + Create All Related Tokens Crear todas las fichas relacionadas - + Create Another Token Crear otra ficha - + Set Annotation... Escribir anotación... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library Parte inferior de la biblioteca - - - - + + + + Exile Exilio - - - - + + + + Graveyard Cementerio - - + + + Hand Mano - - + + Top of Library Parte superior de la biblioteca - - - + + + Battlefield, Face Down Campo de batalla, boca abajo - + Battlefield Campo de batalla - - Sort Hand - Ordenar mano - - - + Library Biblioteca - + Sideboard Banquillo - + Top Cards of Library Carta de la parte superior de la biblioteca - + Bottom Cards of Library - + Close Recent View Cerrar vista reciente - - + + Stack Apilar - - + + Graveyard (Multiple) Cementerio (Multiple) - - + + Exile (Multiple) Exiliar (Multiple) - + Stack Until Found - + Draw Bottom Card Robar carta de la parte inferior - + Draw Multiple Cards from Bottom... Robar múltiples cartas de la parte inferior... - + Draw Arrow... Dibujar flecha - + Remove Local Arrows Eliminar flechas - + Leave Game Abandonar partida - + Concede Conceder - + Roll Dice... Lanzar dado... - + Shuffle Library Barajar biblioteca - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan Mulligan - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card Robar una carta - + Draw Multiple Cards... Robar múltiples cartas... - + Undo Draw Deshacer último robo - + Always Reveal Top Card Mostrar siempre la carta superior - + Always Look At Top Card Mirar siempre la carta superior - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise Rotar vista en sentido horario - + Rotate View Counterclockwise Rotar vista en sentido antihorario - + Unfocus Text Box No resaltar la caja de texto - + Focus Chat Resaltar el chat - + Clear Chat Borrar chat - + Refresh Actualizar - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home Inicio - + Visual Deck Storage - + Deck Storage Almacenamiento de mazos - + Server Servidor - + Account Cuenta - + Administration Administración - + Logs Historial diff --git a/cockatrice/translations/cockatrice_fi.ts b/cockatrice/translations/cockatrice_fi.ts index 74056982f..16f94f1c0 100644 --- a/cockatrice/translations/cockatrice_fi.ts +++ b/cockatrice/translations/cockatrice_fi.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Lisää countteri... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Lisää countteri - + New value for counter '%1': Countterin uusi arvo '%1': @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,79 +36,81 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. + + Add Analytics Panel AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -116,12 +118,12 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard - + Sideboard @@ -129,22 +131,22 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error - + Could not create themes directory at '%1'. - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -155,7 +157,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -166,152 +168,165 @@ Are you sure you would like to disable this feature? - + Confirm Change - + Theme settings Teeman asetukset - + Current theme: Nykyinen teema: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + + Show game filter toolbar above list in room tab + + + + Card rendering Kortin renderöinti - + Display card names on cards having a picture Näytä kortin nimi korteissa, joissa on kuva - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Suurenna kortti kun hiiri on kortin päällä - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Käden layout - + Display hand horizontally (wastes space) Näytä käsikortit vierekkäin vaakatasossa (vie enemmän tilaa) - + Enable left justification Järjestä vasemmalta - + Table grid layout Pöydän grid-asetelma - + Invert vertical coordinate Käänteiset pystykoordinaatit - + Minimum player count for multi-column layout: Pelaajien vähimmäismäärä moniosaisessa layoutissa: - + Maximum font size for information displayed on cards: Suurin fonttikoko korteissa näkyville infoteksteille: + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -333,112 +348,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name Bännää &käyttäjä - + ban &IP address Bännää &IP osoite - + ban client I&D Bännää client I&D - + Ban type Bännäyksen tyyppi - + &permanent ban Bännätty pysyvästi - + &temporary ban Bännätty väliaikaisesti - + &Days: Päivät: - + &Hours: Tunnit: - + &Minutes: Minuutit: - + Duration of the ban Bännin kesto - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Kerro bännäyksen syy. Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. - + Please enter the reason for the ban that will be visible to the banned person. Kerro bännäyksen syy. Tämä näkyy bännätylle henkilölle. - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Peruuta - + Ban user from server Bännää pelaaja serveriltä - + + - - + Error Virhe - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Valitse nimi, IP -osoite, clientid tai joku näiden yhdistelmä asettaaksesi bännin. - + You must have a value in the name ban when selecting the name ban checkbox. Bännää nimi kentässä pitää olla arvo ennen valintaruudun valitsemista - + You must have a value in the ip ban when selecting the ip ban checkbox. Bännää ip kentässä pitää olla arvo ennen valintaruudun valitsemista. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Bännää ClientID kentässä pitää olla arvo ennen valintaruudun valitsemista. @@ -469,32 +484,32 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardDatabaseModel - + Name Nimi - + Sets Setit - + Mana cost Mana cost - + Card type Korttityyppi - + P/T P/T - + Color(s) Väri(t) @@ -502,96 +517,101 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardFilter - + AND Logical conjunction operator used in card filter JA - + OR Logical disjunction operator used in card filter TAI - + AND NOT Negated logical conjunction operator used in card filter JA EI - + OR NOT Negated logical disjunction operator used in card filter TAI EI - + Name Nimi - + + Name (Exact) + + + + Type Muoto - + Color Väri - + Text Teksti - + Set Setti - + Mana Cost Manacosti - + Mana Value - + Rarity Harvinaisuus - + Power Power - + Toughness Toughness - + Loyalty Loyalty - + Format Formaatti - + Main Type - + Sub Type @@ -599,22 +619,22 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardInfoFrameWidget - + Image - + Description - + Both - + View transformation @@ -622,22 +642,22 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -650,12 +670,22 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. - + + Set: + + + + + Collector Number: + + + + Related cards: - + Unknown card: @@ -663,124 +693,124 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -788,7 +818,7 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardSizeWidget - + Card Size @@ -796,133 +826,133 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardZoneLogic - + their hand nominative - + %1's hand nominative - - - their library - look at zone - - - - - %1's library - look at zone - - - - - of their library - top cards of zone, - - - - - of %1's library - top cards of zone - - their library - reveal zone + look at zone %1's library + look at zone + + + + + of their library + top cards of zone, + + + + + of %1's library + top cards of zone + + + + + their library reveal zone - + + %1's library + reveal zone + + + + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - - - their graveyard - nominative - - - - - %1's graveyard - nominative - - - - - their exile - nominative - - - - - %1's exile - nominative - - + their graveyard + nominative + + + + + %1's graveyard + nominative + + + + + their exile + nominative + + + + + %1's exile + nominative + + + + their sideboard look at zone - + %1's sideboard look at zone - - - their sideboard - nominative - - - - - %1's sideboard - nominative - - - their custom zone '%1' + their sideboard nominative + %1's sideboard + nominative + + + + + their custom zone '%1' + nominative + + + + %1's custom zone '%2' nominative @@ -931,7 +961,7 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CockatriceXml3Parser - + Parse error at line %1 col %2: @@ -939,7 +969,7 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CockatriceXml4Parser - + Parse error at line %1 col %2: @@ -958,6 +988,37 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -969,47 +1030,47 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add to Deck - + Add to Sideboard - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1017,87 +1078,97 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing - + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + + Format: + + + + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1105,17 +1176,17 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1123,114 +1194,114 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1238,7 +1309,7 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1246,194 +1317,227 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckEditorSettingsPage - - + + Update Spoilers Päivitä spoilerit - - + + Success Onnistui - + Download URLs have been reset. Latauksen URL:t on resetoitu - + Downloaded card pictures have been reset. Ladattujen korttien kuvat on resetoitu. - + Error Virhe - + One or more downloaded card pictures could not be cleared. Ei voitu poistaa yhtä tai useampaa kortin kuvaa - + Add URL Lisää URL - - + + URL: URL: - - + + Edit URL Muokkaa URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL - + Remove URL - + Day(s) - + Updating... Päivitetään... - + Choose path Valitse polku - + URL Download Priority URL Latausprioriteetti - + Spoilers Spoilerit - + Download Spoilers Automatically Lataa spoilerit automaattisesti - + Spoiler Location: Spoilerien sijainti: - + Last Change Muokattu viimeksi - + Spoilers download automatically on launch Lataa spoilerit latauksen yhteydessä - + Press the button to manually update without relaunching Lataa spoilerit manuaalisesti ilman uudelleenkäynnistystä - + Do not close settings until manual update is complete Älä sulje asetuksia, ennen kuin manuaalinen päivitys on valmis - + Download card pictures on the fly Lataa korttikuvat kun niitä tarvitaan - + How to add a custom URL Miten lisään oman URL:n - + Delete Downloaded Images Poista ladatut kuvat - + Reset Download URLs Resetoi latausten URL:t - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count - + Set - + Number Numero - + Provider ID - + Card Kortti @@ -1441,12 +1545,12 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1454,17 +1558,17 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1472,7 +1576,7 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -1480,62 +1584,62 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -1548,93 +1652,151 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1652,74 +1814,74 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckViewContainer - + Load deck... Lataa pakka... - + Load remote deck... Lataa remote pakka... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start Ready to start - + Force start - + Sideboard unlocked Sideboard unlocked - + Sideboard locked Sideboard locked - - + + Error Virhe - + The selected file could not be loaded. Valittua tiedostoa ei voi avata. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -1752,143 +1914,143 @@ This will kick all non-ready players from the game. Ladataan... - + Known Hosts Tunnetut Isännät - + Delete the currently selected saved server - + Refresh the server list with known public servers Päivitä servereiden lista tunnetuilla julkisilla servereillä - + New Host Lisää Isäntä - + Name: Nimi: - + &Host: &Host: - + &Port: &Portti: - + Player &name: Pelaajan &nimi: - + P&assword: &Salasana: - + &Save password &Tallenna salasana - + A&uto connect Yhdistä &Automaattisesti - + Automatically connect to the most recent login when Cockatrice opens Kirjaudu sisään automaattisesti kun Cockatrice käynnistyy - + If you have any trouble connecting or registering then contact the server staff for help! Jos sinulla on ongelmia yhdistämisessä tai rekisteröinnissä ota yhteyttä serverin henkilökuntaan! - - + + Webpage Websivu - + Reset Password - + Forgot password? - + &Connect &Yhdistä - + Server Serveri - + Login Kirjaudu sisään - + Server Contact Serverin yhteystiedot - + Connect to Server Yhdistä serveriin - + Server URL Serverin URL - + Communication Port Tietoliikenneportti - + Unique Server Name Uniikki serverin nimi - + Connection Warning Yhteysvaroitus - + You need to name your new connection profile. Anna nimi uudelle yhteysprofiilillesi - + Connect Warning Yhdistämisvaroitus - + The player name can't be empty. Pelaajan nimi ei voi olla tyhjä. @@ -1896,117 +2058,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings &Muista asetukset - + &Description: &Kuvaus: - + P&layers: &Pelaajat: - + General Yleinen - + Game type Pelimuoto - + &Password: &Salasana: - + Only &buddies can join Vain &Kaverit voivat liittyä - + Only &registered users can join Vain &Rekisteröityneet käyttäjät voivat liittyä - + Joining restrictions Liittymisehdot - + &Spectators can watch &Salli katsojat - + Spectators &need a password to watch Katsojilta &vaaditaan salasana - + Spectators can &chat Katsojat voivat &keskustella - + Spectators can see &hands Katsojat &näkevät käsikortit - + Create game as spectator - + Spectators Katsojat - + Starting life total: - + Open decklists in lobby - + + Create game as judge + + + + Game setup options - + &Clear &Tyhjennä - + Create game Luo peli - + Game information Tietoa pelistä - + Error Virhe - + Server error. Serverivirhe. @@ -2014,97 +2181,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Nimi: - + Token Tokeni - + C&olor: &Väri - + white valkoinen - + blue sininen - + black musta - + red punainen - + green vihreä - + multicolor monivärinen - + colorless väritön - + &P/T: P/T: - + &Annotation: &Kommentti: - + &Destroy token when it leaves the table &Tuhoa tokeni kun se lähtee pöydästä - + Create face-down (Only hides name) - + Token data Tokenin data - + Show &all tokens Näytä &kaikki tokenit - + Show tokens from this &deck Näytä kaikki tämän &pakan tokenit - + Choose token from list Valitse tokeni listasta - + Create token Luo token @@ -2112,53 +2279,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2166,40 +2333,40 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. Ei valittua kuvaa. - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. Valitse kuva vaihtaaksesi avataria. Poistaaksesi nykyisen avatarisi: Hyväksy valitsematta uutta kuvaa. - + Browse... Selaa... - + Change avatar Vaihda avatar - + Open Image Avaa Kuva - + Image Files (*.png *.jpg *.bmp) Kuvatiedostot (*.png *.jpg *.bmp) - + Invalid image chosen. Kuva ei kelpaa. @@ -2207,17 +2374,17 @@ Poistaaksesi nykyisen avatarisi: Hyväksy valitsematta uutta kuvaa. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2225,38 +2392,38 @@ Poistaaksesi nykyisen avatarisi: Hyväksy valitsematta uutta kuvaa. DlgEditPassword - + Old password: Vanha salasana: - + New password: Uusi salasana: - + Confirm new password: Vahvista uusi salasana: - + Change password Vaihda salasana - - + + Error Virhe - + Your password is too short. - + The new passwords don't match. Uusi salasana ei täsmää @@ -2264,93 +2431,93 @@ Poistaaksesi nykyisen avatarisi: Hyväksy valitsematta uutta kuvaa. DlgEditTokens - + &Name: &Nimi: - + C&olor: &Väri - + white valkoinen - + blue sininen - + black musta - + red punainen - + green vihreä - + multicolor monivärinen - + colorless väritön - + &P/T: P/T: - + &Annotation: &Kommentti: - + Token data Tokenin data - - + + Add token Lisää token - + Remove token Poista token - + Edit custom tokens Muokkaa tokeneita - + Please enter the name of the token: Anna tokenin nimi: - + Error Virhe - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Nimi on ristiriidassa olemassaolevan kortin tai tokenin kanssa. @@ -2444,7 +2611,8 @@ Varmista että 'Token' setti on asetettu näkyväksi 'Muokkaa set - Hide games not created by buddy + Hide games not created by buddies + Hide games not created by buddy @@ -2531,52 +2699,52 @@ Varmista että 'Token' setti on asetettu näkyväksi 'Muokkaa set DlgForgotPasswordChallenge - + Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: &Host: - + &Port: &Portti: - + Player &name: Pelaajan &nimi: - + Email: Email: - + Reset Password Challenge - + Reset Password Challenge Error - + The email address can't be empty. Email -osoite ei voi olla tyhjä @@ -2584,37 +2752,37 @@ Varmista että 'Token' setti on asetettu näkyväksi 'Muokkaa set DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: &Host: - + &Port: &Portti: - + Player &name: Pelaajan &nimi: - + Reset Password Request - + Reset Password Error - + The player name can't be empty. Pelaajan nimi ei voi olla tyhjä. @@ -2622,86 +2790,86 @@ Varmista että 'Token' setti on asetettu näkyväksi 'Muokkaa set DlgForgotPasswordReset - + Reset Password Warning - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: &Host: - + &Port: &Portti: - + Player &name: Pelaajan &nimi: - + Token: Tokeni - - + + New Password: Uusi Salasana: - + Reset Password - + The player name can't be empty. Pelaajan nimi ei voi olla tyhjä. - - - - + + + + Reset Password Error - + The token can't be empty. Tokeni ei voi olla tyhjä - + The new password can't be empty. Uusi salasana ei voi olla tyhjä - + Error - + Your password is too short. - + The passwords do not match. Salasanat eivät täsmää @@ -2717,17 +2885,17 @@ Varmista että 'Token' setti on asetettu näkyväksi 'Muokkaa set DlgLoadDeckFromClipboard - + Load deck from clipboard Avaa pakka leikepöydältä - + Error Virhe - + Invalid deck list. Pakka ei ole kelvollinen. @@ -2735,43 +2903,43 @@ Varmista että 'Token' setti on asetettu näkyväksi 'Muokkaa set DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2785,7 +2953,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck Lataa pakka @@ -2793,37 +2961,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -2831,91 +2999,91 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: &Host: - + &Port: &Portti: - + Player &name: Pelaajan &nimi: - + P&assword: &Salasana: - + Password (again): &Salasana (uudelleen): - + Email: Email: - + Email (again): Email (uudelleen): - + Country: Maa: - + Undefined Ei määritelty - + Real name: Oikea nimi: - + Register to server Rekisteröidy serverille - - - - + + + + Registration Warning Rekisteröintivaroitus - + Your password is too short. - + Your passwords do not match, please try again. Salasanat eivät täsmää, yritä uudelleen - + Your email addresses do not match, please try again. Sähköpostiosoitteet eivät täsmää, yritä uudelleen. - + The player name can't be empty. Pelaajan nimi ei voi olla tyhjä. @@ -2941,40 +3109,55 @@ Your email will be used to verify your account. DlgSelectSetForCards - + Unmodified Cards: - + Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database Tuntematon virhe ladattaessa korttitietokantaa - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -2991,7 +3174,7 @@ Voit joutua ajamaan Oraclen ja päivittämään korttitietokannan uudelleen Haluatko vaihtaa tietokantasi sijaintia? - + Your card database version is too old. This can cause problems loading card information or images @@ -3008,7 +3191,7 @@ Yleensä tämä voidaan korjata palaamalla oracleen päivittämään korttitieto Haluatko vaihtaa tietokantasi sijaintia? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3017,7 +3200,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3026,7 +3209,7 @@ Would you like to change your database location setting? Haluatko vaihtaa tietokantasi sijaintia? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3035,7 +3218,7 @@ Would you like to change your database location setting? Haluatko vaihtaa tietokantasi sijaintia? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3044,59 +3227,59 @@ Would you like to change your database location setting? - - - + + + Error Virhe - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Pakkojen tiedostosijainti on väärä. Haluatko palata takaisin ja asettaa oikean sijainnin pakat -kansiolle? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Kuvien tiedostosijainti on väärä. Haluatko palata takaisin ja asettaa oikean sijainnin kuvat -kansiolle? - + Settings Asetukset - + General Yleinen - + Appearance Ulkonäkö - + User Interface Käyttöliittymä - + Card Sources Korttien lähteet - + Chat Chat - + Sound Äänet - + Shortcuts Pikanäppäimet @@ -3104,39 +3287,39 @@ Would you like to change your database location setting? DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3144,17 +3327,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next Seuraava - + Previous Edellinen - + Tip of the Day Päivän vinkki @@ -3162,165 +3345,165 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel Tämänhetkinen julkaisukanava - + Reinstall Asenna uudelleen - + Cancel Download Peruuta Lataus - + Open Download Page Avaa Lataussivu - + Check for Client Updates - - - - + + + + Error Virhe - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatricessä ei ole SSL tukea, joten päivityksiä ei voi ladata automaattisesti! Vieraile lataussivulla päivittääksesi ohjelman manuaalisesti. - + Downloading update: %1 - + Checking for updates... Etsitään päivityksiä... - + Finished checking for updates Päivitysten haku on päättynyt - + No Update Available Ei uusia päivityksiä - + Cockatrice is up to date! Cockatrice on ajan tasalla! - + You are already running the latest version available in the chosen release channel. Sinulla on käytössä viimeisin versio, joka on saatavilla valitussa julkaisukanavassa. - + Current version Nykyinen versio - + Selected release channel Valitse julkaisukanava - - + + Update Available Päivitys Saatavilla - - + + A new version of Cockatrice is available! Uusi versio Cockatricesta on saatavilla! - - + + New version Uusi versio - - + + Released Julkaistu - - + + Changelog Muutosloki - + Do you want to update now? Haluatko päivittää nyt? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + + Update Error Päivitysvirhe - + An error occurred while checking for updates: Virhe päivityksiä tarkistettaessa: - + An error occurred while downloading an update: Virhe tiedostoa ladatessa: - + Installing... Asennetaan... - + Cockatrice is unable to open the installer. Cockatrice ei voi avata asennustiedostoa. - + Try to update manually by closing Cockatrice and running the installer. Yritä asentaa manuaalisesti sulkemalla Cockatrice ja käynnistämällä asennusohjelma. - + Download location Latausten sijainti @@ -3328,21 +3511,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing Tyhjennä loki sammutuksen yhteydessä - + Copy to clipboard - + Debug Log Debug loki + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3412,33 +3727,39 @@ You may have to manually download the new version. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3454,22 +3775,22 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. @@ -3477,22 +3798,22 @@ You may have to manually download the new version. GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -3500,226 +3821,279 @@ You may have to manually download the new version. GameSelector - - - - - - + + + + + + Error Virhe - + Please join the appropriate room first. Liity ensin oikeaan huoneeseen. - + Wrong password. Väärä salasana. - + Spectators are not allowed in this game. Tähän peliin ei päästetä katsojia. - + The game is already full. Peli on jo täynnä. - + The game does not exist any more. Peliä ei enää ole. - + This game is only open to registered users. Peli on tarkoitettu vain rekisteröityneille käyttäjille. - + This game is only open to its creator's buddies. Peli on tarkoitettu vain sen tekijän kavereille. - + You are being ignored by the creator of this game. Pelin luoja ei huomioi sinua. - + Join Game - + Spectate Game - + Game Information - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game Liity peliin - + Password: Salasana: - + Please join the respective room first. Liity ensin asianmukaiseen huoneeseen. - + &Filter games &Suodata pelit - + C&lear filter &Poista suodattimet - + C&reate &Luo - + &Join &Liity - + + Join as judge + + + + J&oin as spectator Liity &katsojana - + + Join as judge spectator + + + + Games shown: %1 / %2 Games shown: %1 / %2 - + Games Pelit + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password salasana - + buddies only vain kaverit - + reg. users only vain rekisteröityneet käyttäjät - + open decklists - - + + can chat voi jutella - + see hands katso käsikortit - + can see hands voi nähdä käsikortit - + not allowed ei sallittu - + Room Huone - + Age Ikä - + Description Kuvaus - + Creator Luoja - + Type Muoto - + Restrictions Ehdot - + Players Pelaajat - + Spectators Katsojat @@ -3727,143 +4101,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path Valitse polku - + Personal settings Henkilökohtaiset asetukset - + Language: Kieli: - + Paths (editing disabled in portable mode) Polut (editoiminen poistettu käytöstä kannettavassa versiossa) - + Paths Polut - + How to help with translations - + Decks directory: Pakkojen kansio: - + Filters directory: - + Replays directory: Replay kansio: - + Pictures directory: Kuvakansio: - + Card database: Korttitietokanta: - + Custom database directory: - + Token database: Tokenitietokanta: - + Update channel Päivitykset - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client Ilmoita jos ohjelmastani puuttuu jokin serverin tarjoama ominaisuus - + Automatically run Oracle when running a new version of Cockatrice Suorita Oracle automaattisesti kun Cockatricen uusi versio on asennettu - + Show tips on startup Näytä vinkit käynnistyksen yhteydessä - + Last update check on %1 (%2 days ago) @@ -3871,47 +4245,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -3919,111 +4293,141 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - - &Sort hand + + Sort hand by... - - Take &mulligan + + Name - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - Reveal r&andom card to... + + + All players - - - &All players + + Reveal r&andom card to... HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + + Browse Archidekt + + + + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4225,61 +4629,61 @@ You may have to manually download the new version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Serveri on täynnä, yritä myöhemmin uudelleen. - + There are too many concurrent connections from your address. Liian monta samanaikaista yhteyttä osoitteestasi. - + Banned by moderator Bännätty moderaattorin toimesta - + Expected end time: %1 Arvioitu päättymisaika: %1 - + This ban lasts indefinitely. Bänni kestää loputtomasti. - + Scheduled server shutdown. Ajastettu serverin sammutus. - - + + Invalid username. Väärä käyttäjänimi. - + You have been logged out due to logging in at another location. Sinut kirjattiin ulos koska kirjauduit sisään toisesta paikasta. - + Connection closed Yhteys suljettu - + The server has terminated your connection. Reason: %1 Serveri katkaisi yhteyden. Syy: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -4290,583 +4694,583 @@ Kaikki pelit joudutaan lopettamaan Syy: %1 - + Scheduled server shutdown Ajastettu serverin sammutus. - - + + Success Onnistui - + Registration accepted. Will now login. Rekisteröinti hyväksytty Kirjautuu. - + Account activation accepted. Will now login. Tilin aktivointi hyväksytty. Kirjautuu. - + Number of players Pelaajien lukumäärä - + Please enter the number of players. Anna pelaajien lukumäärä. - - + + Player %1 Pelaaja %1 - + Load replay Lataa replay - + About Cockatrice Tieto sovelluksesta - + Version Versio - + Cockatrice Webpage Cockatricen Webbsivut - + Project Manager: Projektipäällikkö: - + Past Project Managers: Edelliset projektipäälliköt: - + Developers: Kehittäjät: - + Our Developers Kehittäjämme - + Help Develop! Auta kehittämään! - + Translators: Kääntäjät: - + Our Translators Kääntäjät - + Help Translate! Auta Kääntämään! - + Support: Tuki: - + Report an Issue Ilmoita puute - + Troubleshooting Vianhaku - + F.A.Q. F.A.Q. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error Virhe - + Server timeout Serverin aikakatkaisu - + Failed Login Kirjautuminen epäonnistui - + Your client seems to be missing features this server requires for connection. Sovelluksestasi puuttuu ominaisuuksia, joita serveri vaatii yhteyden muodostamiseksi. - + To update your client, go to 'Help -> Check for Client Updates'. Päivitä sovellus: 'Apua' -> 'Tarkasta onko päivityksiä'. - + Incorrect username or password. Please check your authentication information and try again. Väärä käyttäjänimi tai salasana. Tarkista todennustietosi ja yritä uudelleen. - + There is already an active session using this user name. Please close that session first and re-login. Tällä käyttäjänimellä on jo aktiivinen sessio käynnissä. Sulje se ensin ja kirjaudu sisään uudelleen - - + + You are banned until %1. Sinut on bännätty %1 saakka. - - + + You are banned indefinitely. Sinut on bännätty lopullisesti - + This server requires user registration. Do you want to register now? Serveri vaatii rekisteröitymisen. Haluatko rekisteröityä nyt? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. Sisäinen virhe! Sulje ja käynnistä Cockatrice uudelleen ennenkuin yrität uudestaan. Jos ongelma jatkuu, tarkasta että käytössäsi on sovelluksen uusin versio ja tarvittaessa ota yhteyttä sovelluksen kehittäjiin. - + Account activation Tilin aktivointi - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Tiliäsi ei ole vielä aktivoitu Ole hyvä ja anna aktivointikoodi, jonka sait sähköpostiisi. - + Server Full Serveri on täynnä - + Unknown login error: %1 Tuntematon kirjautumisvirhe: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Yleensä tämä tarkoittaa, että sovelluksesi on päivittämättä ja serveri lähetti vastauksen, jota sovellus ei ymmärrä. - + Your username must respect these rules: Käyttäjänimesi täytyy olla näiden sääntöjen mukainen: - + is %1 - %2 characters long %1 - %2 merkkiä pitkä - + can %1 contain lowercase characters voi sisältää %1 pientä kirjainta - - - - + + + + NOT EI - + can %1 contain uppercase characters voi sisältää %1 isoa kirjainta - + can %1 contain numeric characters voi sisältää %1 numeroa - + can contain the following punctuation: %1 voi sisältää seuraavat merkit: %1 - + first character can %1 be a punctuation mark ensimmäinen kirjain ei voi olla %1 merkki - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 ei voi sisältää näitä sanoja: %1 - + can not match any of the following expressions: %1 ei voi sisältää seuraavia asioita: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Voit käyttää käyttäjänimessä merkkejä A-Z, a-z, 0-9, _, ., ja - - - - - - - + + + + + + Registration denied Rekisteröinti evätty - + Registration is currently disabled on this server Tämä serveri ei ota tällä hetkellä vastaan rekisteröintejä - + There is already an existing account with the same user name. Käyttäjänimi on jo olemassa - + It's mandatory to specify a valid email address when registering. Rekisteröintiin on annettava toimiva sähköpostiosoite. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. Näyttää siltä että yrität rekisteröidä uutta tiliä tälle serverille, vaikka sinulla on tällä sähköpostilla jo olemassaoleva tili. Tämä serveri rajoittaa tilien määrää, jotka tulevat samasta osoitteesta. Ota yhteyttä serverin tukeen pyytääksesi apua tai saadaksesi kirjautumistietosi. - + Password too short. Salasana on liian lyhyt. - + Registration failed for a technical problem on the server. Rekisteröinti epäonnistui serverin vian vuoksi. - + The connection to the server has been lost. - + Unknown registration error: %1 Tuntematon rekisteröintivirhe: %1 - + Account activation failed Tilin aktivointi epäonnistui - + Socket error: %1 Pistokevirhe: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Yritit yhdistää vanhentuneeseen palvelimeen. Käytä vanhempaa versiota Cockatricesta tai yhdistä sopivaan palvelimeen. Paikallinen versio on %1, etäversio on %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Cockatrice-ohjelmasi on vanhentunut. Päivitä Cockatrice-versiosi. Paikallinen versio on %1, etäversio on %2. - + Connecting to %1... Yhdistetään: %1... - + Registering to %1 as %2... Rekisteröidytään %1:een %2:na - + Disconnected Yhteys katkaistu - + Connected, logging in at %1 Yhteys luotu, kirjaudutaan %1:n + - Requesting forgotten password to %1 as %2... Requesting forgotten password to %1 as %2... - + &Connect... &Yhdistä... - + &Disconnect &Katkaise yhteys - + Start &local game... Aloita &paikallinen peli... - + &Watch replay... &Katso replay... - + &Full screen K&oko näyttö - + &Register to server... &Rekisteröidy serverille... - + &Restore password... - + &Settings... &Asetukset - + &Exit &Exit - + A&ctions &Toiminnot - + &Cockatrice &Cockatrice - + C&ard Database &Korttitietokanta - + &Manage sets... &Manage sets... - + Edit custom &tokens... Edit custom &tokens... - + Open custom image folder Avaa mukautettu kuvakansio - + Open custom sets folder Avaa mukautettu setit -kansio - + Add custom sets/cards Lisää mukautettuja settejä/kortteja - + Reload card database - + Tabs - + &Help &Apua - + &About Cockatrice &Tietoa Cockatricesta - + &Tip of the Day &Päivän vinkki - + Check for Client Updates Etsi päivityksiä - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + Show/Hide - + New Version Uusi versio - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed Cockatrice asennettu - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database Korttitietokanta - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -4875,46 +5279,46 @@ Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes Kyllä - - + + No Ei - + Open settings Avaa asetukset - + New sets found Löytyi uusia settejä! - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets Näytä setit - + Welcome Tervetuloa - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -4923,64 +5327,64 @@ All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information Tietoa - + A card database update is already running. Korttitietokannan päivitys on jo käynnissä - + Unable to run the card database updater: Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -4991,673 +5395,843 @@ This is most likely not a problem, but this message might mean there is a new ve To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards Lataa setit/kortit - + Selected file cannot be found. Selected file cannot be found. - + You can only import XML databases at this time. You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. Salasanasi on muutettu, voit nyt kirjautua sisään uudella salasanallasi. - + Failed to reset user account password, please contact the server operator to reset your password. Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. Activation request received, please check your email for an activation token. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play pelistä - + from their graveyard Graveyardilta - + from exile exilestä - + from their hand kädestään - + the top card of %1's library %1n pakan päällimmäinen kortti - + the top card of their library pakan päällimmäisen kortin - + from the top of %1's library %1n pakan päältä - + from the top of their library Pakkansa päältä - + the bottom card of %1's library %1n pakan alimmainen kortti - + the bottom card of their library pakan alimmaisen kortin - + from the bottom of %1's library %1n pakan pohjalta - + from the bottom of their library pakan alta - + from %1's library %1n pakasta - + from their library pakasta - + from sideboard sideboardilta - + from the stack stäkistä - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 pelaa pakan päällimmäinen kortti %2 esillä. - + %1 is not revealing the top card %2 any longer. %1 ei pelaa enää pakan päällimmäinen kortti %2 esillä, - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 attaches %2 to %3's %4. - + %1 has conceded the game. %1 luovutti. - + %1 has unconceded the game. %1 has unconceded the game. - + %1 has restored connection to the game. %1 on jälleen yhteydessä peliin. - + %1 has lost connection to the game. %1 menetti yhteyden peliin. - + %1 points from their %2 to themselves. %1 osoittaa kortista %2 itseensä. - + %1 points from their %2 to %3. %1 osoittaa kortista %2 korttiin %3. - + %1 points from %2's %3 to themselves. %1 osoittaa %2n kortista %3 itseensä. - + %1 points from %2's %3 to %4. %1 osoittaa %2n kortista %3 korttiin %4 - + %1 points from their %2 to their %3. %1 osoittaa kortistaan %2 korttiin %3. - + %1 points from their %2 to %3's %4. %1 osoittaa kortistaan %2 pelaajan %3 korttiin %4. - + %1 points from %2's %3 to their own %4. %1 osoittaa pelaajan %2 kortista %3 korttiin %4. - + %1 points from %2's %3 to %4's %5. %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. %1 tekee tokenin: %2%3. - + %1 has loaded a deck (%2). %1 on ladannut pakan (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 on ladannut pakan, jossa on %2 sideboard korttia (%3) - + %1 destroys %2. %1 tuhoaa kortin %2. - + a card kortti - + %1 gives %2 control over %3. %1 antaa pelaajalle %2 kontrollin korttiin %3. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 laittaa kortin %2 peliin%3. - + %1 puts %2%3 into their graveyard. %1 laittaa kortin %2%3 graveyardille - + %1 exiles %2%3. %1 laittaa exileen kortin %2%3. - + %1 moves %2%3 to their hand. %1 laittaa kortin %2%3 käteen. - + %1 puts %2%3 into their library. %1 laittaa kortin %2%3 pakkaan. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. %1 laittaa kortin %2%3 pakan päälle. - + %1 puts %2%3 into their library %4 cards from the top. %1 laittaa kortin %2%3 pakkaansa %4 kortiksi päältä lukien. - + %1 moves %2%3 to sideboard. %1 siirtää kortin %2%3 sideboardille. - + %1 plays %2%3. %1 pelaa kortin %2%3. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. %1 turns %2 face-down. - + %1 turns %2 face-up. %1 turns %2 face-up. - + The game has been closed. Peli on suljettu. - + The game has started. Peli alkaa. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 on liittynyt peliin. - + %1 is now watching the game. %1 seuraa peliä. - + You have been kicked out of the game. Sinut on kickattu pois pelistä. - + %1 has left the game (%2). %1 on lähtenyt pelistä. (%2). - + %1 is not watching the game any more (%2). %1 ei enää seuraa peliä (%2) - + %1 is not ready to start the game any more. %1 ei ole enää valmis aloittamaan peliä. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. %1 sekoittaa pakkansa ja nostaa uuden käden. - + You are watching a replay of game #%1. Katsot pelin #%1 replayta. - + %1 is ready to start the game. %1 on valmis aloittamaan pelin. - + cards an unknown amount of cards cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 reveals %2 to %3. - + %1 reveals %2. %1 reveals %2. - + %1 randomly reveals %2%3 to %4. %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. %1 reveals %2%3 to %4. - + %1 reveals %2%3. %1 reveals %2%3. - + %1 reversed turn order, now it's %2. %1 käänsi vuorojärjestyksen; nyt se on %2. - + reversed käänteinen - + normal normaali - + Heads Kruuna - + Tails Klaava - + %1 flipped a coin. It landed as %2. %1 heitti kolikkoa ja tulokseksi tuli %2. - + %1 rolls a %2 with a %3-sided die. %1 heitti tuloksen %2. %3 -sivuisella nopalla - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. %1n vuoro. - + %1 sets annotation of %2 to %3. %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 asettaa countterin %2 arvoon %3 (%4%5). - + %1 sets %2 to not untap normally. %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. %1 sets %2 to untap normally. - + %1 removes the PT of %2. %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. %1 on lukinnut sideboardinsa. - + %1 has unlocked their sideboard. %1 on avannut sideboardinsa. - + %1 taps their permanents. %1 täppää kaikki permanentit. - + %1 untaps their permanents. %1 untäppää kaikki permanentit. - + %1 taps %2. %1 täppää kortin %2. - + %1 untaps %2. %1 untäppää kortin %2. - + %1 shuffles %2. %1 sekoittaa %2. - + %1 shuffles the bottom %3 cards of %2. %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. %1 irrottaa kortin %2. - + %1 undoes their last draw. %1 peruuttaa viimeisimmän kortinnostonsa. - + %1 undoes their last draw (%2). %1 peruuttaa viimeisimmän kortinnostonsa. (%2). @@ -5665,110 +6239,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Lisää viesti - - + + Message: Viesti: - + Edit message Edit message - + Chat settings Keskusteluasetukset - + Custom alert words Custom alert words - + Enable chat mentions Salli keskustelumaininnat - + Enable mention completer Enable mention completer - + In-game message macros Pelinsisäiset viestimakrot - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users Ignore private messages sent by unregistered users - - + + Invert text color Käänteinen tekstin väri - + Enable desktop notifications for private messages Enable desktop notifications for private messages - + Enable desktop notification for mentions Enable desktop notification for mentions - + Enable room message history on join Enable room message history on join - - + + (Color is hexadecimal) (Väri on heksadesimaaliluku) - + Separate words with a space, alphanumeric characters only Separate words with a space, alphanumeric characters only @@ -5885,62 +6459,62 @@ Cockatrice will now reload the card database. Phase - + Unknown Phase Unknown Phase - + Untap Untap - + Upkeep Upkeep - + Draw Draw - + First Main First Main - + Beginning of Combat Beginning of Combat - + Declare Attackers Declare Attackers - + Declare Blockers Declare Blockers - + Combat Damage Combat Damage - + End of Combat End of Combat - + Second Main Second Main - + End/Cleanup End/Cleanup @@ -6006,7 +6580,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -6015,134 +6589,134 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - + Move top cards to grave - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6150,17 +6724,17 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters - + S&ay @@ -6168,7 +6742,7 @@ Cockatrice will now reload the card database. PrintingSelector - + Display Navigation Buttons @@ -6176,22 +6750,22 @@ Cockatrice will now reload the card database. PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6207,17 +6781,17 @@ Cockatrice will now reload the card database. PrintingSelectorCardSelectionWidget - + Previous Card in Deck - + Bulk Selection - + Next Card in Deck @@ -6225,28 +6799,28 @@ Cockatrice will now reload the card database. PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -6312,37 +6886,37 @@ Cockatrice will now reload the card database. QMenuBar - + Services Services - + Hide %1 Hide %1 - + Hide Others Hide Others - + Show All Show All - + Preferences... Preferences... - + Quit %1 Quit %1 - + About %1 About %1 @@ -6350,42 +6924,42 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) Cockatrice card database (*.xml) - + All files (*.*) All files (*.*) - + Cockatrice replays (*.cor) Cockatrice replays (*.cor) - + Maindeck Maindeck - + Sideboard Sideboard - + Tokens Tokens - + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? @@ -6393,92 +6967,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK OK - + Save Save - + Save All Save All - + Open Avaa - + &Yes &Yes - + Yes to &All Yes to &All - + &No &Ei - + N&o to All N&o to All - + Abort Abort - + Retry Retry - + Ignore Sivuuta - + Close Close - + Cancel Cancel - + Discard Discard - + Help Help - + Apply Apply - + Reset Nollaa - + Restore Defaults Restore Defaults @@ -6575,37 +7149,37 @@ Cockatrice will now reload the card database. RoomSelector - + Rooms Rooms - + Joi&n Joi&n - + Room Room - + Description Description - + Permissions Permissions - + Players Players - + Games Games @@ -6646,27 +7220,27 @@ Cockatrice will now reload the card database. SetsModel - + Enabled Enabled - + Set type Set type - + Set code Set code - + Long name Long name - + Release date Release date @@ -6674,53 +7248,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts Restore all default shortcuts - + Do you really want to restore all default shortcuts? Do you really want to restore all default shortcuts? - + Clear all default shortcuts Clear all default shortcuts - + Do you really want to clear all shortcuts? Do you really want to clear all shortcuts? - + Section: Section: - + Action: Action: - + Shortcut: Shortcut: - + How to set custom shortcuts How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -6728,12 +7302,12 @@ Cockatrice will now reload the card database. ShortcutTreeView - + Action - + Shortcut @@ -6741,14 +7315,14 @@ Cockatrice will now reload the card database. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: The following shortcuts have been set to default: @@ -6776,12 +7350,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -6789,27 +7363,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Enable &sounds - + Current sounds theme: Current sounds theme: - + Test system sound engine Test system sound engine - + Sound settings Sound settings - + Master volume Master volume @@ -6817,48 +7391,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed Spoilers download failed - + No internet connection No internet connection - + Error Error - + Spoilers already up to date Spoilers already up to date - + No new spoilers added No new spoilers added - + Spoilers have been updated! Spoilers have been updated! - + Last change: Last change: @@ -7022,56 +7596,123 @@ Please check your shortcut settings! + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info Card Info - + Deck Deck - + Filters Filters - + &View &View - + + Card Database + + + + Printing - - - - + + + + + Visible Visible - - - - + + + + + Floating Floating - + Reset layout Reset layout - + Deck: %1 Deck: %1 @@ -7079,61 +7720,61 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + + + + Visible - - - - + + + + Floating - + Reset layout @@ -7141,22 +7782,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7164,134 +7805,134 @@ Please check your shortcut settings! TabDeckStorage - + Local file system Local file system - + Server deck storage Server deck storage - - + + Open in deck editor Open in deck editor - + Rename deck or folder - + Upload deck Upload deck - + Download deck Download deck + - - - + + New folder New folder + - Delete Delete - + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + Error - + Rename failed - - + + Invalid deck file - + Enter deck name Enter deck name - + This decklist does not have a name. Please enter a name: This decklist does not have a name. Please enter a name: - + Unnamed deck Unnamed deck - + Failed to upload deck to server - + Delete local file Delete local file - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + Name of new folder: Name of new folder: @@ -7304,17 +7945,17 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 @@ -7355,7 +7996,7 @@ Please enter a name: - + EDHRec: @@ -7363,197 +8004,197 @@ Please enter a name: TabGame - - - + + + Replay Replay - - + + Game Game - - + + Player List Player List - - + + Card Info Card Info - - + + Messages Messages - - + + Replay Timeline Replay Timeline - + &Phases &Phases - + &Game &Game - + Next &phase Next &phase - + Next phase with &action Next phase with &action - + Next &turn Next &turn - + Reverse turn order Käännä vuorojärjestys - + &Remove all local arrows &Remove all local arrows - + Rotate View Cl&ockwise Rotate View Cl&ockwise - + Rotate View Co&unterclockwise Rotate View Co&unterclockwise - + Game &information Game &information - + Un&concede - - - + + + &Concede &Concede - + &Leave game &Leave game - + C&lose replay C&lose replay - + &Focus Chat &Focus Chat - + &Say: &Say: - + Selected cards - + &View &View - - + + + - Visible Visible - - + + + - Floating Floating - + Reset layout Reset layout - + Concede Concede - + Are you sure you want to concede this game? Are you sure you want to concede this game? - + Unconcede Unconcede - + You have already conceded. Do you want to return to this game? You have already conceded. Do you want to return to this game? - + Leave game Poistu pelistä - + Are you sure you want to leave this game? Haluatko varmasti poistua pelistä? - + A player has joined game #%1 Pelaaja on littynyt peliin #%1 - + %1 has joined the game - + You have been kicked out of the game. You have been kicked out of the game. @@ -7561,7 +8202,7 @@ Please enter a name: TabHome - + Home @@ -7569,158 +8210,158 @@ Please enter a name: TabLog - + Logs Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs Room Logs - + Game Logs Game Logs - + Chat Logs Chat Logs - - + + Error Virhe - + You must select at least one filter. You must select at least one filter. - + You have to select a valid number of days to locate. You have to select a valid number of days to locate. - + Username: Käyttäjätunnus: - + IP Address: IP-osoite: - + Game Name: Pelin Nimi: - + GameID: Peli-ID: - + Message: Viesti: - + Main Room Main Room - + Game Room Game Room - + Private Chat Private Chat - + Past X Days: Past X Days: - + Today Tänään - + Last Hour Last Hour - + Maximum Results: Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs Get User Logs - + Clear Filters Clear Filters - + Filters Filters - + Log Locations Log Locations - + Date Range Date Range - + Maximum Results Maximum Results - - + + Message History Message History - + Failed to collect message history information. Failed to collect message history information. - + There are no messages for the selected filters. There are no messages for the selected filters. @@ -7766,180 +8407,180 @@ The more information you put in, the more specific your results will be. TabReplays - + Local file system Local file system - + Server replay storage Server replay storage - - + + Watch replay Watch replay - + Rename - - + + New folder - - + + Delete Delete - + Open replays folder - + Download replay Download replay - + Toggle expiration lock Toggle expiration lock - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file Delete local file - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay Delete remote replay @@ -8005,30 +8646,30 @@ The more information you put in, the more specific your results will be.Server + - Error Virhe - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -8036,92 +8677,97 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - - - Home - - - - - &Visual Deck Storage - - - - - Visual Database Display - - - Server + Archidekt - Account + Home - Deck Storage + &Visual Deck Storage - Game Replays + Visual Database Display - Administration + Server + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + Logs - + Are you sure? Oletko varma? - + There are still open games. Are you sure you want to quit? There are still open games. Are you sure you want to quit? - + Click to view Click to view - + Your buddy %1 has signed on! Your buddy %1 has signed on! - + Unknown Event Tuntematon Tapahtuma - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8132,39 +8778,39 @@ This message might mean there is a new version of Cockatrice available or this s To update your client, go to Help -> Check for Updates. - + Idle Timeout Idle Timeout - + You are about to be logged out due to inactivity. You are about to be logged out due to inactivity. - + Promotion Promotion - + You have been promoted. Please log out and back in for changes to take effect. You have been promoted. Please log out and back in for changes to take effect. - + Warned Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) You have received the following message from the server. @@ -8174,7 +8820,12 @@ Please refrain from engaging in this activity or further actions may be taken ag TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display @@ -8256,7 +8907,7 @@ Please refrain from engaging in this activity or further actions may be taken ag UpdateDownloader - + Could not open the file for reading. Could not open the file for reading. @@ -8264,206 +8915,206 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details User &details - + Private &chat Private &chat - + Show this user's &games Show this user's &games - + Add to &buddy list Add to &buddy list - + Remove from &buddy list Remove from &buddy list - + Add to &ignore list Add to &ignore list - + Remove from &ignore list Remove from &ignore list - + Kick from &game Kick from &game - + Warn user Warn user - + View user's war&n history View user's war&n history - + Ban from &server Ban from &server - + View user's &ban history View user's &ban history - + &Promote user to moderator &Promote user to moderator - + Dem&ote user from moderator Dem&ote user from moderator - + Promote user to &judge - + Demote user from judge Demote user from judge - + View admin notes - - - + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games %1's games - - - + + + Ban History Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. User has never been banned. - + Failed to collect ban information. Failed to collect ban information. - - - + + + Warning History Warning History - + Warning Time;Moderator;User Name;Reason Warning Time;Moderator;User Name;Reason - + User has never been warned. User has never been warned. - + Failed to collect warning information. Failed to collect warning information. - + Failed to get admin notes. - - + + Success Success - + Successfully promoted user. Successfully promoted user. - + Successfully demoted user. Successfully demoted user. - + + - Failed Failed - + Failed to promote user. Failed to promote user. - + Failed to demote user. Failed to demote user. - + Copy hash to clipboard Copy hash to clipboard - + Remove this user's messages @@ -8645,137 +9296,142 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings General interface settings - + &Double-click cards to play them (instead of single-click) &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default &Play all nonlands onto the stack (not the battlefield) by default - - - Close card view window when last card is removed - - - - - Auto focus search bar when card view window is opened - - - - - Annotate card text on tokens - Annotate card text on tokens - - - - Use tear-off menus, allowing right click menus to persist on screen - Use tear-off menus, allowing right click menus to persist on screen - - - - Notifications settings - Ilmoitusasetukset - - - - Enable notifications in taskbar - Ota tehtäväpalkin ilmoitukset käyttöön - - - - Notify in the taskbar for game events while you are spectating - Notify in the taskbar for game events while you are spectating - - - - Notify in the taskbar when users in your buddy list connect - Notify in the taskbar when users in your buddy list connect - - - - Animation settings - Animaatioasetukset - - - - &Tap/untap animation - &Tap/untap animation - - Deck editor/storage settings + Do not delete &arrows inside of subphases - Open deck in new tab by default + Close card view window when last card is removed - Use visual deck storage in game lobby + Auto focus search bar when card view window is opened - Use selection animation for Visual Deck Storage - + Annotate card text on tokens + Annotate card text on tokens + + + + Use tear-off menus, allowing right click menus to persist on screen + Use tear-off menus, allowing right click menus to persist on screen - When adding a tag in the visual deck storage to a .txt deck: - + Notifications settings + Ilmoitusasetukset + + + + Enable notifications in taskbar + Ota tehtäväpalkin ilmoitukset käyttöön - do nothing - + Notify in the taskbar for game events while you are spectating + Notify in the taskbar for game events while you are spectating + + + + Notify in the taskbar when users in your buddy list connect + Notify in the taskbar when users in your buddy list connect - ask to convert to .cod - + Animation settings + Animaatioasetukset + + + + &Tap/untap animation + &Tap/untap animation - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -8783,22 +9439,22 @@ Please refrain from engaging in this activity or further actions may be taken ag UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 @@ -8806,32 +9462,32 @@ Please refrain from engaging in this activity or further actions may be taken ag UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -8839,22 +9495,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match - + Mode: Includes - + Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -8862,21 +9518,49 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8902,28 +9586,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - Load from Deck + Filter by name... (Exact match) - Apply all card names in currently loaded deck as exact match name filters + Load from Deck - Load from Clipboard + Apply all card names in currently loaded deck as exact match name filters + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -8987,50 +9671,115 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - + + Sort by: + + + + + Filter by: + + + + Save and load filters - + Filter by exact card name - + Filter by card sub-type - + Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9038,46 +9787,25 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -9133,7 +9861,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9141,22 +9869,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -9164,17 +9892,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + Visual Deck Storage Settings @@ -9182,43 +9910,43 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Cancel - + Warn user for misconduct Warn user for misconduct - - + + Error Virhe - + User name to send a warning to can not be blank, please specify a user to warn. User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. Warning to use can not be blank, please select a valid warning to send. @@ -9226,133 +9954,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top Move selected set to the top - + Move selected set up Move selected set up - + Move selected set down Move selected set down - + Move selected set to the bottom Move selected set to the bottom - + Search by set name, code, or type Search by set name, code, or type - + Default order Default order - + Restore original art priority order Restore original art priority order - + Enable all sets Enable all sets - + Disable all sets Disable all sets - + Enable selected set(s) Enable selected set(s) - + Disable selected set(s) Disable selected set(s) - + Deck Editor Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art Card Art - + How to use custom card art How to use custom card art - + Hints Hints - + Note Note - + Sorting by column allows you to find a set while not changing set priority. Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead Use the current sorting as the set priority instead - + Sorts the set priority using the same column Sorts the set priority using the same column - + Manage sets Manage sets @@ -9360,72 +10088,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing shuffle when closing - + pile view pile view @@ -9433,7 +10161,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English Suomi (Finnish) @@ -9441,12 +10169,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup Connect on startup - + Debug to file Debug to file @@ -9454,1005 +10182,1041 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window Main Window - - + + Deck Editor Deck Editor - + Game Lobby Game Lobby - + Card Counters Card Counters - + Player Counters Player Counters - + Power and Toughness Power and Toughness - + Game Phases Game Phases - + Playing Area Playing Area - + Move Selected Card Move Selected Card - + View View - + Move Top Card Move Top Card - + Move Bottom Card Move Bottom Card - + Gameplay Gameplay - + Drawing Drawing - + Chat Room Chat Room - + Game Window Game Window - + Load Deck from Clipboard Load Deck from Clipboard - - + + Replays - + Tabs - + Check for Card Updates... Check for Card Updates... - + Connect... Connect... - + Disconnect Katkaise yhteys - + Exit Exit - + Full screen Full screen - + Register... Register... - + Settings... Settings... - + Start a Local Game... Start a Local Game... - + Watch Replay... Watch Replay... - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters Clear All Filters - + Clear Selected Filter Clear Selected Filter - + Close Close - + Remove Card Remove Card - + Manage Sets... Manage Sets... - + Edit Custom Tokens... Edit Custom Tokens... - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card Add Card - + Load Deck... Load Deck... - - + + Load Deck from Clipboard... Load Deck from Clipboard... - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck New Deck - + Open Custom Pictures Folder Open Custom Pictures Folder - + Print Deck... Print Deck... - + Delete Card Delete Card - - + + Reset Layout Reset Layout - + Save Deck Tallenna Pakka - + Save Deck as... Save Deck as... - + Save Deck to Clipboard, Annotated Save Deck to Clipboard, Annotated - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard Save Deck to Clipboard - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... Load Local Deck... - + Load Remote Deck... Load Remote Deck... - + Set Ready to Start Set Ready to Start - + Toggle Sideboard Lock Toggle Sideboard Lock - + Add Green Counter Add Green Counter - + Remove Green Counter Remove Green Counter - + Set Green Counters... Set Green Counters... - + Add Red Counter Add Red Counter - + Remove Red Counter Remove Red Counter - + Set Red Counters... Set Red Counters... - + Add Life Counter Add Life Counter - + Show Status Bar - + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter Remove Life Counter - + Set Life Counters... Set Life Counters... - + Add White Counter Add White Counter - + Remove White Counter Remove White Counter - + Set White Counters... Set White Counters... - + Add Blue Counter Add Blue Counter - + Remove Blue Counter Remove Blue Counter - + Set Blue Counters... Set Blue Counters... - + Add Black Counter Add Black Counter - + Remove Black Counter Remove Black Counter - + Set Black Counters... Set Black Counters... - + Add Colorless Counter Add Colorless Counter - + Remove Colorless Counter Remove Colorless Counter - + Set Colorless Counters... Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Increment all card counters - + Add Power (+1/+0) Add Power (+1/+0) - + Remove Power (-1/-0) Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) Add Toughness (+0/+1) - + Remove Toughness (-0/-1) Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) Remove Power and Toughness (-1/-1) - + Set Power and Toughness... Set Power and Toughness... - + Reset Power and Toughness Reset Power and Toughness - + Untap Untap - + Upkeep Upkeep - + Draw Draw - + First Main Phase First Main Phase - + Start Combat Start Combat - + Attack Attack - + Block Block - + Damage Damage - + End Combat End Combat - + Second Main Phase Second Main Phase - + End End - + Next Phase Next Phase - + Next Phase Action Next Phase Action - + Next Turn Next Turn - + Hide Card in Reveal Window - + Tap / Untap Card Tap / Untap Card - + Untap All Untap All - + Toggle Untap Toggle Untap - + Turn Card Over Turn Card Over - + Peek Card Peek Card - + Play Card Play Card - + Attach Card... Attach Card... - + Unattach Card Unattach Card - + Clone Card Clone Card - + Create Token... Create Token... - + Create All Related Tokens Create All Related Tokens - + Create Another Token Create Another Token - + Set Annotation... Set Annotation... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library Bottom of Library - - - - + + + + Exile Exile - - - - + + + + Graveyard Graveyard - - + + + Hand Hand - - + + Top of Library Top of Library - - - + + + Battlefield, Face Down Battlefield, Face Down - + Battlefield Battlefield - - Sort Hand - - - - + Library Library - + Sideboard Sideboard - + Top Cards of Library Top Cards of Library - + Bottom Cards of Library - + Close Recent View Close Recent View - - + + Stack Stack - - + + Graveyard (Multiple) Graveyard (Multiple) - - + + Exile (Multiple) Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... Draw Arrow... - + Remove Local Arrows Remove Local Arrows - + Leave Game Leave Game - + Concede Concede - + Roll Dice... Roll Dice... - + Shuffle Library Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan Mulligan - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card Draw a Card - + Draw Multiple Cards... Draw Multiple Cards... - + Undo Draw Undo Draw - + Always Reveal Top Card Always Reveal Top Card - + Always Look At Top Card - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise Rotate View Clockwise - + Rotate View Counterclockwise Rotate View Counterclockwise - + Unfocus Text Box Unfocus Text Box - + Focus Chat Focus Chat - + Clear Chat Clear Chat - + Refresh Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_fr.ts b/cockatrice/translations/cockatrice_fr.ts index 76a42dcd3..d01e564c0 100644 --- a/cockatrice/translations/cockatrice_fr.ts +++ b/cockatrice/translations/cockatrice_fr.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... Nombre de marqueur&s… @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Nombre de marqueurs - + New value for counter '%1': Nouvelle valeur des marqueurs « %1 » : @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh &Rafraîchir - + Parse Set Name and Number (if available) Analyser le nom et le numéro de l'extension (si disponibles) @@ -36,81 +36,83 @@ AbstractTabDeckEditor - + Open in new tab Ouvrir dans un nouvel onglet - + Are you sure? Êtes-vous sûr ? - + The decklist has been modified. Do you want to save the changes? La liste de deck a été modifiée. Voulez-vous enregistrer les modifications ? - - - - - - - + + + + + + Error Erreur - + Could not open deck at %1 N'a pas pu ouvrir le deck à %1 - + Could not save remote deck N'a pas pu sauvegarder le deck distant - - + + The deck could not be saved. Please check that the directory is writable and try again. Le deck n'a pas pu être enregistré. Vérifiez que le répertoire ne soit pas en lecture seule et réessayez. - + Save deck Sauvegarder le deck - + The deck could not be saved. Le deck n'a pas pu être enregistré. - + There are no cards in your deck to be exported Il n'y a pas de cartes dans le deck à exporter + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. - Aucun deck n'a été sélectionné pour être exporté. + + Add Analytics Panel + AdminNotesDialog - + Update Notes Notes de mises à jour - + Admin Notes for %1 Notes d'administrateur pour %1 @@ -118,12 +120,12 @@ Vérifiez que le répertoire ne soit pas en lecture seule et réessayez. AllZonesCardAmountWidget - + Mainboard Deck principal - + Sideboard Réserve @@ -131,22 +133,22 @@ Vérifiez que le répertoire ne soit pas en lecture seule et réessayez. AppearanceSettingsPage - + seconds secondes - + Error Erreur - + Could not create themes directory at '%1'. N'a pas pu créer le dossier Thèmes à '%1' - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -163,7 +165,7 @@ Vous devrez utiliser le Gestionnaire d'extensions, accessible via Base de d Êtes-vous sûr de vouloir activer cette fonction ? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -180,152 +182,165 @@ Vous pouvez également utiliser le gestionnaire de d'extension pour personn Êtes-vous sûr de vouloir désactiver cette fonction ? - + Confirm Change Confirmer le changement - + Theme settings Paramètres du thème - + Current theme: Thème actuel : - + Open themes folder Ouvrir le dossier des thèmes - + Home tab background source: Source d'arrière-plan de l'onglet Accueil: - + Home tab background shuffle frequency: Fréquence de mélange de l'arrière-plan de l'onglet Accueil: - + Disabled Désactivé - + Menu settings Options du Menu - + Show keyboard shortcuts in right-click menus Montrer les raccourcis clavier dans les menus clic-droit - + + Show game filter toolbar above list in room tab + + + + Card rendering Rendu des cartes - + Display card names on cards having a picture Afficher les noms des cartes ayant une image - + Auto-Rotate cards with sideways layout Rotation automatique des cartes avec disposition horizontale - + Override all card art with personal set preference (Pre-ProviderID change behavior) Remplacer toutes les illustrations de cartes par les préférences personnelles (comportement avant modification du ProviderID) - + Bump sets that the deck contains cards from to the top in the printing selector Mettre en avant que ce deck contient des cartes depuis le sommet, dans le sélecteur d'impression - + Scale cards on mouse over Agrandir les cartes lors du survol du curseur - + Use rounded card corners Utiliser des bords de cartes arrondis - + Minimum overlap percentage of cards on the stack and in vertical hand Pourcentage minimum de chevauchement des cartes dans la pile et dans la main verticale - + Maximum initial height for card view window: Hauteur initiale maximale pour la fenêtre d'affichage des cartes : - - + + rows lignes - + Maximum expanded height for card view window: Hauteur étendue maximale pour la fenêtre d'affichage des cartes : - + Card counters Marqueurs sur la carte - + Counter %1 Marqueur %1 - + Hand layout Disposition de la main - + Display hand horizontally (wastes space) Afficher la main horizontalement (perte d'espace) - + Enable left justification Activer la justification à gauche - + Table grid layout Disposition en forme de grille - + Invert vertical coordinate Inverser la disposition du champ de bataille - + Minimum player count for multi-column layout: Nombre minimum de joueurs pour la disposition multi-colonnes : - + Maximum font size for information displayed on cards: Taille maximale de la police pour les informations affichées sur les cartes  : + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -347,112 +362,112 @@ Vous pouvez également utiliser le gestionnaire de d'extension pour personn BanDialog - + ban &user name bannir &nom d'utilisateur - + ban &IP address bannir &adresse IP - + ban client I&D bannir &ID du client - + Ban type Type du bannissement - + &permanent ban &bannissement permanent - + &temporary ban bannissement &temporaire - + &Days: &Jours : - + &Hours: &Heures : - + &Minutes: &Minutes : - + Duration of the ban Durée du bannissement - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Veuillez expliquer la raison du bannissement. Cette information sera consultable uniquement par les modérateurs et ne sera pas visible par la personne bannie. - + Please enter the reason for the ban that will be visible to the banned person. Veuillez expliquer la raison du bannissement. Cette information sera visible par la personne bannie. - + Redact all messages from this user in all rooms Censurer tous les messages de cet utilisateur dans toutes les salles - + &OK &OK - + &Cancel &Annuler - + Ban user from server Bannir le joueur du serveur - + + - - + Error Erreur - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Vous devez choisir un bannissement à partir du nom, de l'adresse IP, de l'ID du client, ou une combinaison des trois. - + You must have a value in the name ban when selecting the name ban checkbox. Vous devez remplir le champ du nom du banni en le choisissant dans la liste. - + You must have a value in the ip ban when selecting the ip ban checkbox. Vous devez saisir l'adresse IP du banni en le choisissant dans la liste. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Vous devez saisir l'ID du client banni en le choisissant dans la liste. @@ -483,32 +498,32 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardDatabaseModel - + Name Nom - + Sets Éditions - + Mana cost Coût de mana - + Card type Type de carte - + P/T F/E - + Color(s) Couleur(s) @@ -516,96 +531,101 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardFilter - + AND Logical conjunction operator used in card filter ET - + OR Logical disjunction operator used in card filter OU - + AND NOT Negated logical conjunction operator used in card filter ET PAS - + OR NOT Negated logical disjunction operator used in card filter OU PAS - + Name Nom - + + Name (Exact) + + + + Type Type - + Color Couleur - + Text Texte - + Set Édition - + Mana Cost Coût de mana - + Mana Value Valeur de Mana - + Rarity Rareté - + Power Force - + Toughness Endurance - + Loyalty Loyauté - + Format Format - + Main Type Type principal - + Sub Type Sous-type @@ -613,22 +633,22 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardInfoFrameWidget - + Image Image - + Description Description - + Both Les deux - + View transformation Voir la transformation @@ -636,22 +656,22 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardInfoPictureWidget - + View related cards Voir les cartes associées - + Add card to deck Ajouter la carte au deck - + Mainboard Deck principal - + Sideboard Réserve @@ -664,12 +684,22 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa Nom: - + + Set: + + + + + Collector Number: + + + + Related cards: Cartes associées : - + Unknown card: Carte inconnue : @@ -677,124 +707,124 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardMenu - + Re&veal to... Révéler à... - + &All players &Tous les joueurs - + View related cards Voir les cartes associées - + Token: Jeton : - + All tokens Tout les jetons - + &Select All &Sélectionner Tout - + S&elect Row Sél&ectionne une ligne - + S&elect Column Sél&ectionne une colonne - + &Play &Jouer - + &Hide &Cacher - + Play &Face Down Jouer &Face Cachée - + &Tap / Untap Turn sideways or back again &Engager / Dégager - + Toggle &normal untapping Activer dégagement &normal - + T&urn Over Turn face up/face down Reto&urner - + &Peek at card face &Regarder furtivement la carte face cachée - + &Clone &Cloner - + Attac&h to card... Attac&her à une carte... - + Unattac&h Détac&her - + &Draw arrow... &Tracer une flèche... - + &Set annotation... &Annoter... - + Ca&rd counters Ma&rqueurs sur la carte - + &Add counter (%1) &Ajouter un marqueur (%1) - + &Remove counter (%1) &Retirer un marqueur (%1) - + &Set counters (%1)... &Changer le nombre de marqueurs (%1)... @@ -802,7 +832,7 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardSizeWidget - + Card Size Taille de la Carte @@ -810,133 +840,133 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardZoneLogic - + their hand nominative sa main - + %1's hand nominative main de %1 - - - their library - look at zone - sa bibliothèque - - - - %1's library - look at zone - bibliothèque de %1 - - - - of their library - top cards of zone, - de sa bibliothèque - - - - of %1's library - top cards of zone - de la bibliothèque de %1 - their library - reveal zone + look at zone sa bibliothèque %1's library + look at zone + bibliothèque de %1 + + + + of their library + top cards of zone, + de sa bibliothèque + + + + of %1's library + top cards of zone + de la bibliothèque de %1 + + + + their library + reveal zone + sa bibliothèque + + + + %1's library reveal zone bibliothèque de %1 - + their library shuffle sa bibliothèque - + %1's library shuffle bibliothèque de %1 - + their library nominative sa bibliothèque - + %1's library nominative bibliothèque de %1 - + their graveyard nominative son cimetière - + %1's graveyard nominative le cimetière de %1 - + their exile nominative sa zone d'exil - + %1's exile nominative la zone d'exil de %1 - + their sideboard look at zone sa réserve - + %1's sideboard look at zone la réserve de %1 - - - their sideboard - nominative - sa réserve - - - - %1's sideboard - nominative - la réserve de %1 - + their sideboard + nominative + sa réserve + + + + %1's sideboard + nominative + la réserve de %1 + + + their custom zone '%1' nominative sa zone personnalisée '%1' - + %1's custom zone '%2' nominative La zone personnalisée '%2' de %1 @@ -945,7 +975,7 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CockatriceXml3Parser - + Parse error at line %1 col %2: Erreur de lecture à la ligne %1 (colonne %2) @@ -953,7 +983,7 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CockatriceXml4Parser - + Parse error at line %1 col %2: Erreur de lecture à la ligne %1 (colonne %2) @@ -972,6 +1002,37 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa Voir la zone personnalisée '%1' + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -983,47 +1044,47 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) Rechercher par nom de carte (ou expression de recherche) - + Add to Deck Ajouter au deck - + Add to Sideboard Ajouter à la réserve - + Select Printing Choisir l'impression - + Show on EDHRec (Commander) Afficher sur EDHRec (Commandant) - + Show on EDHRec (Card) Afficher sur EDHRec (carte) - + Show Related cards Afficher les cartes associées - + Add card to &maindeck Ajouter la carte au &deck - + Add card to &sideboard Ajouter carte à la ré&serve @@ -1031,87 +1092,97 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card Carte bannière - + Main Type Type principal - + Mana Cost Coût de mana - + Colors Couleurs - + Select Printing Choisir l'impression - + Deck Deck - + Deck &name: &Nom du deck : - + Banner Card/Tags Visibility Settings Paramètres de visibilité de la carte bannière et des étiquettes - + Show banner card selection menu Montrer le menu de sélection de carte bannière - + Show tags selection menu Montrer le menu de sélection d'étiquettes - + &Comments: &Commentaires : - + Group by: Grouper par : - + + Format: + + + + Hash: Empreinte : - + &Increment number Augmenter quant&ité - + &Decrement number &Diminuer quantité - + &Remove row &Retirer la ligne - + Swap card to/from sideboard Échanger la carte vers/depuis la réserve @@ -1119,17 +1190,17 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckEditorFilterDockWidget - + Filters Filtres - + &Clear all filters &Effacer tous les filtres - + Delete selected Enlever la sélection @@ -1137,114 +1208,114 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckEditorMenu - + &Deck Editor &Éditeur de deck - + &New deck &Nouveau deck - + &Load deck... &Charger un deck... - + Load recent deck... Charger un deck récent... - + Clear Effacer - + &Save deck &Sauvegarder le deck - + Save deck &as... S&auvegarder le deck sous... - + Load deck from cl&ipboard... Charger un deck depuis le presse-pap&ier... - + Edit deck in clipboard Éditer le deck dans le presse-papier - - + + Annotated Avec annotations - - + + Not Annotated Sans annotation - + Save deck to clipboard Copier le deck dans le presse-papier - + Annotated (No set info) Annoté (pas d'info sur l'extension) - + Not Annotated (No set info) Pas annoté (pas d'info sur l'extension) - + &Print deck... Im&primer le deck... - + Load deck from online service... Charger un deck depuis un service en ligne... - + &Send deck to online service Envoyer le deck au service en &ligne - + Create decklist (decklist.org) Créer une liste de deck (decklist.org) - + Create decklist (decklist.xyz) Créer une liste de deck (decklist.xyz) - + Analyze deck (deckstats.net) &Analyser le deck sur deckstats.net - + Analyze deck (tappedout.net) Analyser le deck (tappedout.net) - + &Close &Fermer @@ -1252,7 +1323,7 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckEditorPrintingSelectorDockWidget - + Printing Selector Sélection d'impression @@ -1260,194 +1331,227 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckEditorSettingsPage - - + + Update Spoilers Mettre à jour les spoilers - - + + Success Réussite - + Download URLs have been reset. Les URLs de téléchargement ont été réinitialisées. - + Downloaded card pictures have been reset. Les images des cartes téléchargées ont été supprimées. - + Error Erreur - + One or more downloaded card pictures could not be cleared. Une ou plusieurs images de cartes téléchargées n'ont pas pu être supprimées. - + Add URL Ajouter une URL - - + + URL: URL : - - + + Edit URL Modifier l'URL - + Network Cache Size: Taille du cache du réseau : - + Redirect Cache TTL: Rediriger le TTL du cache : - + How long cached redirects for urls are valid for. Combien de temps les redirections mises en cache pour les URL sont-elles valides. - + Picture Cache Size: Taille du cache des images : - + Add New URL Ajouter Nouveau Lien - + Remove URL Effacer Lien - + Day(s) Jour(s) - + Updating... Mise à jour... - + Choose path Choisir le chemin - + URL Download Priority URL de téléchargement prioritaire - + Spoilers Spoilers - + Download Spoilers Automatically Télécharger automatiquement les spoilers - + Spoiler Location: Emplacement des spoilers : - + Last Change Dernier changement - + Spoilers download automatically on launch Télécharger automatiquement les spoilers au lancement - + Press the button to manually update without relaunching Appuyez sur le bouton pour mettre à jour manuellement sans redémarrer. - + Do not close settings until manual update is complete Ne pas fermer la fenêtre des paramètres avant la mise à jour manuelle complète. - + Download card pictures on the fly Télécharger les images des cartes à la volée - + How to add a custom URL Comment ajouter une URL personnalisée - + Delete Downloaded Images Supprimer les images téléchargées - + Reset Download URLs Réinitialiser les URL de téléchargement - + On-disk cache for downloaded pictures Cache pour les images téléchargées - + In-memory cache for pictures not currently on screen Cache en mémoire pour les imges non affichées actuellement + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count Compter - + Set Édition - + Number Nombre - + Provider ID Provider ID - + Card Carte @@ -1455,12 +1559,12 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckLoader - + Common deck formats (%1) Formats de decks courants (%1) - + All files (*.*) Tous les fichiers (*.*) @@ -1468,17 +1572,17 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match Mode : correspondance exacte - + Mode: Includes Mode : inclure - + Color identity filter mode (AND/OR/NOT conjunctions of filters) Mode de filtre par identité de couleur (conjonctions AND/OR/NOT de filtres) @@ -1486,7 +1590,7 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckPreviewDeckTagsDisplayWidget - + Edit tags ... Éditer les étiquettes... @@ -1494,62 +1598,62 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckPreviewTagDialog - + Deck Tags Manager Gestionnaire d'étiquettes de decks - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: Gérer vos étiquettes de decks. Ajouter ou retirer les étiquettes comme nécessaire, ou en créer de nouveaux : - + Add a new tag (e.g., Aggro️) Ajouter une nouvelle étiquette (e.g., Elfes) - + Add Tag Ajouter Étiquette - + Filter tags... Filtrer les étiquettes... - + OK OK - + Edit default tags Éditer les étiquettes par défaut - + Cancel Annuler - + Invalid Input Entrée invalide - + Tag name cannot be empty! Le nom de l'étiquette ne peut pas être vide ! - + Duplicate Tag Étiquette dupliquée - + This tag already exists. Cette étiquette existe déjà. @@ -1562,93 +1666,151 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa Carte bannière - + Open in deck editor Ouvrir dans l'éditeur de deck - + Edit Tags Éditer les étiquettes - + Rename Deck Renommer le deck - + Save Deck to Clipboard Copier le deck dans le presse-papier - + Annotated Avec annotations - + Annotated (No set info) Avec annotations (pas d'info sur l'extension) - + Not Annotated Sans annotation - + Not Annotated (No set info) Sans annotation (pas d'info sur l'extension) - + Rename File Renommer le fichier - + Delete File Supprimer le fichier - + Set Banner Card Choisir la carte bannière - - + + New name: Nouveau nom : - - + + Error Erreur - + Rename failed Renommage échoué - + Delete file Supprimer le fichier - + Are you sure you want to delete the selected file? Êtes-vous certain de vouloir supprimer le fichier sélectionné ? - + Delete failed Suppression échouée + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1666,75 +1828,75 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckViewContainer - + Load deck... Charger un deck... - + Load remote deck... Charger un deck distant... - + Load from clipboard... Charger depuis le presse-papier... - + Load from website... Charger depuis un site web... - + Unload deck Annuler le chargement du deck - + Ready to start Prêt à démarrer - + Force start Forcer le démarrage - + Sideboard unlocked Réserve déverrouillée - + Sideboard locked Réserve verrouillée - - + + Error Erreur - + The selected file could not be loaded. Le fichier sélectionné n'a pas pu être chargé. - + Deck is greater than maximum file size. Le deck est plus grand que la taille maximum de fichier. - + Are you sure you want to force start? This will kick all non-ready players from the game. Êtes-vous sûr de vouloir forcer le démarrage ? Cela va éjecter de la partie tous les joueurs non prêts à démarrer. - + Cockatrice Cockatrice @@ -1769,143 +1931,143 @@ Voulez-vous convertir le deck au format .cod ? Téléchargement - + Known Hosts Hôtes connus - + Delete the currently selected saved server Supprimer le serveur enregistré actuellement sélectionné - + Refresh the server list with known public servers Actualiser la liste de serveurs avec des serveurs publics connus - + New Host Nouvel hôte - + Name: Nom  : - + &Host: &Hôte : - + &Port: &Port : - + Player &name: &Nom du joueur : - + P&assword: Mot de p&asse : - + &Save password &Se souvenir du mot de passe - + A&uto connect Connexion a&utomatique - + Automatically connect to the most recent login when Cockatrice opens Se connecter automatiquement avec les identifiants les plus récents à l'ouverture de Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! Si vous rencontrez des difficultés pour vous connecter ou vous inscrire, contactez le personnel du serveur pour obtenir de l'aide ! - - + + Webpage Page web - + Reset Password Réinitialiser le mot de masse - + Forgot password? Mot de passe oublié ? - + &Connect &Connecter - + Server Serveur - + Login Identifiants - + Server Contact Contact du serveur - + Connect to Server Se connecter au serveur - + Server URL URL du serveur - + Communication Port Port de communication - + Unique Server Name Nom de serveur unique - + Connection Warning Avertissement de connexion - + You need to name your new connection profile. Vous devez attribuer un nom à votre nouveau profil de connexion. - + Connect Warning Avertissement de connexion - + The player name can't be empty. Le nom du joueur ne peut être vide. @@ -1913,117 +2075,122 @@ Voulez-vous convertir le deck au format .cod ? DlgCreateGame - + Re&member settings Conserver les para&mètres - + &Description: &Description : - + P&layers: &Joueurs : - + General Général - + Game type Type de partie - + &Password: Mot de &passe : - + Only &buddies can join Seuls les &amis peuvent rejoindre - + Only &registered users can join Seuls les joueurs en&registrés peuvent rejoindre - + Joining restrictions Conditions pour rejoindre - + &Spectators can watch Les &spectateurs peuvent observer - + Spectators &need a password to watch Les spectateurs ont besoin d'un &mot de passe pour observer - + Spectators can &chat Les spectateurs peuvent dis&cuter - + Spectators can see &hands Les spectateurs peuvent voir les &mains des joueurs - + Create game as spectator Créer la partie en tant que spectateur - + Spectators Spectateurs - + Starting life total: Total initial de point de vie - + Open decklists in lobby Ouvrir les listes de deck dans le lobby - + + Create game as judge + + + + Game setup options Options de configuration de la partie - + &Clear Effa&cer - + Create game Créer une partie - + Game information Informations sur la partie - + Error Erreur - + Server error. Erreur serveur. @@ -2031,97 +2198,97 @@ Voulez-vous convertir le deck au format .cod ? DlgCreateToken - + &Name: &Nom : - + Token Jeton - + C&olor: C&ouleur : - + white blanc - + blue bleu - + black noir - + red rouge - + green vert - + multicolor multicolore - + colorless incolore - + &P/T: &F/E : - + &Annotation: &Annotation : - + &Destroy token when it leaves the table &Detruire le jeton lorsqu'il quitte le champ de bataille - + Create face-down (Only hides name) Créer face-cachée (cache seulement le nom) - + Token data Données du jeton - + Show &all tokens Afficher &tous les jetons - + Show tokens from this &deck Afficher les jetons présents dans ce &deck - + Choose token from list Choisir un jeton dans la liste - + Create token Créer un jeton @@ -2129,53 +2296,53 @@ Voulez-vous convertir le deck au format .cod ? DlgDefaultTagsEditor - + Edit Tags Éditer les étiquettes - + Add Ajouter - + Confirm Confirmer - + Cancel Annuler - + Enter a tag and press Enter Entrer une étiquette et valider avec Entrée - - + + - + Invalid Input Entrée invalide - + Tag name cannot be empty! Le nom de l'étiquette ne peut pas être vide ! - + Duplicate Tag Étiquette dupliquée - + This tag already exists. Cette étiquette existe déjà. @@ -2183,40 +2350,40 @@ Voulez-vous convertir le deck au format .cod ? DlgEditAvatar - - + + No image chosen. Aucune image choisie. - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. Pour changer votre avatar, choisissez une nouvelle image. Pour enlever votre avatar actuel, confirmez sans choisir une nouvelle image. - + Browse... Parcourir... - + Change avatar Changer d'avatar - + Open Image Ouvrir l'image - + Image Files (*.png *.jpg *.bmp) Fichiers image (*.png *.jpg *.bmp) - + Invalid image chosen. L'image choisie est invalide. @@ -2224,17 +2391,17 @@ Pour enlever votre avatar actuel, confirmez sans choisir une nouvelle image. DlgEditDeckInClipboard - + Edit deck in clipboard Éditer le deck dans le presse-papier - + Error Erreur - + Invalid deck list. Liste de deck invalide. @@ -2242,38 +2409,38 @@ Pour enlever votre avatar actuel, confirmez sans choisir une nouvelle image. DlgEditPassword - + Old password: Ancien mot de passe : - + New password: Nouveau mot de passe : - + Confirm new password: Confirmer le nouveau mot de passe : - + Change password Changer le mot de passe - - + + Error Erreur - + Your password is too short. Mot de passe trop court - + The new passwords don't match. Les nouveaux mots de passe ne correspondent pas. @@ -2281,93 +2448,93 @@ Pour enlever votre avatar actuel, confirmez sans choisir une nouvelle image. DlgEditTokens - + &Name: &Nom : - + C&olor: C&ouleur : - + white blanc - + blue bleu - + black noir - + red rouge - + green vert - + multicolor multicolore - + colorless incolore - + &P/T: &F/E : - + &Annotation: &Annotation : - + Token data Données du jeton - - + + Add token Ajouter un jeton - + Remove token Retirer le jeton - + Edit custom tokens Éditer les jetons personnalisés - + Please enter the name of the token: Entrez le nom du jeton : - + Error Erreur - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Le nom choisi est en conflit avec une carte ou un jeton existant. @@ -2461,8 +2628,9 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je - Hide games not created by buddy - Cacher les parties non créées par des amis. + Hide games not created by buddies + Hide games not created by buddy + @@ -2548,52 +2716,52 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je DlgForgotPasswordChallenge - + Reset Password Challenge Warning Avertissement Réinitialisation du mot de passe Challenge - + A problem has occurred. Please try to request a new password again. Un probleme est survenu. Veuillez réessayer de demander un nouveau mot de passe. - + Enter the information of the server and the account you'd like to request a new password for. Veuillez entrer les informations du serveur et du compte pour lesquels vous voulez demander un nouveau mot de passe. - + &Host: &Hôte : - + &Port: &Port : - + Player &name: &Nom du joueur : - + Email: Adresse e-mail : - + Reset Password Challenge Réinitialisation du mot de passe Challenge - + Reset Password Challenge Error Erreur Réinitialisation du mot de passe Challenge - + The email address can't be empty. L'adresse e-mail ne peut être vide. @@ -2601,37 +2769,37 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Entrez les informations du serveur pour lequel vous souhaitez demander un nouveau mot de passe - + &Host: &Hôte : - + &Port: &Port : - + Player &name: &Nom du joueur : - + Reset Password Request Demande de réinitialisation du mot de passe - + Reset Password Error Erreur lors de la réinitialisation du mot de passe - + The player name can't be empty. Le nom du joueur ne peut être vide. @@ -2639,86 +2807,86 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je DlgForgotPasswordReset - + Reset Password Warning Avertissement lors de la réinitialisation du mot de passe - + A problem has occurred. Please try to request a new password again. Un problème est survenu. Veuillez demander un nouveau mot de passe. - + Enter the received token and the new password in order to set your new password. Entrez le code reçu et le nouveau mot de passe afin d'initialiser le nouveau mot de passe - + &Host: &Hôte : - + &Port: &Port : - + Player &name: &Nom du joueur : - + Token: Jeton : - - + + New Password: Nouveau mot de passe : - + Reset Password Réinitialiser le mot de passe - + The player name can't be empty. Le nom du joueur ne peut être vide. - - - - + + + + Reset Password Error Erreur de réinitialisation du mot de passe - + The token can't be empty. Le jeton ne peut pas être vide. - + The new password can't be empty. Le nouveau mot de passe ne peut être vide. - + Error Erreur - + Your password is too short. Mot de passe trop court - + The passwords do not match. Les mots de passe ne correspondent pas. @@ -2734,17 +2902,17 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je DlgLoadDeckFromClipboard - + Load deck from clipboard Charger le deck depuis le presse-papier - + Error Erreur - + Invalid deck list. Liste de Deck invalide. @@ -2752,43 +2920,43 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Collez ici un lien vers un site de liste de deck pour l'importer.(Archidekt, Deckstats, Moxfield et TappedOut sont supportés.) - - - - - + + + + + Load Deck from Website Charger un deck depuis un site web - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Aucun analyseur disponible pour ce fournisseur de deck.(Archidekt, Deckstats, Moxfield et TappedOut sont supportés.) - + Network error: %1 Erreur réseau : %1 - + Received empty deck data. Données de deck vide reçues. - + Failed to parse deck data: %1 Échec de l'analyse des données du deck : %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2808,7 +2976,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck Charger Deck @@ -2816,37 +2984,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): Nom de la carte (ou expression de recherche) : - + Number of hits: Nombre de cartes trouvées : - + Auto play hits Jouer les cartes trouvées automatiquement - + Put top cards on stack until... Placer la carte du dessus sur la pile jusqu'à... - + No cards matching the search expression exists in the card database. Proceed anyways? Aucune carte ne correspondant à l'expression recherchée n'existe dans la base de données de cartes. Continuer malgré tout ? - + Cockatrice Cockatrice - + Invalid filter Filtre invalide @@ -2854,91 +3022,91 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Entrez votre identifiant et l'identifiant du serveur auquel vous voulez vous connecter. Un e-mail va vous être envoyé pour vérifier votre compte. - + &Host: &Hôte : - + &Port: &Port : - + Player &name: &Nom du joueur : - + P&assword: Mot de p&asse : - + Password (again): Mot de passe (encore) : - + Email: Adresse mail : - + Email (again): Adresse mail (encore) : - + Country: Pays : - + Undefined Indéfini - + Real name: Nom réel : - + Register to server S'enregistrer sur le serveur - - - - + + + + Registration Warning Avertissement d'enregistrement - + Your password is too short. Mot de passe trop court - + Your passwords do not match, please try again. Vos mots de passe ne correspondent pas, veuillez réessayer. - + Your email addresses do not match, please try again. Vos adresses mail ne correspondent pas, veuillez réessayer. - + The player name can't be empty. Le nom du joueur ne peut pas être vide. @@ -2964,40 +3132,55 @@ Your email will be used to verify your account. DlgSelectSetForCards - + Unmodified Cards: Cartes non modifiées : - + Modified Cards: Cartes modifiées : - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. Cliquer sur des Extensions pour les activer. Glisser-et-déposer pour les réarranger et changer leur priorité. Les cartes utiliseront la version de l'extension activée ayant la plus haut priorité. - + Clear all set information Nettoyer toutes les informations sur les extensions - + Set all to preferred Tout régler selon vos préférences + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database Erreur inconnue lors du chargement de la base de données de cartes. - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3014,7 +3197,7 @@ Vous devrez peut-être redémarrer Oracle pour mettre à jour votre base de donn Voulez-vous changer l'emplacement de votre base de données ? - + Your card database version is too old. This can cause problems loading card information or images @@ -3031,7 +3214,7 @@ Généralement, il suffit de redémarrer oracle pour mettre à jour votre base d Voulez-vous changer l'emplacement de votre base de données ? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3044,7 +3227,7 @@ Veuillez ouvrir un ticket sur https://github.com/Cockatrice/Cockatrice/issues av Voulez-vous changer l'emplacement des données ? - + File Error loading your card database. Would you like to change your database location setting? @@ -3053,7 +3236,7 @@ Would you like to change your database location setting? Voulez-vous changer l'emplacement de votre base de données ? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3062,7 +3245,7 @@ Would you like to change your database location setting? Voulez-vous changer l'emplacement de votre base de données ? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3075,59 +3258,59 @@ Veuillez ouvrir un ticket sur https://github.com/Cockatrice/Cockatrice/issues Voulez-vous changer l'emplacement de votre base de données ? - - - + + + Error Erreur - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Le chemin d'accès vers votre répertoire de decks est invalide. Voulez-vous redéfinir le chemin d'accès ? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Le chemin d'accès vers votre répertoire d'illustrations de cartes est invalide. Voulez-vous redéfinir le chemin d'accès ? - + Settings Paramètres - + General Général - + Appearance Apparence - + User Interface Interface utilisateur - + Card Sources Origine de carte - + Chat Chat - + Sound Son - + Shortcuts Raccourcis @@ -3135,12 +3318,12 @@ Voulez-vous changer l'emplacement de votre base de données ? DlgStartupCardCheck - + Card Update Check Vérification de la mise à jour des cartes - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. @@ -3149,27 +3332,27 @@ Choisissez comment vous souhaitez exécuter le programme de mise à jour de la b Vous pouvez toujours modifier ce comportement dans l'onglet « Général » des paramètres. - + Run in foreground S'exécuter en tâche principale - + Run in background S'exécuter en tâche de fond - + Run in background and always from now on S'exécuter en tâche de fond, et toujours le faire à partir de maintenant - + Don't prompt again and don't run Ne pas demander à nouveau et ne pas exécuter - + Don't run this time Ne pas exécuter cette fois @@ -3177,17 +3360,17 @@ Vous pouvez toujours modifier ce comportement dans l'onglet « Général » DlgTipOfTheDay - + Next suivant - + Previous précédent - + Tip of the Day Conseil du jour @@ -3195,166 +3378,166 @@ Vous pouvez toujours modifier ce comportement dans l'onglet « Général » DlgUpdate - + Current release channel Branche logicielle actuelle - + Reinstall Réinstaller - + Cancel Download Annuler le téléchargement - + Open Download Page Ouvrir la page de téléchargement - + Check for Client Updates Vérification des mises à jour - - - - + + + + Error Erreur - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice n'as pas été construit avec le support du SSL, donc il n'est pas possible de télécharger les mises à jour automatiquement. Veuillez s'il vous plaît visiter la page de téléchargement, pour mettre à jour manuellement. - + Downloading update: %1 Téléchargement de la mise à jour : %1 - + Checking for updates... Vérification des mises à jour... - + Finished checking for updates Fin de la recherche des mises à jour - + No Update Available Pas de mises à jour disponibles - + Cockatrice is up to date! Cockatrice est à jour ! - + You are already running the latest version available in the chosen release channel. Vous avez déjà la dernière version correspondant à votre branche. - + Current version Version actuelle - + Selected release channel Branche logicielle sélectionnée - - + + Update Available Mise à jour disponible - - + + A new version of Cockatrice is available! Une nouvelle version de Cockatrice est disponible ! - - + + New version Nouvelle version - - + + Released Sortie - - + + Changelog Notes de version - + Do you want to update now? Voulez-vous mettre à jour maintenant ? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. Malheureusement, la mise à jour automatique n'a pas pu trouver un téléchargement compatible. Vous allez peut-être devoir télécharger manuellement la nouvelle version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. Veuillez vérifier la <a href="%1">page "releases"</a> sur notre Github et télécharger le programme pour votre système. - - - + + + Update Error Erreur de mise à jour - + An error occurred while checking for updates: Une erreur est survenur pendant la vérification des mises à jour : - + An error occurred while downloading an update: Une erreur est survenue pendant le téléchargement de la mise à jour : - + Installing... Installation... - + Cockatrice is unable to open the installer. Cockatrice n'a pas pu ouvrir l'installateur. - + Try to update manually by closing Cockatrice and running the installer. Essayer de mettre à jour manuellement en fermant Cockatrice et en exécutant l'installateur. - + Download location Répertoire de téléchargement @@ -3362,21 +3545,153 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. DlgViewLog - + Clear log when closing Effacer le journal lors de la fermeture - + Copy to clipboard Charger vers le presse-papier - + Debug Log Journal de debug + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3446,33 +3761,39 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version.%1% synergie + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos Combos - + Average Deck Deck moyen - - - Game Changers - Game Changers - - - - Budget - Budget - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: Sel : @@ -3488,22 +3809,22 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. FilterDisplayWidget - + Confirm Delete Confirmer la suppression - + Are you sure you want to delete the filter '%1'? Êtes-vous sûr de vouloir supprimer le filtre '%1' ? - + Delete Failed Suppression échouée - + Failed to delete filter '%1'. Échec de la suppression du filtre '%1'. @@ -3511,22 +3832,22 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. GameEventHandler - + kicked by game host or moderator Éjecté par l'hôte de la partie ou un modérateur - + player left the game joueur a quitté la partie - + player disconnected from server joueur déconnecté du serveur - + reason unknown raison inconnue @@ -3534,226 +3855,279 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. GameSelector - - - - - - + + + + + + Error Erreur - + Please join the appropriate room first. Veuillez d'abord rejoindre le bon salon. - + Wrong password. Mot de passe incorrect. - + Spectators are not allowed in this game. Les spectateurs ne sont pas autorisés dans cette partie. - + The game is already full. Cette partie est déjà complète. - + The game does not exist any more. La partie n'existe plus. - + This game is only open to registered users. Cette partie n'est accessible qu'aux joueurs enregistrés. - + This game is only open to its creator's buddies. Cette partie n'est accessible qu'aux amis de son créateur. - + You are being ignored by the creator of this game. Vous avez été ignoré par le créateur de la partie. - + Join Game Rejoindre la partie - + Spectate Game Rejoindre la partie comme spectateur - + Game Information Informations sur la partie - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game Rejoindre partie - + Password: Mot de passe : - + Please join the respective room first. Veuillez d'abord rejoindre le bon salon. - + &Filter games &Filtrer les jeux - + C&lear filter Effacer les fi&ltres - + C&reate C&réer - + &Join Re&joindre - + + Join as judge + + + + J&oin as spectator Rej&oindre en tant que spectateur - + + Join as judge spectator + + + + Games shown: %1 / %2 Parties affichées : %1 / %2 - + Games Parties + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day > 1 jour - + %1%2 hr short age in hours %1%2 h%1%2 h%1%2 h - + new Nouveau - + %1%2 min short age in minutes %1%2 min%1%2 min%1%2 min - + password mot de passe - + buddies only amis uniquement - + reg. users only joueurs enregistrés uniquement - + open decklists Ouvrir les listes de deck - - + + can chat peut discuter - + see hands voir les mains - + can see hands peut voir les mains - + not allowed non autorisé - + Room Salon - + Age Âge - + Description Description - + Creator Créateur - + Type Type - + Restrictions Restrictions - + Players Joueurs - + Spectators Spectateurs @@ -3761,143 +4135,143 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. GeneralSettingsPage - + Reset all paths Réinitialiser tous les chemins - + All paths have been reset Les chemins ont été réinitialisés - - - - - - - + + + + + + + Choose path Choisir un chemin d'accès - + Personal settings Paramètres personnels - + Language: Langue : - + Paths (editing disabled in portable mode) Chemins (édition impossible en mode portable) - + Paths Chemins d’accès - + How to help with translations Comment aider avec les traductions - + Decks directory: Répertoire des decks : - + Filters directory: Répertoire des filtres : - + Replays directory: Répertoire des replays : - + Pictures directory: Répertoire des images : - + Card database: Base de données des cartes : - + Custom database directory: Répertoire de la base de données personnalisé - + Token database: Bases de données des jetons : - + Update channel Branche de la mise à jour : - + Check for client updates on startup Vérifier les mises à jour du client au démarrage - + Check for card database updates on startup Vérifier les mises à jour de la base de données des cartes au démarrage... - + Don't check Ne pas vérifier - + Prompt for update Confirmer les mises à jour - + Always update in the background Toujours mettre à jour en tâche de fond - + Check for card database updates every Vérifier les mises à jour de la base de données des cartes tous les - + days jour(s) - + Notify if a feature supported by the server is missing in my client M'avertir si une fonctionnalité supportée par le serveur est manquante sur mon client. - + Automatically run Oracle when running a new version of Cockatrice Lancer Oracle automatiquement quand Cockatrice a été mis à jour. - + Show tips on startup Afficher les astuces au démarrage. - + Last update check on %1 (%2 days ago) Dernière vérification de mise à jour le %1 (il y a %2 jours) @@ -3905,47 +4279,47 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. GraveyardMenu - + &Graveyard &Cimetière - + &View graveyard &Voir le cimetière - + &Move graveyard to... &Déplacer le cimetière vers... - + &Top of library &Dessus de la bibliothèque - + &Bottom of library &Dessous de la bibliothèque - + &All players &Tous les joueurs - + &Hand &Main - + &Exile &Exil - + Reveal random card to... Révéler une carte au hasard à... @@ -3953,111 +4327,141 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. HandMenu - + &Hand &Main - + &View hand &Voir la main - - &Sort hand - &Trier la main + + Sort hand by... + - - Take &mulligan - Faire un &mulligan + + Name + - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... &Déplacer la main vers… - + &Top of library &Dessus de la bibliothèque - + &Bottom of library &Dessous de la bibliothèque - + &Graveyard &Cimetière - + &Exile &Exil - + &Reveal hand to... &Révéler la main à... - - Reveal r&andom card to... - Révéler une c&arte au hasard à... + + + All players + - - - &All players - &Tous les joueurs + + Reveal r&andom card to... + Révéler une c&arte au hasard à... HomeWidget - + Create New Deck Créer un Nouveau Deck - + Browse Decks Parcourir les Decks - + Browse Card Database Parcourir la Base de Carte - + Browse EDHRec Parcourir EDHRec - + + Browse Archidekt + + + + View Replays Voir les replays - + Quit Quitter - + Connecting... Connection... - + Connect Se connecter - + Play Jouer @@ -4259,61 +4663,61 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Le serveur a atteint sa capacité maximale de joueurs simultanés, veuillez réessayer plus tard. - + There are too many concurrent connections from your address. Il y a trop de connexions simultanées depuis votre ordinateur. - + Banned by moderator Banni par un modérateur - + Expected end time: %1 Fin théorique : %1 - + This ban lasts indefinitely. Banni définitivement. - + Scheduled server shutdown. Fermeture prévue du serveur. - - + + Invalid username. Nom d'utilisateur non valide. - + You have been logged out due to logging in at another location. Vous avez avez été déconnecté pour avoir changé d'endroit. - + Connection closed Connexion fermée - + The server has terminated your connection. Reason: %1 Le serveur a coupé votre connexion. Raison: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -4326,584 +4730,584 @@ Aucune partie en cours ne sera sauvegardée. Raison de la fermeture : %1 - + Scheduled server shutdown Fermeture prévue du serveur - - + + Success Réussite - + Registration accepted. Will now login. Enregistrement accepté. Connexion en cours. - + Account activation accepted. Will now login. Activation du compte acceptée. Connexion en cours. - + Number of players Nombre de joueurs - + Please enter the number of players. Entrez s'il vous plaît le nombre de joueurs. - - + + Player %1 Joueur %1 - + Load replay Charger replay - + About Cockatrice À propos de Cockatrice - + Version Version - + Cockatrice Webpage Page web de Cockatrice - + Project Manager: Chef de projet: - + Past Project Managers: Anciens chefs de projet: - + Developers: Développeurs : - + Our Developers Nos développeurs - + Help Develop! Aidez à développer ! - + Translators: Traducteurs : - + Our Translators Nos traducteurs - + Help Translate! Aidez à traduire ! - + Support: Assistance : - + Report an Issue Signaler un problème - + Troubleshooting Dépannage - + F.A.Q. F.À.Q. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error Erreur - + Server timeout Délai de la demande dépassé - + Failed Login Connexion échouée - + Your client seems to be missing features this server requires for connection. Il semble que certaines fonctionnalités nécessaires à la connexion à ce serveur sont absentes de votre client. - + To update your client, go to 'Help -> Check for Client Updates'. Pour mettre à jour Cockatrice, allez dans « Aide -> Vérifier les mises à jour' ». - + Incorrect username or password. Please check your authentication information and try again. Nom d'utilisateur ou mot de passe incorrect. Veuillez vérifier vos identifiants et réessayer. - + There is already an active session using this user name. Please close that session first and re-login. Il y a déjà une session ouverte avec le même nom d'utilisateur. Fermez cette session puis reconnectez vous. - - + + You are banned until %1. Vous êtes banni jusqu'au %1 - - + + You are banned indefinitely. Vous êtes banni indéfiniment. - + This server requires user registration. Do you want to register now? Vous devez être enregistré pour accéder à ce serveur. Voulez-vous vous enregistrer ? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. Ce serveur requiert un ID de client. Soit votre client a échoué lors de la génération d'un ID, soit vous utilisez un client modifié. Veuillez fermer et rouvrir votre client pour réessayer. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. Une erreur interne est survenue. Veuillez fermer et rouvrir Cockatrice avant de réessayer. Si l'erreur persiste, vérifiez que utilisez la dernière version du logiciel, ou contactez les développeurs si nécessaire. - + Account activation Activation du compte - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Votre compte n'a pas encore été activé. Vous devez activer le jeton de l'activation reçu dans le mail d'activation. - + Server Full Serveur complet - + Unknown login error: %1 Erreur de connexion inconnue : %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Cela veut généralement dire que votre client n'est plus à jour, et que le serveur a envoyé une réponse que votre client ne comprend pas. - + Your username must respect these rules: Votre nom d'utilisateur doit respecter ces règles : - + is %1 - %2 characters long est long de %1 - %2 caractères - + can %1 contain lowercase characters peut %1 contenir des caractères minuscules - - - - + + + + NOT PAS - + can %1 contain uppercase characters peut %1 contenir des caractères majuscules - + can %1 contain numeric characters peut %1 contenir des caractères numériques - + can contain the following punctuation: %1 peut contenir la ponctuation suivante : %1 - + first character can %1 be a punctuation mark le premier caractère peut %1 être une marque de ponctuation - + no unacceptable language as specified by these server rules: note that the following lines will not be translated Pas de langage inacceptable comme spécifié par ces règles du serveur: - + can not contain any of the following words: %1 ne peut contenir les mots suivants : %1 - + can not match any of the following expressions: %1 ne correspond à aucune des expressions suivantes : %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Vous pouvez utiliser seulement A-Z, a-z, 0-9, _, ., et - dans votre nom d'utilisateur - - - - - - + + + + + + Registration denied Enregistrement refusé - + Registration is currently disabled on this server L'enregistrement est désactivé sur ce serveur - + There is already an existing account with the same user name. Il existe déjà un compte avec le même nom d'utilisateur. - + It's mandatory to specify a valid email address when registering. Il est obligatoire de spécifier une adresse e-mail valide lors de l'enregistrement. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. Il semble que vous essayez d'enregistrer un nouveau compte alors que vous en avez déjà un enregistré avec cet e-mail sur ce serveur. Ce serveur restreint le nombre de comptes par e-mail. Veuillez contacter l'administrateur du serveur pour obtenir vos identifiants ou plus d'informations. - + Password too short. Mot de passe trop court. - + Registration failed for a technical problem on the server. Enregistrement échoué lié à un problème technique du serveur. - + The connection to the server has been lost. La connexion au serveur a été perdue. - + Unknown registration error: %1 Erreur d'enregistrement inconnue : %1 - + Account activation failed Activation du compte échouée - + Socket error: %1 Erreur de socket : %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Vous tentez de vous connecter à un serveur obsolète. Veuillez utiliser une ancienne version de Cockatrice, ou connectez-vous à un serveur approprié. La version locale est %1, la version distante est %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Votre client Cockatrice est obsolète. Veuillez mettre à jour votre version de Cockatrice. La version locale est %1, la nouvelle version est %2. - + Connecting to %1... Connexion à %1... - + Registering to %1 as %2... Enregistrement de %1 en tant que %2... - + Disconnected Déconnecté - + Connected, logging in at %1 Connecté, connexion à %1 + - Requesting forgotten password to %1 as %2... Envoi de la requête de mot de passe oublié à %1 en tant que %2... - + &Connect... &Connecter... - + &Disconnect &Déconnecter - + Start &local game... Démarrer une partie &locale... - + &Watch replay... &Regarder un replay... - + &Full screen &Plein écran - + &Register to server... S'enregistrer sur le serveur... - + &Restore password... &Restaurer le mot de passe... - + &Settings... &Paramètres... - + &Exit &Quitter - + A&ctions Actions - + &Cockatrice &Cockatrice - + C&ard Database B&ase de données de cartes - + &Manage sets... &Gérer les éditions... - + Edit custom &tokens... Éditer les &jetons personnalisés… - + Open custom image folder Ouvrir le dossier d'images personnalisées - + Open custom sets folder Ouvrir le dossier d'éditions personnalisées - + Add custom sets/cards Ajouter des éditions / cartes personnalisées - + Reload card database Recharger la base de cartes - + Tabs Onglets - + &Help A&ide - + &About Cockatrice À propos de Cock&atrice - + &Tip of the Day &Conseil du jour - + Check for Client Updates Vérifier les mises à jour - + Check for Card Updates... Vérifier les mises à jour des cartes… - + Check for Card Updates (Automatic) Vérifier les mises à jour des cartes (automatiquement) - + Show Status Bar Montrer la barre de statut - + View &Debug Log Voir journal de &Debug - + Open Settings Folder Ouvrir le dossier des paramètres - + Show/Hide Montrer/Cacher - + New Version Nouvelle version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Bravo pour avoir mis à jour Cockatrice %1 ! Oracle va maintenant se lancer pour mettre à jour votre base de données de cartes. - + Cockatrice installed Installation de Cockatrice terminée - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Félicitations ! Vous venez d'installer Cockatrice %1 ! Oracle va maintenant se lancer pour installer la base de données de cartes initiale. - + Card database Base de données de cartes - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -4912,29 +5316,29 @@ Voulez-vous la mettre à jour maintenant ? Si vous n’êtes pas sûr, ou si c'est la première fois que vous jouez, choisissez « Oui ». - - + + Yes Oui - - + + No Non - + Open settings Ouvrir les paramètres - + New sets found Nouvelles éditions détectées - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -4947,17 +5351,17 @@ Code(s) d'édition(s) : %1 Voulez-vous l'(les) activer ? - + View sets Voir les éditions - + Welcome Bienvenue - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -4966,65 +5370,65 @@ Toutes les éditions de la base de données de cartes ont été activées. Pour plus d'informations sur la modification de l'ordre des éditions, la désactivation d'éditions spécifiques, et leurs effets, consultez le menu « Gérer les éditions… ». - - + + Information Informations - + A card database update is already running. Une mise à jour de la base de données de cartes est déjà en cours. - + Unable to run the card database updater: Impossible de lancer la mise à jour de la base de données de cartes : - + Card database update running. Une mise à jour de la base de données de cartes est en cours. - + Failed to start. The file might be missing, or permissions might be incorrect. Échec au démarrage. Le fichier peut être manquant, ou les permissions sont incorrectes. - + The process crashed some time after starting successfully. Le processus a échoué quelque temps après avoir démarré avec succès. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. Temps limite dépassé. Le processus a pris trop longtemps pour répondre. La dernière fonction waitFor...() a terminé. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. Une erreur a eu lieu en essayant d'écrire vers le processus. Par exemple, le processus pourrait ne pas tourner, ou il pourrait avoir son canal d'entrée fermé. - + An error occurred when attempting to read from the process. For example, the process may not be running. Une erreur a eu lieu en essayant d'écrire vers le processus. Par exemple, le processus pourrait ne pas tourner, ou il pourrait avoir son canal de sortie inactif. - + Unknown error occurred. Une erreur inconnue est arrivée. - + The card database updater exited with an error: %1 L'outil de mise à jour de la base de données de cartes s'est arrêté avec l'erreur : %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5035,673 +5439,843 @@ Ce n'est probablement pas un problème, mais ce message peut signifier qu&a Pour mettre à jour votre client, allez dans « Aide -> Vérifier les mises à jour ». - - - - - + + + + + Load sets/cards Charger des éditions/cartes - + Selected file cannot be found. Le fichier sélectionné n'a pas pu être trouvé. - + You can only import XML databases at this time. Il n'est actuellement possible que d'importer des bases de données XML. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Les nouvelles éditions/cartes ont été ajoutées avec succès. Cockactrice va maintenant recharger la base de données de cartes. - + Sets/cards failed to import. Échec de l'importation des éditions/cartes. - - - + + + Reset Password Réinitialiser le mot de passe - + Your password has been reset successfully, you can now log in using the new credentials. Votre mot de passe a été réinitialisé avec succès. Vous pouvez maintenant vous connecter en utilisant vos nouveaux identifiants. - + Failed to reset user account password, please contact the server operator to reset your password. Échec de la réinitialisation du mot de passe du compte. Veuillez contacter l'administrateur du serveur pour réinitialiser votre mot de passe. - + Activation request received, please check your email for an activation token. Demande d'activation reçue, vérifiez votre courrier électronique pour un jeton d'activation. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base Base de mana + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve Courbe de mana + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion Dévotion au mana + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play depuis le jeu - + from their graveyard depuis son cimetière - + from exile depuis l'exil - + from their hand depuis sa main - + the top card of %1's library la carte du dessus de la bibliothèque de %1 - + the top card of their library le carte du dessus de sa bibliothèque - + from the top of %1's library à partir du dessus de la bibliothèque de %1 - + from the top of their library du dessus de sa bibliothèque - + the bottom card of %1's library la carte du dessous de la bibliothèque de %1 - + the bottom card of their library la carte du dessous de sa bibliothèque - + from the bottom of %1's library à partir du dessous de la bibliothèque de %1 - + from the bottom of their library du dessous de sa bibliothèque - + from %1's library de la bibliothèque de %1 - + from their library depuis sa bibliothèque - + from sideboard depuis sa réserve - + from the stack depuis la pile - + from custom zone '%1' depuis la zone personnalisée '%1' - + %1 is now keeping the top card %2 revealed. %1 garde maintenant la carte du dessus de sa bibliothèque %2 révélée. - + %1 is not revealing the top card %2 any longer. %1 ne révèle plus la carte du dessus de sa bibliothèque %2. - + %1 can now look at top card %2 at any time. %1 peut maintenant regarder la carte du dessus de sa bibliothèque %2 à n'importe quel moment. - + %1 no longer can look at top card %2 at any time. %1 ne peut plus regarder la carte du dessus de sa bibliothèque %2 à n'importe quel moment. - + %1 attaches %2 to %3's %4. %1 attache %2 à %4 de %3. - + %1 has conceded the game. %1 a concédé la partie. - + %1 has unconceded the game. %1 reste dans la partie. - + %1 has restored connection to the game. %1 est revenu dans la partie. - + %1 has lost connection to the game. %1 a perdu la connexion à la partie. - + %1 points from their %2 to themselves. %1 se cible avec son %2. - + %1 points from their %2 to %3. %1 cible %3 avec son %2. - + %1 points from %2's %3 to themselves. %1 se cible avec %3 de %2. - + %1 points from %2's %3 to %4. %1 cible %4 avec %3 de %2. - + %1 points from their %2 to their %3. %1 cible de son %3 vers son %2. - + %1 points from their %2 to %3's %4. %1 cible %3 de %4 avec %2. - + %1 points from %2's %3 to their own %4. %1 cible son %4 avec %3 de %2. - + %1 points from %2's %3 to %4's %5. %1 cible %5 de %4 avec %3 de %2. - + %1 creates a face down token. %1 crée un jeton face-cachée. - + %1 creates token: %2%3. %1 crée un jeton %2%3. - + %1 has loaded a deck (%2). %1 a chargé un deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 a chargé un deck avec %2 cartes en réserve (%3). - + %1 destroys %2. %1 détruit %2. - + a card une carte - + %1 gives %2 control over %3. %1 donne le contrôle de %2 à %3. - + %1 puts %2 into play%3 face down. %1 met %2 en jeu%3 face cachée. - + %1 puts %2 into play%3. %1 met %2 en jeu %3. - + %1 puts %2%3 into their graveyard. %1 met %2%3 dans son cimetière. - + %1 exiles %2%3. %1 exile %2%3. - + %1 moves %2%3 to their hand. %1 met %2%3 dans sa main. - + %1 puts %2%3 into their library. %1 met %2%3 dans sa bibliothèque. - + %1 puts %2%3 onto the bottom of their library. %1 met %2%3 au-dessous de sa bibliothèque. - + %1 puts %2%3 on top of their library. %1 met %2%3 au-dessus de sa bibliothèque. - + %1 puts %2%3 into their library %4 cards from the top. %1 met %2%3 dans sa bibliothèque %4 cartes depuis le dessus - + %1 moves %2%3 to sideboard. %1 met %2%3 à sa réserve. - + %1 plays %2%3. %1 joue %2%3. - + %1 moves %2%3 to custom zone '%4'. %1 déplace %2%3 vers la zone personnalisée '%4'. - + %1 tries to draw from an empty library %1 essaie de piocher dans une bibliothèque vide - + %1 draws %2 card(s). %1 pioche %2 carte.%1 pioche %2 cartes.%1 pioche %2 carte(s). - + %1 is looking at %2. %1 regarde %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 regarde la %4 %3 carte %2.%1 regarde les %4 %3 cartes %2.%1 regarde la / les %4 %3 carte(s) %2. - + bottom dessous - + top dessus - + %1 turns %2 face-down. %1 retourne %2 face cachée. - + %1 turns %2 face-up. %1 retourne %2 face visible. - + The game has been closed. La partie a été fermée. - + The game has started. La partie commence. - + You are flooding the game. Please wait a couple of seconds. Vous floodez la partie. Veuillez patienter quelques secondes. - + %1 has joined the game. %1 a rejoint la partie. - + %1 is now watching the game. %1 est maintenant spectateur. - + You have been kicked out of the game. Vous avez été expulsé de la partie. - + %1 has left the game (%2). %1 a quitté la partie (%2). - + %1 is not watching the game any more (%2). %1 n'observe plus la partie (%2). - + %1 is not ready to start the game any more. %1 n'est plus prêt à démarrer la partie. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 mélange son deck et pioche une nouvelle main de %2 carte.%1 mélange sa bibliothèque et pioche une nouvelle main de %2 cartes.%1 mélange sa bibliothèque et pioche une nouvelle main de %2 carte(s). - + %1 shuffles their deck and draws a new hand. %1 mélange sa bibliothèque et pioche une nouvelle main. - + You are watching a replay of game #%1. Vous regardez un replay de la partie n° %1. - + %1 is ready to start the game. %1 est prêt à démarrer la partie. - + cards an unknown amount of cards cartes - + %1 card(s) a card for singular, %1 cards for plural %1 carte%1 cartes%1 carte(s) - + %1 lends %2 to %3. %1 prêtes %2 à %3. - + %1 reveals %2 to %3. %1 révèle %2 à %3. - + %1 reveals %2. %1 révèle %2. - + %1 randomly reveals %2%3 to %4. %1 révèle au hasard %2%3 à %4. - + %1 randomly reveals %2%3. %1 révèle au hasard %2%3. - + %1 peeks at face down card #%2. %1 regarde furtivement la carte face cachée n° %2. - + %1 peeks at face down card #%2: %3. %1 regarde furtivement la carte face cachée n° %2 : %3. - + %1 reveals %2%3 to %4. %1 révèle %2%3 à %4. - + %1 reveals %2%3. %1 révèle %2%3. - + %1 reversed turn order, now it's %2. %1 a inversé l'ordre des tours, c'est maintenant %2. - + reversed inversé - + normal normal - + Heads Face - + Tails Pile - + %1 flipped a coin. It landed as %2. %1 a lancé une pièce. Il a fait %2. - + %1 rolls a %2 with a %3-sided die. %1 a fait %2 avec un dé %3 faces. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 lances %2 pièces. Il y'a %3 faces et %4 piles. - + %1 rolls a %2-sided dice %3 times: %4. %1 lances un dé à %2-faces %3 fois: %4. - + %1's turn. Tour de %1 - + %1 sets annotation of %2 to %3. %1 met l'annotation %3 à %2. - + %1 places %2 "%3" counter(s) on %4 (now %5). %1 place %2 "%3" compteur sur %4 (désormais %5).%1 place %2 "%3" compteurs sur %4 (désormais %5).%1 place %2 "%3" compteur(s) sur %4 (désormais %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). %1 retire %2 "%3" compteur sur %4 (désormais %5).%1 retire %2 "%3" compteurs sur %4 (désormais %5).%1 retire %2 "%3" compteur(s) sur %4 (désormais %5). - + %1 sets counter %2 to %3 (%4%5). %1 met les marqueurs %2 à %3 (%4%5). - + %1 sets %2 to not untap normally. %2 de %1 ne se dégagera pas lors de l'étape de dégagement. - + %1 sets %2 to untap normally. %2 de %1 se dégagera lors de l'étape de dégagement. - + %1 removes the PT of %2. %1 retire la F/E de %2. - + %1 changes the PT of %2 from nothing to %4. %1 met la F/E de %2 à %4. - + %1 changes the PT of %2 from %3 to %4. %1 passe la F/E de %2 de %3 à %4. - + %1 has locked their sideboard. %1 a verrouillé sa réserve. - + %1 has unlocked their sideboard. %1 a déverrouillé sa réserve. - + %1 taps their permanents. %1 engage ses permanents. - + %1 untaps their permanents. %1 dégage ses permanents. - + %1 taps %2. %1 engage %2. - + %1 untaps %2. %1 dégage %2. - + %1 shuffles %2. %1 mélange %2. - + %1 shuffles the bottom %3 cards of %2. %1 mélange les %3 cartes du dessous de la bibliothèque de %2. - + %1 shuffles the top %3 cards of %2. %1 mélange les %3 cartes du dessus de la bibliothèque de %2. - + %1 shuffles cards %3 - %4 of %2. %1 mélange les cartes %3 - %4 de %2. - + %1 unattaches %2. %1 détache %2. - + %1 undoes their last draw. %1 annule sa dernière pioche. - + %1 undoes their last draw (%2). %1 annule sa dernière pioche (%2). @@ -5709,110 +6283,110 @@ Cockactrice va maintenant recharger la base de données de cartes. MessagesSettingsPage - + Word1 Word2 Word3 Mot1 Mot2 Mot3 - + Add New Message Ajouter un message - + Edit Message Éditer le message - + Remove Message Supprimer le message - + Add message Ajouter message - - + + Message: Message : - + Edit message Éditer le message - + Chat settings Paramètres du chat - + Custom alert words Mots d'alertes personnalisées - + Enable chat mentions Activer les mentions dans le chat - + Enable mention completer Activer les mentions dans le chat - + In-game message macros Macros de message en jeu - + How to use in-game message macros Comment utiliser les macro pour les messages en jeu - + Ignore chat room messages sent by unregistered users Ignorer les messages dans le chat principal envoyés par des joueurs non enregistrés. - + Ignore private messages sent by unregistered users Ignorer les messages privés envoyés par des joueurs non enregistrés. - - + + Invert text color Inverser la couleur du texte - + Enable desktop notifications for private messages Activer les notifications de bureau pour les messages privés. - + Enable desktop notification for mentions Activer les notifications de bureau pour les mentions. - + Enable room message history on join Activer l'historique des messages du chat principal dès la connexion - - + + (Color is hexadecimal) (La couleur est hexadécimale) - + Separate words with a space, alphanumeric characters only Séparer les mots avec un espace, seulement les caractères alphanumériques @@ -5929,62 +6503,62 @@ Cockactrice va maintenant recharger la base de données de cartes. Phase - + Unknown Phase Phase inconnue - + Untap Dégagement - + Upkeep Entretien - + Draw Pioche - + First Main Première phase principale - + Beginning of Combat Début de combat - + Declare Attackers Déclaration des attaquants - + Declare Blockers Déclaration des bloqueurs - + Combat Damage Blessures de combat - + End of Combat Fin de combat - + Second Main Seconde phase principale - + End/Cleanup Phase de fin @@ -6050,7 +6624,7 @@ Cockactrice va maintenant recharger la base de données de cartes. PictureLoader - + en code for scryfall's language property, not available for all languages fr @@ -6059,134 +6633,134 @@ Cockactrice va maintenant recharger la base de données de cartes. PlayerActions - + View top cards of library Voir les cartes du dessus de la bibliothèque - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) Nombre de cartes : (max. %1) - + View bottom cards of library Voir les cartes du dessous de la bibliothèque - + Shuffle top cards of library Mélanger les cartes du dessus de la bibliothèque - + Shuffle bottom cards of library Mélanger les cartes du dessous de la bibliothèque - + Draw hand Piocher une main - + 0 and lower are in comparison to current hand size 0 et moins sont en comparaison avec la taille actuelle de la main - + Draw cards Piocher des cartes - + Move top cards to grave Mettre les cartes du dessus dans le cimetière - + Move top cards to exile Exiler les cartes du dessus - + Move bottom cards to grave Mettre les cartes du dessous dans le cimetière - + Move bottom cards to exile Exiler les cartes du dessous - + Draw bottom cards Piocher les cartes du dessous - - + + C&reate another %1 token C&réer un autre jeton %1 - + Create tokens Créer des jetons - - + + Number: Nombre : - + Place card X cards from top of library Placer les X cartes depuis le dessus de la bibliothèque - + Which position should this card be placed: Dans quelle position cette carte doit-elle être placée : - + (max. %1) (max. %1) - + Change power/toughness Changer la force/l'endurance - + Change stats to: Changer les stats pour : - + Set annotation Annoter - + Please enter the new annotation: Veuillez entrer la nouvelle annotation : - + Set counters Définir les marqueurs @@ -6194,17 +6768,17 @@ Cockactrice va maintenant recharger la base de données de cartes. PlayerMenu - + Player "%1" Joueur "%1" - + &Counters &Marqueurs - + S&ay D&ire @@ -6212,7 +6786,7 @@ Cockactrice va maintenant recharger la base de données de cartes. PrintingSelector - + Display Navigation Buttons Afficher les boutons de navigation @@ -6220,22 +6794,22 @@ Cockactrice va maintenant recharger la base de données de cartes. PrintingSelectorCardOverlayWidget - + Preference Préférence - + Pin Printing Épingler Imprimer - + Unpin Printing Désépingler Imprimer - + Show Related cards Afficher les cartes associées @@ -6251,17 +6825,17 @@ Cockactrice va maintenant recharger la base de données de cartes. PrintingSelectorCardSelectionWidget - + Previous Card in Deck Carte précédente dans le deck - + Bulk Selection Sélection groupée - + Next Card in Deck Carte suivante dans le deck @@ -6269,28 +6843,28 @@ Cockactrice va maintenant recharger la base de données de cartes. PrintingSelectorCardSortingWidget - + Alphabetical Alphabétiquement - + Preference Préférences - + Release Date Date de Sortie - - + + Descending Descendant - + Ascending Ascendant @@ -6356,37 +6930,37 @@ Cockactrice va maintenant recharger la base de données de cartes. QMenuBar - + Services Services - + Hide %1 Cacher %1 - + Hide Others Cacher les autres - + Show All Montrer tout - + Preferences... Préférences... - + Quit %1 Quitter %1 - + About %1 À propos %1 @@ -6394,42 +6968,42 @@ Cockactrice va maintenant recharger la base de données de cartes. QObject - + Cockatrice card database (*.xml) Base de données de cartes Cockatrice (*.xml) - + All files (*.*) Tous les fichiers (*.*) - + Cockatrice replays (*.cor) Replays Cockatrice (*.cor) - + Maindeck Maindeck - + Sideboard Sideboard - + Tokens Jetons - + Overwrite Existing File? Écraser le fichier existant ? - + A .cod version of this deck already exists. Overwrite it? Une version en fichier .cod existe déjà pour ce deck. L'écraser ? @@ -6437,92 +7011,92 @@ Cockactrice va maintenant recharger la base de données de cartes. QPlatformTheme - + OK OK - + Save Sauvegarder - + Save All Sauvegarder tout - + Open Ouvrir - + &Yes &Oui - + Yes to &All Dire oui à tout - + &No &Non - + N&o to All Dire non à tout - + Abort Abandonner - + Retry Réessayer - + Ignore Ignorer - + Close Fermer - + Cancel Annuler - + Discard Effacer - + Help Aide - + Apply Appliquer - + Reset Réinitialiser - + Restore Defaults Remettre à zéro @@ -6619,37 +7193,37 @@ Cockactrice va maintenant recharger la base de données de cartes. RoomSelector - + Rooms Salons - + Joi&n &Rejoindre - + Room Salon - + Description Description - + Permissions Permissions - + Players Joueurs - + Games Parties @@ -6690,27 +7264,27 @@ Cockactrice va maintenant recharger la base de données de cartes. SetsModel - + Enabled Activée - + Set type Type - + Set code Code - + Long name Nom complet - + Release date Date de sortie @@ -6718,53 +7292,53 @@ Cockactrice va maintenant recharger la base de données de cartes. ShortcutSettingsPage - - + + Restore all default shortcuts Réinitialiser tous les raccourcis - + Do you really want to restore all default shortcuts? Êtes-vous sûr de vouloir réinitialiser tous les raccourcis ? - + Clear all default shortcuts Effacer tous les raccourcis par défaut - + Do you really want to clear all shortcuts? Êtes-vous sûr de vouloir effacer tous les raccourcis ? - + Section: Section : - + Action: Action : - + Shortcut: Raccourci : - + How to set custom shortcuts Comment assigner des raccourcis personnalisés - + Clear all shortcuts Effacer tous les raccourcis - + Search by shortcut name Rechercher par nom de raccourci @@ -6772,12 +7346,12 @@ Cockactrice va maintenant recharger la base de données de cartes. ShortcutTreeView - + Action Action - + Shortcut Raccourci @@ -6785,13 +7359,13 @@ Cockactrice va maintenant recharger la base de données de cartes. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! Votre fichier de configuration contenait des raccourcis invalides. Merci de vérifier vos paramètres de raccourci ! - + The following shortcuts have been set to default: Les raccourcis suivants ont été définis par défaut: @@ -6819,12 +7393,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard &Réserve - + &View sideboard &Voir la réserve @@ -6832,27 +7406,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Activer les &sons - + Current sounds theme: Thème sonore actuel: - + Test system sound engine Tester les effets sonores - + Sound settings Paramètres de son - + Master volume Volume principal @@ -6860,48 +7434,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended La saison des spoilers est terminée - + Deleting spoiler.xml. Please run Oracle Suppression de spoiler.xml. S'il vous plaît exécuter Oracle - - + + Spoilers download failed Échec du téléchargement des spoilers - + No internet connection Pas de connexion Internet - + Error Erreur - + Spoilers already up to date Spoilers déjà à jour - + No new spoilers added Aucun nouveau spoilers ajouté - + Spoilers have been updated! Les spoilers ont été mis à jour! - + Last change: Dernier changement: @@ -7065,56 +7639,123 @@ Please check your shortcut settings! Incapable d'activer l'utilisateur. Erreur interne + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info Infos de la carte - + Deck Bibliothèque - + Filters Filtres - + &View &Voir - + + Card Database + + + + Printing Imprimer - - - - + + + + + Visible Visible - - - - + + + + + Floating Flottant - + Reset layout Réinitialiser l'interface - + Deck: %1 Deck : %1 @@ -7122,61 +7763,61 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 Deck visuel : %1 - + &Visual Deck Editor &Éditeur de deck visuel - - + + Card Info Infos de la carte - - + + Deck Deck - - + + Filters Filtres - + &View &Voir - + Printing Imprimer - - - - + + + + Visible Visible - - - - + + + + Floating Flottant - + Reset layout Réinitialiser l'interface @@ -7184,22 +7825,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View Vue de deck visuel - + Visual Database Display Affichage visuel de la base de données - + Deck Analytics Analyse de deck - + Sample Hand Exemple de main @@ -7207,134 +7848,134 @@ Please check your shortcut settings! TabDeckStorage - + Local file system Système de fichier local - + Server deck storage Serveur de stockage de deck - - + + Open in deck editor Ouvrir dans l'éditeur de deck - + Rename deck or folder Renommer le deck ou le dossier - + Upload deck Envoyer deck - + Download deck Télécharger deck + - - - + + New folder Nouveau dossier + - Delete Supprimer - + Open decks folder Ouvrir le dossier des decks - + Rename local folder Renommer le dossier local - + Rename local file Renommer le fichier local - + New name: Nouveau nom : - - - - + + + + Error Érreur - + Rename failed Renommage échoué - - + + Invalid deck file Fichier de deck invalide - + Enter deck name Entrez le nom du deck - + This decklist does not have a name. Please enter a name: Ce deck n'a pas de nom. Veuillez entrer un nom: - + Unnamed deck Deck sans nom - + Failed to upload deck to server Erreur lors de l'importation du deck sur le serveur - + Delete local file Supprimer fichier local - + Are you sure you want to delete the selected files? Êtes-vous certain de vouloir supprimer les fichiers sélectionnés ? - + Delete remote decks Supprimer les decks dinstants - + Are you sure you want to delete the selected decks? Êtes-vous certain de vouloir supprimer les decks sélectionnés ? - - + + Name of new folder: Nom du nouveau dossier : @@ -7347,17 +7988,17 @@ Veuillez entrer un nom: TabDeckStorageVisual - + Visual Deck Storage Stockage visuel de deck - + Error Erreur - + Could not open deck at %1 N'a pas pu ouvrir le deck à %1 @@ -7398,7 +8039,7 @@ Veuillez entrer un nom: Rechercher - + EDHRec: EDHRec : @@ -7406,197 +8047,197 @@ Veuillez entrer un nom: TabGame - - - + + + Replay Replay - - + + Game Partie - - + + Player List Liste des joueurs - - + + Card Info Infos de la carte - - + + Messages Messages - - + + Replay Timeline Historique des replays - + &Phases &Phases - + &Game &Partie - + Next &phase Étape &suivante - + Next phase with &action Étape suivante avec &action - + Next &turn &Tour suivant - + Reverse turn order Inverser l'ordre des tours - + &Remove all local arrows &Retirer toutes les flèches locales - + Rotate View Cl&ockwise Pivoter la vue dans le sens h&oraire - + Rotate View Co&unterclockwise Pivoter la vue dans le sens &anti-horaire - + Game &information &Informations sur la partie - + Un&concede Annuler la &concession - - - + + + &Concede &Concéder - + &Leave game &Quitter la partie - + C&lose replay Fermer &le replay - + &Focus Chat &Focus sur le chat - + &Say: &Dire : - + Selected cards Cartes sélectionnées - + &View &Voir - - + + + - Visible Visible - - + + + - Floating Flottant - + Reset layout Réinitialiser l'interface - + Concede Concéder - + Are you sure you want to concede this game? Êtes-vous sûr de vouloir concéder la partie ? - + Unconcede Rester dans la partie - + You have already conceded. Do you want to return to this game? Vous avez déjà concédé la partie. Voulez-vous la reprendre ? - + Leave game Quitter la partie - + Are you sure you want to leave this game? Êtes-vous sûr de vouloir quitter la partie ? - + A player has joined game #%1 Un joueur a rejoint la partie #%1. - + %1 has joined the game %1 a rejoint la partie. - + You have been kicked out of the game. Vous avez été expulsé de la partie. @@ -7604,7 +8245,7 @@ Veuillez entrer un nom: TabHome - + Home Accueil @@ -7612,158 +8253,158 @@ Veuillez entrer un nom: TabLog - + Logs Journaux - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Durée;Expéditeur;IPExpéditeur;Message;IDCible;NomCible - + Room Logs Journaux du salon - + Game Logs Journaux de la partie - + Chat Logs Journaux du chat - - + + Error Erreur - + You must select at least one filter. Vous devez sélectionner au moins un filtre. - + You have to select a valid number of days to locate. Vous devez sélectionner un nombre valide de jours à identifier. - + Username: Nom d'utilisateur: - + IP Address: Adresse IP : - + Game Name: Nom de la partie : - + GameID: ID de la partie : - + Message: Message : - + Main Room Salon principal - + Game Room Salon de jeu - + Private Chat Chat privé - + Past X Days: Les X derniers jours : - + Today Aujourd'hui - + Last Hour La dernière heure - + Maximum Results: Résultats maximum : - + At least one filter is required. The more information you put in, the more specific your results will be. Au moins un filtre est requis. Plus vous entrez d'informations, meilleurs seront les résultats. - + Get User Logs Obtenir les journaux de l'utilisateur - + Clear Filters Effacer les filtres - + Filters Filtres - + Log Locations Emplacement des journaux - + Date Range Intervalle de dates - + Maximum Results Résultats maximum - - + + Message History Historique des messages - + Failed to collect message history information. Impossible de récupérer l'historique des messages. - + There are no messages for the selected filters. Il n'y a pas de messages pour les filtres sélectionnés. @@ -7809,180 +8450,180 @@ Plus vous entrez d'informations, meilleurs seront les résultats. TabReplays - + Local file system Système de fichier local - + Server replay storage Stockage du serveur de replays - - + + Watch replay Voir replay - + Rename Renommer - - + + New folder Nouveau fichier - - + + Delete Supprimer - + Open replays folder Ouvrir le dossier des Replays - + Download replay Télécharger replay - + Toggle expiration lock Changer le verrouillage de l'expiration - + Get replay share code Obtenir le code de partage du replay - - + + Look up replay by share code Regarder un replay depuis un code de partage - + Rename local folder Renommer le dossier local - + Rename local file Renommer le fichier local - + New name: Nouveau nom: - + Error Erreur - + Rename failed Renommage échoué - + Name of new folder: Nom du nouveau fichier: - + Delete local file Supprimer fichier local - + Are you sure you want to delete the selected files? Êtes-vous certain de vouloir supprimer les fichiers sélectionnés? - + Are you sure you want to delete the selected replays? Êtes-vous certain de vouloir supprimer cet enregistrement? - + Failed to get code Echec d'obtention du code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. Soit ce serveur ne prend pas en charge le partage de replay, soit il ne vous autorise pas à partager des replays. - - - + + + Failed Échec - + Could not get replay code Impossible d'obtenir le code de replay - + Replay Share Code Code de Partage de Replay - + Others can use this code to add the replay to their list of remote replays: %1 D'autres peuvent utiliser ce code pour ajouter la replay à leur liste de replay distantes :%1 - + Copy to clipboard Copier vers le presse-papier - + Replay share code Code de partage de replay - + Replay code found Code de replay trouvé - + Replay was added, or you already had access to it. Le replay a été ajoutée, ou vous y aviez déjà accès. - + Replay code not found Code de replay non trouvé - + Failed to submit code Échec de l'envoi du code - + Unexpected error Erreur inattendue - + Delete remote replay Supprimer le replay distant @@ -8048,30 +8689,30 @@ Plus vous entrez d'informations, meilleurs seront les résultats.Serveur + - Error Erreur - + Failed to join the server room: it doesn't exist on the server. Impossible de rejoindre le salon: il n'existe pas sur le serveur - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. Le serveur considère que vous êtes dans le salon mais votre client est incapable de l'afficher. Essayez en relançant votre client. - + You do not have the required permission to join this server room. Vous n'avez pas la permission requise pour rejoindre ce salon. - + Failed to join the server room due to an unknown error: %1. Impossible de rejoindre le salon à cause d'un erreur inconnue: %1 @@ -8079,92 +8720,97 @@ Plus vous entrez d'informations, meilleurs seront les résultats. TabSupervisor - + Deck Editor Éditeur de deck - + Visual Deck Editor Éditeur de deck visuel - + EDHRec EDHrec - + + Archidekt + + + + Home Accueil - + &Visual Deck Storage Stockage &visuel de decks - + Visual Database Display Affichage visuel de la base de données - + Server Serveur - + Account Compte - + Deck Storage Stockage de deck - + Game Replays Replays de parties - + Administration Administration - + Logs Journaux - + Are you sure? Êtes-vous sûr ? - + There are still open games. Are you sure you want to quit? Il y a encore des parties en cours. Êtes-vous sûr de vouloir quitter ? - + Click to view Cliquer pour regarder - + Your buddy %1 has signed on! Votre ami %1 s'est inscrit ! - + Unknown Event Événement inconnu - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8175,39 +8821,39 @@ Ce message peut signifier qu'il y a une nouvelle version de Cockatrice disp Pour mettre à jour votre client, allez dans « Aide -> Vérifier les mises à jour ». - + Idle Timeout Temporisation inactive - + You are about to be logged out due to inactivity. Vous allez d'être déconnecté en raison de l'inactivité. - + Promotion Promotion - + You have been promoted. Please log out and back in for changes to take effect. Vous avez été promu modérateur. Veuillez vous reloguer pour que les changements prennent effet. - + Warned Averti - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. You avez reçu un avertissement pour la raison : %1. Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre vous. Si vous avez des questions, veuillez contacter un modérateur en privé. - + You have received the following message from the server. (custom messages like these could be untranslated) Vous avez reçu le message suivant du serveur. @@ -8217,7 +8863,12 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display Affichage visuel de la base de données @@ -8299,7 +8950,7 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre UpdateDownloader - + Could not open the file for reading. Impossible d'ouvrir le fichier en lecture. @@ -8307,206 +8958,206 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre UserContextMenu - + User &details &Détails utilisateur - + Private &chat &Chat privé - + Show this user's &games Voir les parties du &joueur - + Add to &buddy list Ajouter à la liste d'&amis - + Remove from &buddy list Retirer de la liste d'&amis - + Add to &ignore list Ajouter à la l&iste noire - + Remove from &ignore list Retirer de la l&iste noire - + Kick from &game Exclure de la &partie - + Warn user Avertir le joueur - + View user's war&n history Voir l'historique des avertisseme&nts du joueur - + Ban from &server Bannir du &serveur - + View user's &ban history Voir l'historique de &bannissement du joueur - + &Promote user to moderator &Promouvoir d'utilisateur à modérateur - + Dem&ote user from moderator Rétr&ograder de modérateur à utilisateur - + Promote user to &judge Promouvoir utilisateur à &arbitre - + Demote user from judge Destituer arbitre - + View admin notes Voir les notes d'administrateur - - - + + + Error Erreur - + This user does not exist. Cet utilisateur n'existe pas. - + You are being ignored by %1 and can't see their games. Vous avez été ignoré par %1 et ne pouvez pas voir ses parties. - + Could not get %1's games. Impossible d'obtenir les parties de %1. - + %1's games Parties de %1 - - - + + + Ban History Historique des bannissements - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Heure de bannissement;Modérateur;Durée de bannissement;Raison du bannissement;Raison visible - + User has never been banned. Le joueur n'a jamais été banni. - + Failed to collect ban information. Impossible de collecter les informations de bannissement. - - - + + + Warning History Historique des avertissements - + Warning Time;Moderator;User Name;Reason Heure d'avertissement;Modérateur;Nom d'utilisateur;Raison - + User has never been warned. Le joueur n'a jamais reçu d'avertissement. - + Failed to collect warning information. Impossible de collecter les informations d'avertissements. - + Failed to get admin notes. Impossible de récupérer les notes d'administrateur. - - + + Success Succès - + Successfully promoted user. L'utilisateur a été promu avec succès. - + Successfully demoted user. L'utilisateur a été rétrogradé avec succès. - + + - Failed Échec - + Failed to promote user. L'utilisateur n'a pas été promu. - + Failed to demote user. L'utilisateur n'a pas été rétrogradé. - + Copy hash to clipboard Copier le hash - + Remove this user's messages Supprimer les messages de cet utilisateur @@ -8688,137 +9339,142 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre UserInterfaceSettingsPage - + General interface settings Paramètres généraux de l'interface - + &Double-click cards to play them (instead of single-click) &Double cliquer sur les cartes pour les jouer (au lieu d'un simple clic) - + &Clicking plays all selected cards (instead of just the clicked card) &Cliquer joue toutes les cartes sélectionnées (à la place de seulement la carte cliquée) - + &Play all nonlands onto the stack (not the battlefield) by default &Jouer toutes les cartes non terrain dans la pile (pas sur le champ de bataille) par défaut - + + Do not delete &arrows inside of subphases + + + + Close card view window when last card is removed Fermer la fenêtre d'affichage des cartes lorsque la dernière carte est supprimée - + Auto focus search bar when card view window is opened Focalise automatiquement la barre de recherche lorsque la fenêtre d'affichage des cartes est ouverte - + Annotate card text on tokens Annoter le texte des cartes sur les jetons - + Use tear-off menus, allowing right click menus to persist on screen Utiliser les menus détachables, permettant les clics droits sur les rendre persistants - + Notifications settings Paramètres des notifications - + Enable notifications in taskbar Activer les notifications dans la barre des tâches - + Notify in the taskbar for game events while you are spectating Notifier dans la barre des tâches pour les évènements des parties quand vous y êtes spectateur - + Notify in the taskbar when users in your buddy list connect Notifier dans la barre des tâches quand un ami se connecte - + Animation settings Paramètres des animations - + &Tap/untap animation &Animation d'engagement / dégagement - + Deck editor/storage settings Paramètres d'éditeur et de stockage de deck - + Open deck in new tab by default Ouvre le deck dans un nouvel onglet par défaut - + Use visual deck storage in game lobby Utiliser le stockage visuel de decks dans le lobby des parties - + Use selection animation for Visual Deck Storage Utilisez l'animation de sélection pour le stockage visuel des deck - + When adding a tag in the visual deck storage to a .txt deck: Lorsque vous ajoutez une étiquette dans le stockage visuel du deck à un deck stocké comme un fichier .txt : - + do nothing ne rien faire - + ask to convert to .cod demander à convertir en fichier .cod - + always convert to .cod toujours convertir en fichier .cod - + Default deck editor type Type d'éditeur de deck par défaut - + Classic Deck Editor Éditeur de deck classique - + Visual Deck Editor Éditeur de deck visuel - + Replay settings Paramètres du replay - + Buffer time for backwards skip via shortcut: Temps de tampon pour les sauts en arrière via un raccourci : @@ -8826,22 +9482,22 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre UserListWidget - + Users connected to server: %1 Joueurs connectés au serveur : %1 - + Users in this room: %1 Joueurs dans ce salon : %1 - + Buddies online: %1 / %2 Amis connectés : %1 / %2 - + Ignored users online: %1 / %2 Joueurs sur liste noire connectés : %1 / %2 @@ -8849,32 +9505,32 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre UtilityMenu - + Increment all card counters Incrémenter tous les marqueurs de la carte - + &Untap all permanents &Dégager tous les permanents - + R&oll die... L&ancer un dé... - + &Create token... &Créer un jeton... - + C&reate another token C&réer un autre jeton - + Cr&eate predefined token C&réer un jeton prédéfini @@ -8882,22 +9538,22 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match Mode : correspondance exacte - + Mode: Includes Mode : inclure - + Mode: Include/Exclude Mode : inclure / exclure - + Filter mode (AND/OR/NOT conjunctions of filters) Mode de filtre (conjonctions AND/OR/NOT de filtres) @@ -8905,21 +9561,49 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter Sauvegarder le filtre - + Save all currently applied filters to a file Sauvegarder tous les filtres actuellement appliqués dans un fichier - + Enter filename... Entrer le nom du fichier... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8946,27 +9630,27 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDatabaseDisplayNameFilterWidget - - Filter by name... - Filtrer par nom... + + Filter by name... (Exact match) + - + Load from Deck Charger depuis un deck - + Apply all card names in currently loaded deck as exact match name filters Appliquer tous les noms de cartes du deck actuellement chargé comme filtres de nom exacts - + Load from Clipboard Charger depuis le presse-papier... - + Apply all card names in clipboard as exact match name filters Appliquer tous les noms de cartes du presse-papier comme filtres de nom exacts @@ -9030,50 +9714,115 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDatabaseDisplayWidget - + Search by card name (or search expressions) Rechercher par nom de carte (ou expression de recherche) - + + + Visual + + + + Loading database ... Chargement de la base de données... - + Clear all filters Effacer tous les filtres - + + Sort by: + + + + + Filter by: + + + + Save and load filters Sauvegarder et charger des filtres - + Filter by exact card name Filtrer par nom exact de carte - + Filter by card sub-type Filtrer par sous-type de carte - + Filter by set Filtrer par extension + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand Piocher un nouvel exemple de main - + Sample hand size Taille de l'exemple de main @@ -9081,46 +9830,25 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDeckEditorWidget - - Click and drag to change the sort order within the groups - Cliquer et glisser pour changer l'ordre dans les groupes + + Type a card name here for suggestions from the database... + - + Quick search and add card Recherche et ajout rapide d'une carte - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter Recherchez la correspondance la plus proche dans la base de données (avec suggestions automatiques) et ajoutez l'impression souhaitée au deck en appuyant sur Entrée - - - Configure how cards are sorted within their groups - Configurez le mode de tri des cartes dans leurs groupes. - - - - - Overlap Layout - Interface de superposition - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - Modifier l'affichage des cartes dans les zones (i.e. superposées ou entièrement visibles). - - - - Flat Layout - Interface plate - VisualDeckStorageFolderDisplayWidget - + Deck Storage Stockage de deck @@ -9176,7 +9904,7 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDeckStorageSearchWidget - + Search by filename (or search expression) Rechercher par chemin de fichier (ou expressions de recherche) @@ -9184,22 +9912,22 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) Trier par ordre alphabétique (nom de deck) - + Sort Alphabetically (Filename) Trier par ordre alphabétique (nom de fichier) - + Sort by Last Modified Trier par date de dernière modification - + Sort by Last Loaded Trier par date de dernier chargement @@ -9207,17 +9935,17 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDeckStorageWidget - + Loading database ... Chargement de la base de données ... - + Refresh loaded files Recharger les fichiers chargés - + Visual Deck Storage Settings Paramètres du stockage visuel des decks @@ -9225,43 +9953,43 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre WarningDialog - + Which warning would you like to send? Quel avertissement voudriez-vous envoyer ? - + Redact all messages from this user in all rooms Censurer tous les messages de cet utilisateur dans toutes les salles - + &OK &OK - + &Cancel &Annuler - + Warn user for misconduct Envoyer un avertissement au joueur pour mauvaise conduite - - + + Error Erreur - + User name to send a warning to can not be blank, please specify a user to warn. Le nom d'utilisateur du joueur à avertir ne peut pas être vide, merci d'entrer un joueur à avertir. - + Warning to use can not be blank, please select a valid warning to send. L'avertissement à utiliser ne peut pas être vide, merci de sélectionner un avertissement valide à envoyer. @@ -9269,133 +9997,133 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre WndSets - + Move selected set to the top Déplacer l'édition sélectionnée tout en haut - + Move selected set up Déplacer l'édition sélectionnée vers le haut - + Move selected set down Déplacer l'édition sélectionnée vers le bas - + Move selected set to the bottom Déplacer l'édition sélectionnée tout en bas - + Search by set name, code, or type Rechercher par ensemble de nom, code ou type - + Default order Ordre par défaut - + Restore original art priority order Restaurer l'ordre original de priorité artistique - + Enable all sets Activer toutes les éditions - + Disable all sets Désactiver toutes les éditions - + Enable selected set(s) Activer l(es) édition(s) sélectionnée(s) - + Disable selected set(s) Désactiver l(es) éditions(s) sélectionné(s) - + Deck Editor Éditeur de deck - + Use CTRL+A to select all sets in the view. Utiliser Ctrl+A pour sélectionner toutes les éditions dans la vue actuelle. - + Only cards in enabled sets will appear in the card list of the deck editor. Seules les cartes des éditions activées apparaitront dans la liste de cartes de l'éditeur de deck. - + Image priority is decided in the following order: La priorité des images est décidé dans l'ordre suivant: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki D'abord le dossier PERSONNALISÉ (%1), puis les éditions activées dans cette fenêtre (de haut en bas) - + Include cards rebalanced for Alchemy [requires restart] Inclure les cartes rééquilibrées pour Alchemy [nécessite un redémarrage] - + Card Art Images des cartes - + How to use custom card art Comment utiliser une illustration personnalisée de carte - + Hints Astuces - + Note Note - + Sorting by column allows you to find a set while not changing set priority. Trier par colonne vous permet de trouver une édition sans changer la priorité des éditions. - + To enable ordering again, click the column header until this message disappears. Pour trier à nouveau, cliquez sur l'entête de la colonne jusqu'à ce que ce message disparaisse. - + Use the current sorting as the set priority instead Utiliser le tri actuel comme ordre de priorité des éditions - + Sorts the set priority using the same column Sortir la priorité sur la même colonne - + Manage sets Gérer les éditions @@ -9403,72 +10131,72 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre ZoneViewWidget - + Search by card name (or search expressions) Rechercher par nom de carte (ou expression de recherche) - + Ungrouped Non groupé - + Group by Type Grouper par Types - + Group by Mana Value Grouper par Valeur de Mana - + Group by Color Grouper par Couleurs - + Unsorted Non trié - + Sort by Name Trier par Nom - + Sort by Type Trier par Type - + Sort by Mana Cost Trier par Coût de Mana - + Sort by Colors Trier par Couleurs - + Sort by P/T Trier par F/E - + Sort by Set Trier par extension - + shuffle when closing mélanger en quittant - + pile view vue de la pile @@ -9476,7 +10204,7 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre i18n - + English Français (French) @@ -9484,12 +10212,12 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre main - + Connect on startup Connecter au démarrage - + Debug to file Débuguer dans journal @@ -9497,1005 +10225,1041 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre shortcutsTab - + Main Window Fenêtre principale - - + + Deck Editor Éditeur de deck - + Game Lobby Hall - + Card Counters Marqueurs sur la carte - + Player Counters Marqueurs sur le joueur - + Power and Toughness Force et Endurance - + Game Phases Phases de jeu - + Playing Area Zone de jeu - + Move Selected Card Déplacer la carte sélectionnée - + View Vue - + Move Top Card Déplacer la carte du dessus - + Move Bottom Card Déplacer la carte du dessous - + Gameplay Gameplay - + Drawing Piocher - + Chat Room Salon de discussion - + Game Window Fenêtre de jeu - + Load Deck from Clipboard Charger un deck depuis le presse-papier - - + + Replays Replays - + Tabs Onglets - + Check for Card Updates... Vérifier les mises à jour des cartes… - + Connect... Se connecter… - + Disconnect Déconnecter - + Exit Quitter - + Full screen Plein écran - + Register... S'enregistrer… - + Settings... Paramètres… - + Start a Local Game... Démarrer une partie locale… - + Watch Replay... Regarder un replay… - + Analyze Deck (deckstats.net) Analyser le deck (deckstats.net) - + Analyze Deck (tappedout.net) Analyser le deck (tappedout.net) - + Clear All Filters Effacer tous les filtres - + Clear Selected Filter Effacer le filtre sélectionné - + Close Fermer - + Remove Card Retirer la carte - + Manage Sets... Gérer les éditions… - + Edit Custom Tokens... Éditer les jetons personnalisés… - + Export Deck (decklist.org) Exporter le deck (decklist.org) - + Export Deck (decklist.xyz) Exporter le deck (decklist.xyz) - + Add Card Ajouter une carte - + Load Deck... Charger un deck… - - + + Load Deck from Clipboard... Charger un deck depuis le presse-papier… - + Edit Deck in Clipboard, Annotated Éditer le deck dans le presse-papier, avec annotations - + Edit Deck in Clipboard Éditer le deck dans le presse-papier - + New Deck Nouveau deck - + Open Custom Pictures Folder Ouvrir le dossier d'images personnalisées - + Print Deck... Imprimer le deck… - + Delete Card Supprimer la carte - - + + Reset Layout Réinitialiser l'interface - + Save Deck Sauvegarder le deck - + Save Deck as... Sauvegarder le deck sous… - + Save Deck to Clipboard, Annotated Copier le deck annoté - + Save Deck to Clipboard, Annotated (No Set Info) Copier le deck, avec annotations (sans information d'extension) - + Save Deck to Clipboard Copier le deck - + Save Deck to Clipboard (No Set Info) Copier le deck (sans information d'extension) - + Load Local Deck... Charger un deck local… - + Load Remote Deck... Charger un deck distant… - + Set Ready to Start Marquer prêt à démarrer - + Toggle Sideboard Lock Verrouiller / déverrouiller la réserve - + Add Green Counter Ajouter un marqueur vert - + Remove Green Counter Retirer un marqueur vert - + Set Green Counters... Nombre de marqueurs verts… - + Add Red Counter Ajouter un marqueur rouge - + Remove Red Counter Retirer un marqueur rouge - + Set Red Counters... Nombre de marqueurs rouges… - + Add Life Counter Gagner 1 point de vie - + Show Status Bar Montrer la barre de statut - + Unload Deck Annuler le chargement du deck - + Force Start Forcer le démarrage - + Add Card Counter (F) Ajouter un marqueur de carte (F) - + Remove Card Counter (F) Retirer un marqueur de carte (F) - + Set Card Counters (F)... Nombre de marqueurs de carte (F)... - + Add Card Counter (E) Ajouter un marqueur de carte (E) - + Remove Card Counter (E) Retirer un marqueur de carte (E) - + Set Card Counters (E)... Nombre de marqueurs de carte (E)... - + Add Card Counter(D) Ajouter un marqueur de carte (D) - + Remove Card Counter (D) Retirer un marqueur de carte (D) - + Set Card Counters (D)... Nombre de marqueurs de carte (D)... - + Add Card Counter (C) Ajouter un marqueur de carte (C) - + Remove Card Counter (C) Retirer un marqueur de carte (C) - + Set Card Counters (C)... Nombre de marqueurs de carte (C)... - + Add Card Counter (B) Ajouter un marqueur de carte (B) - + Remove Card Counter (B) Retirer un marqueur de carte (B) - + Set Card Counters (B)... Nombre de marqueurs de carte (B)... - + Add Card Counter (A) Ajouter un marqueur de carte (A) - + Remove Card Counter (A) Retirer un marqueur de carte (A) - + Set Card Counters (A)... Nombre de marqueurs de carte (A)... - + Remove Life Counter Perdre 1 point de vie - + Set Life Counters... Nombre de points de vie… - + Add White Counter Ajouter un marqueur blanc - + Remove White Counter Retirer un marqueur blanc - + Set White Counters... Nombre de marqueurs blancs… - + Add Blue Counter Ajouter un marqueur bleu - + Remove Blue Counter Retirer un marqueur bleu - + Set Blue Counters... Nombre de marqueurs bleus… - + Add Black Counter Ajouter un marqueur noir - + Remove Black Counter Retirer un marqueur noir - + Set Black Counters... Nombre de marqueurs noirs… - + Add Colorless Counter Ajouter un marqueur incolore - + Remove Colorless Counter Retirer un marqueur incolore - + Set Colorless Counters... Nombre de marqueurs incolores… - + Add Other Counter Ajouter un autre marqueur - + Remove Other Counter Supprimer un autre marqueur - + Set Other Counters... Mettre d'autres compteurs... - + Increment all card counters Incrémenter tous les marqueurs de la carte - + Add Power (+1/+0) Augmenter la force (+1/+0) - + Remove Power (-1/-0) Diminuer la force (-1/-0) - + Move Toughness to Power (+1/-1) Endurance vers force (+1/-1) - + Add Toughness (+0/+1) Augmenter l'endurance (+0/+1) - + Remove Toughness (-0/-1) Diminuer l'endurance (-0/-1) - + Move Power to Toughness (-1/+1) Force vers endurance (-1/+1) - + Add Power and Toughness (+1/+1) Augmenter la force et l'endurance (+1/+1) - + Remove Power and Toughness (-1/-1) Diminuer la force et l'endurance (-1/-1) - + Set Power and Toughness... Changer la force et l'endurance… - + Reset Power and Toughness Réinitialiser la force et l'endurance - + Untap Dégager - + Upkeep Entretien - + Draw Piocher - + First Main Phase Première phase principale - + Start Combat Début de combat - + Attack Attaquer - + Block Bloquer - + Damage Blessures - + End Combat Fin de combat - + Second Main Phase Seconde phase principale - + End Fin - + Next Phase Phase suivante - + Next Phase Action Étape suivante avec action - + Next Turn Tour suivant - + Hide Card in Reveal Window Cache la carte dans la fenêtre Révéler - + Tap / Untap Card Engage / dégager la carte - + Untap All Tout dégager - + Toggle Untap Activer / désactiver le dégagement normal - + Turn Card Over Retourner la carte - + Peek Card Regarder la carte - + Play Card Jouer la carte - + Attach Card... Attacher la carte… - + Unattach Card Détacher la carte - + Clone Card Cloner la carte - + Create Token... Créer un jeton… - + Create All Related Tokens Créer tous les jetons associés - + Create Another Token Créer un autre jeton - + Set Annotation... Annoter… - + Select All Cards in Zone Sélectionne toutes les cartes dans la zone - + Select All Cards in Row Sélectionne toutes les cartes dans la ligne - + Select All Cards in Column Sélectionne toutes les cartes dans la colonne - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library Dessous de la bibliothèque - - - - + + + + Exile Exil - - - - + + + + Graveyard Cimetière - - + + + Hand Main - - + + Top of Library Dessus de la bibliothèque - - - + + + Battlefield, Face Down Champ de bataille, face cachée - + Battlefield Champ de bataille - - Sort Hand - Trier la main - - - + Library Bibliothèque - + Sideboard Réserve - + Top Cards of Library Cartes du dessus de la bibliothèque - + Bottom Cards of Library Cartes du dessous de la bibliothèque - + Close Recent View Fermer la vue récente - - + + Stack Pile - - + + Graveyard (Multiple) Cimetière (plusieurs) - - + + Exile (Multiple) Exil (plusieurs) - + Stack Until Found Empiler jusqu'à ce que trouvé - + Draw Bottom Card Piocher la carte du dessous - + Draw Multiple Cards from Bottom... Piocher plusieurs cartes du dessous... - + Draw Arrow... Tracer une flèche… - + Remove Local Arrows Retirer les flèches locales - + Leave Game Quitter la partie - + Concede Concéder - + Roll Dice... Lancer un dé… - + Shuffle Library Mélanger la bibliothèque - + Shuffle Top Cards of Library Mélanger les cartes du dessus de la bibliothèque - + Shuffle Bottom Cards of Library Mélanger les cartes du dessous de la bibliothèque - + Mulligan Mulligan - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card Piocher une carte - + Draw Multiple Cards... Piocher plusieurs cartes… - + Undo Draw Annuler la dernière pioche - + Always Reveal Top Card Toujours révéler la carte du dessus - + Always Look At Top Card Toujours révéler la carte du dessus - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise Pivoter la vue dans le sens horaire - + Rotate View Counterclockwise Pivoter la vue dans le sens anti-horaire - + Unfocus Text Box Lâcher le focus de la boîte de texte - + Focus Chat Focus sur le chat - + Clear Chat Effacer le chat - + Refresh Rafraichir - + Skip Forward Avance en avant - + Skip Backward Avant en arrière - + Skip Forward by a lot Avance beaucoup en avant - + Skip Backward by a lot Avance beaucoup en arrière - + Play/Pause Jouer/Pause - + Toggle Fast Forward Activer l'avance rapide - + Home Accueil - + Visual Deck Storage Stockage visuel de deck - + Deck Storage Stockage de deck - + Server Serveur - + Account Compte - + Administration Administration - + Logs Journaux diff --git a/cockatrice/translations/cockatrice_it.ts b/cockatrice/translations/cockatrice_it.ts index ab2472189..a0765fbfb 100644 --- a/cockatrice/translations/cockatrice_it.ts +++ b/cockatrice/translations/cockatrice_it.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... Imposta &contatore... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Imposta contatore - + New value for counter '%1': Nuovo valore per il contatore '%1': @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh &Ricarica - + Parse Set Name and Number (if available) Acquisisci set e numero (se disponibili) @@ -36,81 +36,83 @@ AbstractTabDeckEditor - + Open in new tab Apri in una nuova scheda - + Are you sure? Sei sicuro? - + The decklist has been modified. Do you want to save the changes? La lista del mazzo è stata modificata. Vuoi salvare i cambiamenti? - - - - - - - + + + + + + Error Errore - + Could not open deck at %1 Impossibile aprire il mazzo a %1 - + Could not save remote deck Impossibile salvare mazzo remoto - - + + The deck could not be saved. Please check that the directory is writable and try again. Il mazzo non può essere salvato. Controlla se la cartella è valida e prova ancora. - + Save deck Salva mazzo - + The deck could not be saved. Il mazzo non può essere salvato. - + There are no cards in your deck to be exported Non ci sono carte da esportare nel tuo mazzo + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. - Nessun mazzo da esportare è stato selezionato. + + Add Analytics Panel + AdminNotesDialog - + Update Notes Aggiorna note - + Admin Notes for %1 Note amministratori su %1 @@ -118,12 +120,12 @@ Controlla se la cartella è valida e prova ancora. AllZonesCardAmountWidget - + Mainboard Mazzo - + Sideboard Sideboard @@ -131,22 +133,22 @@ Controlla se la cartella è valida e prova ancora. AppearanceSettingsPage - + seconds secondi - + Error Errore - + Could not create themes directory at '%1'. Impossibile creare la cartella dei temi in '%1'. - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -157,7 +159,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -168,152 +170,165 @@ Are you sure you would like to disable this feature? - + Confirm Change - + Theme settings Impostazioni temi - + Current theme: Tema attuale: - + Open themes folder Apri cartella temi - + Home tab background source: Sorgente dello sfondo della Home: - + Home tab background shuffle frequency: Frequenza di modifica dello sfondo della Home: - + Disabled Disattivato - + Menu settings Impostazioni menù - + Show keyboard shortcuts in right-click menus Mostra scorciatoie da tastiera nel menù del tasto destro del mouse - + + Show game filter toolbar above list in room tab + + + + Card rendering Visualizzazione delle carte - + Display card names on cards having a picture Visualizza nome delle carte sopra le immagini - + Auto-Rotate cards with sideways layout Ruota automaticamente carte con disposizione orizzontale - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector Nel selettore di stampa, mostra per primi i set delle carte che il mazzo contiene - + Scale cards on mouse over Ingrandisci la carta sotto il mouse - + Use rounded card corners Usa i bordi delle carte arrotondati - + Minimum overlap percentage of cards on the stack and in vertical hand Sovrapposizione % minima delle carte in pila e nella mano verticale: - + Maximum initial height for card view window: Altezza iniziale massima per la finestra di visualizzazione delle carte: - - + + rows file - + Maximum expanded height for card view window: Altezza massima per la finestra di visualizzazione delle carte: - + Card counters Segnalini della carta - + Counter %1 Segnalino %1 - + Hand layout Disposizione della mano - + Display hand horizontally (wastes space) Disponi la mano orizzontalmente (spreca spazio) - + Enable left justification Allinea a sinistra - + Table grid layout Disposizione delle aree di gioco - + Invert vertical coordinate Inverti disposizione verticale - + Minimum player count for multi-column layout: Numero di giocatori minimo per disposizione multicolonna: - + Maximum font size for information displayed on cards: Dimensione massima carattere per le informazioni mostrate sulle carte: + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -335,112 +350,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name Banna &user name - + ban &IP address Banna indirizzo &IP - + ban client I&D Banna client I&D - + Ban type Tipo di ban - + &permanent ban Ban &permanente - + &temporary ban Ban &temporaneo - + &Days: &Giorni: - + &Hours: &Ore: - + &Minutes: &Minuti: - + Duration of the ban Durata del ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Per favore inserisci la ragione del ban. Questa è visibile solo ai moderatori e non alla persona bannata. - + Please enter the reason for the ban that will be visible to the banned person. Per favore inserisci la ragione del ban che sarà visibile alla persona bannata. - + Redact all messages from this user in all rooms Elimina tutti i messaggi di questo utente in tutte le stanze - + &OK &OK - + &Cancel &Annulla - + Ban user from server Banna utente dal server - + + - - + Error Errore - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Seleziona un nome, un IP, un client ID o una combinazione di questi per impostare un ban. - + You must have a value in the name ban when selecting the name ban checkbox. Devi inserire un valore nel campo nome quando il ban per nome è selezionato. - + You must have a value in the ip ban when selecting the ip ban checkbox. Devi inserire un valore nel campo ip quando il ban per ip è selezionato. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Devi inserire un valore nel campo clientid quando il ban per clientid è selezionato. @@ -471,32 +486,32 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardDatabaseModel - + Name Nome - + Sets Set - + Mana cost Costo - + Card type Tipo - + P/T F/C - + Color(s) Colore @@ -504,96 +519,101 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardFilter - + AND Logical conjunction operator used in card filter AND - + OR Logical disjunction operator used in card filter OR - + AND NOT Negated logical conjunction operator used in card filter AND NOT - + OR NOT Negated logical disjunction operator used in card filter OR NOT - + Name Nome - + + Name (Exact) + + + + Type Tipo - + Color Colore - + Text Testo - + Set Set - + Mana Cost Costo di mana - + Mana Value Valore di mana - + Rarity Rarità - + Power Forza - + Toughness Costituzione - + Loyalty Fedeltà - + Format Formato - + Main Type Tipo - + Sub Type Sottotipo @@ -601,22 +621,22 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardInfoFrameWidget - + Image Immagine - + Description Descrizione - + Both Entrambi - + View transformation Visualizza trasformazione @@ -624,22 +644,22 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardInfoPictureWidget - + View related cards Guarda carte correlate - + Add card to deck Aggiungi la carta al &mazzo - + Mainboard Mazzo - + Sideboard Sideboard @@ -652,12 +672,22 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Nome: - + + Set: + + + + + Collector Number: + + + + Related cards: Carte correlate: - + Unknown card: Carta sconosciuta: @@ -665,124 +695,124 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardMenu - + Re&veal to... Ri&vela a... - + &All players &Tutti i giocatori - + View related cards Guarda carte correlate - + Token: Pedina: - + All tokens Tutte le pedine - + &Select All &Seleziona tutto - + S&elect Row S&eleziona fila - + S&elect Column S&eleziona colonna - + &Play &Gioca - + &Hide &Nascondi - + Play &Face Down Gioca a &faccia in giù - + &Tap / Untap Turn sideways or back again &TAPpa/STAPpa - + Toggle &normal untapping Blocca/Sblocca &STAP normale - + T&urn Over Turn face up/face down Capovolgi - + &Peek at card face &Sbircia la faccia della carta - + &Clone &Copia - + Attac&h to card... Asse&gna alla carta... - + Unattac&h Tog&li - + &Draw arrow... &Disegna una freccia... - + &Set annotation... &Imposta note... - + Ca&rd counters Segnalini delle ca&rte - + &Add counter (%1) &Aggiungi segnalino (%1) - + &Remove counter (%1) &Rimuovi segnalino (%1) - + &Set counters (%1)... Imposta &segnalini (%1)... @@ -790,7 +820,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardSizeWidget - + Card Size Dimensioni della carta @@ -798,133 +828,133 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardZoneLogic - + their hand nominative la sua mano - + %1's hand nominative Mano di %1 - - - their library - look at zone - il suo grimorio - - - - %1's library - look at zone - Grimorio di %1 - - - - of their library - top cards of zone, - del suo grimorio - - - - of %1's library - top cards of zone - del grimorio di %1 - their library - reveal zone + look at zone il suo grimorio %1's library + look at zone + Grimorio di %1 + + + + of their library + top cards of zone, + del suo grimorio + + + + of %1's library + top cards of zone + del grimorio di %1 + + + + their library + reveal zone + il suo grimorio + + + + %1's library reveal zone Grimorio di %1 - + their library shuffle il suo grimorio - + %1's library shuffle Grimorio di %1 - + their library nominative il suo grimorio - + %1's library nominative Grimorio di %1 - + their graveyard nominative il suo cimitero - + %1's graveyard nominative Cimitero di %1 - + their exile nominative la sua zona di esilio - + %1's exile nominative Esilio di %1 - + their sideboard look at zone la sua sideboard - + %1's sideboard look at zone Sideboard di %1 - - - their sideboard - nominative - la sua sideboard - - - - %1's sideboard - nominative - Sideboard di %1 - + their sideboard + nominative + la sua sideboard + + + + %1's sideboard + nominative + Sideboard di %1 + + + their custom zone '%1' nominative la sua zona personalizzata '%1' - + %1's custom zone '%2' nominative la zona personalizzata '%2' di %1 @@ -933,7 +963,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CockatriceXml3Parser - + Parse error at line %1 col %2: Errore di lettura alla riga %1 posizione %2: @@ -941,7 +971,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CockatriceXml4Parser - + Parse error at line %1 col %2: Errore di lettura alla riga %1 posizione %2: @@ -960,6 +990,37 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Visualizza la zona personalizzata '%1' + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -971,47 +1032,47 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) Cerca per nome della carta (o espressioni di ricerca) - + Add to Deck Aggiungi al mazzo - + Add to Sideboard Aggiungi alla sideboard - + Select Printing Seleziona stampa - + Show on EDHRec (Commander) Mostra su EDHRec (Commander) - + Show on EDHRec (Card) Mostra su EDHRec (Carta) - + Show Related cards Mostra carte correlate - + Add card to &maindeck Aggiungi carta al &mazzo - + Add card to &sideboard Aggiungi carta alla &sideboard @@ -1019,87 +1080,97 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card Carta copertina - + Main Type Tipo - + Mana Cost Costo di mana - + Colors Colori - + Select Printing Seleziona stampa - + Deck Mazzo - + Deck &name: &Nome mazzo: - + Banner Card/Tags Visibility Settings Impostazioni carta copertina/etichette - + Show banner card selection menu Visualizza menu di selezione carta copertina - + Show tags selection menu Visualizza menu di selezione etichette - + &Comments: &Commenti: - + Group by: Raggruppa per: - + + Format: + + + + Hash: Hash: - + &Increment number &Aumenta il numero - + &Decrement number &Diminuisci il numero - + &Remove row &Rimuovi carta - + Swap card to/from sideboard Sposta carta in maindeck/sideboard @@ -1107,17 +1178,17 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckEditorFilterDockWidget - + Filters Filtri - + &Clear all filters &Elimina tutti i filtri - + Delete selected Elimina filtro @@ -1125,114 +1196,114 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckEditorMenu - + &Deck Editor &Editor dei mazzi - + &New deck &Nuovo mazzo - + &Load deck... &Carica mazzo... - + Load recent deck... Carica mazzo recente... - + Clear Svuota - + &Save deck &Salva mazzo - + Save deck &as... Salva mazzo &con nome... - + Load deck from cl&ipboard... Carica mazzo dagli app&unti... - + Edit deck in clipboard Modifica mazzo negli appunti - - + + Annotated Con annotazioni - - + + Not Annotated Senza annotazioni - + Save deck to clipboard Salva il mazzo negli appunti - + Annotated (No set info) Con annotazioni (senza info set) - + Not Annotated (No set info) Senza annotazioni (senza info set) - + &Print deck... Stam&pa mazzo... - + Load deck from online service... Carica mazzo dal servizio online... - + &Send deck to online service &Invia mazzo al servizio online - + Create decklist (decklist.org) Crea una decklist (decklist.org) - + Create decklist (decklist.xyz) Crea una decklist (decklist.xyz) - + Analyze deck (deckstats.net) Analizza mazzo (deckstats.net) - + Analyze deck (tappedout.net) Analizza mazzo (tappedout.net) - + &Close &Chiudi @@ -1240,7 +1311,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckEditorPrintingSelectorDockWidget - + Printing Selector Selettore di stampa @@ -1248,194 +1319,227 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckEditorSettingsPage - - + + Update Spoilers Aggiorna Spoiler - - + + Success Fatto - + Download URLs have been reset. Gli indirizzi di download sono stati resettati. - + Downloaded card pictures have been reset. Le immagini delle carte scaricate sono state eliminate. - + Error Errore - + One or more downloaded card pictures could not be cleared. Non è stato possibile eliminare alcune delle immagini delle carte scaricate. - + Add URL Aggiungi indirizzo - - + + URL: Indirizzo: - - + + Edit URL Modifica indirizzo: - + Network Cache Size: Dimensione cache di rete: - + Redirect Cache TTL: TTL cache dei reindirizzamenti: - + How long cached redirects for urls are valid for. Per quanto tempo sono validi i reindirizzamenti per gli URL memorizzati nella cache. - + Picture Cache Size: Dimensione cache immagini: - + Add New URL Aggiungi indirizzo URL - + Remove URL Elimina indirizzo URL - + Day(s) Giorno/i - + Updating... Aggiornando... - + Choose path Scegli il percorso - + URL Download Priority Ordine di priorità degli indirizzi - + Spoilers Spoiler - + Download Spoilers Automatically Scarica spoiler automaticamente - + Spoiler Location: Indirizzo spoiler: - + Last Change Ultima modifica - + Spoilers download automatically on launch Scarica spoiler automaticamente all'avvio - + Press the button to manually update without relaunching Premi il pulsante per aggiornare manualmente senza riavviare - + Do not close settings until manual update is complete Non chiudere le impostazioni fino a che l'aggiornamento manuale sia completato - + Download card pictures on the fly Scarica immagini delle carte in tempo reale - + How to add a custom URL Come aggiungere indirizzi personalizzati - + Delete Downloaded Images Elimina immagini scaricate - + Reset Download URLs Resetta indirizzi di download - + On-disk cache for downloaded pictures Cache su disco immagini scaricate - + In-memory cache for pictures not currently on screen Cache in memoria per immagini non attualmente su schermo + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count Quantità - + Set Set - + Number Numero - + Provider ID ID Provider - + Card Carta @@ -1443,12 +1547,12 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckLoader - + Common deck formats (%1) Formati di mazzo comuni (%1) - + All files (*.*) Tutti i file (*.*) @@ -1456,17 +1560,17 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match Modalità: Corrispondenza esatta - + Mode: Includes Modalità: Include - + Color identity filter mode (AND/OR/NOT conjunctions of filters) Filtraggio identità di colore (utilizzabile con connettivi logici AND/OR/NOT) @@ -1474,7 +1578,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... Imposta etichette... @@ -1482,62 +1586,62 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckPreviewTagDialog - + Deck Tags Manager Gestore etichette del mazzo - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: Gestisci le etichette del mazzo. Assegna o rimuovi etichette, oppure creane di nuove. - + Add a new tag (e.g., Aggro️) Aggiungi una nuova etichetta (Es.: Aggro️) - + Add Tag Aggiungi etichetta - + Filter tags... Filtra etichette... - + OK Conferma - + Edit default tags Modifica etichette - + Cancel Annulla - + Invalid Input Input non valido - + Tag name cannot be empty! Il nome dell'etichetta non può essere vuoto! - + Duplicate Tag Duplica Etichetta - + This tag already exists. Questa etichetta esiste già. @@ -1550,93 +1654,151 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Carta copertina - + Open in deck editor Apri nell'editor dei mazzi - + Edit Tags Modifica etichette - + Rename Deck Rinomina mazzo - + Save Deck to Clipboard Salva il mazzo negli appunti - + Annotated Con annotazioni - + Annotated (No set info) Con annotazioni (senza info set) - + Not Annotated Senza annotazioni - + Not Annotated (No set info) Senza annotazioni (senza info set) - + Rename File Rinomina file - + Delete File Elimina file - + Set Banner Card Imposta carta copertina - - + + New name: Nuovo nome: - - + + Error Errore - + Rename failed Rinomina non riuscita - + Delete file Elimina file - + Are you sure you want to delete the selected file? Vuoi davvero eliminare il file selezionato? - + Delete failed Eliminazione non riuscita + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1654,75 +1816,75 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckViewContainer - + Load deck... Carica mazzo... - + Load remote deck... Carica mazzo remoto... - + Load from clipboard... Carica dagli appunti... - + Load from website... Carica dal sito... - + Unload deck Deseleziona mazzo - + Ready to start Pronto ad iniziare - + Force start Forza avvio - + Sideboard unlocked Sideboard sbloccata - + Sideboard locked Sideboard bloccata - - + + Error Errore - + The selected file could not be loaded. I file selezionati non posso essere caricati. - + Deck is greater than maximum file size. Il mazzo è più grande della dimensione massima del file consentita. - + Are you sure you want to force start? This will kick all non-ready players from the game. Sicuro di voler forzare l'avvio? Ciò espellerà dalla partita tutti i giocatori che non sono pronti. - + Cockatrice Cockatrice @@ -1757,143 +1919,143 @@ Vuoi convertire il mazzo al formato .cod? Scaricamento... - + Known Hosts Host Conosciuti - + Delete the currently selected saved server Elimina il server salvato attualmente selezionato - + Refresh the server list with known public servers Aggiorna la lista dei server con server pubblici - + New Host Nuovo indirizzo - + Name: Nome: - + &Host: &Host: - + &Port: &Porta: - + Player &name: &Nome giocatore: - + P&assword: P&assword: - + &Save password &Salva password - + A&uto connect Connetti a&utomaticamente - + Automatically connect to the most recent login when Cockatrice opens Connetti automaticamente all'ultimo server utilizzato all'avvio di Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! Se hai problemi a connetterti o registrarti contatta lo staff del server per aiuto! - - + + Webpage Sito web - + Reset Password Resetta Password - + Forgot password? Hai dimenticato la password? - + &Connect &Connetti - + Server Server - + Login Login - + Server Contact Contatto server - + Connect to Server Connetti al server - + Server URL URL Server - + Communication Port Numero porta - + Unique Server Name Nome univoco del server - + Connection Warning Avviso di connessione - + You need to name your new connection profile. È necessario impostare un nome per il nuovo profilo di connessione. - + Connect Warning Problema di connessione - + The player name can't be empty. Il nome del giocatore non può essere vuoto. @@ -1901,117 +2063,122 @@ Vuoi convertire il mazzo al formato .cod? DlgCreateGame - + Re&member settings Ricorda impostazioni - + &Description: &Descrizione: - + P&layers: G&iocatori: - + General Generali - + Game type Formato di gioco - + &Password: &Password: - + Only &buddies can join Solo gli &amici possono entrare - + Only &registered users can join Solo &utenti registrati possono entrare - + Joining restrictions Restrizioni d'ingresso - + &Spectators can watch &Gli spettatori possono osservare - + Spectators &need a password to watch Gli spettatori &necessitano di password - + Spectators can &chat Gli spettatori possono &chattare - + Spectators can see &hands Gli spettatori vedono le mani - + Create game as spectator Crea partita come spettatore - + Spectators Spettatori - + Starting life total: Punti vita iniziali: - + Open decklists in lobby Apri mazzi nella lobby - + + Create game as judge + + + + Game setup options Configurazione partita - + &Clear Pulisci - + Create game Crea partita - + Game information Informazioni partita - + Error Errore - + Server error. Errore del server. @@ -2019,97 +2186,97 @@ Vuoi convertire il mazzo al formato .cod? DlgCreateToken - + &Name: &Nome: - + Token Pedina - + C&olor: C&olore: - + white bianco - + blue blu - + black nero - + red rosso - + green verde - + multicolor multicolore - + colorless incolore - + &P/T: &F/C: - + &Annotation: &Note: - + &Destroy token when it leaves the table &Elimina la pedina quando lascia il campo - + Create face-down (Only hides name) Crea a faccia in giù (nasconde solo il nome) - + Token data Dati pedina - + Show &all tokens Mostra &tutte le pedine - + Show tokens from this &deck Mostra pedine di questo &mazzo - + Choose token from list Scegli pedina dalla lista - + Create token Crea pedina @@ -2117,53 +2284,53 @@ Vuoi convertire il mazzo al formato .cod? DlgDefaultTagsEditor - + Edit Tags Modifica etichette - + Add Aggiungi - + Confirm Conferma - + Cancel Annulla - + Enter a tag and press Enter Scrivi il nome dell'etichetta e premi Invio - - + + - + Invalid Input Input non valido - + Tag name cannot be empty! Il nome dell'etichetta non può essere vuoto! - + Duplicate Tag Duplica Etichetta - + This tag already exists. Questa etichetta esiste già. @@ -2171,40 +2338,40 @@ Vuoi convertire il mazzo al formato .cod? DlgEditAvatar - - + + No image chosen. Nessuna immagine selezionata. - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. Per cambiare il tuo avatar, scegli una nuova immagine. Per rimuovere il tuo avatar attuale, conferma senza scegliere una nuova immagine. - + Browse... Cerca... - + Change avatar Cambia avatar - + Open Image Apri immagine - + Image Files (*.png *.jpg *.bmp) File immagine (*.png *.jpg *.bmp) - + Invalid image chosen. Immagine selezionata non valida. @@ -2212,17 +2379,17 @@ Per rimuovere il tuo avatar attuale, conferma senza scegliere una nuova immagine DlgEditDeckInClipboard - + Edit deck in clipboard Modifica mazzo negli appunti - + Error Errore - + Invalid deck list. Lista del mazzo non valida. @@ -2230,38 +2397,38 @@ Per rimuovere il tuo avatar attuale, conferma senza scegliere una nuova immagine DlgEditPassword - + Old password: Vecchia password: - + New password: Nuova password: - + Confirm new password: Conferma nuova password: - + Change password Cambia password - - + + Error Errore - + Your password is too short. La nuova password è troppo corta. - + The new passwords don't match. Le password non coincidono. @@ -2269,93 +2436,93 @@ Per rimuovere il tuo avatar attuale, conferma senza scegliere una nuova immagine DlgEditTokens - + &Name: &Nome: - + C&olor: C&olore: - + white bianco - + blue blu - + black nero - + red rosso - + green verde - + multicolor multicolore - + colorless incolore - + &P/T: &F/C: - + &Annotation: &Note: - + Token data Dati pedina - - + + Add token Aggiungi pedina - + Remove token Rimuovi pedina - + Edit custom tokens Modifica pedine personalizzate - + Please enter the name of the token: Inserisci il nome della pedina: - + Error Errore - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Il nome scelto è in conflitto con una carta o pedina esistente. @@ -2449,8 +2616,9 @@ Assicurati di abilitare il set "Pedine" nella finestra "Organizza - Hide games not created by buddy - Nascondi partite non create dagli amici + Hide games not created by buddies + Hide games not created by buddy + @@ -2536,52 +2704,52 @@ Assicurati di abilitare il set "Pedine" nella finestra "Organizza DlgForgotPasswordChallenge - + Reset Password Challenge Warning Avviso di reimpostazione della password di verifica della sfida - + A problem has occurred. Please try to request a new password again. Si è verificato un problema. Prova a richiedere di nuovo una nuova password. - + Enter the information of the server and the account you'd like to request a new password for. Inserisci le informazioni del server e dell'account per cui desideri richiedere una nuova password. - + &Host: &Host: - + &Port: &Porta: - + Player &name: &Nome giocatore: - + Email: Email: - + Reset Password Challenge Reimposta password sfida - + Reset Password Challenge Error Errore di reimpostazione della password di verifica della sfida - + The email address can't be empty. L'indirizzo email non può essere vuoto. @@ -2589,37 +2757,37 @@ Assicurati di abilitare il set "Pedine" nella finestra "Organizza DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Inserisci le informazioni del server per il quale desideri richiedere una nuova password. - + &Host: &Host: - + &Port: &Porta: - + Player &name: &Nome giocatore: - + Reset Password Request Richiesta di reimpostazione della password - + Reset Password Error Errore di ripristino della password - + The player name can't be empty. Il nome del giocatore non può essere vuoto. @@ -2627,86 +2795,86 @@ Assicurati di abilitare il set "Pedine" nella finestra "Organizza DlgForgotPasswordReset - + Reset Password Warning Avviso di reimpostazione della password - + A problem has occurred. Please try to request a new password again. Si è verificato un problema. Prova a richiedere di nuovo una nuova password. - + Enter the received token and the new password in order to set your new password. Inserisci il token ricevuto e la nuova password per impostare la tua nuova password. - + &Host: &Host: - + &Port: &Porta: - + Player &name: &Nome giocatore: - + Token: Token: - - + + New Password: Nuova password: - + Reset Password Resetta la password - + The player name can't be empty. Il nome del giocatore non può essere vuoto. - - - - + + + + Reset Password Error Errore di ripristino della password - + The token can't be empty. Il token non può essere vuoto. - + The new password can't be empty. La nuova password non può essere vuota. - + Error Errore - + Your password is too short. La tua password è troppo corta. - + The passwords do not match. Le password non coincidono. @@ -2722,17 +2890,17 @@ Assicurati di abilitare il set "Pedine" nella finestra "Organizza DlgLoadDeckFromClipboard - + Load deck from clipboard Carica mazzo dagli appunti - + Error Errore - + Invalid deck list. Lista del mazzo non valida. @@ -2740,45 +2908,45 @@ Assicurati di abilitare il set "Pedine" nella finestra "Organizza DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Incolla il link a una lista di mazzo online per importarla. (Sono supportati Archidekt, Deckstats, Moxfield, e TappedOut). - - - - - + + + + + Load Deck from Website Carica Mazzo dal Sito - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Impossibile acquisire lista di mazzo da questo sito. (Sono supportati Archidekt, Deckstats, Moxfield, e TappedOut). - + Network error: %1 Errore di rete: %1 - + Received empty deck data. Ricevuta lista di mazzo vuota. - + Failed to parse deck data: %1 Impossibile acquisire i dati del mazzo: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2798,7 +2966,7 @@ https://tappedout.net/mtg-decks/nome-del-tuo-mazzo/ DlgLoadRemoteDeck - + Load deck Carica mazzo @@ -2806,37 +2974,37 @@ https://tappedout.net/mtg-decks/nome-del-tuo-mazzo/ DlgMoveTopCardsUntil - + Card name (or search expressions): Nome della carta (o ricerca espressioni): - + Number of hits: Numero di risultati: - + Auto play hits Riproduzione automatica dei successi - + Put top cards on stack until... Metti le carte in cima alla pila fino a... - + No cards matching the search expression exists in the card database. Proceed anyways? Non esiste alcuna carta corrispondente agli estremi di ricerca nel database. Procedere comunque? - + Cockatrice Cockatrice - + Invalid filter Filtro non valido @@ -2844,92 +3012,92 @@ https://tappedout.net/mtg-decks/nome-del-tuo-mazzo/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Inserisci le tue informazioni e le informazioni del server a cui desideri registrarti. La tua email verrà utilizzata per verificare il tuo account. - + &Host: &Host: - + &Port: &Porta: - + Player &name: &Nome giocatore: - + P&assword: P&assword: - + Password (again): Password (conferma): - + Email: Email: - + Email (again): Email (conferma): - + Country: Stato: - + Undefined Non definito - + Real name: Nome reale: - + Register to server Registrati sul server - - - - + + + + Registration Warning Avviso Registrazione - + Your password is too short. La tua password è troppo corta. - + Your passwords do not match, please try again. Le password non combaciano, riprova. - + Your email addresses do not match, please try again. Gli indirizzi email non combaciano, riprova. - + The player name can't be empty. Il nome del giocatore non può essere vuoto. @@ -2955,40 +3123,55 @@ La tua email verrà utilizzata per verificare il tuo account. DlgSelectSetForCards - + Unmodified Cards: Carte non modificate: - + Modified Cards: Carte modificate: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. Seleziona i set per abilitarli. Trascina per riordinarli e modificare la loro priorità. Le carte useranno la stampa del set abilitato con la priorità massima. - + Clear all set information Elimina tutte le info sui set - + Set all to preferred Imposta tutti come preferiti + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database Errore sconosciuto durante il caricamento del database delle carte - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3005,7 +3188,7 @@ Ti consigliamo di avviare oracle per aggiornare il tuo database delle carte. Vuoi modificare le impostazioni della posizione del database della carte? - + Your card database version is too old. This can cause problems loading card information or images @@ -3022,7 +3205,7 @@ Ti consigliamo di avviare oracle per aggiornare il tuo database delle carte. Vuoi modificare le impostazioni della posizione del database della carte? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3034,7 +3217,7 @@ Per favore crea un ticket di assistenza su https://github.com/Cockatrice/Cockatr Desideri modificare l'impostazione della posizione del database? - + File Error loading your card database. Would you like to change your database location setting? @@ -3043,7 +3226,7 @@ Would you like to change your database location setting? Vuoi modificare le impostazioni della posizione del database della carte? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3052,7 +3235,7 @@ Would you like to change your database location setting? Vuoi modificare le impostazioni della posizione del database della carte? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3065,59 +3248,59 @@ https://github.com/Cockatrice/Cockatrice/issues Desideri modificare l'impostazione della posizione del database? - - - + + + Error Errore - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Il percorso della cartella del mazzo non è valido. Vuoi tornare in dietro e impostare il percorso corretto? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Il percorso della cartella delle immagini delle carte è invilido. Vuoi tornare indietro e impostare il percorso corretto? - + Settings Impostazioni - + General Generale - + Appearance Aspetto - + User Interface Interfaccia - + Card Sources Immagini carte - + Chat Chat - + Sound Suoni - + Shortcuts Scorciatoie @@ -3125,12 +3308,12 @@ Desideri modificare l'impostazione della posizione del database? DlgStartupCardCheck - + Card Update Check Controllo aggiornamenti carte - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. @@ -3139,27 +3322,27 @@ Seleziona la modalità di esecuzione dell'aggiornamento. Potrai comunque cambiarla in seguito dalle impostazioni, nella scheda 'Generale'. - + Run in foreground Esegui in primo piano - + Run in background Esegui in background - + Run in background and always from now on Esegui in background da ora in poi - + Don't prompt again and don't run Non eseguire e non chiedere più - + Don't run this time Non eseguire stavolta @@ -3167,17 +3350,17 @@ Potrai comunque cambiarla in seguito dalle impostazioni, nella scheda 'Gene DlgTipOfTheDay - + Next Prossimo - + Previous Precedente - + Tip of the Day Suggerimento del giorno @@ -3185,40 +3368,40 @@ Potrai comunque cambiarla in seguito dalle impostazioni, nella scheda 'Gene DlgUpdate - + Current release channel Canale della versione corrente - + Reinstall Reinstalla - + Cancel Download Annulla download - + Open Download Page Apri pagina di download - + Check for Client Updates Verifica la presenza di aggiornamenti del client - - - - + + + + Error Errore - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice non è stato creato con il supporto SSL, quindi gli aggiornamenti non possono essere scaricati automaticamente! @@ -3226,126 +3409,126 @@ Please visit the download page to update manually. Per favore, visitate la pagina di download per aggiornare manualmente. - + Downloading update: %1 Download dell'aggiornamento: %1 - + Checking for updates... Ricerca aggiornamenti... - + Finished checking for updates Controllo aggiornamenti terminato - + No Update Available Nessun aggiornamento disponibile - + Cockatrice is up to date! Cockatrice è aggiornato! - + You are already running the latest version available in the chosen release channel. Hai già la versione più recente nel canale della versione scelto. - + Current version Versione corrente - + Selected release channel Canale della versione selezionato - - + + Update Available Aggiornamento disponibile - - + + A new version of Cockatrice is available! Una nuova versione di Cockatrice è disponibile! - - + + New version Nuova versione - - + + Released Data di rilascio - - + + Changelog Changelog - + Do you want to update now? Vuoi aggiornare adesso? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. Purtroppo l'aggiornamento automatico non è riuscito a trovare una versione compatibile. Dovrai scaricare la nuova versione manualmente. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. Per favore, controlla la <a href="%1">pagina delle release</a> sul nostro Github e scarica la versione giusta per il tuo sistema. - - - + + + Update Error Errore durante l'aggiornamento - + An error occurred while checking for updates: Errore durante il controllo degli aggiornamenti: - + An error occurred while downloading an update: Errore durante il download di un aggiornamento: - + Installing... Installazione... - + Cockatrice is unable to open the installer. Cockatrice non riesce ad aprire il file di installazione. - + Try to update manually by closing Cockatrice and running the installer. Prova ad aggiornare manualmente chiudendo Cockatrice ed eseguento il file di installazione. - + Download location Percorso download @@ -3353,21 +3536,153 @@ Dovrai scaricare la nuova versione manualmente. DlgViewLog - + Clear log when closing Pulisci la cronologia al riavvio - + Copy to clipboard Copia negli appunti - + Debug Log Registro di debug + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3437,33 +3752,39 @@ Dovrai scaricare la nuova versione manualmente. Sinergia %1% + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos Combo - + Average Deck Mazzo medio - - - Game Changers - Game Changer - - - - Budget - Budget - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: Sale: @@ -3479,22 +3800,22 @@ Dovrai scaricare la nuova versione manualmente. FilterDisplayWidget - + Confirm Delete Conferma eliminazione - + Are you sure you want to delete the filter '%1'? Vuoi davvero eliminare il filtro '%1'? - + Delete Failed Eliminazione non riuscita - + Failed to delete filter '%1'. Impossibile eliminare il filtro '%1'. @@ -3502,22 +3823,22 @@ Dovrai scaricare la nuova versione manualmente. GameEventHandler - + kicked by game host or moderator espulso da proprietario del gioco o moderatore - + player left the game il giocatore ha lasciato la partita - + player disconnected from server il giocatore si è disconnesso dal server - + reason unknown ragione sconosciuta @@ -3525,226 +3846,279 @@ Dovrai scaricare la nuova versione manualmente. GameSelector - - - - - - + + + + + + Error Errore - + Please join the appropriate room first. Si prega di entrare prima in una stanza adeguata. - + Wrong password. Password errata. - + Spectators are not allowed in this game. Spettatori non ammessi in questa partita. - + The game is already full. La partita è piena. - + The game does not exist any more. Questa partita non esiste più. - + This game is only open to registered users. Questa partita è solo per utenti registrati. - + This game is only open to its creator's buddies. Questa stanza è aperta solo agli amici del suo creatore. - + You are being ignored by the creator of this game. Sei stato ingnorato dal creatore di questa partita. - + Join Game Entra nella partita - + Spectate Game Osserva partita - + Game Information Informazioni partita - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game Entra nella partita - + Password: Password: - + Please join the respective room first. Si prega di entrare prima nella rispettiva stanza. - + &Filter games &Filtra partite - + C&lear filter E&limina filtri - + C&reate Cr&ea - + &Join &Entra - + + Join as judge + + + + J&oin as spectator Entra c&ome spettatore - + + Join as judge spectator + + + + Games shown: %1 / %2 Partite mostrate: %1 / %2 - + Games Partite + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day >1 giorno - + %1%2 hr short age in hours %1%2 ora%1%2 ore%1%2 ora(e) - + new nuovo - + %1%2 min short age in minutes %1%2 min%1%2 min%1%2 min - + password password - + buddies only solo amici - + reg. users only solo utenti registrati - + open decklists Apri impostazioni - - + + can chat può chattare - + see hands vede mani - + can see hands può vedere mani - + not allowed non ammessi - + Room Stanza - + Age Età - + Description Descrizione - + Creator Creatore - + Type Tipo - + Restrictions Restrizioni - + Players Giocatori - + Spectators Spettatori @@ -3752,143 +4126,143 @@ Dovrai scaricare la nuova versione manualmente. GeneralSettingsPage - + Reset all paths Reimposta tutti i percorsi - + All paths have been reset I percorsi sono stati resettati - - - - - - - + + + + + + + Choose path Seleziona il percorso - + Personal settings Impostazioni personali - + Language: Lingua: - + Paths (editing disabled in portable mode) Destinazioni (non si può personalizzare in modalità portatile) - + Paths Percorsi - + How to help with translations Come aiutare con le traduzioni - + Decks directory: Cartella mazzi: - + Filters directory: Cartella filtri: - + Replays directory: Cartella replay: - + Pictures directory: Cartella immagini: - + Card database: Database carte: - + Custom database directory: Cartella database personalizzata: - + Token database: Database pedine: - + Update channel Canale di aggiornamento: - + Check for client updates on startup Controlla gli aggiornamenti del client all'avvio - + Check for card database updates on startup Controlla gli aggiornamenti delle carte all'avvio - + Don't check Non controllare - + Prompt for update Chiedi se aggiornare - + Always update in the background Aggiorna sempre in background - + Check for card database updates every Controlla gli aggiornamenti delle carte ogni - + days giorni - + Notify if a feature supported by the server is missing in my client Avvisami se una funzionalità supportata dal server manca nel mio programma - + Automatically run Oracle when running a new version of Cockatrice Avvia automaticamente Oracle se Cockatrice è stato aggiornato - + Show tips on startup Mostra suggerimenti all'avvio - + Last update check on %1 (%2 days ago) Ultimo controllo %1 (%2 giorni fa) @@ -3896,47 +4270,47 @@ Dovrai scaricare la nuova versione manualmente. GraveyardMenu - + &Graveyard &Cimitero - + &View graveyard &Guarda il cimitero - + &Move graveyard to... &Muovi cimitero in... - + &Top of library &Cima al grimorio - + &Bottom of library &Fondo al grimorio - + &All players - + &Hand &Mano - + &Exile &Esilio - + Reveal random card to... Rivela carta casuale a... @@ -3944,111 +4318,141 @@ Dovrai scaricare la nuova versione manualmente. HandMenu - + &Hand &Mano - + &View hand &Vedi mano - - &Sort hand - &Ordina mano + + Sort hand by... + - - Take &mulligan - Mu&lliga + + Name + - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... &Sposta mano in... - + &Top of library &Cima al grimorio - + &Bottom of library &Fondo al grimorio - + &Graveyard &Cimitero - + &Exile &Esilio - + &Reveal hand to... &Rivela mano a... - - Reveal r&andom card to... - Rivela carta c&asuale a... + + + All players + - - - &All players - + + Reveal r&andom card to... + Rivela carta c&asuale a... HomeWidget - + Create New Deck Crea un nuovo mazzo - + Browse Decks Esplora i mazzi - + Browse Card Database Esplora il database delle carte - + Browse EDHRec Esplora EDHRec - + + Browse Archidekt + + + + View Replays Guarda i replay - + Quit Esci - + Connecting... Connessione in corso... - + Connect Connetti - + Play Gioca @@ -4250,61 +4654,61 @@ Dovrai scaricare la nuova versione manualmente. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Il server ha raggiunto la sua massima capacità di utenti, riprova più tardi. - + There are too many concurrent connections from your address. Ci sono troppe connessioni contemporanee dal tuo indirizzo. - + Banned by moderator Bannato dal moderatore - + Expected end time: %1 Fine prevista: %1 - + This ban lasts indefinitely. Questo ban dura a tempo indeterminato. - + Scheduled server shutdown. Spegnimento del server in programma. - - + + Invalid username. Nome utente non valido. - + You have been logged out due to logging in at another location. Sei stato disconnesso: ti sei connesso da un'altra postazione. - + Connection closed Connessione chiusa - + The server has terminated your connection. Reason: %1 Hai perso la connessione con il server. Ragione: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -4317,584 +4721,584 @@ Tutti le partite saranno perse. Ragione dello spegnimento: %1 - + Scheduled server shutdown Spegnimento del server in programma - - + + Success Successo - + Registration accepted. Will now login. Registrazione accettata. Login in corso. - + Account activation accepted. Will now login. Attivazione account accettata. Login in corso. - + Number of players Numero di giocatori - + Please enter the number of players. Inserisci il numero di giocatori: - - + + Player %1 Giocatore %1 - + Load replay Carica replay - + About Cockatrice Info su Cockatrice - + Version Versione - + Cockatrice Webpage Sito di Cockatrice - + Project Manager: Manager del progetto: - + Past Project Managers: Manager precedenti: - + Developers: Sviluppatori: - + Our Developers I nostri sviluppatori - + Help Develop! Aiutaci nello sviluppo! - + Translators: Traduttori: - + Our Translators I nostri traduttori - + Help Translate! Aiutaci nella traduzione! - + Support: Supporto: - + Report an Issue Segnala un problema - + Troubleshooting Risoluzione dei problemi - + F.A.Q. F.A.Q. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error Errore - + Server timeout Timeout del server - + Failed Login Login fallito - + Your client seems to be missing features this server requires for connection. La tua versione del programma non supporta funzionalità richieste dal server, aggiorna il programma e riprova. - + To update your client, go to 'Help -> Check for Client Updates'. Per aggiornare la tua versione di Cockatrice, vai su 'Aiuto -> Controlla aggiornamenti client' - + Incorrect username or password. Please check your authentication information and try again. Nome utente o password non validi. Ricontrolla i tuoi dati di accesso e riprova. - + There is already an active session using this user name. Please close that session first and re-login. Sei già loggato in un'altra sessione con questo username. Chiudi prima quella sessione e riprova a loggare. - - + + You are banned until %1. Sei bannato fino a %1. - - + + You are banned indefinitely. Sei stato bannato per tempo indeterminato. - + This server requires user registration. Do you want to register now? Questo server richiede la registrazione degli utenti. Vuoi registrarti adesso? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. Il server richiede un client ID. Il tuo programma non ha generato un client ID. Riprova dopo aver chiuso e riaperto il programma. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. Si è verificato un errore, riprova dopo aver chiuso e riaperto il programma. Se l'errore persiste, aggiorna il programma all'ultima versione e se necessario chiedi assistenza agli sviluppatori. - + Account activation Attivazione account - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Il tuo account non è ancora stato attivato. Per attivarlo inserisci il codice di attivazione ricevuto tramite email, - + Server Full Server pieno - + Unknown login error: %1 Errore di login sconosciuto: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Solitamente questo significa che stai usando una vecchia versione del programma, e il server ha inviato un messaggio che il tuo programma non è in grado di comprendere. - + Your username must respect these rules: Il nome utente deve seguire queste regole: - + is %1 - %2 characters long essere lungo %1 - %2 caratteri - + can %1 contain lowercase characters %1 può contenere caratteri minuscoli - - - - + + + + NOT NON - + can %1 contain uppercase characters %1 può contenere caratteri maiuscoli - + can %1 contain numeric characters %1 può contenere numeri - + can contain the following punctuation: %1 può contenere la punteggiatura: %1 - + first character can %1 be a punctuation mark il primo carattere %1 può essere punteggiatura - + no unacceptable language as specified by these server rules: note that the following lines will not be translated non usare un linguaggio inaccettabile, come specificato nelle regole del server: - + can not contain any of the following words: %1 non può contenere le seguenti parole: %1 - + can not match any of the following expressions: %1 non può corrispondere alle seguenti espressioni: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Puoi usare solo i caratteri A-Z, a-z, 0-9, _, ., e - nel tuo nome utente. - - - - - - + + + + + + Registration denied Registrazione negata - + Registration is currently disabled on this server La registrazione è disabilitata su questo server - + There is already an existing account with the same user name. Esiste già un account con lo stesso nome utente. - + It's mandatory to specify a valid email address when registering. E' obbligatorio specificare un indirizzo email valido per la registrazione. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. Sembra che tu stia tentando di registrare un nuovo account su questo server ma hai già un account registrato sull'indirizzo email fornito. Questo server restringe il numero di account un utente più avere per indirizzo. Per favore contatta l'amministratore del server per ulteriore assistenza o per ottenere le tue credenziali di accesso. - + Password too short. Password troppo corta. - + Registration failed for a technical problem on the server. La registrazione è fallita a causa di un problema tecnico sul server. - + The connection to the server has been lost. La connessione al server è stata interrotta. - + Unknown registration error: %1 Errore di registrazione sconosciuto: %1 - + Account activation failed Attivazione account fallita - + Socket error: %1 Errore nella connessione: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Stai cercando di connetterti a un server obsoleto. Declassa la versione di Cockatrice per farlo funzionare o connetti ad un altro server. La tua versione è la %1, la versione remota è la %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. La tua versione di Cockatrice è obsoleta. Aggiorna la tua versione di Cockatrice. La tua versione è la %1, la versione online è la %2. - + Connecting to %1... Connessione a %1... - + Registering to %1 as %2... Registrazione su %1 come %2... - + Disconnected Disconnesso - + Connected, logging in at %1 Connesso, login in corso su %1 + - Requesting forgotten password to %1 as %2... Richiesta recupero password su %1 come %2... - + &Connect... &Connetti... - + &Disconnect &Disconnetti - + Start &local game... Inizia partita in &locale... - + &Watch replay... &Guarda replay... - + &Full screen &Schermo intero - + &Register to server... &Registrati sul server... - + &Restore password... Recupera &password... - + &Settings... &Impostazioni... - + &Exit &Esci - + A&ctions A&zioni - + &Cockatrice &Cockatrice - + C&ard Database &Database carte - + &Manage sets... &Organizza set... - + Edit custom &tokens... Modifica pedine personalizzate - + Open custom image folder Apri cartella immagini personalizzate - + Open custom sets folder Apri cartella set personalizzati - + Add custom sets/cards Aggiungi set di carte personalizzato - + Reload card database Ricarica database carte - + Tabs &Schede - + &Help &Aiuto - + &About Cockatrice &Info su Cockatrice - + &Tip of the Day Suggerimento del giorno - + Check for Client Updates Controlla aggiornamenti client - + Check for Card Updates... Controlla aggiornamenti carte... - + Check for Card Updates (Automatic) Controlla aggiornamenti carte in background - + Show Status Bar Mostra barra di stato - + View &Debug Log Vedi Registro di &debug - + Open Settings Folder Apri cartella impostazioni - + Show/Hide Mostra/Nascondi - + New Version Nuova versione - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Congratulazioni per aver aggiornato Cockatrice alla versione %1! Oracle verrà avviato per aggiornare anche il database delle carte. - + Cockatrice installed Cockatrice installata - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Congratulazioni per aver installato Cockatrice %1! Oracle verrà avviato per installare il database delle carte. - + Card database Database carte - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -4903,29 +5307,29 @@ Vuoi provare ad aggiornare il database adesso? Se è la prima volta che usi Cockatrice o non sei sicuro, scegli "Sì" - - + + Yes - - + + No No - + Open settings Apri impostazioni - + New sets found Nuovi set trovati - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -4938,17 +5342,17 @@ Codici dei set: %1 Desideri attivarli? - + View sets Vedi set - + Welcome Benvenuto - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -4957,65 +5361,65 @@ Tutti i set nell'archivio delle carte sono stati abilitati. Scopri metodi alternativi per visualizzare i set o disabilitare set ed effetti nella finestra "Organizza set". - - + + Information Informazione - + A card database update is already running. L'aggiornamento delle carte è già in corso. - + Unable to run the card database updater: Impossibile avviare l'aggiornamento delle carte: - + Card database update running. Aggiornamento delle carte in corso. - + Failed to start. The file might be missing, or permissions might be incorrect. Avvio non riuscito. Il file potrebbe essere assente, o i permessi errati. - + The process crashed some time after starting successfully. Il processo si è arrestato in modo anomalo poco dopo essersi avviato con successo. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. Richiesta scaduta. Il processo ci ha messo troppo a rispondere. L'ultima funzione waitFor...() è scaduta. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. Si è verificato un errore nel cercare di scrivere al processo. Ad esempio, il processo potrebbe non essere in esecuzione, o potrebbe aver chiuso il suo canale di input. - + An error occurred when attempting to read from the process. For example, the process may not be running. Si è verificato un errore nel cercare di leggere dal processo. Ad esempio, il processo potrebbe non essere in esecuzione. - + Unknown error occurred. Si è verificato un errore sconosciuto. - + The card database updater exited with an error: %1 L'aggiornamento del database delle carte è terminato con un errore: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5026,673 +5430,843 @@ Solitamente non si dovrebbe incorrere in problemi, ma questo messaggio può sign Per aggiornare il tuo client, vai su Aiuto -> Controlla aggiornamenti client - - - - - + + + + + Load sets/cards Carica set di carte - + Selected file cannot be found. Il file selezionato non può essere caricato. - + You can only import XML databases at this time. E' possibile importare solamente database XML. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Il nuovo set di carte è stato aggiunto correttamente. Il database delle carte verrà ricaricato. - + Sets/cards failed to import. Il nuovo set di carte non è stato importato a causa di un errore. - - - + + + Reset Password Reimposta Password - + Your password has been reset successfully, you can now log in using the new credentials. La tua password è stata reimpostata correttamente, ora puoi accedere utilizzando le nuove credenziali. - + Failed to reset user account password, please contact the server operator to reset your password. La reimpostazione della password è fallita, contatta l'amministratore del server per reimpostare la password. - + Activation request received, please check your email for an activation token. Richiesta di attivazione ricevuta, controlla il tuo indirizzo email per il codice di attivazione. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base Base di mana + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve Curva di mana + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion Simboli di mana + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play dal campo di battaglia - + from their graveyard dal suo cimitero - + from exile dall'esilio - + from their hand dalla sua mano - + the top card of %1's library la prima carta del grimorio di %1 - + the top card of their library la prima carta del suo grimorio - + from the top of %1's library dalla cima del grimorio di %1 - + from the top of their library dalla cima del suo grimorio - + the bottom card of %1's library l'ultima carta del grimorio di %1 - + the bottom card of their library l'ultima carta del suo grimorio - + from the bottom of %1's library dal fondo del grimorio di %1 - + from the bottom of their library dal fondo del suo grimorio - + from %1's library dal grimorio di %1 - + from their library dal suo grimorio - + from sideboard dalla sideboard - + from the stack dalla pila - + from custom zone '%1' dalla zona personalizzata '%1' - + %1 is now keeping the top card %2 revealed. %1 sta tenendo la prima carta %2 rivelata. - + %1 is not revealing the top card %2 any longer. %1 non sta più rivelando la prima carta %2. - + %1 can now look at top card %2 at any time. %1 può guardare la prima carta %2 della libreria in qualunque momento. - + %1 no longer can look at top card %2 at any time. %1 non può più guardare la prima carta %2 del mazzo in qualunque momento. - + %1 attaches %2 to %3's %4. %1 assegna %2 a %4 di %3. - + %1 has conceded the game. %1 ha concesso la partita. - + %1 has unconceded the game. %1 ha annullato la concessione della partita. - + %1 has restored connection to the game. %1 ha ripristinato il collegamento alla partita. - + %1 has lost connection to the game. %1 ha perso il collegamento alla partita. - + %1 points from their %2 to themselves. %1 disegna una freccia dal suo %2 a sé stesso. - + %1 points from their %2 to %3. %1 disegna una freccia dal suo %2 a %3. - + %1 points from %2's %3 to themselves. %1 disegna una freccia dal %3 di %2 a sé stesso. - + %1 points from %2's %3 to %4. %1 disegna una freccia dal %3 di %2 a %4. - + %1 points from their %2 to their %3. %1 disegna una freccia dal suo %2 al suo %3. - + %1 points from their %2 to %3's %4. %1 disegna una freccia dal suo %2 a %4 di %3. - + %1 points from %2's %3 to their own %4. %1 disegna una freccia da %3 di %2 al suo %4. - + %1 points from %2's %3 to %4's %5. %1 disegna una freccia dal %3 di %2 al %5 di %4. - + %1 creates a face down token. %1 crea una pedina a faccia in giù. - + %1 creates token: %2%3. %1 crea una pedina: %2%3. - + %1 has loaded a deck (%2). %1 ha caricato un mazzo (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 ha caricato un mazzo con %2 carte nella sideboard (%3). - + %1 destroys %2. %1 distrugge %2. - + a card una carta - + %1 gives %2 control over %3. %1 da il controllo di %3 a %2. - + %1 puts %2 into play%3 face down. %1 mette %2 sul campo di battaglia %3 a faccia in giù. - + %1 puts %2 into play%3. %1 mette %2 sul campo di battaglia%3. - + %1 puts %2%3 into their graveyard. %1 mette %2 nel suo cimitero%3. - + %1 exiles %2%3. %1 esilia %2%3. - + %1 moves %2%3 to their hand. %1 mette %2 in mano%3. - + %1 puts %2%3 into their library. %1 mette %2 nel suo grimorio%3. - + %1 puts %2%3 onto the bottom of their library. %1 mette %2%3 in fondo al proprio grimorio. - + %1 puts %2%3 on top of their library. %1 mette %2 in cima al suo grimorio%3. - + %1 puts %2%3 into their library %4 cards from the top. %1 mette %2%3 nel suo grimorio %4 carte dalla cima. - + %1 moves %2%3 to sideboard. %1 mette %2 nella sideboard%3. - + %1 plays %2%3. %1 gioca %2%3. - + %1 moves %2%3 to custom zone '%4'. %1 mette %2 nella zona personalizzata '%4'%3. - + %1 tries to draw from an empty library %1 prova a pescare da un grimorio vuoto - + %1 draws %2 card(s). %1 pesca una carta.%1 pesca %2 carte.%1 pesca %2 carte. - + %1 is looking at %2. %1 sta guardando %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 sta guardando %3 carta %4 %2.%1 sta guardando %3 carte %4 %2.%1 sta guardando %3 carte %4 %2. - + bottom in fondo - + top in cima - + %1 turns %2 face-down. %1 gira %2 a faccia in giù. - + %1 turns %2 face-up. %1 gira %2 a faccia in su. - + The game has been closed. La partita è stata chiusa. - + The game has started. La partita è iniziata. - + You are flooding the game. Please wait a couple of seconds. Stai spammando la partita. Attendi un paio di secondi. - + %1 has joined the game. %1 è entrato nella partita. - + %1 is now watching the game. %1 sta osservando la partita. - + You have been kicked out of the game. Sei stato cacciato dalla partita. - + %1 has left the game (%2). %1 ha abbandonato la partita (%2). - + %1 is not watching the game any more (%2). %1 non sta più guardando la partita (%2). - + %1 is not ready to start the game any more. %1 non è più pronto a iniziare la partita. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 mischia il proprio mazzo e pesca una nuova mano di %2 carta.%1 mischia il proprio mazzo e pesca una nuova mano di %2 carte.%1 mischia il proprio mazzo e pesca una nuova mano di %2 carte. - + %1 shuffles their deck and draws a new hand. %1 mischia il proprio mazzo e pesca una nuova mano. - + You are watching a replay of game #%1. Stai guardando il replay della partita #%1. - + %1 is ready to start the game. %1 è pronto a iniziare la partita. - + cards an unknown amount of cards carte - + %1 card(s) a card for singular, %1 cards for plural una carta%1 carte%1 carte - + %1 lends %2 to %3. %1 presta %2 a %3. - + %1 reveals %2 to %3. %1 rivela %2 a %3. - + %1 reveals %2. %1 rivela %2. - + %1 randomly reveals %2%3 to %4. %1 rivela a caso %2%3 a %4. - + %1 randomly reveals %2%3. %1 rivela a caso %2%3. - + %1 peeks at face down card #%2. %1 sbircia la carta a faccia in giù #%2. - + %1 peeks at face down card #%2: %3. %1 sbircia la carta a faccia in giù #%2: %3. - + %1 reveals %2%3 to %4. %1 rivela %2%3 a %4. - + %1 reveals %2%3. %1 rivela %2%3. - + %1 reversed turn order, now it's %2. %1 ha rovesciato l'ordine dei turni, ora è %2. - + reversed invertito - + normal normale - + Heads Testa - + Tails Croce - + %1 flipped a coin. It landed as %2. %1 ha lanciato una moneta. Il risultato è %2. - + %1 rolls a %2 with a %3-sided die. %1 lancia un dado a %3 facce e ottiene %2. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 lancia %2 monete. Sono uscite %3 teste e %4 croci. - + %1 rolls a %2-sided dice %3 times: %4. %1 lancia un dado a %2 facce %3 volte: %4. - + %1's turn. Turno di %1 - + %1 sets annotation of %2 to %3. %1 imposta le note di %2 a %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). %1 mette %2 segnalino "%3" su %4 (totale %5).%1 mette %2 segnalini "%3" su %4 (totale %5).%1 mette %2 segnalino(i) "%3" su %4 (totale %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). %1 toglie %2 segnalino "%3" su %4 (totale %5).%1 toglie %2 segnalini "%3" su %4 (totale %5).%1 toglie %2 segnalino(i) "%3" su %4 (totale %5). - + %1 sets counter %2 to %3 (%4%5). %1 imposta il contatore %2 a %3 (%4%5). - + %1 sets %2 to not untap normally. %1 imposta che %2 non STAPpi normalmente. - + %1 sets %2 to untap normally. %1 imposta che %2 STAPpi normalmente. - + %1 removes the PT of %2. %1 elimina i valori F/C di %2. - + %1 changes the PT of %2 from nothing to %4. %1 cambia F/C di %2 da vuota a %4. - + %1 changes the PT of %2 from %3 to %4. %1 cambia F/C di %2 da %3 a %4. - + %1 has locked their sideboard. %1 ha bloccato la sua sideboard. - + %1 has unlocked their sideboard. %1 ha sbloccato la sua sideboard. - + %1 taps their permanents. %1 TAPpa i suoi permanenti. - + %1 untaps their permanents. %1 STAPpa i suoi permanenti. - + %1 taps %2. %1 TAPpa %2. - + %1 untaps %2. %1 STAPpa %2. - + %1 shuffles %2. %1 mescola %2. - + %1 shuffles the bottom %3 cards of %2. %1 mescola le %3 carte sul fondo da %2. - + %1 shuffles the top %3 cards of %2. %1 mescola le prime %3 carte da %2. - + %1 shuffles cards %3 - %4 of %2. %1 mescola le carte %3 - %4 da %2. - + %1 unattaches %2. %1 toglie %2. - + %1 undoes their last draw. %1 annulla la sua ultima pescata. - + %1 undoes their last draw (%2). %1 annulla la sua ultima pescata (%2). @@ -5700,110 +6274,110 @@ Il database delle carte verrà ricaricato. MessagesSettingsPage - + Word1 Word2 Word3 Parola1 Parola2 Parola3 - + Add New Message Aggiungi Nuovo Messaggio - + Edit Message Modifica Messaggio - + Remove Message Rimuovi Messaggio - + Add message Aggiungi messaggio - - + + Message: Messaggio: - + Edit message Modifica messaggio - + Chat settings Impostazioni chat - + Custom alert words Lista parole evidenziate - + Enable chat mentions Abilita menzioni in chat - + Enable mention completer Abilita completamento menzioni - + In-game message macros Messaggi rapidi in partita - + How to use in-game message macros Come usare i messaggi macro in gioco - + Ignore chat room messages sent by unregistered users Ignora i messaggi in chat inviati dagli utenti non registrati - + Ignore private messages sent by unregistered users Ignora i messaggi privati inviati dagli utenti non registrati - - + + Invert text color Inverti colore testo - + Enable desktop notifications for private messages Abilita notifiche desktop per i messaggi privati - + Enable desktop notification for mentions Abilita notifiche sul desktop per le menzioni - + Enable room message history on join Abilita messaggi recenti all'ingresso - - + + (Color is hexadecimal) (Colore in esadecimale) - + Separate words with a space, alphanumeric characters only Separare le parole con uno spazio; solo caratteri alfanumerici @@ -5920,62 +6494,62 @@ Il database delle carte verrà ricaricato. Phase - + Unknown Phase Fase sconosciuta - + Untap STAP - + Upkeep Mantenimento - + Draw Acquisizione - + First Main Prima fase principale - + Beginning of Combat Inizio combattimento - + Declare Attackers Dichiarazione attaccanti - + Declare Blockers Dichiarazione bloccanti - + Combat Damage Danno da combattimento - + End of Combat Fine combattimento - + Second Main Seconda fase principale - + End/Cleanup Finale / Cancellazione @@ -6041,7 +6615,7 @@ Il database delle carte verrà ricaricato. PictureLoader - + en code for scryfall's language property, not available for all languages it @@ -6050,134 +6624,134 @@ Il database delle carte verrà ricaricato. PlayerActions - + View top cards of library Guarda le carte in cima al grimorio - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) Numero di carte: (max %1) - + View bottom cards of library Guarda le carte in fondo al grimorio - + Shuffle top cards of library Mescola le carte in cima al grimorio - + Shuffle bottom cards of library Mescola le carte in fondo al grimorio - + Draw hand Pesca mano - + 0 and lower are in comparison to current hand size 0 e inferiore sono relativi al numero attuale di carte in mano - + Draw cards Pesca carte - + Move top cards to grave Metti le prime carte nel cimitero - + Move top cards to exile Esilia le prime carte - + Move bottom cards to grave Metti le ultime care nel cimitero - + Move bottom cards to exile Esilia le ultime carte - + Draw bottom cards Pesca le ultime carte - - + + C&reate another %1 token C&rea un'altra pedina %1 - + Create tokens Crea pedine - - + + Number: Numero: - + Place card X cards from top of library Metti la carta in posizione X dalla cima del grimorio - + Which position should this card be placed: In che posizione dovrebbe essere messa questa carta: - + (max. %1) (max %1) - + Change power/toughness Cambia forza/costituzione - + Change stats to: Cambia valori a: - + Set annotation Imposta note - + Please enter the new annotation: Inserisci le nuove note: - + Set counters Imposta i segnalini @@ -6185,17 +6759,17 @@ Il database delle carte verrà ricaricato. PlayerMenu - + Player "%1" Giocatore "%1" - + &Counters &Contatori - + S&ay Invi&a @@ -6203,7 +6777,7 @@ Il database delle carte verrà ricaricato. PrintingSelector - + Display Navigation Buttons Visualizza pulsanti di navigazione @@ -6211,22 +6785,22 @@ Il database delle carte verrà ricaricato. PrintingSelectorCardOverlayWidget - + Preference Preferenza - + Pin Printing Fissa stampa in cima alla lista - + Unpin Printing Rimuovi stampa dalla cima della lista - + Show Related cards Mostra carte correlate @@ -6242,17 +6816,17 @@ Il database delle carte verrà ricaricato. PrintingSelectorCardSelectionWidget - + Previous Card in Deck Carta precedente nel mazzo - + Bulk Selection Selezione massiva - + Next Card in Deck Carta successiva nel mazzo @@ -6260,28 +6834,28 @@ Il database delle carte verrà ricaricato. PrintingSelectorCardSortingWidget - + Alphabetical Alfabetico - + Preference Preferenza - + Release Date Data di rilascio - - + + Descending Discendente - + Ascending Ascendente @@ -6347,37 +6921,37 @@ Il database delle carte verrà ricaricato. QMenuBar - + Services Servizi - + Hide %1 Nascondi %1 - + Hide Others Nascondi altre - + Show All Mostra tutte - + Preferences... Preferenze... - + Quit %1 Esci da %1 - + About %1 Informazioni su %1 @@ -6385,42 +6959,42 @@ Il database delle carte verrà ricaricato. QObject - + Cockatrice card database (*.xml) Database delle carte di Cockatrice (*.xml) - + All files (*.*) Tutti i file (*.*) - + Cockatrice replays (*.cor) Replay di Cockatrice (*.cor) - + Maindeck Maindeck - + Sideboard Sideboard - + Tokens Token - + Overwrite Existing File? Sovrascrivere il file esistente? - + A .cod version of this deck already exists. Overwrite it? Una versione .cod di questo mazzo esiste già. Sovrascriverla? @@ -6428,92 +7002,92 @@ Il database delle carte verrà ricaricato. QPlatformTheme - + OK Conferma - + Save Salva - + Save All Salva tutto - + Open Apri - + &Yes &Sì - + Yes to &All Sì a &tutto - + &No &No - + N&o to All N&o a tutto - + Abort Interrompi - + Retry Riprova - + Ignore Ignora - + Close Chiudi - + Cancel Annulla - + Discard Abbandona - + Help Aiuto - + Apply Applica - + Reset Resetta - + Restore Defaults Ripristina predefiniti @@ -6610,37 +7184,37 @@ Il database delle carte verrà ricaricato. RoomSelector - + Rooms Stanze - + Joi&n E&ntra - + Room Stanza - + Description Descrizione - + Permissions Permessi - + Players Giocatori - + Games Partite @@ -6681,27 +7255,27 @@ Il database delle carte verrà ricaricato. SetsModel - + Enabled Abilitato - + Set type Tipo set - + Set code Codice set - + Long name Nome esteso - + Release date Data di rilascio @@ -6709,53 +7283,53 @@ Il database delle carte verrà ricaricato. ShortcutSettingsPage - - + + Restore all default shortcuts Ripristina scorciatoie predefinite - + Do you really want to restore all default shortcuts? Sei scuro di voler ripristinare tutte le scorciatoie predefinite? - + Clear all default shortcuts Rimuovi tutte le scorciatoie predefinite - + Do you really want to clear all shortcuts? Sei sicuro di voler rimuovere tutte le scorciatoie? - + Section: Sezione: - + Action: Azione: - + Shortcut: Scorciatoia: - + How to set custom shortcuts Come impostare scorciatoie personalizzate - + Clear all shortcuts Elimina tutte le scorciatoie - + Search by shortcut name Cerca per nome della scorciatoia @@ -6763,12 +7337,12 @@ Il database delle carte verrà ricaricato. ShortcutTreeView - + Action Azione - + Shortcut Scorciatoia @@ -6776,14 +7350,14 @@ Il database delle carte verrà ricaricato. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! Il file di configurazione conteneva scorciatoie non valide. Controlla le impostazioni! - + The following shortcuts have been set to default: Le seguenti scorciatoie sono state reimpostate al valore predefinito: @@ -6811,12 +7385,12 @@ Controlla le impostazioni! SideboardMenu - + &Sideboard &Sideboard - + &View sideboard &Guarda la sideboard @@ -6824,27 +7398,27 @@ Controlla le impostazioni! SoundSettingsPage - + Enable &sounds Abilita &suoni - + Current sounds theme: Tema sonoro attuale: - + Test system sound engine Prova il funzionamento dei suoni - + Sound settings Impostazioni suoni - + Master volume Volume @@ -6852,48 +7426,48 @@ Controlla le impostazioni! SpoilerBackgroundUpdater - + Spoilers season has ended La stagione degli spoiler è finita - + Deleting spoiler.xml. Please run Oracle Cancellando spoiler.xml. Per favore avviare Oracle - - + + Spoilers download failed Download degli spoiler fallito - + No internet connection Connessione internet assente - + Error Errore - + Spoilers already up to date Gli spoiler sono aggiornati - + No new spoilers added Nessun nuovo spoiler aggiunto - + Spoilers have been updated! Gli spoiler sono stati aggiornati! - + Last change: Ultima modifica: @@ -7057,56 +7631,123 @@ Controlla le impostazioni! Impossibile attivare utente. Errore interno + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info Info carta - + Deck Mazzo - + Filters Filtri - + &View &Visualizza - + + Card Database + + + + Printing Stampa - - - - + + + + + Visible Mostra - - - - + + + + + Floating Separata - + Reset layout Reimposta disposizione - + Deck: %1 Mazzo: %1 @@ -7114,61 +7755,61 @@ Controlla le impostazioni! TabDeckEditorVisual - + Visual Deck: %1 Mazzo visuale: %1 - + &Visual Deck Editor Editor &visuale - - + + Card Info Info carta - - + + Deck Mazzo - - + + Filters Filtri - + &View &Visualizza - + Printing Stampa - - - - + + + + Visible Mostra - - - - + + + + Floating Separata - + Reset layout Reimposta disposizione @@ -7176,22 +7817,22 @@ Controlla le impostazioni! TabDeckEditorVisualTabWidget - + Visual Deck View Galleria mazzo - + Visual Database Display Galleria database - + Deck Analytics Analisi mazzo - + Sample Hand Mano di esempio @@ -7199,133 +7840,133 @@ Controlla le impostazioni! TabDeckStorage - + Local file system File locali - + Server deck storage Mazzi su server - - + + Open in deck editor Apri nell'editor dei mazzi - + Rename deck or folder Rinomina mazzo o cartella - + Upload deck Upload mazzo - + Download deck Scarica mazzo + - - - + + New folder Nuova cartella + - Delete Elimina - + Open decks folder Apri cartella mazzi - + Rename local folder Rinomina cartella locale - + Rename local file Rinomina file locale - + New name: Nuovo nome: - - - - + + + + Error Errore - + Rename failed Rinomina non riuscita - - + + Invalid deck file File mazzo non valido - + Enter deck name Inserisci il nome del mazzo - + This decklist does not have a name. Please enter a name: Questo mazzo non ha un nome. Per favore inserisci un nome: - + Unnamed deck Mazzo senza nome - + Failed to upload deck to server Impossibile caricare il mazzo sul server - + Delete local file Elimina file locali - + Are you sure you want to delete the selected files? Vuoi davvero eliminare i file selezionati? - + Delete remote decks Elimina i deck remoti - + Are you sure you want to delete the selected decks? Vuoi davvero eliminare i mazzi selezionati? - - + + Name of new folder: Nome della nuova cartella: @@ -7338,17 +7979,17 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage Galleria mazzi - + Error Errore - + Could not open deck at %1 Impossibile aprire il mazzo a %1 @@ -7389,7 +8030,7 @@ Please enter a name: Cerca - + EDHRec: EDHREC: @@ -7397,197 +8038,197 @@ Please enter a name: TabGame - - - + + + Replay Replay - - + + Game Partita - - + + Player List Giocatori - - + + Card Info Info carta - - + + Messages Messaggi - - + + Replay Timeline Replay - + &Phases &Fasi - + &Game &Partita - + Next &phase Prossima &fase - + Next phase with &action Prossima sottofase + &azione - + Next &turn Prossimo &turno - + Reverse turn order Inverti l'ordine dei turni - + &Remove all local arrows &Rimuovi tutte le frecce - + Rotate View Cl&ockwise Ruota vista in senso &orario - + Rotate View Co&unterclockwise R&uota vista in senso antiorario - + Game &information &Informazioni partita - + Un&concede Rientra in gioco - - - + + + &Concede &Concedi - + &Leave game &Lascia partita - + C&lose replay C&hiudi replay - + &Focus Chat Vai alla &chat - + &Say: &Parla: - + Selected cards Carte selezionate - + &View &Visualizza - - + + + - Visible Mostra - - + + + - Floating Separata - + Reset layout Reimposta disposizione - + Concede Concedi - + Are you sure you want to concede this game? Vuoi veramente concedere la partita? - + Unconcede Annulla concedi - + You have already conceded. Do you want to return to this game? Hai già concesso. Vuoi ritornare in questa partita? - + Leave game Lascia la partita - + Are you sure you want to leave this game? Sei sicuro di voler lasciare la partita? - + A player has joined game #%1 Un giocatore si è unito alla partita #%1 - + %1 has joined the game %1 si è unito alla partita - + You have been kicked out of the game. Sei stato kickato fuori dalla partita. @@ -7595,7 +8236,7 @@ Please enter a name: TabHome - + Home Home @@ -7603,158 +8244,158 @@ Please enter a name: TabLog - + Logs Registri - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Ora;NomeMittente;IPMittente;Messaggio;IDDestinatario;NomeDestinatario - + Room Logs Registri stanze - + Game Logs Registri partite - + Chat Logs Registri chat - - + + Error Errore - + You must select at least one filter. Seleziona almeno un filtro. - + You have to select a valid number of days to locate. Seleziona un numero di giorni valido. - + Username: Nome utente: - + IP Address: Indirizzo IP: - + Game Name: Nome partita: - + GameID: ID Partita: - + Message: Messaggio: - + Main Room Stanza principale - + Game Room Stanza partita - + Private Chat Chat privata - + Past X Days: Ultimi X giorni: - + Today Oggi - + Last Hour Ultima ora - + Maximum Results: Massimo numero di risultati: - + At least one filter is required. The more information you put in, the more specific your results will be. E' richiesto perlomeno un filtro. Più informazioni inserisci, più specifici saranno i risultati. - + Get User Logs Ottieni log utente - + Clear Filters Elimina filtri - + Filters Filtri - + Log Locations Posizioni registro - + Date Range Periodo - + Maximum Results Massimo numero di risultati - - + + Message History Storico messaggi - + Failed to collect message history information. Impossibile recuperare la storia dei ban. - + There are no messages for the selected filters. Nessun messaggio corrisponde ai filtri selezionati. @@ -7800,181 +8441,181 @@ Più informazioni inserisci, più specifici saranno i risultati. TabReplays - + Local file system File locali - + Server replay storage Replay sul server - - + + Watch replay Guarda replay - + Rename Rinomina - - + + New folder Nuova cartella - - + + Delete Elimina - + Open replays folder Apri cartella replay - + Download replay Scarica replay - + Toggle expiration lock Metti/togli blocco scadenza - + Get replay share code Ottieni codice di condivisione replay - - + + Look up replay by share code Cerca replay dal codice di condivisione - + Rename local folder Rinomina cartella locale - + Rename local file Rinomina file locale - + New name: Nuovo nome: - + Error Errore - + Rename failed Rinomina non riuscita - + Name of new folder: Nome della nuova cartella: - + Delete local file Elimina il file locale - + Are you sure you want to delete the selected files? Vuoi davvero eliminare i file selezionati? - + Are you sure you want to delete the selected replays? Vuoi davvero eliminare i replay selezionati? - + Failed to get code Impossibile ottenere il codice - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. O questo server non supporta la condivisione dei replay, o non consente la condivisione dei replay per te. - - - + + + Failed Fallito - + Could not get replay code Impossibile ottenere il codice replay - + Replay Share Code Codice di condivisione replay - + Others can use this code to add the replay to their list of remote replays: %1 Altre persone possono usare questo codice per aggiungere il replay alla loro lista di replay remoti: %1 - + Copy to clipboard Copia negli appunti - + Replay share code Codice di condivisione replay - + Replay code found Codice replay trovato - + Replay was added, or you already had access to it. Il replay è stato aggiunto oppure ne avevi già accesso. - + Replay code not found Codice replay non trovato - + Failed to submit code Impossibile inviare il codice - + Unexpected error Errore inatteso - + Delete remote replay Elimina replay remoto @@ -8040,30 +8681,30 @@ Più informazioni inserisci, più specifici saranno i risultati. Server + - Error Errore - + Failed to join the server room: it doesn't exist on the server. Impossibile unirsi alla stanza: non esiste sul server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. Il server pensa che tu sia nella stanza ma il tuo client non è in grado di mostrarlo. Prova a riavviare il client. - + You do not have the required permission to join this server room. Non hai i requisiti necessari per unirti a questa stanza. - + Failed to join the server room due to an unknown error: %1. Impossibile unirsi alla stanza a causa di un errore sconosciuto: %1. @@ -8071,92 +8712,97 @@ Più informazioni inserisci, più specifici saranno i risultati. TabSupervisor - + Deck Editor &Editor dei mazzi - + Visual Deck Editor Editor visuale - + EDHRec EDHRec - + + Archidekt + + + + Home Home - + &Visual Deck Storage &Galleria mazzi - + Visual Database Display Galleria database - + Server Server - + Account Account - + Deck Storage &Archivio mazzi - + Game Replays &Replay partite - + Administration Amministrazione - + Logs Registri - + Are you sure? Sei sicuro? - + There are still open games. Are you sure you want to quit? Ci sono ancora delle partite aperte. Sei sicuro di voler uscire? - + Click to view Clicca per visualizzare - + Your buddy %1 has signed on! Il tuo amico %1 si è collegato - + Unknown Event Evento sconosciuto. - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8167,39 +8813,39 @@ Questo messaggio può significare che è disponibile una nuove versione di Cocka Per aggiornare il tuo client, vai su Aiuto -> Controlla aggiornamenti client - + Idle Timeout Timeout inattività - + You are about to be logged out due to inactivity. Stai per essere disconnesso per inattività. - + Promotion Promozione - + You have been promoted. Please log out and back in for changes to take effect. Sei stato promosso. Esci e rientra per dare effetto alle modifiche. - + Warned Avviso - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Hai ricevuto un avviso a causa di %1. Se pregato di evitare di continuare questa attività o potrebbero venire presi ulteriori provvedimenti nel tuoi confronti. Per qualsiasi domanda, manda un messaggio ad un moderatore. - + You have received the following message from the server. (custom messages like these could be untranslated) Hai ricevuto il seguente messaggio dal server. @@ -8209,7 +8855,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display Galleria database @@ -8291,7 +8942,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u UpdateDownloader - + Could not open the file for reading. Impossibile aprire il file in lettura. @@ -8299,206 +8950,206 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u UserContextMenu - + User &details &Dettagli utente - + Private &chat &Chat privata - + Show this user's &games Visualizza le &partite dell'utente - + Add to &buddy list Aggiungi alla lista &amici - + Remove from &buddy list Rimuovi dalla lista &amici - + Add to &ignore list Aggiungi alla lista &ignorati - + Remove from &ignore list Rimuovi dalla lista &ignorati - + Kick from &game Caccia dalla &partita - + Warn user Avvisa utente - + View user's war&n history Mostra storia avvisi ute&nte - + Ban from &server Banna dal &server - + View user's &ban history Mostra storia &ban utente - + &Promote user to moderator &Promuovi utente a moderatore - + Dem&ote user from moderator Degrada il m&oderatore ad utente - + Promote user to &judge Promuovi utente a &giudice - + Demote user from judge Degrada utente da giudice - + View admin notes Visualizza le note degli amministratori - - - + + + Error Errore - + This user does not exist. L'utente non esiste. - + You are being ignored by %1 and can't see their games. Stai venendo ignorato da %1 e non puoi vedere le sue partite. - + Could not get %1's games. Impossibile ottenere le partite di %1. - + %1's games Partite di %1 - - - + + + Ban History Storico ban - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Ora Ban;Moderatore;Durata Ban;Ragione Ban;Ragione Visibile - + User has never been banned. L'utente non è mai stato bannato. - + Failed to collect ban information. Impossibile recuperare le informazioni sui ban. - - - + + + Warning History Storico avvisi - + Warning Time;Moderator;User Name;Reason Ora Avviso;Moderatore;Nome Utente;Ragione - + User has never been warned. L'utente non ha mai ricevuto avvisi. - + Failed to collect warning information. Impossibile recuperare le informazioni sugli avvisi. - + Failed to get admin notes. Impossibile ottenere le note degli amministratori - - + + Success Successo - + Successfully promoted user. Utente promosso. - + Successfully demoted user. Utente degradato. - + + - Failed Fallito - + Failed to promote user. Promozione fallita. - + Failed to demote user. Degradazione fallita. - + Copy hash to clipboard Copia hash negli appunti - + Remove this user's messages Rimuovi i messaggi di questo utente @@ -8680,137 +9331,142 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u UserInterfaceSettingsPage - + General interface settings Impostazioni generali di interfaccia - + &Double-click cards to play them (instead of single-click) &Doppio click sulle carte per giocarle (anzichè un solo click) - + &Clicking plays all selected cards (instead of just the clicked card) &Cliccare gioca tutte le carte selezionate (anziché solo quella cliccata) - + &Play all nonlands onto the stack (not the battlefield) by default Gioca tutte le carte non terra nella &pila (invece che sul campo di battaglia) - + + Do not delete &arrows inside of subphases + + + + Close card view window when last card is removed Chiudi la finestra di visualizzazione carte quando l'ultima carta viene rimossa - + Auto focus search bar when card view window is opened Passa alla barra di ricerca quando si apre la finestra di visualizzazione carta - + Annotate card text on tokens Annota il testo della carta sulle pedine - + Use tear-off menus, allowing right click menus to persist on screen Usa menù a strappo, permettendo ai menù del tasto destro del mouse di rimanere sullo schermo - + Notifications settings Impostazioni notifiche - + Enable notifications in taskbar Abilita notifiche nella barra delle applicazioni - + Notify in the taskbar for game events while you are spectating Notifica anche per le partite in cui si è solo uno spettatore - + Notify in the taskbar when users in your buddy list connect Notifca quando utenti nella tua lista amici si connettono - + Animation settings Impostazioni delle animazioni - + &Tap/untap animation Animazioni &TAPpa/STAPpa - + Deck editor/storage settings Impostazioni editor/archivio mazzi - + Open deck in new tab by default Apri il mazzo in una nuova scheda automaticamente - + Use visual deck storage in game lobby Usa galleria mazzi nella lobby - + Use selection animation for Visual Deck Storage Mostra animazione al passaggio del mouse nella galleria mazzi - + When adding a tag in the visual deck storage to a .txt deck: Quando aggiungi un'etichetta ad un mazzo in formato .txt nella Galleria mazzi: - + do nothing non fare nulla - + ask to convert to .cod chiedi se convertire in file .cod - + always convert to .cod converti sempre in file .cod - + Default deck editor type Editor dei mazzi predefinito - + Classic Deck Editor Editor classico - + Visual Deck Editor Editor visuale - + Replay settings Impostazioni di riproduzione - + Buffer time for backwards skip via shortcut: Tempo di attesa per saltare indietro dopo aver premuto la scorciatoia @@ -8818,22 +9474,22 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u UserListWidget - + Users connected to server: %1 Utenti connessi al server: %1 - + Users in this room: %1 Utenti in questa stanza: %1 - + Buddies online: %1 / %2 Amici online: %1 / %2 - + Ignored users online: %1 / %2 Utenti ignorati online: %1 / %2 @@ -8841,32 +9497,32 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u UtilityMenu - + Increment all card counters Aumenta tutti i segnalini della carta - + &Untap all permanents &STAPpa tutti i permanenti - + R&oll die... L&ancia un dado... - + &Create token... &Crea una pedina... - + C&reate another token C&rea un'altra pedina - + Cr&eate predefined token Cr&ea pedina predefinita @@ -8874,22 +9530,22 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match Modalità: Corrispondenza esatta - + Mode: Includes Modalità: Include - + Mode: Include/Exclude Modalità: Include/Esclude - + Filter mode (AND/OR/NOT conjunctions of filters) Modalità di filtraggio (utilizzabile con connettivi logici AND/OR/NOT) @@ -8897,21 +9553,49 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter Salva filtro - + Save all currently applied filters to a file Salva su file i filtri correnti - + Enter filename... Inserisci il nome del file... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8938,27 +9622,27 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDatabaseDisplayNameFilterWidget - - Filter by name... - Filtra per nome... + + Filter by name... (Exact match) + - + Load from Deck Carica da mazzo - + Apply all card names in currently loaded deck as exact match name filters Usa i nomi delle carte nel mazzo caricato come insieme di filtri a corrispondenza esatta - + Load from Clipboard Carica dagli appunti - + Apply all card names in clipboard as exact match name filters Usa i nomi delle carte negli appunti come insieme di filtri a corrispondenza esatta @@ -9022,50 +9706,115 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDatabaseDisplayWidget - + Search by card name (or search expressions) Cerca per nome della carta (o espressioni di ricerca) - + + + Visual + + + + Loading database ... Caricamento database... - + Clear all filters Elimina tutti i filtri - + + Sort by: + + + + + Filter by: + + + + Save and load filters Salva e carica filtri - + Filter by exact card name Filtra per nome della carta esatto - + Filter by card sub-type Filtra per sottotipo - + Filter by set Filtra per set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand Pesca una nuova mano - + Sample hand size Dimensione della mano @@ -9073,46 +9822,25 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDeckEditorWidget - - Click and drag to change the sort order within the groups - Clicca e trascina per modificare i criteri di ordinamento all'interno dei gruppi + + Type a card name here for suggestions from the database... + - + Quick search and add card Cerca e aggiungi carta al mazzo - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter Cerca per la corrispondenza più prossima nel database (con auto-completamento) e premendo Invio, aggiungi la stampa preferita della carta al mazzo. - - - Configure how cards are sorted within their groups - Configura l'ordinamento delle carte all'interno dei gruppi - - - - - Overlap Layout - Disposizione sovrapposta - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - Imposta come le carte vengono mostrate all'interno delle relative sezioni (sovrapposte o affiancate) - - - - Flat Layout - Disposizione affiancata - VisualDeckStorageFolderDisplayWidget - + Deck Storage Archivio mazzi @@ -9168,7 +9896,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDeckStorageSearchWidget - + Search by filename (or search expression) Cerca per nome del file (o espressioni di ricerca) @@ -9176,22 +9904,22 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) Ordina alfabeticamente (Nome del mazzo) - + Sort Alphabetically (Filename) Ordina alfabeticamente (Nome del file) - + Sort by Last Modified Ordina per ultima modifica - + Sort by Last Loaded Ordina per ultimo caricato @@ -9199,17 +9927,17 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDeckStorageWidget - + Loading database ... Caricamento database... - + Refresh loaded files Aggiorna i file caricati - + Visual Deck Storage Settings Impostazioni galleria mazzi @@ -9217,43 +9945,43 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u WarningDialog - + Which warning would you like to send? Che avviso vorresti inviare? - + Redact all messages from this user in all rooms Ottieni tutti i messaggi di questo utente in tutte le stanze - + &OK &OK - + &Cancel &Annulla - + Warn user for misconduct Avvisa l'utente per cattivo comportamento - - + + Error Errore - + User name to send a warning to can not be blank, please specify a user to warn. Il nome utente a cui inviare l'avviso non può essere vuoto. - + Warning to use can not be blank, please select a valid warning to send. L'avviso da inviare non può essere vuoto. @@ -9261,133 +9989,133 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u WndSets - + Move selected set to the top Muovi set selezionato in cima - + Move selected set up Muovi set selezionato su - + Move selected set down Muovi set selezionato giù - + Move selected set to the bottom Muovi set selezionato in fondo - + Search by set name, code, or type Cerca per nome, codice o tipo del set - + Default order Ordine predefinito - + Restore original art priority order Ripristina ordine originale priorità immagini - + Enable all sets Abilita tutti i set - + Disable all sets Disabilita tutti i set - + Enable selected set(s) Abilita i set selezionati - + Disable selected set(s) Disabilita i set selezionati - + Deck Editor Editor dei mazzi - + Use CTRL+A to select all sets in the view. Usa CTRL+A per selezionare le espansioni nella visuale. - + Only cards in enabled sets will appear in the card list of the deck editor. Solo carte in espansioni abilitate appariranno nella lista carte dell'editor di mazzo. - + Image priority is decided in the following order: La priorità di immagine è decisa nell'ordine seguente: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki innanzitutto la cartella CUSTOM (%1), poi le Espansioni abilitate in questo dialogo (da cima a fondo) - + Include cards rebalanced for Alchemy [requires restart] Includi carte ribilanciate per Alchemy [richiede riavvio] - + Card Art Immagine carta - + How to use custom card art Come usare immagini personalizzate per le carte - + Hints Suggerimenti - + Note Note - + Sorting by column allows you to find a set while not changing set priority. L'ordinamento per colonne ti permette di trovare un set senza cambiare il loro ordine di priorità. - + To enable ordering again, click the column header until this message disappears. Per riabilitare l'ordinamento, clicca sull'intestazione della colonna finché questo messaggio non scompare. - + Use the current sorting as the set priority instead Usa l'ordinamento corrente per impostare la priorità dei set - + Sorts the set priority using the same column Imposta priorità dei set usando l'ordinamento per colonna - + Manage sets Organizza set @@ -9395,72 +10123,72 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u ZoneViewWidget - + Search by card name (or search expressions) Cerca per nome della carta (o espressioni di ricerca) - + Ungrouped Non raggruppare - + Group by Type Raggruppa per tipo - + Group by Mana Value Raggruppa per costo - + Group by Color Raggruppa per colore - + Unsorted Non ordinato - + Sort by Name Ordina per nome - + Sort by Type Ordina per tipo - + Sort by Mana Cost Ordina per costo - + Sort by Colors Ordina per colori - + Sort by P/T Ordina per F/C - + Sort by Set Ordina per Set - + shuffle when closing Mescola alla chiusura - + pile view Raggruppa per tipo @@ -9468,7 +10196,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u i18n - + English Italiano (Italian) @@ -9476,12 +10204,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u main - + Connect on startup Connetti all'avvio - + Debug to file Debug su file @@ -9489,1005 +10217,1041 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u shortcutsTab - + Main Window Finestra principale - - + + Deck Editor Editor dei mazzi - + Game Lobby Lobby della partita - + Card Counters Segnalini della carta - + Player Counters Contatori del giocatore - + Power and Toughness Forza e costituzione - + Game Phases Fasi di gioco - + Playing Area Area di gioco - + Move Selected Card Muovi la carta selezionata - + View Guarda - + Move Top Card Muovi la prima carta - + Move Bottom Card Muovi l'ultima carta - + Gameplay Partita - + Drawing Pescare - + Chat Room Chat Room - + Game Window Finestra di gioco - + Load Deck from Clipboard Carica mazzo dagli appunti - - + + Replays Replay - + Tabs Schede - + Check for Card Updates... Controlla Aggiornamenti Carte... - + Connect... Connetti... - + Disconnect Disconnetti - + Exit Esci - + Full screen Schermo Intero - + Register... Registrati... - + Settings... Impostazioni... - + Start a Local Game... Inizia una Partita in Locale... - + Watch Replay... Guarda Replay... - + Analyze Deck (deckstats.net) Analizza mazzo (deckstats.net) - + Analyze Deck (tappedout.net) Analizza mazzo (tappedout.net) - + Clear All Filters Elimina tutti i filtri - + Clear Selected Filter Elimina il filtro selezionato - + Close Chiudi - + Remove Card Rimuovi carta - + Manage Sets... Gestisci Espansioni... - + Edit Custom Tokens... Modifica pedine personalizzate - + Export Deck (decklist.org) Esporta mazzo (decklist.org) - + Export Deck (decklist.xyz) Esporta mazzo (decklist.xyz) - + Add Card Aggiungi Carta - + Load Deck... Carica Mazzo... - - + + Load Deck from Clipboard... Carica il Mazzo dagli Appunti... - + Edit Deck in Clipboard, Annotated Modifica Mazzo negli Appunti con Annotazioni - + Edit Deck in Clipboard Modifica Mazzo negli Appunti - + New Deck Nuovo Mazzo - + Open Custom Pictures Folder Apri Cartella Immagini Personalizzate - + Print Deck... Stampa Mazzo... - + Delete Card Elimina carta - - + + Reset Layout Reimposta Disposizione - + Save Deck Salva Mazzo - + Save Deck as... Salva Mazzo con Nome... - + Save Deck to Clipboard, Annotated Salva Mazzo in Appunti, Annotato - + Save Deck to Clipboard, Annotated (No Set Info) Salva Mazzo negli Appunti con Annotazioni (Senza Info Set) - + Save Deck to Clipboard Salva Mazzo in Appunti - + Save Deck to Clipboard (No Set Info) Salva Mazzo negli Appunti (Senza Info Set) - + Load Local Deck... Carica Mazzo Locale... - + Load Remote Deck... Carica Mazzo da Remoto... - + Set Ready to Start Imposta Pronto a Iniziare - + Toggle Sideboard Lock Attiva Blocco della Sideboard - + Add Green Counter Aumenta Segnalino Verde - + Remove Green Counter Riduci Segnalino Verde - + Set Green Counters... Imposta Segnalino Verde... - + Add Red Counter Aumenta Segnalino Rosso - + Remove Red Counter Riduci Segnalino Rosso - + Set Red Counters... Imposta Segnalino Rosso... - + Add Life Counter Aumenta Contatore Vita - + Show Status Bar Mostra barra di stato - + Unload Deck Deseleziona mazzo - + Force Start Forza avvio - + Add Card Counter (F) Aggiungi Segnalino (F) - + Remove Card Counter (F) Togli Segnalino (F) - + Set Card Counters (F)... Imposta Segnalini (F)... - + Add Card Counter (E) Aggiungi Segnalino (E) - + Remove Card Counter (E) Togli Segnalino (E) - + Set Card Counters (E)... Imposta Segnalini (E)... - + Add Card Counter(D) Aggiungi Segnalino(D) - + Remove Card Counter (D) Togli Segnalino (D) - + Set Card Counters (D)... Imposta Segnalini (D)... - + Add Card Counter (C) Aggiungi Segnalino (C) - + Remove Card Counter (C) Togli Segnalino (C) - + Set Card Counters (C)... Imposta Segnalini (C)... - + Add Card Counter (B) Aggiungi Segnalino (B) - + Remove Card Counter (B) Togli Segnalino (B) - + Set Card Counters (B)... Imposta Segnalini (B)... - + Add Card Counter (A) Aggiungi Segnalino (A) - + Remove Card Counter (A) Togli Segnalino (A) - + Set Card Counters (A)... Imposta Segnalini (A)... - + Remove Life Counter Riduci Contatore Vita - + Set Life Counters... Imposta Contatore Vita... - + Add White Counter Aumenta Contatore Bianco - + Remove White Counter Riduci Contatore Bianco - + Set White Counters... Imposta Contatore Bianco... - + Add Blue Counter Aumenta Contatore Blu - + Remove Blue Counter Riduci Contatore Blu - + Set Blue Counters... Imposta Contatore Blu... - + Add Black Counter Aumenta Contatore Nero - + Remove Black Counter Riduci Contatore Nero - + Set Black Counters... Imposta Contatore Nero... - + Add Colorless Counter Aumenta Contatore Incolore - + Remove Colorless Counter Riduci Contatore Incolore - + Set Colorless Counters... Imposta Contatore Incolore... - + Add Other Counter Aumenta Contatore Altro - + Remove Other Counter Riduci Contatore Altro - + Set Other Counters... Imposta Contatore Altro... - + Increment all card counters Aumenta tutti i segnalini della carta - + Add Power (+1/+0) Aumenta Forza (+1/+0) - + Remove Power (-1/-0) Diminuisci Forza (-1/-0) - + Move Toughness to Power (+1/-1) Sposta Costituzione a Forza (+1/-1) - + Add Toughness (+0/+1) Aumenta Costituzione (+0/+1) - + Remove Toughness (-0/-1) Riduci Costituzione (-0/-1) - + Move Power to Toughness (-1/+1) Sposta Forza a Costituzione (-1/+1) - + Add Power and Toughness (+1/+1) Aumenta Forza e Costituzione (+1/+1) - + Remove Power and Toughness (-1/-1) Diminuisci Forza e Costituzione (-1/-1) - + Set Power and Toughness... Imposta Forza e Costituzione... - + Reset Power and Toughness Resetta Forza e Costituzione - + Untap STAP - + Upkeep Mantenimento - + Draw Acquisizione - + First Main Phase Prima Fase Principale - + Start Combat Inizio Combattimento - + Attack Dichiarazione attaccanti - + Block Dichiarazione bloccanti - + Damage Danno da combattimento - + End Combat Fine Combattimento - + Second Main Phase Seconda Fase Principale - + End Finale - + Next Phase Fase Successiva - + Next Phase Action Azione della Fase Successiva - + Next Turn Turno Successivo - + Hide Card in Reveal Window Nascondi Carta nella Finestra di Visualizzazione - + Tap / Untap Card TAPpa / STAPpa carta - + Untap All STAPpa Tutto - + Toggle Untap Abilita STAPpa - + Turn Card Over Gira Carta - + Peek Card Sbircia Carta - + Play Card Gioca Carta - + Attach Card... Assegna Carta... - + Unattach Card Togli Carta - + Clone Card Clona Carta - + Create Token... Crea Pedina... - + Create All Related Tokens Crea Tutte le Pedine Correlate - + Create Another Token Crea un'Altra Pedina - + Set Annotation... Imposta Note... - + Select All Cards in Zone Seleziona Tutte le Carte nella Zona - + Select All Cards in Row Seleziona Tutte le Carte nella Riga - + Select All Cards in Column Seleziona Tutte le Carte nella Colonna - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library Fondo al Grimorio - - - - + + + + Exile Esilio - - - - + + + + Graveyard Cimitero - - + + + Hand Mano - - + + Top of Library Cima al Grimorio - - - + + + Battlefield, Face Down Campo di Battaglia, faccia in giù - + Battlefield Campo di Battaglia - - Sort Hand - Ordina mano - - - + Library Grimorio - + Sideboard Sideboard - + Top Cards of Library Carte in Cima al Grimorio - + Bottom Cards of Library Carte in Fondo al Grimorio - + Close Recent View Chiudi Viste di Recente - - + + Stack Pila - - + + Graveyard (Multiple) Cimitero (multiplo) - - + + Exile (Multiple) Esilia (multiplo) - + Stack Until Found Impila Fino a Trovare - + Draw Bottom Card Pesca Carta in Fondo - + Draw Multiple Cards from Bottom... Pesca Multiple Carte in Fondo - + Draw Arrow... Disegna Freccia... - + Remove Local Arrows Rimuovi Frecce Locali - + Leave Game Abbandona Partita - + Concede Concedi - + Roll Dice... Tira un Dado... - + Shuffle Library Mescola il Grimorio - + Shuffle Top Cards of Library Mescola Carte in Cima al Grimorio - + Shuffle Bottom Cards of Library Mescola Carte in Fondo al Grimorio - + Mulligan Mulligan - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card Pesca una Carta - + Draw Multiple Cards... Pesca più Carte... - + Undo Draw Annulla Pescata - + Always Reveal Top Card Rivela Sempre la Carta in Cima - + Always Look At Top Card Guarda Sempre la Carta in Cima - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise Ruota Vista in Senso Orario - + Rotate View Counterclockwise Ruota Vista in Senso Antiorario - + Unfocus Text Box Togli Focus dalla Casella di Testo - + Focus Chat Vai alla chat - + Clear Chat Cancella chat - + Refresh Aggiorna - + Skip Forward Salta Avanti - + Skip Backward Salta Indietro - + Skip Forward by a lot Salta Avanti di Molto - + Skip Backward by a lot Salta Indietro di Molto - + Play/Pause Riproduci/Pausa - + Toggle Fast Forward Attiva/Disattiva Avanzamento Rapido - + Home Home - + Visual Deck Storage Galleria mazzi - + Deck Storage Archivio mazzi - + Server Server - + Account Account - + Administration Amministrazione - + Logs Registri diff --git a/cockatrice/translations/cockatrice_ja.ts b/cockatrice/translations/cockatrice_ja.ts index c1e57ac23..d10718a44 100644 --- a/cockatrice/translations/cockatrice_ja.ts +++ b/cockatrice/translations/cockatrice_ja.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... (&S)カウンターの数を決める... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter カウンターの設定 - + New value for counter '%1': '%1'カウンターの新しい値: @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh (&R)更新 - + Parse Set Name and Number (if available) 可能ならセット名と番号を解析する @@ -36,80 +36,82 @@ AbstractTabDeckEditor - + Open in new tab 新しいタブで開く - + Are you sure? よろしいですか? - + The decklist has been modified. Do you want to save the changes? デッキリストが変更されています。 変更を保存しますか? - - - - - - - + + + + + + Error エラー - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. + + Add Analytics Panel AdminNotesDialog - + Update Notes リリースノート - + Admin Notes for %1 @@ -117,12 +119,12 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard メインボード - + Sideboard サイドボード @@ -130,22 +132,22 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error エラー - + Could not create themes directory at '%1'. '%1' にテーマディレクトリを作成できませんでした。 - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -156,7 +158,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -167,152 +169,165 @@ Are you sure you would like to disable this feature? - + Confirm Change - + Theme settings テーマ設定 - + Current theme: 現在のテーマ - + Open themes folder テーマフォルダを開く - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings メニュー設定 - + Show keyboard shortcuts in right-click menus 右クリックメニューにキーボードショートカットを表示する - + + Show game filter toolbar above list in room tab + + + + Card rendering カードの描画 - + Display card names on cards having a picture 画像のあるカードにカード名を表示 - + Auto-Rotate cards with sideways layout 横向きレイアウトのカードを自動回転 - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector デッキに含まれるカードを印刷セレクターの一番上に上げる - + Scale cards on mouse over マウスオーバーでカードを拡大 - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand スタックと手札垂直配置時の重なり率の最小値 - + Maximum initial height for card view window: カードビューウィンドウの初期高さの最大値 - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout 手札のレイアウト - + Display hand horizontally (wastes space) 手札を横に並べる - + Enable left justification 左揃えを有効 - + Table grid layout テーブルのレイアウト - + Invert vertical coordinate カード配置の垂直反転 - + Minimum player count for multi-column layout: プレイヤーを複数列レイアウトにする最少人数: - + Maximum font size for information displayed on cards: カードに表示する情報の最大のフォントサイズ: + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -334,112 +349,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name ユーザー名でBAN - + ban &IP address IPアドレスでBAN - + ban client I&D クライアントIDでBAN - + Ban type BANタイプ - + &permanent ban 永久BAN - + &temporary ban 一時BAN - + &Days: - + &Hours: 時間 - + &Minutes: - + Duration of the ban BAN期間 - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. BANの理由を入力してください。 これはモデレーターによって保存されBANされた人には見えません。 - + Please enter the reason for the ban that will be visible to the banned person. BANの理由を入力してください。これはBANされる人に通知されます。 - + Redact all messages from this user in all rooms すべてのルームでこのユーザーからのすべてのメッセージを編集します - + &OK OK - + &Cancel キャンセル - + Ban user from server サーバーからBANされたユーザーです - + + - - + Error エラー - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. BANの理由を3 つの組み合わせを選択する必要があります:不適切な名前、不適切なIPアドレス、クライアントID - + You must have a value in the name ban when selecting the name ban checkbox. 名前BANチェックボックスを選択するときは、名前BANの値を持っている必要があります。 - + You must have a value in the ip ban when selecting the ip ban checkbox. IP BANチェックボックスを選択するときは、IP BANの値を持っている必要があります。 - + You must have a value in the clientid ban when selecting the clientid ban checkbox. クライアントID BANチェックボックスを選択するときは、クライアントID BANに値を持っている必要があります。 @@ -470,32 +485,32 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name カード名 - + Sets カードセット - + Mana cost マナ・コスト - + Card type カード・タイプ - + P/T P/T - + Color(s) @@ -503,96 +518,101 @@ This is only saved for moderators and cannot be seen by the banned person. CardFilter - + AND Logical conjunction operator used in card filter AND - + OR Logical disjunction operator used in card filter OR - + AND NOT Negated logical conjunction operator used in card filter AND NOT - + OR NOT Negated logical disjunction operator used in card filter OR NOT - + Name カード名 - + + Name (Exact) + + + + Type タイプ - + Color - + Text ルール文章 - + Set カードセット - + Mana Cost マナ・コスト - + Mana Value マナ総量 - + Rarity 稀少度 - + Power パワー - + Toughness タフネス - + Loyalty 忠誠度 - + Format フォーマット - + Main Type - + Sub Type @@ -600,22 +620,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoFrameWidget - + Image カード画像 - + Description カード文章 - + Both 画像+文章 - + View transformation 別面を見る @@ -623,22 +643,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards 関連カードを見る - + Add card to deck デッキにカードを追加 - + Mainboard メインボード - + Sideboard サイドボード @@ -651,12 +671,22 @@ This is only saved for moderators and cannot be seen by the banned person.カード名: - + + Set: + + + + + Collector Number: + + + + Related cards: 関連カード: - + Unknown card: 謎のカード: @@ -664,124 +694,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -789,7 +819,7 @@ This is only saved for moderators and cannot be seen by the banned person. CardSizeWidget - + Card Size カードの大きさ @@ -797,133 +827,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative - + %1's hand nominative - - - their library - look at zone - - - - - %1's library - look at zone - - - - - of their library - top cards of zone, - - - - - of %1's library - top cards of zone - - their library - reveal zone + look at zone %1's library + look at zone + + + + + of their library + top cards of zone, + + + + + of %1's library + top cards of zone + + + + + their library reveal zone - + + %1's library + reveal zone + + + + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - - - their graveyard - nominative - - - - - %1's graveyard - nominative - - - - - their exile - nominative - - - - - %1's exile - nominative - - + their graveyard + nominative + + + + + %1's graveyard + nominative + + + + + their exile + nominative + + + + + %1's exile + nominative + + + + their sideboard look at zone - + %1's sideboard look at zone - - - their sideboard - nominative - - - - - %1's sideboard - nominative - - - their custom zone '%1' + their sideboard nominative + %1's sideboard + nominative + + + + + their custom zone '%1' + nominative + + + + %1's custom zone '%2' nominative @@ -932,7 +962,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml3Parser - + Parse error at line %1 col %2: @@ -940,7 +970,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml4Parser - + Parse error at line %1 col %2: @@ -959,6 +989,37 @@ This is only saved for moderators and cannot be seen by the banned person. + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -970,47 +1031,47 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add to Deck - + Add to Sideboard - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1018,87 +1079,97 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing - + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + + Format: + + + + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1106,17 +1177,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1124,114 +1195,114 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1239,7 +1310,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1247,194 +1318,227 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers スポイラーを更新 - - + + Success 完了 - + Download URLs have been reset. ダウンロードURLをリセットしました。 - + Downloaded card pictures have been reset. ダウンロードしたカード画像を削除しました。 - + Error エラー - + One or more downloaded card pictures could not be cleared. 一部のカード画像が削除できませんでした。 - + Add URL URLを追加 - - + + URL: URL: - - + + Edit URL URLを編集 - + Network Cache Size: ネットワークキャッシュサイズ: - + Redirect Cache TTL: リダイレクトキャッシュの有効期限: - + How long cached redirects for urls are valid for. キャッシュされたURLの転送の有効期間。 - + Picture Cache Size: 画像キャッシュサイズ: - + Add New URL 新しいURLを追加 - + Remove URL URLを削除 - + Day(s) - + Updating... 更新中... - + Choose path パスを選ぶ - + URL Download Priority ダウンロードURL優先設定 - + Spoilers スポイラー - + Download Spoilers Automatically スポイラーを自動的にダウンロード - + Spoiler Location: スポイラーの保存場所: - + Last Change 最新に更新 - + Spoilers download automatically on launch スポイラーは起動時に自動的にダウンロードされます。 - + Press the button to manually update without relaunching ボタンを押すと再起動せずに手動で更新します。 - + Do not close settings until manual update is complete 手動更新が完了するまでこのウィンドウを閉じないでください。 - + Download card pictures on the fly カード画像を自動的にダウンロード(英語) - + How to add a custom URL URLの追加方法について(英語) - + Delete Downloaded Images ダウンロードした画像を削除 - + Reset Download URLs ダウンロードURLをデフォルトに戻す - + On-disk cache for downloaded pictures ダウンロードした画像のディスク上キャッシュ - + In-memory cache for pictures not currently on screen 現在画面に表示されていない画像のメモリ内キャッシュ + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count 枚数 - + Set セット - + Number 枚数 - + Provider ID プロバイダーID - + Card カード名 @@ -1442,12 +1546,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1455,17 +1559,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match 検索モード: 完全一致 - + Mode: Includes 検索モード: 一部に含む - + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1473,7 +1577,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... タグを編集... @@ -1481,62 +1585,62 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewTagDialog - + Deck Tags Manager デッキタグマネージャー - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: デッキタグの管理ができます。新たなタグを追加することもできます: - + Add a new tag (e.g., Aggro️) 新しいタグを追加する(例: イニシアチブ) - + Add Tag タグを追加 - + Filter tags... タグをフィルタ... - + OK OK - + Edit default tags - + Cancel キャンセル - + Invalid Input 無効な入力 - + Tag name cannot be empty! タグ名を入力してください! - + Duplicate Tag タグの重複 - + This tag already exists. このタグはすでに存在します。 @@ -1549,93 +1653,151 @@ This is only saved for moderators and cannot be seen by the banned person. - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1653,74 +1815,74 @@ This is only saved for moderators and cannot be seen by the banned person. DeckViewContainer - + Load deck... デッキを開く... - + Load remote deck... サーバーのデッキを開く... - + Load from clipboard... - + Load from website... - + Unload deck デッキを撤去 - + Ready to start 準備完了!( ・`д・´) - + Force start 強制開始 - + Sideboard unlocked サイドボード使用可能 - + Sideboard locked サイドボードロック中 - - + + Error エラー - + The selected file could not be loaded. 選択したファイルが開けませんでした。 - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -1755,143 +1917,143 @@ This will kick all non-ready players from the game. ダウンロード中... - + Known Hosts ホストを選択 - + Delete the currently selected saved server 選択中のサーバーを削除 - + Refresh the server list with known public servers デフォルトの公開サーバーリストに更新します - + New Host 新しいホスト - + Name: サーバー名: - + &Host: ホストアドレス: - + &Port: ポート: - + Player &name: プレイヤーネーム: - + P&assword: パスワード: - + &Save password パスワードを保存 - + A&uto connect 起動時に自動的に接続 - + Automatically connect to the most recent login when Cockatrice opens Cockatriceを開いた時に自動的に最後のログインに接続 - + If you have any trouble connecting or registering then contact the server staff for help! 接続や登録に問題がある場合は、サーバー管理者にお問い合わせください。 - - + + Webpage ウェブページ - + Reset Password パスワードをリセット - + Forgot password? パスワード忘れた - + &Connect 接続 - + Server サーバー - + Login ログイン - + Server Contact サーバーの連絡先 - + Connect to Server サーバーに接続 - + Server URL サーバーURL - + Communication Port 通信ポート - + Unique Server Name ユニークサーバー名 - + Connection Warning 接続の警告 - + You need to name your new connection profile. 新しい接続プロファイルに名前を付ける必要があります。 - + Connect Warning 接続の警告 - + The player name can't be empty. プレイヤー名を入力して下さい。 @@ -1899,117 +2061,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings 設定を保存する - + &Description: 説明: - + P&layers: プレイヤー人数: - + General 全般 - + Game type ゲームタイプ - + &Password: パスワード: - + Only &buddies can join フレンドのみ - + Only &registered users can join 登録プレイヤーのみ - + Joining restrictions 参加制限 - + &Spectators can watch 観戦を許可 - + Spectators &need a password to watch 観戦にパスワードが必要 - + Spectators can &chat 観戦者はチャットに参加できる - + Spectators can see &hands 観戦者に手札を見せる - + Create game as spectator 観戦者としてゲームを作成 - + Spectators 観戦者 - + Starting life total: 開始時のライフ: - + Open decklists in lobby - + + Create game as judge + + + + Game setup options ゲーム準備オプション - + &Clear 設定をクリア - + Create game ゲームを作成 - + Game information ゲーム情報 - + Error エラー - + Server error. サーバーエラー。 @@ -2017,97 +2184,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: トークン名: - + Token トークン - + C&olor: 色: - + white - + blue - + black - + red - + green - + multicolor 多色 - + colorless 無色 - + &P/T: P/T: - + &Annotation: 注釈: - + &Destroy token when it leaves the table 戦場を離れる際に破棄 - + Create face-down (Only hides name) - + Token data トークン設定 - + Show &all tokens すべてのトークンを見る - + Show tokens from this &deck このデッキのトークンを見る - + Choose token from list 一覧からトークンを作成 - + Create token トークンを作成 @@ -2115,53 +2282,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2169,40 +2336,40 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. 画像がありません。 - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. アバターを変更するには、新しい画像を選択してください。 現在のアバターを削除するには、新しい画像を選ばずにOKを押してください。 - + Browse... 参照... - + Change avatar アバター変更 - + Open Image アバター画像を開く - + Image Files (*.png *.jpg *.bmp) 画像ファイル (*.png *.jpg *.bmp) - + Invalid image chosen. この画像は使用できません。 @@ -2210,17 +2377,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2228,38 +2395,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: 前のパスワード: - + New password: 新しいパスワード: - + Confirm new password: 新しいパスワード(確認): - + Change password パスワード変更 - - + + Error エラー - + Your password is too short. パスワードが短すぎます。 - + The new passwords don't match. 新しいパスワードが一致しませんでした。 @@ -2267,93 +2434,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: トークン名: - + C&olor: 色: - + white - + blue - + black - + red - + green - + multicolor 多色 - + colorless 無色 - + &P/T: P/T: - + &Annotation: 注釈: - + Token data トークンの設定 - - + + Add token トークンを追加 - + Remove token トークンを削除 - + Edit custom tokens カスタムトークンを編集 - + Please enter the name of the token: トークン名を入力: - + Error エラー - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. 選択された名前は、既存のカードやトークンと競合しています。 @@ -2447,7 +2614,8 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games not created by buddy + Hide games not created by buddies + Hide games not created by buddy @@ -2534,52 +2702,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning パスワードリセットの警告 - + A problem has occurred. Please try to request a new password again. 問題が発生しました。新しいパスワードのリクエストを再試行してください。 - + Enter the information of the server and the account you'd like to request a new password for. 新しいパスワードをリクエストしたいサーバーの情報とアカウントを入力してください。 - + &Host: ホストアドレス: - + &Port: ポート: - + Player &name: プレイヤー名: - + Email: メールアドレス: - + Reset Password Challenge パスワードリセットチャレンジ - + Reset Password Challenge Error パスワードリセットエラー - + The email address can't be empty. メールアドレスを入力してください。 @@ -2587,37 +2755,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. 新しいパスワードをリクエストするサーバーの情報を入力してください。 - + &Host: ホストアドレス: - + &Port: ポート: - + Player &name: プレイヤー名: - + Reset Password Request パスワードリセット要求 - + Reset Password Error パスワードリセットエラー - + The player name can't be empty. プレイヤー名を入力して下さい。 @@ -2625,86 +2793,86 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning パスワードリセットの警告 - + A problem has occurred. Please try to request a new password again. 問題が発生しました。新しいパスワードのリクエストを再試行してください。 - + Enter the received token and the new password in order to set your new password. 新しいパスワードを設定するには、受け取ったトークンと新しいパスワードを入力します。 - + &Host: ホストアドレス: - + &Port: ポート: - + Player &name: プレイヤー名: - + Token: トークン: - - + + New Password: 新しいパスワード: - + Reset Password パスワードリセット - + The player name can't be empty. プレイヤー名を入力して下さい。 - - - - + + + + Reset Password Error パスワードリセットエラー - + The token can't be empty. トークンを入力してください。 - + The new password can't be empty. 新しいパスワードを入力してください。 - + Error エラー - + Your password is too short. パスワードが短すぎます。 - + The passwords do not match. パスワードが一致しませんでした。 @@ -2720,17 +2888,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard クリップボードからデッキを開く - + Error エラー - + Invalid deck list. 無効なデッキリストです。 @@ -2738,43 +2906,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2788,7 +2956,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck デッキを開く @@ -2796,37 +2964,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): カード名または検索条件: - + Number of hits: 枚数 - + Auto play hits 自動的にプレイ - + Put top cards on stack until... 見つかるまで上からスタックに置く... - + No cards matching the search expression exists in the card database. Proceed anyways? 条件に一致するカードはデータベースに存在しません。続行しますか? - + Cockatrice 悪魔からの助言 - + Invalid filter 不正な条件 @@ -2834,92 +3002,92 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. あなたの情報と登録したいサーバーの情報を入力してください。 あなたのメールアドレスはアカウントの確認に使用されます。 - + &Host: ホストアドレス: - + &Port: ポート: - + Player &name: プレイヤー名: - + P&assword: パスワード: - + Password (again): パスワード (確認) : - + Email: メールアドレス: - + Email (again): メールアドレス (確認) : - + Country: 国籍: - + Undefined 指定なし - + Real name: 本名: - + Register to server サーバーに登録 - - - - + + + + Registration Warning 登録に関する警告 - + Your password is too short. パスワードが短すぎます。 - + Your passwords do not match, please try again. パスワードが一致しませんでした。もう一度入力して下さい。 - + Your email addresses do not match, please try again. メールアドレスが一致しませんでした。もう一度入力して下さい。 - + The player name can't be empty. プレイヤー名を入力して下さい。 @@ -2945,40 +3113,55 @@ Your email will be used to verify your account. DlgSelectSetForCards - + Unmodified Cards: - + Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. セットを有効にするには、チェックを入れてください。ドラッグ&ドロップで順序を変更し、優先順位を変更できます。カードは、最も優先度の高い有効なセットの印刷を使用します。 - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database カードデータベース読み込みの不明なエラー。 - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -2995,7 +3178,7 @@ Oracle Importerでデータベースを更新する必要があります。 データベースの場所の設定を変更してみてください。 - + Your card database version is too old. This can cause problems loading card information or images @@ -3012,7 +3195,7 @@ Oracle Importerでデータベースを更新する必要があります。 データベースの場所の設定を変更してみてください。 - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3025,7 +3208,7 @@ Would you like to change your database location setting? データベースの場所の設定を変更しますか? - + File Error loading your card database. Would you like to change your database location setting? @@ -3034,7 +3217,7 @@ Would you like to change your database location setting? データベースの場所の設定を変更してみてください。 - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3043,7 +3226,7 @@ Would you like to change your database location setting? データベースの場所の設定を変更してみてください。 - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3056,59 +3239,59 @@ Would you like to change your database location setting? データベースの場所設定を変更しますか? - - - + + + Error エラー - + The path to your deck directory is invalid. Would you like to go back and set the correct path? あなたのデッキディレクトリへのパスは無効です。前に戻って正しいパスを設定してください。 - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? あなたのカード画像ディレクトリへのパスは無効です。前に戻って正しいパスを設定してください。 - + Settings 設定 - + General 全般 - + Appearance 外観 - + User Interface UI - + Card Sources カードダウンロード - + Chat チャット - + Sound サウンド - + Shortcuts ショートカット @@ -3116,12 +3299,12 @@ Would you like to change your database location setting? DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. @@ -3130,27 +3313,27 @@ You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3158,17 +3341,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next - + Previous - + Tip of the Day 今日のヒント @@ -3176,166 +3359,166 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel 現在のリリースチャンネル: - + Reinstall 再インストール - + Cancel Download ダウンロードをキャンセル - + Open Download Page ダウンロードページを開く - + Check for Client Updates クライアントの更新チェック - - - - + + + + Error エラー - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. CockatriceはSSLをサポートしていなかったため、更新を自動ダウンロード出来ません。 ダウンロードページから手動で更新してください。 - + Downloading update: %1 - + Checking for updates... 更新のチェック中... - + Finished checking for updates 更新のチェックが完了しました - + No Update Available 更新がありません - + Cockatrice is up to date! Cockatriceは最新版です。やったね! - + You are already running the latest version available in the chosen release channel. 選択したリリースチャンネルで最新のバージョンを使用しています。 - + Current version 現在のバージョン - + Selected release channel 選択したリリースチャンネル - - + + Update Available 更新できます!!! - - + + A new version of Cockatrice is available! 新しいバージョンのCockatriceが利用できます! - - + + New version 新しいバージョン - - + + Released 公開日時 - - + + Changelog 変更履歴 - + Do you want to update now? 今すぐ更新しますか? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. 残念ながら、自動アップデータは互換性のあるダウンロードを見つけることができませんでした。 You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + + Update Error 更新エラー - + An error occurred while checking for updates: 更新のチェック中にエラーが発生しました: - + An error occurred while downloading an update: 更新のダウンロード中にエラーが発生しました: - + Installing... インストール中... - + Cockatrice is unable to open the installer. インストーラーが開けませんでした。 - + Try to update manually by closing Cockatrice and running the installer. Cockatriceを終了した後、インストーラーから手動で更新してください。 - + Download location ダウンロード場所 @@ -3343,21 +3526,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing 閉じるときにログをクリア - + Copy to clipboard - + Debug Log デバッグログ + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3427,33 +3742,39 @@ You may have to manually download the new version. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3469,22 +3790,22 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. @@ -3492,22 +3813,22 @@ You may have to manually download the new version. GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -3515,226 +3836,279 @@ You may have to manually download the new version. GameSelector - - - - - - + + + + + + Error エラー - + Please join the appropriate room first. 適切な部屋に参加してください。 - + Wrong password. パスワードが間違っています。 - + Spectators are not allowed in this game. このゲームは観戦出来ません。 - + The game is already full. このゲームはすでに満員です。 - + The game does not exist any more. このゲームはすでに存在しません。 - + This game is only open to registered users. このゲームは登録済みプレイヤーのみ参加できます。 - + This game is only open to its creator's buddies. このゲームは作成者のフレンドのみ参加できます。 - + You are being ignored by the creator of this game. あなたはゲームの作成者によって参加拒否されています。 - + Join Game 参加 - + Spectate Game 観戦 - + Game Information ゲーム情報 - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game 参加 - + Password: パスワード: - + Please join the respective room first. 最初にルームに参加してください。 - + &Filter games ゲームフィルタ - + C&lear filter フィルタ解除 - + C&reate ゲームを作成 - + &Join 参加 - + + Join as judge + + + + J&oin as spectator 観戦 - + + Join as judge spectator + + + + Games shown: %1 / %2 表示されているゲーム: %1 / %2 - + Games ゲーム + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day >1 日 - + %1%2 hr short age in hours %1%2 時 - + new できたて - + %1%2 min short age in minutes %1%2 分 - + password パスワード - + buddies only フレンドのみ - + reg. users only 登録ユーザーのみ - + open decklists - - + + can chat チャット可 - + see hands 手札閲覧可 - + can see hands 手札閲覧可 - + not allowed 観戦不可 - + Room ルーム - + Age 作成時 - + Description 説明 - + Creator 作成者 - + Type タイプ - + Restrictions 制限 - + Players 人数 - + Spectators 観戦者 @@ -3742,143 +4116,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths すべてのパスをリセット - + All paths have been reset すべてのパスをリセットしました - - - - - - - + + + + + + + Choose path パスを選択 - + Personal settings 個人設定 - + Language: 言語: - + Paths (editing disabled in portable mode) パス(ポータブルモードでの編集は無効) - + Paths パス - + How to help with translations - + Decks directory: デッキフォルダ: - + Filters directory: - + Replays directory: リプレイフォルダ: - + Pictures directory: カード画像フォルダ: - + Card database: カードデータベース: - + Custom database directory: カスタムデータベースフォルダ: - + Token database: トークンデータベース: - + Update channel 更新チャンネル - + Check for client updates on startup 起動時にクライアントの更新をチェック - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client サーバーでサポートされている機能がクライアントに存在しない場合に通知する - + Automatically run Oracle when running a new version of Cockatrice 新バージョンのCockatriceを起動したら自動的にOracleも起動する - + Show tips on startup 起動時にヒントを表示 - + Last update check on %1 (%2 days ago) @@ -3886,47 +4260,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -3934,111 +4308,141 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - - &Sort hand + + Sort hand by... - - Take &mulligan + + Name - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - Reveal r&andom card to... + + + All players - - - &All players + + Reveal r&andom card to... HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + + Browse Archidekt + + + + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4240,61 +4644,61 @@ You may have to manually download the new version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. サーバーが満員です。 - + There are too many concurrent connections from your address. あなたのアドレスからの同時接続が多すぎます。 - + Banned by moderator モデレーターによるBAN - + Expected end time: %1 予想終了時間: %1 - + This ban lasts indefinitely. 永久BANされています。 - + Scheduled server shutdown. サーバーシャットダウン予定時間。 - - + + Invalid username. ユーザー名が無効です。 - + You have been logged out due to logging in at another location. 別の場所でログインしているため、ログアウトしました。 - + Connection closed 通信切断 - + The server has terminated your connection. Reason: %1 サーバーはあなたの接続を切断しました。 理由: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -4303,585 +4707,585 @@ Reason for shutdown: %1 シャットダウンの理由: %1 - + Scheduled server shutdown サーバーシャットダウン予定時刻 - - + + Success 成功 - + Registration accepted. Will now login. 登録が完了しました。 ログインします。 - + Account activation accepted. Will now login. アカウント有効化が完了しました。 ログインします。 - + Number of players プレイヤー人数 - + Please enter the number of players. プレイヤーの人数を入れてください. - - + + Player %1 プレイヤー %1 - + Load replay リプレイを開く - + About Cockatrice Cockatriceについて - + Version バージョン - + Cockatrice Webpage Cockatriceウェブページ - + Project Manager: プロジェクトマネージャ: - + Past Project Managers: 元プロジェクトマネージャ: - + Developers: デベロッパ: - + Our Developers リンク先参照 - + Help Develop! 開発者募集中! - + Translators: 翻訳者: - + Our Translators 翻訳者一覧 - + Help Translate! 翻訳者募集中! - + Support: サポート: - + Report an Issue 問題を報告 - + Troubleshooting トラブルシューティング - + F.A.Q. F.A.Q. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error エラー - + Server timeout サーバータイムアウト - + Failed Login ログイン失敗 - + Your client seems to be missing features this server requires for connection. あなたのクライアントは、サーバーが必要とする機能をサポートしていません。クライアントを更新して再度お試しください。 - + To update your client, go to 'Help -> Check for Client Updates'. クライアントをアップデートするには、メニューの「ヘルプ」から「クライアントの更新チェック」をクリックします。 - + Incorrect username or password. Please check your authentication information and try again. ユーザー名かパスワードが正しくありません。 - + There is already an active session using this user name. Please close that session first and re-login. 既にこのユーザー名を使用しているアクティブなセッションが存在します。 アクティブなセッションを閉じて再ログインしてください。 - - + + You are banned until %1. あなたは%1までBANされています。 - - + + You are banned indefinitely. あなたは無期限BANされています。 - + This server requires user registration. Do you want to register now? このサーバーはユーザー登録が必要です。 今すぐ登録しますか? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. このサーバはクライアントのIDを必要とします。あなたのクライアントはIDの生成に失敗しているか、変更されたクライアントを実行しています。 クライアントを再起動してもう一度お試し下さい。 - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. 内部エラーが発生しました。Cockatriceを再起動して再度お試しください。 エラーが解決しない場合は、ソフトウェアの最新バージョンを実行していることを確認し、必要に応じてソフトウェア開発者にお問い合わせください。 - + Account activation アカウント有効化 - + Your account has not been activated yet. You need to provide the activation token received in the activation email. このアカウントはまだ有効化されていません。 登録されたアドレスに送信されているアクティベーショントークンを入力して下さい。 - + Server Full サーバーが満員です。 - + Unknown login error: %1 不明なログインエラー: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. これは通常、あなたのクライアントのバージョンが古くなっていて、サーバーがあなたのクライアントを認識できないと返答してきていることを意味しています。 - + Your username must respect these rules: ユーザー名は、これらの規則を遵守する必要があります: - + is %1 - %2 characters long 文字数は%1~%2文字まで - + can %1 contain lowercase characters 小文字%1可 - - - - + + + + NOT - + can %1 contain uppercase characters 大文字%1可 - + can %1 contain numeric characters 数字%1可 - + can contain the following punctuation: %1 次の記号が使用可:%1 - + first character can %1 be a punctuation mark 最初の文字を記号にすることは%1可能 - + no unacceptable language as specified by these server rules: note that the following lines will not be translated サーバールールで定められている不適切な言葉は使用できません: - + can not contain any of the following words: %1 次の単語を含めることは不可能:%1 - + can not match any of the following expressions: %1 次の文字列を含めることは不可能:%1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. ユーザー名には『半角英数字』、および半角の「_」「.」「-」のみが使えます。 - - - - - - + + + + + + Registration denied 登録が拒否されました。 - + Registration is currently disabled on this server 現在このサーバーでは登録が無効になっています。 - + There is already an existing account with the same user name. 既に同じユーザー名のアカウントが登録されています。 - + It's mandatory to specify a valid email address when registering. 登録時に有効な電子メールアドレスを指定する必要があります。 - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. このサーバーに新しいアカウントを登録しようとしていますが、提供されたメールアドレスに登録されているアカウントが既に存在しています。このサーバーは、ユーザーがアドレスごとに登録できるアカウントの数を制限しています。詳細なサポートや資格情報の入手については、サーバーのオペレーターにお問い合わせください。 - + Password too short. パスワードが短すぎます。 - + Registration failed for a technical problem on the server. ユーザー登録に失敗しました(サーバーの技術的問題)。 - + The connection to the server has been lost. - + Unknown registration error: %1 不明な登録エラー: %1 - + Account activation failed アカウントの有効化に失敗しました。 - + Socket error: %1 ソケットエラー: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. あなたは古いバージョンのサーバーに接続しようとしています。Cockatriceのバージョンをダウングレードするか適切なサーバーに接続してください。 ローカルVer %1,サーバーVer %2 - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. あなたのCockatriceのバージョンがサーバーのバージョンより古いです。Cockatriceをアップデートしてください。 ローカルVer %1,サーバーVer %2 - + Connecting to %1... %1へ接続しています... - + Registering to %1 as %2... %1に%2として登録中... - + Disconnected 切断されました - + Connected, logging in at %1 接続完了、%1にログイン中 + - Requesting forgotten password to %1 as %2... 忘れたパスワードを %1 に %2 として要求しています... - + &Connect... サーバーに接続... - + &Disconnect サーバーから切断 - + Start &local game... ローカルゲームを開始... - + &Watch replay... リプレイを見る... - + &Full screen フルスクリーン - + &Register to server... サーバーに登録する - + &Restore password... パスワードリセット... - + &Settings... 設定... - + &Exit 終了 - + A&ctions アクション - + &Cockatrice Cockatrice - + C&ard Database カードデータベース - + &Manage sets... セットを管理... - + Edit custom &tokens... カスタムトークンを編集... - + Open custom image folder カスタム画像フォルダを開く - + Open custom sets folder カスタムセットフォルダを開く - + Add custom sets/cards カスタムセット/カードを追加 - + Reload card database カードデータベースを再読み込み - + Tabs タブ - + &Help ヘルプ - + &About Cockatrice Cockatriceについて - + &Tip of the Day 今日のヒント - + Check for Client Updates クライアントの更新チェック - + Check for Card Updates... カードデータベース更新 - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log デバッグログを見る - + Open Settings Folder 設定フォルダを開く - + Show/Hide 表示/非表示 - + New Version 新しいバージョン - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Cockatrice %1へのアップデートおめでとうございます! Oracleが起動し、カードデータベースが更新されます。 - + Cockatrice installed Cockatriceがインストールされました - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Cockatrice %1のインストールおめでとうございます! Oracleを起動して、初期カードデータベースをインストールします。 - + Card database カードデータベース - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -4890,29 +5294,29 @@ If unsure or first time user, choose "Yes" よくわからない場合、初回起動時などは「はい」を押して下さい。 - - + + Yes はい - - + + No いいえ - + Open settings 設定を開く - + New sets found 新しいセットが見つかりました - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -4921,17 +5325,17 @@ Do you want to enable it/them? 有効にしますか? - + View sets セットを見る - + Welcome ようこそ - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -4940,64 +5344,64 @@ Read more about changing the set order or disabling specific sets and consequent 「セットの設定」ウィンドウ内の特定のセットを無効にしたり、順序を変更をする方法の詳細をお読みください。 - - + + Information 情報 - + A card database update is already running. データベース更新は既に起動中です。 - + Unable to run the card database updater: データベース更新が実行できませんでした: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. プロセスへの書き込み中にエラーが発生しました。プロセスが実行されていないか、入力チャネルが閉じられている等の可能性があります。 - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5007,673 +5411,843 @@ To update your client, go to Help -> Check for Updates. クライアントを更新するには、ヘルプ→クライアントの更新のチェックをクリックしてください。 - - - - - + + + + + Load sets/cards セット/カードを読み込む - + Selected file cannot be found. 選択したファイルが見つかりませんでした。 - + You can only import XML databases at this time. ここではXMLデータベースのみをインポートできます。 - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. 新しいセット/カードが正常に追加されました。 データベースが再読込されます。 - + Sets/cards failed to import. セット/カードのインポートに失敗 - - - + + + Reset Password パスワードリセット - + Your password has been reset successfully, you can now log in using the new credentials. パスワードをリセットしました。 - + Failed to reset user account password, please contact the server operator to reset your password. パスワードをリセットできませんでした。サーバー管理者にリセットしてもらってください。 - + Activation request received, please check your email for an activation token. アクティベーションリクエストを受信しました。メールでアクティベーショントークンを確認してください。 + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play スタックから - + from their graveyard 墓地から - + from exile 追放領域から - + from their hand 手札から - + the top card of %1's library %1のライブラリーの一番上のカード - + the top card of their library ライブラリーの一番上のカード - + from the top of %1's library %1のライブラリーの一番上から - + from the top of their library ライブラリーの一番上から - + the bottom card of %1's library %1のライブラリーの一番下のカード - + the bottom card of their library ライブラリーの一番下のカード - + from the bottom of %1's library %1のライブラリーの一番下から - + from the bottom of their library ライブラリーの一番下のカード - + from %1's library %1のライブラリーから - + from their library ライブラリーから - + from sideboard サイドボードから - + from the stack スタックから - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 は%2一番上のカードを公開した状態でゲームをプレイしている。 - + %1 is not revealing the top card %2 any longer. %1 は%2一番上のカードの公開を終えた。 - + %1 can now look at top card %2 at any time. %1はいつでも%2一番上のカードを見ることができる。 - + %1 no longer can look at top card %2 at any time. %1はもう%2一番上のカードを見ることができない。 - + %1 attaches %2 to %3's %4. %1は%3の%4に%2をつけた。 - + %1 has conceded the game. %1 が投了した!!! - + %1 has unconceded the game. %1が投了を撤回した。 - + %1 has restored connection to the game. %1 がゲームに再接続した。 - + %1 has lost connection to the game. %1 はゲームから切断された。 - + %1 points from their %2 to themselves. %1 は %2 から自分自身へ対象を指定した。 - + %1 points from their %2 to %3. %1 は %2 から %3 へ対象を指定した。 - + %1 points from %2's %3 to themselves. %1 は %2 の %3 から自分自身へ対象を指定した。 - + %1 points from %2's %3 to %4. %1 は %2 の %3 から %4 へ対象を指定した。 - + %1 points from their %2 to their %3. %1 は %2 から %3 へ対象を指定した。 - + %1 points from their %2 to %3's %4. %1 は %2 から %3 の %4 へ対象を指定した。 - + %1 points from %2's %3 to their own %4. %1 は %2 の %3 から %4 へ対象を指定した。 - + %1 points from %2's %3 to %4's %5. %1 は %2 の %3 から %4 の %5 へ対象を指定した。 - + %1 creates a face down token. - + %1 creates token: %2%3. %1 はトークン: %2 を%3作成した。 - + %1 has loaded a deck (%2). %1 はデッキをロードした。ハッシュ:%2 - + %1 has loaded a deck with %2 sideboard cards (%3). %1 はデッキをロードした。サイドボード数:%2, ハッシュ:%3 - + %1 destroys %2. %1 は %2 を取り除いた。 - + a card カード - + %1 gives %2 control over %3. %1 は %2 に %3 のコントロールを渡した。 - + %1 puts %2 into play%3 face down. %1 は %2 を%3裏向きでプレイした。 - + %1 puts %2 into play%3. %1 は %2 を%3プレイした。 - + %1 puts %2%3 into their graveyard. %1 は %2 を%3墓地に置いた。 - + %1 exiles %2%3. %1 は %2 を%3追放した。 - + %1 moves %2%3 to their hand. %1は %2 を%3手札に加えた。 - + %1 puts %2%3 into their library. %1 は %2 を%3ライブラリーに加えた。 - + %1 puts %2%3 onto the bottom of their library. %1 は %2 を%3ライブラリーの一番下に置いた。 - + %1 puts %2%3 on top of their library. %1 は %2 を%3ライブラリーの一番上に置いた。 - + %1 puts %2%3 into their library %4 cards from the top. %1 は %2 を%3ライブラリーの一番上から%4枚目に置いた。 - + %1 moves %2%3 to sideboard. %1 は %2 を%3サイドボードに置いた。 - + %1 plays %2%3. %1 は %2 を%3プレイした。 - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1 は……カードがないライブラリーからカードを引こうとした! - + %1 draws %2 card(s). %1は%2枚カードを引いた。 - + %1 is looking at %2. %1 は%2を見ている。 - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 は%2%4%3枚のカードを見ている。 - + bottom 下から - + top 上から - + %1 turns %2 face-down. %1 は %2 を裏向きにした。 - + %1 turns %2 face-up. %1 は %2 を表向きにした。 - + The game has been closed. ゲームがクローズされました。 - + The game has started. ◇◇ゲーム開始!◇◇ - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 がゲームに参加した。 - + %1 is now watching the game. %1 が観戦に参加した。 - + You have been kicked out of the game. ゲームからキックされました。 - + %1 has left the game (%2). %1 はゲームから離脱した(%2)。 - + %1 is not watching the game any more (%2). %1 が観戦から離脱した(%2)。 - + %1 is not ready to start the game any more. %1 は準備完了を解除した。 - + %1 shuffles their deck and draws a new hand of %2 card(s). %1はライブラリーを切り直し、新たにカードを%2枚引いた。 - + %1 shuffles their deck and draws a new hand. %1はライブラリーを切り直し、新たに手札を引いた。 - + You are watching a replay of game #%1. ゲーム#%1のリプレイを再生しています。 - + %1 is ready to start the game. %1 はゲーム開始の準備が完了した!! - + cards an unknown amount of cards カード - + %1 card(s) a card for singular, %1 cards for plural %1枚のカード - + %1 lends %2 to %3. %1 は %3 に%2を貸した。 - + %1 reveals %2 to %3. %1 は %3 に%2を見せた。 - + %1 reveals %2. %1 は%2を公開した。 - + %1 randomly reveals %2%3 to %4. %1 は%3無作為に %2 を選び、%4に見せた。 - + %1 randomly reveals %2%3. %1 は%3無作為に %2 を選び、公開した。 - + %1 peeks at face down card #%2. %1 は裏向きのカード#%2の表面を確認した。 - + %1 peeks at face down card #%2: %3. %1 は裏向きのカード#%2の表面を確認した ( %3 ) 。 - + %1 reveals %2%3 to %4. %1 は%3 %2 を %4 に見せた。 - + %1 reveals %2%3. %1 は%3 %2 を公開した。 - + %1 reversed turn order, now it's %2. %1はターン順を逆にした(%2)。 - + reversed 逆転 - + normal 順転 - + Heads - + Tails - + %1 flipped a coin. It landed as %2. %1 はコインを投げた。結果は……【%2】だ! - + %1 rolls a %2 with a %3-sided die. %1 は%3面ダイスをふり、【%2】を出した。 - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 は%2枚のコインを投げた。表が【%3】枚、裏が【%4】枚出た。 - + %1 rolls a %2-sided dice %3 times: %4. %1 は%2面体サイコロを%3個振り、それぞれ【%4】を出した。 - + %1's turn. ■ %1 のターン■ - + %1 sets annotation of %2 to %3. %1 は %2 に注釈をつけた (%3)。 - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 は%2カウンターを%3に設定した (%4%5)。 - + %1 sets %2 to not untap normally. %1 は %2 をアンタップ・ステップの間にアンタップしないようにした。 - + %1 sets %2 to untap normally. %1 は %2 を通常どおりアンタップするように設定した。 - + %1 removes the PT of %2. %1 は %2 のP/Tを取り除いた。 - + %1 changes the PT of %2 from nothing to %4. %1 は %2 のP/Tを%4にした。 - + %1 changes the PT of %2 from %3 to %4. %1 は %2 のP/Tを%3から%4にした。 - + %1 has locked their sideboard. %1 はサイドボードをロックした。 - + %1 has unlocked their sideboard. %1 はサイドボードを解禁した。 - + %1 taps their permanents. %1 は自分のコントロールするパーマネントをタップした。 - + %1 untaps their permanents. %1 は自分のコントロールするパーマネントをアンタップした。 - + %1 taps %2. %1 は %2 をタップした。 - + %1 untaps %2. %1 は %2 をアンタップした。 - + %1 shuffles %2. %1 は%2を切り直した。 - + %1 shuffles the bottom %3 cards of %2. %1 は%2の一番下の%3枚のカードをに無作為の順番で置いた。 - + %1 shuffles the top %3 cards of %2. %1 は%2の一番上の%3枚のカードをに無作為の順番で置いた。 - + %1 shuffles cards %3 - %4 of %2. %1 は%2の %3 - %4 枚を無作為の順番で置いた。 - + %1 unattaches %2. %1 は %2 をはずした。 - + %1 undoes their last draw. %1 は最後に引いたカードを戻した。 - + %1 undoes their last draw (%2). %1 は最後に引いたカード ( %2 ) を戻した 。 @@ -5681,110 +6255,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 ここに入力してください - + Add New Message 定型文を追加 - + Edit Message 定型文を編集 - + Remove Message 定型文を削除 - + Add message 定型文を追加 - - + + Message: 定型文: - + Edit message 定型文を編集 - + Chat settings チャット設定 - + Custom alert words 反応するキーワード - + Enable chat mentions メンション機能を有効 - + Enable mention completer メンション補完を有効 - + In-game message macros 定型文 - + How to use in-game message macros ゲーム内メッセージマクロの使い方 - + Ignore chat room messages sent by unregistered users 未登録ユーザーのルームメッセージを無視 - + Ignore private messages sent by unregistered users 未登録ユーザーの個人チャットを無視 - - + + Invert text color 反転テキストの色 - + Enable desktop notifications for private messages 個人チャットのデスクトップ通知を有効 - + Enable desktop notification for mentions メンションのデスクトップ通知を有効 - + Enable room message history on join 参加時にルームのメッセージ履歴を表示 - - + + (Color is hexadecimal) (色は16進数) - + Separate words with a space, alphanumeric characters only 英数字のみ使用可能。単語を半角スペースで区切って下さい。 @@ -5901,62 +6475,62 @@ Cockatrice will now reload the card database. Phase - + Unknown Phase 謎のフェイズ - + Untap アンタップ・ステップ - + Upkeep アップキープ・ステップ - + Draw ドロー・ステップ - + First Main 戦闘前メイン・フェイズ - + Beginning of Combat 戦闘開始ステップ - + Declare Attackers 攻撃クリーチャー指定ステップ - + Declare Blockers ブロック・クリーチャー指定ステップ - + Combat Damage 戦闘ダメージ・ステップ - + End of Combat 戦闘終了ステップ - + Second Main 戦闘後メイン・フェイズ - + End/Cleanup 最終フェイズ @@ -6022,7 +6596,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages ja @@ -6031,134 +6605,134 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - + Move top cards to grave - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6166,17 +6740,17 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters - + S&ay @@ -6184,7 +6758,7 @@ Cockatrice will now reload the card database. PrintingSelector - + Display Navigation Buttons @@ -6192,22 +6766,22 @@ Cockatrice will now reload the card database. PrintingSelectorCardOverlayWidget - + Preference 優先設定 - + Pin Printing ピン留めする - + Unpin Printing ピン留めを外す - + Show Related cards 関連カードを見る @@ -6223,17 +6797,17 @@ Cockatrice will now reload the card database. PrintingSelectorCardSelectionWidget - + Previous Card in Deck デッキの前のカード - + Bulk Selection - + Next Card in Deck デッキの次のカード @@ -6241,28 +6815,28 @@ Cockatrice will now reload the card database. PrintingSelectorCardSortingWidget - + Alphabetical 名前順 - + Preference - + Release Date 提供日時順 - - + + Descending 降順 - + Ascending 昇順 @@ -6328,37 +6902,37 @@ Cockatrice will now reload the card database. QMenuBar - + Services サービス - + Hide %1 %1を隠す - + Hide Others 他を隠す - + Show All すべて表示 - + Preferences... 設定... - + Quit %1 %1から出る - + About %1 %1について @@ -6366,42 +6940,42 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) Cockatriceカードデータベース(*.xml) - + All files (*.*) 全てのファイル (*.*) - + Cockatrice replays (*.cor) Cockatriceリプレイファイル (*.cor) - + Maindeck メインデッキ - + Sideboard サイドボード - + Tokens トークン - + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? @@ -6409,92 +6983,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK OK - + Save 保存 - + Save All 全て保存 - + Open 開く - + &Yes はい - + Yes to &All すべてはい - + &No いいえ - + N&o to All すべていいえ - + Abort 中止 - + Retry 再試行 - + Ignore 無視 - + Close 閉じる - + Cancel キャンセル - + Discard カードを捨てる - + Help ヘルプ - + Apply 適用 - + Reset リセット - + Restore Defaults デフォルトを復元 @@ -6591,37 +7165,37 @@ Cockatrice will now reload the card database. RoomSelector - + Rooms ルーム - + Joi&n 参加する - + Room ルーム名 - + Description 説明 - + Permissions アクセス権限 - + Players 人数 - + Games ゲーム数 @@ -6662,27 +7236,27 @@ Cockatrice will now reload the card database. SetsModel - + Enabled 可能 - + Set type セットタイプ - + Set code セット略称 - + Long name 正式名称 - + Release date 公開日時 @@ -6690,53 +7264,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts すべてのショートカットを元に戻す - + Do you really want to restore all default shortcuts? 本当にすべてのショートカットキーを元に戻しますか? - + Clear all default shortcuts すべてのショートカットをクリア - + Do you really want to clear all shortcuts? 本当にすべてのショートカットキーをクリアしますか? - + Section: セクション: - + Action: アクション: - + Shortcut: ショートカット: - + How to set custom shortcuts ショートカットキーの設定方法について(英語) - + Clear all shortcuts すべてのショートカットをクリア - + Search by shortcut name ショートカット名で検索 @@ -6744,12 +7318,12 @@ Cockatrice will now reload the card database. ShortcutTreeView - + Action アクション - + Shortcut ショートカットキー @@ -6757,14 +7331,14 @@ Cockatrice will now reload the card database. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! あなたの設定ファイルには無効なショートカットキーが含まれています。 ショートカット設定を見直してください! - + The following shortcuts have been set to default: 次のショートカットがデフォルトに設定されています: @@ -6792,12 +7366,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -6805,27 +7379,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds サウンドを有効 - + Current sounds theme: 現在のサウンドテーマ: - + Test system sound engine サウンドテスト - + Sound settings サウンド設定 - + Master volume 音量 @@ -6833,48 +7407,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended スポイラーシーズンが終了しました - + Deleting spoiler.xml. Please run Oracle spoiler.xmlを削除します。Oracleを起動してください。 - - + + Spoilers download failed スポイラーのダウンロードに失敗しました。 - + No internet connection インターネット接続がありません(´・ω・`) - + Error エラー - + Spoilers already up to date スポイラーリストは最新です。 - + No new spoilers added 新たなスポイラーはありません。 - + Spoilers have been updated! スポイラーが更新されました!! - + Last change: 最後の変更: @@ -7038,56 +7612,123 @@ Please check your shortcut settings! ユーザーを有効化できません。内部エラー + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info カード情報 - + Deck デッキ - + Filters フィルタ - + &View レイアウト - + + Card Database + + + + Printing - - - - + + + + + Visible 表示する - - - - + + + + + Floating 別ウィンドウにする - + Reset layout レイアウトをリセット - + Deck: %1 デッキ: %1 @@ -7095,61 +7736,61 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + + + + Visible - - - - + + + + Floating - + Reset layout @@ -7157,22 +7798,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7180,134 +7821,134 @@ Please check your shortcut settings! TabDeckStorage - + Local file system ローカルのデッキ - + Server deck storage サーバーに保存されているデッキ - - + + Open in deck editor デッキエディターで開く - + Rename deck or folder - + Upload deck デッキをアップロード - + Download deck デッキをダウンロード + - - - + + New folder 新しいフォルダーを作成する + - Delete 削除 - + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + Error エラー - + Rename failed - - + + Invalid deck file 無効なデッキファイル - + Enter deck name デッキの名前を入力 - + This decklist does not have a name. Please enter a name: このデッキリストには名前がありません。 名前を入力してください: - + Unnamed deck 無名のデッキ - + Failed to upload deck to server - + Delete local file ローカルファイルの削除 - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? 選択したデッキを削除してもよろしいですか? - - + + Name of new folder: 新しいフォルダの名前: @@ -7320,17 +7961,17 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage ビジュアルデッキストレージ - + Error - + Could not open deck at %1 @@ -7371,7 +8012,7 @@ Please enter a name: - + EDHRec: @@ -7379,197 +8020,197 @@ Please enter a name: TabGame - - - + + + Replay リプレイ - - + + Game ゲーム - - + + Player List プレイヤーリスト - - + + Card Info カード情報 - - + + Messages メッセージ - - + + Replay Timeline リプレイタイムライン - + &Phases フェイズ - + &Game ゲーム - + Next &phase 次のフェイズ - + Next phase with &action ターン起因処理をしながら次のフェイズ - + Next &turn 次のターン - + Reverse turn order ゲームのターン順を逆転する - + &Remove all local arrows 全ての対象指定を消す - + Rotate View Cl&ockwise 席を時計回りに回転 - + Rotate View Co&unterclockwise 席を反時計回りに回転 - + Game &information ゲーム情報 - + Un&concede 投了を撤回 - - - + + + &Concede 投了する - + &Leave game ゲームから離脱する - + C&lose replay リプレイを閉じる - + &Focus Chat - + &Say: 発言欄: - + Selected cards - + &View レイアウト - - + + + - Visible 表示する - - + + + - Floating 別ウィンドウにする - + Reset layout レイアウトをリセット - + Concede 投了する - + Are you sure you want to concede this game? 本当にこのゲームを投了しますか? - + Unconcede 投了を取り消す - + You have already conceded. Do you want to return to this game? あなたはすでに投了しています! 本当にゲームに復帰しますか? - + Leave game ゲームから離脱する - + Are you sure you want to leave this game? 本当にこのゲームから離脱しますか? - + A player has joined game #%1 プレイヤーがゲーム#%1に参加した。 - + %1 has joined the game %1 がゲームに参加した - + You have been kicked out of the game. ゲームからキックされました。 @@ -7577,7 +8218,7 @@ Please enter a name: TabHome - + Home @@ -7585,158 +8226,158 @@ Please enter a name: TabLog - + Logs ログ - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName 時間;送信者名;送信者IP;メッセージ;対象ID;対象名 - + Room Logs ルームログ - + Game Logs ゲームログ - + Chat Logs チャットログ - - + + Error エラー - + You must select at least one filter. フィルターを選んで下さい。 - + You have to select a valid number of days to locate. 検索するには、有効な日数を選択する必要があります。 - + Username: ユーザー名: - + IP Address: IPアドレス: - + Game Name: ゲーム名: - + GameID: ゲームID: - + Message: メッセージ: - + Main Room メインルーム - + Game Room ゲームルーム - + Private Chat プライベートチャット - + Past X Days: X日経過: - + Today 今日 - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. 少なくとも一つのフィルタが必要です。 情報を入力すればするほど詳細な検索結果になります。 - + Get User Logs ユーザーログを取得 - + Clear Filters フィルタ解除 - + Filters フィルタ - + Log Locations ログの場所 - + Date Range 日付指定 - + Maximum Results - - + + Message History メッセージ履歴 - + Failed to collect message history information. メッセージ履歴情報の収集に失敗しました。 - + There are no messages for the selected filters. 選択したフィルタのためのメッセージはありません。 @@ -7782,180 +8423,180 @@ The more information you put in, the more specific your results will be. TabReplays - + Local file system ローカルのリプレイ - + Server replay storage サーバーに保存されたリプレイ - - + + Watch replay リプレイを再生 - + Rename 名前変更 - - + + New folder 新しいフォルダ - - + + Delete 削除 - + Open replays folder リプレイフォルダを開く - + Download replay リプレイをダウンロード - + Toggle expiration lock 有効期限ロックの切り替え - + Get replay share code - - + + Look up replay by share code - + Rename local folder ローカルフォルダの名前を変更 - + Rename local file ローカルファイルの名前を変更 - + New name: 新しい名前: - + Error エラー - + Rename failed 変更失敗 - + Name of new folder: 新しいフォルダの名前: - + Delete local file ローカルファイルの削除 - + Are you sure you want to delete the selected files? 選択したファイルを削除してもよろしいですか? - + Are you sure you want to delete the selected replays? 選択したリプレイを削除してもよろしいですか? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay サーバーファイルの削除 @@ -8021,30 +8662,30 @@ The more information you put in, the more specific your results will be.サーバー + - Error エラー - + Failed to join the server room: it doesn't exist on the server. サーバールームに参加できませんでした: サーバーに存在しません。 - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. サーバーからサーバールームに入室中の応答がありましたが、クライアントが表示できませんでした。クライアントを再起動してみてください。 - + You do not have the required permission to join this server room. あなたはこのサーバールームに参加するための権限がありません。 - + Failed to join the server room due to an unknown error: %1. 原因不明のエラーによってサーバールームに参加できませんでした: %1。 @@ -8052,92 +8693,97 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor デッキエディター - + Visual Deck Editor - + EDHRec - + + Archidekt + + + + Home - + &Visual Deck Storage ビジュアルデッキストレージ - + Visual Database Display - + Server サーバー - + Account アカウント - + Deck Storage デッキストレージ - + Game Replays リプレイ - + Administration 管理 - + Logs ログ - + Are you sure? よろしいですか? - + There are still open games. Are you sure you want to quit? ゲームがまだ開いています。本当に退出しますか? - + Click to view クリックで見る - + Your buddy %1 has signed on! %1がオンライン! - + Unknown Event 不明なイベント - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8148,39 +8794,39 @@ To update your client, go to Help -> Check for Updates. クライアントを更新するには、ヘルプ→更新のチェックをクリックしてください。 - + Idle Timeout 放置タイムアウト - + You are about to be logged out due to inactivity. 非アクティブのためログアウトしています。 - + Promotion 昇格 - + You have been promoted. Please log out and back in for changes to take effect. あなたは昇格されました。変更を有効にするためにログインしなおして下さい。 - + Warned 警告 - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. 以下の理由によって警告を受けました: %1。 これらのような行動はご遠慮頂ますようお願い致します。質問がある場合は、モデレーターにプライベートメッセージを送信してください。 - + You have received the following message from the server. (custom messages like these could be untranslated) サーバーから次のメッセージを受け取りました。 @@ -8190,7 +8836,12 @@ Please refrain from engaging in this activity or further actions may be taken ag TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display @@ -8272,7 +8923,7 @@ Please refrain from engaging in this activity or further actions may be taken ag UpdateDownloader - + Could not open the file for reading. ファイルが開けませんでした。 @@ -8280,206 +8931,206 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details ユーザー詳細 - + Private &chat プライベートチャット - + Show this user's &games このユーザーのゲームを表示 - + Add to &buddy list フレンドリストに追加 - + Remove from &buddy list フレンドリストから削除 - + Add to &ignore list 無視リストに追加 - + Remove from &ignore list 無視リストから削除 - + Kick from &game ゲームからキックする - + Warn user ユーザーに警告する - + View user's war&n history ユーザーの警告履歴を見る - + Ban from &server サーバーからBANする - + View user's &ban history ユーザーのBAN履歴を見る - + &Promote user to moderator ユーザーを昇格する - + Dem&ote user from moderator ユーザーを降格する - + Promote user to &judge ユーザーをジャッジに昇格する - + Demote user from judge ユーザーをジャッジから降格する - + View admin notes 管理者ノートを見る - - - + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games %1のゲーム - - - + + + Ban History BAN履歴 - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason BAN回数;モデレーター;BAN期間;BAN理由;可視理由 - + User has never been banned. ユーザーはBANされていません。 - + Failed to collect ban information. BAN情報の収集に失敗しました。 - - - + + + Warning History 警告履歴 - + Warning Time;Moderator;User Name;Reason 警告回数;モデレーター;ユーザー名;理由 - + User has never been warned. ユーザーは警告されていません。 - + Failed to collect warning information. 警告情報の収集に失敗しました。 - + Failed to get admin notes. 管理者ノートの取得に失敗しました。 - - + + Success 成功 - + Successfully promoted user. ユーザーを昇格しました。 - + Successfully demoted user. ユーザーを降格しました。 - + + - Failed 失敗 - + Failed to promote user. 昇格に失敗しました。 - + Failed to demote user. 降格に失敗しました。 - + Copy hash to clipboard クリップボードにハッシュをコピー - + Remove this user's messages このユーザーのメッセージを削除 @@ -8661,137 +9312,142 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings インターフェース全般設定 - + &Double-click cards to play them (instead of single-click) ダブルクリックでカードをプレイする (シングルクリックの代わり) - + &Clicking plays all selected cards (instead of just the clicked card) クリックしたカードだけでなく、選択したすべてのカードをプレイする。 - + &Play all nonlands onto the stack (not the battlefield) by default 土地でないカードをプレイする時、すぐに戦場に出さずにスタックに置く - + + Do not delete &arrows inside of subphases + + + + Close card view window when last card is removed 最後のカードが削除されたときにカード表示ウィンドウを閉じる - + Auto focus search bar when card view window is opened - + Annotate card text on tokens トークンのカードテキストを注釈にもつける - + Use tear-off menus, allowing right click menus to persist on screen ティアオフメニューを使用して、右クリックメニューを画面に表示したままにする - + Notifications settings 通知設定 - + Enable notifications in taskbar タスクバーの通知を有効 - + Notify in the taskbar for game events while you are spectating タスクバーに観戦中のゲームイベントを通知する - + Notify in the taskbar when users in your buddy list connect フレンドリストのユーザーが接続したときにタスクバーで通知する - + Animation settings アニメーション設定 - + &Tap/untap animation タップ/アンタップアニメーション - + Deck editor/storage settings デッキエディタ/ストレージ設定 - + Open deck in new tab by default デフォルトでデッキを新しいタブで開く - + Use visual deck storage in game lobby ゲームロビーでビジュアルデッキストレージを使用 - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - + do nothing - + ask to convert to .cod - + always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + Replay settings リプレイ設定 - + Buffer time for backwards skip via shortcut: @@ -8799,22 +9455,22 @@ Please refrain from engaging in this activity or further actions may be taken ag UserListWidget - + Users connected to server: %1 ユーザーがサーバーに接続: %1 - + Users in this room: %1 ルームのユーザー数: %1 - + Buddies online: %1 / %2 フレンドオンライン: %1 / %2 - + Ignored users online: %1 / %2 無視ユーザーオンライン: %1 / %2 @@ -8822,32 +9478,32 @@ Please refrain from engaging in this activity or further actions may be taken ag UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -8855,22 +9511,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match - + Mode: Includes - + Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -8878,21 +9534,49 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8918,28 +9602,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - Load from Deck + Filter by name... (Exact match) - Apply all card names in currently loaded deck as exact match name filters + Load from Deck - Load from Clipboard + Apply all card names in currently loaded deck as exact match name filters + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -9003,50 +9687,115 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - + + Sort by: + + + + + Filter by: + + + + Save and load filters - + Filter by exact card name - + Filter by card sub-type - + Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9054,46 +9803,25 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter データベース内で最も近いものを検索し(自動提案付き)、Enter キーを押すと、優先印刷がデッキに追加されます。 - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage デッキストレージ @@ -9149,7 +9877,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9157,22 +9885,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) デッキ名順 - + Sort Alphabetically (Filename) ファイル名順 - + Sort by Last Modified 更新日順 - + Sort by Last Loaded 最近使った順 @@ -9180,17 +9908,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... データベースをロード中... - + Refresh loaded files - + Visual Deck Storage Settings @@ -9198,43 +9926,43 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? どの警告を送信しますか? - + Redact all messages from this user in all rooms すべてのルームでこのユーザーからのすべてのメッセージを編集します - + &OK OK - + &Cancel キャンセル - + Warn user for misconduct 不正行為をユーザーに警告 - - + + Error エラー - + User name to send a warning to can not be blank, please specify a user to warn. ユーザー名は空欄に出来ません。有効なユーザー名を選択して下さい。 - + Warning to use can not be blank, please select a valid warning to send. 警告は空欄に出来ません。有効な警告を選択して下さい。 @@ -9242,133 +9970,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top 選択したセットを一番上へ - + Move selected set up 選択中のセットを上へ - + Move selected set down 選択中のセットを下へ - + Move selected set to the bottom 選択したセットを一番下へ - + Search by set name, code, or type セット名、略称、タイプで検索 - + Default order 初期順に戻す - + Restore original art priority order 元のアートの優先順位を復元する - + Enable all sets すべてのセットを有効 - + Disable all sets すべてのセットを無効 - + Enable selected set(s) 選択したセットを有効 - + Disable selected set(s) 選択したセットを無効 - + Deck Editor デッキエディター - + Use CTRL+A to select all sets in the view. Ctrl + A キーですべてのセットを選択します。 - + Only cards in enabled sets will appear in the card list of the deck editor. 有効にしたセットのカードのみがデッキエディタのカードリストに表示されます。 - + Image priority is decided in the following order: 画像の優先順位は、次の順序で決定されます: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki 最優先はCUSTOMフォルダ(%1)、以降はこのダイアログで有効化されたセットの並び順 - + Include cards rebalanced for Alchemy [requires restart] - + Card Art カードアート - + How to use custom card art カスタムカードアートの使い方 - + Hints ヒント - + Note 注意 - + Sorting by column allows you to find a set while not changing set priority. 列でソートすると、セットの優先度設定を変えずにセットを探すことができます。 - + To enable ordering again, click the column header until this message disappears. 優先度設定を行うには、このメッセージが消えるまで同じ列見出しをクリックしてください。 - + Use the current sorting as the set priority instead 現在のソートを優先度設定として使用する - + Sorts the set priority using the same column 同じ列を使用して優先度をソートする - + Manage sets セットを管理 @@ -9376,72 +10104,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped グループなし - + Group by Type タイプ別グループ - + Group by Mana Value マナ・コスト別グループ - + Group by Color 色別グループ - + Unsorted 順不同 - + Sort by Name 名前順 - + Sort by Type タイプ順 - + Sort by Mana Cost マナ・コスト順 - + Sort by Colors 色順 - + Sort by P/T P/T順 - + Sort by Set セット順 - + shuffle when closing 閉じる時に切り直す - + pile view タイプ別に重ねて表示 @@ -9449,7 +10177,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English 日本語 (Japanese) @@ -9457,12 +10185,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup 起動時に接続 - + Debug to file @@ -9470,1005 +10198,1041 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window メインウィンドウ - - + + Deck Editor デッキエディター - + Game Lobby ゲームロビー - + Card Counters - + Player Counters - + Power and Toughness パワーとタフネス - + Game Phases フェイズ - + Playing Area プレイエリア - + Move Selected Card 選択中のカードを移動 - + View 場を見る - + Move Top Card 一番上のカードを移動 - + Move Bottom Card 一番下のカードを移動 - + Gameplay ゲームプレイ - + Drawing ドロー - + Chat Room チャットルーム - + Game Window ゲームウィンドウ - + Load Deck from Clipboard クリップボードからデッキを開く - - + + Replays リプレイ - + Tabs タブ - + Check for Card Updates... カードデータベース更新... - + Connect... 接続... - + Disconnect 切断 - + Exit 終了 - + Full screen フルスクリーン - + Register... ユーザー登録... - + Settings... 設定... - + Start a Local Game... ローカルゲームを開始... - + Watch Replay... リプレイを見る... - + Analyze Deck (deckstats.net) デッキを分析 (deckstats.net) - + Analyze Deck (tappedout.net) デッキを分析 (tappedout.net) - + Clear All Filters 全フィルタ解除 - + Clear Selected Filter 選択したフィルタを解除 - + Close 閉じる - + Remove Card カードを取り除く - + Manage Sets... セットを管理... - + Edit Custom Tokens... カスタムトークンを編集... - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card カードを追加 - + Load Deck... デッキを開く... - - + + Load Deck from Clipboard... クリップボードからデッキを開く... - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck 新しいデッキ - + Open Custom Pictures Folder カスタム画像フォルダを開く - + Print Deck... デッキを印刷... - + Delete Card カードを削除 - - + + Reset Layout レイアウトをリセット - + Save Deck デッキを保存 - + Save Deck as... 名前を付けて保存... - + Save Deck to Clipboard, Annotated - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard クリップボードにデッキをコピー - + Save Deck to Clipboard (No Set Info) クリップボードにデッキをコピー(セット情報なし) - + Load Local Deck... デッキ読み込み... - + Load Remote Deck... サーバーのデッキを開く... - + Set Ready to Start 準備完了する - + Toggle Sideboard Lock サイドボードロックの切り替え - + Add Green Counter 緑カウンターを置く - + Remove Green Counter 緑カウンターを取り除く - + Set Green Counters... 緑カウンターの数を決める... - + Add Red Counter 赤カウンターを置く - + Remove Red Counter 赤カウンターを取り除く - + Set Red Counters... 赤カウンターの数を決める... - + Add Life Counter ライフを得る - + Show Status Bar - + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter ライフを失う - + Set Life Counters... ライフ総量を決める... - + Add White Counter 白カウンターを置く - + Remove White Counter 白カウンターを取り除く - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Increment all card counters - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap アンタップ - + Upkeep アップキープ - + Draw ドロー - + First Main Phase 戦闘前メイン・フェイズ - + Start Combat - + Attack 攻撃 - + Block ブロック - + Damage ダメージ - + End Combat - + Second Main Phase 戦闘後メイン・フェイズ - + End エンド - + Next Phase 次のフェイズ - + Next Phase Action 処理しながら次のフェイズ - + Next Turn 次のターン - + Hide Card in Reveal Window - + Tap / Untap Card タップ / アンタップ - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card カードをプレイ - + Attach Card... - + Unattach Card - + Clone Card カードをコピー - + Create Token... トークンを生成する... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library ライブラリーの一番下 - - - - + + + + Exile 追放領域 - - - - + + + + Graveyard 墓地 - - + + + Hand 手札 - - + + Top of Library ライブラリーの一番上 - - - + + + Battlefield, Face Down - + Battlefield 戦場 - - Sort Hand - - - - + Library 自分のライブラリー - + Sideboard サイドボード - + Top Cards of Library - + Bottom Cards of Library - + Close Recent View - - + + Stack スタック - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game ゲームから退出 - + Concede 投了 - + Roll Dice... - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan マリガン - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh 更新 - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_ko.ts b/cockatrice/translations/cockatrice_ko.ts index 5c9e37b01..15237cc62 100644 --- a/cockatrice/translations/cockatrice_ko.ts +++ b/cockatrice/translations/cockatrice_ko.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... 카운터 설정... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter 카운터 설정 - + New value for counter '%1': 카운터 '%1'의 값을 지정해 주세요: @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,79 +36,81 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. + + Add Analytics Panel AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -116,12 +118,12 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard - + Sideboard @@ -129,22 +131,22 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error 오류 - + Could not create themes directory at '%1'. - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -155,7 +157,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -166,152 +168,165 @@ Are you sure you would like to disable this feature? - + Confirm Change - + Theme settings 테마 설정 - + Current theme: 현재 테마: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + + Show game filter toolbar above list in room tab + + + + Card rendering 카드 렌더링 - + Display card names on cards having a picture 이미지가 존재하는 카드에도 카드 이름 표시 - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over 마우스 오버 시 카드 확대 - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout 손 레이아웃 - + Display hand horizontally (wastes space) 손의 카드를 가로로 정렬 - + Enable left justification 손의 카드를 좌측으로 정렬 - + Table grid layout 테이블 격자 레이아웃 - + Invert vertical coordinate 전장의 상하배치를 전환 (대지를 앞에 배치) - + Minimum player count for multi-column layout: 다열 레이아웃를 위한 최소 플레이어 인원 (4명 이상 권장) - + Maximum font size for information displayed on cards: 카드 정보에 표시할 폰트의 최대 크기: + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -333,112 +348,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name 사용자 계정 추방 - + ban &IP address IP 주소 추방 - + ban client I&D 클라이언트 ID 추방 - + Ban type 추방 유형 - + &permanent ban 영구 추방 - + &temporary ban 기간제 추방 - + &Days: 일: - + &Hours: 시간: - + &Minutes: 분: - + Duration of the ban 추방 기간 - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. 추방 사유를 적어주세요. 해당 사유는 관리자들만 볼 수 있고 추방 당한 사용자는 볼 수 없습니다. - + Please enter the reason for the ban that will be visible to the banned person. 추방된 사용자가 보게 될 추방 사유를 적어주세요. - + Redact all messages from this user in all rooms - + &OK 확인 - + &Cancel 취소 - + Ban user from server 사용자를 서버에서 추방 - + + - - + Error 오류 - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. 추방의 유형을 선택해 주세요. - + You must have a value in the name ban when selecting the name ban checkbox. 사용자명 추방을 하기 위해선 사용자명을 입력하셔야 합니다. - + You must have a value in the ip ban when selecting the ip ban checkbox. 아이피 추방을 하기 위해선 아이피 주소를 입력하셔야 합니다. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. 클라이언트 ID 추방을 하기 위해선 클라이언트 ID를 입력하셔야 합니다. @@ -469,32 +484,32 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name 카드 이름 - + Sets 확장판 - + Mana cost 마나 비용 - + Card type 카드 유형 - + P/T 공/방 - + Color(s) 색(들) @@ -502,96 +517,101 @@ This is only saved for moderators and cannot be seen by the banned person. CardFilter - + AND Logical conjunction operator used in card filter - + OR Logical disjunction operator used in card filter - + AND NOT Negated logical conjunction operator used in card filter - + OR NOT Negated logical disjunction operator used in card filter - + Name 카드 이름 - + + Name (Exact) + + + + Type - + Color - + Text - + Set - + Mana Cost 마나 비용 - + Mana Value 마나 값이 - + Rarity - + Power 공격력 - + Toughness 방어력 - + Loyalty 충성도 - + Format - + Main Type - + Sub Type @@ -599,22 +619,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoFrameWidget - + Image - + Description - + Both - + View transformation @@ -622,22 +642,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -650,12 +670,22 @@ This is only saved for moderators and cannot be seen by the banned person. - + + Set: + + + + + Collector Number: + + + + Related cards: - + Unknown card: @@ -663,124 +693,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -788,7 +818,7 @@ This is only saved for moderators and cannot be seen by the banned person. CardSizeWidget - + Card Size @@ -796,133 +826,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative - + %1's hand nominative - - - their library - look at zone - - - - - %1's library - look at zone - - - - - of their library - top cards of zone, - - - - - of %1's library - top cards of zone - - their library - reveal zone + look at zone %1's library + look at zone + + + + + of their library + top cards of zone, + + + + + of %1's library + top cards of zone + + + + + their library reveal zone - + + %1's library + reveal zone + + + + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - - - their graveyard - nominative - - - - - %1's graveyard - nominative - - - - - their exile - nominative - - - - - %1's exile - nominative - - + their graveyard + nominative + + + + + %1's graveyard + nominative + + + + + their exile + nominative + + + + + %1's exile + nominative + + + + their sideboard look at zone - + %1's sideboard look at zone - - - their sideboard - nominative - - - - - %1's sideboard - nominative - - - their custom zone '%1' + their sideboard nominative + %1's sideboard + nominative + + + + + their custom zone '%1' + nominative + + + + %1's custom zone '%2' nominative @@ -931,7 +961,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml3Parser - + Parse error at line %1 col %2: @@ -939,7 +969,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml4Parser - + Parse error at line %1 col %2: @@ -958,6 +988,37 @@ This is only saved for moderators and cannot be seen by the banned person. + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -969,47 +1030,47 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add to Deck - + Add to Sideboard - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1017,87 +1078,97 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing - + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + + Format: + + + + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1105,17 +1176,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1123,114 +1194,114 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1238,7 +1309,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1246,194 +1317,227 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers - - + + Success 성공 - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error 오류 - + One or more downloaded card pictures could not be cleared. - + Add URL URL 추가 - - + + URL: URL: - - + + Edit URL URL 수정 - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL 새 URL 추가 - + Remove URL URL 제거 - + Day(s) - + Updating... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count - + Set - + Number 카드 장수 - + Provider ID - + Card 카드 이름 @@ -1441,12 +1545,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1454,17 +1558,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1472,7 +1576,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -1480,62 +1584,62 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -1548,93 +1652,151 @@ This is only saved for moderators and cannot be seen by the banned person. - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1652,74 +1814,74 @@ This is only saved for moderators and cannot be seen by the banned person. DeckViewContainer - + Load deck... 덱 불러오기 - + Load remote deck... 원격 덱 불러오기 - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start 시작 준비 - + Force start - + Sideboard unlocked 사이드보드 카드 교체 가능 - + Sideboard locked 사이드보드 카드 교체 불가능 - - + + Error 오류 - + The selected file could not be loaded. 지정하신 파일을 불러올 수 없었습니다. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -1752,143 +1914,143 @@ This will kick all non-ready players from the game. 다운로드중... - + Known Hosts 기록한 호스트 - + Delete the currently selected saved server - + Refresh the server list with known public servers - + New Host 새로운 서버 - + Name: 호스트명: - + &Host: 호스트 IP: - + &Port: 포트: - + Player &name: 사용자명: - + P&assword: 비밀번호: - + &Save password 비밀번호 저장 - + A&uto connect 자동연결 - + Automatically connect to the most recent login when Cockatrice opens 코카트리스를 실행할 때 최근에 로그인한 서버로 연결합니다. - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + Forgot password? - + &Connect - + Server 서버 - + Login 로그인 - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning - + You need to name your new connection profile. - + Connect Warning - + The player name can't be empty. 사용자명은 비어있을 수 없습니다. @@ -1896,117 +2058,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings 설정 기억하기 - + &Description: 게임 이름: - + P&layers: 게임 참가 인원: - + General - + Game type 게임 종류 - + &Password: 비밀번호: - + Only &buddies can join 친구만 입장 가능 - + Only &registered users can join 서버에 가입한 사용자만 입장 가능 - + Joining restrictions 입장 제한 - + &Spectators can watch 관전자 허용 - + Spectators &need a password to watch 관전자 입장 시 비밀번호 필요 - + Spectators can &chat 관전자 대화 가능 - + Spectators can see &hands 관전자에게 플레이어의 손 공개 - + Create game as spectator - + Spectators 관전자 - + Starting life total: - + Open decklists in lobby - + + Create game as judge + + + + Game setup options - + &Clear 설정 초기화 - + Create game 게임 만들기 - + Game information 게임 정보 - + Error 오류 - + Server error. 서버 오류. @@ -2014,97 +2181,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: 토큰 이름: - + Token 토큰 - + C&olor: 토큰 색: - + white 백색 - + blue 청색 - + black 흑색 - + red 적색 - + green 녹색 - + multicolor 다색 - + colorless 무색 - + &P/T: 공/방 - + &Annotation: 주석: - + &Destroy token when it leaves the table 토큰이 전장을 떠나면 게임에서 제거됨 - + Create face-down (Only hides name) - + Token data 토큰 정보 - + Show &all tokens 모든 토큰 보기 - + Show tokens from this &deck 이 덱에서 사용하는 토큰 보기 - + Choose token from list 목록에서 토큰 선택 - + Create token 토큰 생성 @@ -2112,53 +2279,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2166,40 +2333,40 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. 이미지 없음 - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. 아바타를 변경하려면 새로운 이미지 파일을 선택하여 주십시오. 현재 아바타를 삭제하시려면 이미지를 선택하지 않고 확인을 하시면 됩니다. - + Browse... 찾아보기 - + Change avatar 아바타 변경 - + Open Image 이미지 불러오기 - + Image Files (*.png *.jpg *.bmp) 이미지 파일 (*.png *.jpg *.bmp) - + Invalid image chosen. 잘못된 이미지를 선택하셨습니다. @@ -2207,17 +2374,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2225,38 +2392,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: 예전 비밀번호: - + New password: 새로운 비밀번호: - + Confirm new password: 새로운 비밀번호 확인: - + Change password 비밀번호 변경 - - + + Error 오류 - + Your password is too short. - + The new passwords don't match. 새로운 비밀번호가 일치하지 않습니다. @@ -2264,93 +2431,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: 토큰 이름: - + C&olor: 토큰 색: - + white 백색 - + blue 청색 - + black 흑색 - + red 적색 - + green 녹색 - + multicolor 다색 - + colorless 무색 - + &P/T: 공/방 - + &Annotation: 주석: - + Token data 토큰 정보 - - + + Add token 토큰 추가 - + Remove token 토큰 삭제 - + Edit custom tokens - + Please enter the name of the token: 토큰의 이름을 입력해 주세요. - + Error 오류 - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. @@ -2443,7 +2610,8 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games not created by buddy + Hide games not created by buddies + Hide games not created by buddy @@ -2530,52 +2698,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: 호스트: - + &Port: 포트: - + Player &name: 사용자명: - + Email: 이메일: - + Reset Password Challenge - + Reset Password Challenge Error - + The email address can't be empty. @@ -2583,37 +2751,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: 호스트: - + &Port: 포트: - + Player &name: 사용자명: - + Reset Password Request - + Reset Password Error - + The player name can't be empty. @@ -2621,86 +2789,86 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: 호스트: - + &Port: 포트: - + Player &name: 사용자명: - + Token: 토큰: - - + + New Password: 새로운 비밀번호: - + Reset Password - + The player name can't be empty. 사용자명은 비어있을 수 없습니다. - - - - + + + + Reset Password Error - + The token can't be empty. - + The new password can't be empty. - + Error 오류 - + Your password is too short. - + The passwords do not match. 비밀번호가 일치하지 않습니다. @@ -2716,17 +2884,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard 클립보드에서 덱 가져오기 - + Error 오류 - + Invalid deck list. 클립보드의 덱 리스트를 읽어올 수 없습니다. @@ -2734,43 +2902,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2784,7 +2952,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck 덱 불러오기 @@ -2792,37 +2960,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -2830,91 +2998,91 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: 호스트 IP: - + &Port: 포트: - + Player &name: 사용자명: - + P&assword: 비밀번호: - + Password (again): 비밀번호 확인: - + Email: 이메일: - + Email (again): 이메일 확인: - + Country: 국가: - + Undefined 미지정 - + Real name: 실명: - + Register to server 서버에 가입 - - - - + + + + Registration Warning 가입 실패 - + Your password is too short. - + Your passwords do not match, please try again. 입력된 비밀번호가 서로 다릅니다. 다시 시도하여 주십시오. - + Your email addresses do not match, please try again. 입력된 이메일이 서로 다릅니다. 다시 시도하여 주십시오. - + The player name can't be empty. 사용자명은 비어있을 수 없습니다. @@ -2940,40 +3108,55 @@ Your email will be used to verify your account. DlgSelectSetForCards - + Unmodified Cards: - + Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database 데이터베이스를 불러올 때 미상의 오류가 발생하였습니다. - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -2990,7 +3173,7 @@ Would you like to change your database location setting? 데이터베이스 경로를 다시 설정하시겠습니까? - + Your card database version is too old. This can cause problems loading card information or images @@ -3007,7 +3190,7 @@ Would you like to change your database location setting? 데이터베이스 경로를 다시 설정하시겠습니까? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3016,7 +3199,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3025,7 +3208,7 @@ Would you like to change your database location setting? 데이터베이스 경로를 다시 설정하시겠습니까? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3034,7 +3217,7 @@ Would you like to change your database location setting? 데이터베이스 경로를 다시 설정하시겠습니까? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3043,61 +3226,61 @@ Would you like to change your database location setting? - - - + + + Error 오류 - + The path to your deck directory is invalid. Would you like to go back and set the correct path? 덱 파일을 보관하는 디렉토리의 경로가 잘못되었습니다. 경로를 다시 설정하시겠습니까? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? 카드 이미지 파일을 보관하는 디렉토리의 경로가 잘못되었습니다. 경로를 다시 설정하시겠습니까? - + Settings 환경설정 - + General 일반 - + Appearance 외형 - + User Interface 인터페이스 - + Card Sources - + Chat 대화 - + Sound 소리 - + Shortcuts 단축키 @@ -3105,39 +3288,39 @@ Would you like to change your database location setting? DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3145,17 +3328,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next 다음 - + Previous 이전 - + Tip of the Day @@ -3163,165 +3346,165 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel - + Reinstall 재설치 - + Cancel Download 다운로드 취소 - + Open Download Page 다운로드 페이지 열기 - + Check for Client Updates - - - - + + + + Error 오류 - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. 코카트리스가 SSL 지원 없이 컴파일되어 자동 업데이트를 할 수 없습니다. 공식 홈페이지에서 수동으로 다운 받아주세요. - + Downloading update: %1 - + Checking for updates... 업데이트 확인중... - + Finished checking for updates - + No Update Available - + Cockatrice is up to date! Cockatrice는 최신 버젼입니다! - + You are already running the latest version available in the chosen release channel. - + Current version 현재 버젼 - + Selected release channel - - + + Update Available - - + + A new version of Cockatrice is available! - - + + New version 새로운 버젼 - - + + Released - - + + Changelog 변경점 - + Do you want to update now? 업데이트 하시겠습니까? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + + Update Error 업데이트 오류 - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... 설치중... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location 다운로드 위치 @@ -3329,21 +3512,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing - + Copy to clipboard - + Debug Log 디버그 로그 + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3413,33 +3728,39 @@ You may have to manually download the new version. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3455,22 +3776,22 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. @@ -3478,22 +3799,22 @@ You may have to manually download the new version. GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -3501,226 +3822,279 @@ You may have to manually download the new version. GameSelector - - - - - - + + + + + + Error 오류 - + Please join the appropriate room first. 해당 게임이 열려있는 채널에 먼저 들어가시기 바랍니다. - + Wrong password. 잘못된 비밀번호를 입력하셨습니다. - + Spectators are not allowed in this game. 관전이 허용되지 않은 게임입니다. - + The game is already full. 게임 인원이 전부 찼습니다. - + The game does not exist any more. 게임이 더 이상 존재하지 않습니다. - + This game is only open to registered users. 서버에 가입한 사용자만 참가 할 수 있습니다. - + This game is only open to its creator's buddies. 방장의 친구 목록에 등록된 사용자만 참가 할 수 있습니다. - + You are being ignored by the creator of this game. 방장이 당신을 차단하였습니다. - + Join Game - + Spectate Game - + Game Information - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game 게임 참가 - + Password: 비밀번호: - + Please join the respective room first. 해당 게임이 열려있는 채널에 먼저 들어가시기 바랍니다. - + &Filter games 게임 필터 - + C&lear filter 필터 해제 - + C&reate 게임 생성 - + &Join 게임 참가 - + + Join as judge + + + + J&oin as spectator 관전자로 참가하기 - + + Join as judge spectator + + + + Games shown: %1 / %2 - + Games 게임 목록 + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day >1 일 - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password 비밀번호 필요 - + buddies only 친구만 - + reg. users only 가입한 사용자 전용 - + open decklists - - + + can chat 관전자 대화 가능 - + see hands 손 공개 - + can see hands 관전자에게 손 공개 - + not allowed 허용 안됨 - + Room 게임 - + Age - + Description 게임 이름 - + Creator 방장 - + Type - + Restrictions 입장 제한 - + Players 플레이어 - + Spectators 관전자 @@ -3728,143 +4102,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path 경로 선택 - + Personal settings 개인 설정 - + Language: 언어: - + Paths (editing disabled in portable mode) - + Paths 디렉토리 경로 - + How to help with translations - + Decks directory: 덱 파일 경로: - + Filters directory: - + Replays directory: 리플레이 파일 경로: - + Pictures directory: 카드 이미지 경로: - + Card database: 카드 데이터베이스 경로: - + Custom database directory: - + Token database: 토큰 데이터베이스 경로: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client 서버에서 서포트하는 기능이 클라이언트에 존재하지 않을 경우 알림 - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup - + Last update check on %1 (%2 days ago) @@ -3872,47 +4246,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -3920,111 +4294,141 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - - &Sort hand + + Sort hand by... - - Take &mulligan + + Name - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - Reveal r&andom card to... + + + All players - - - &All players + + Reveal r&andom card to... HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + + Browse Archidekt + + + + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4226,61 +4630,61 @@ You may have to manually download the new version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. 서버가 꽉 찼습니다. 나중에 다시 접속하여 주십시오. - + There are too many concurrent connections from your address. 해당 IP주소에서 동시에 연결된 회선이 너무 많습니다. - + Banned by moderator 관리자에 의해 서버에서 추방 당하였습니다. - + Expected end time: %1 예상 추방 종료 시간: %1 - + This ban lasts indefinitely. 영구적 추방입니다. - + Scheduled server shutdown. 정기 점검 중입니다. - - + + Invalid username. 잘못된 사용자명입니다. - + You have been logged out due to logging in at another location. 다른 장소에서 로그인하여 접속이 해제되었습니다. - + Connection closed 서버와의 연결이 끊어졌습니다. - + The server has terminated your connection. Reason: %1 서버에서 연결을 끊었습니다. 사유: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -4289,369 +4693,369 @@ Reason for shutdown: %1 서버 재시작 사유: %1 - + Scheduled server shutdown 정기 점검 - - + + Success 성공 - + Registration accepted. Will now login. 가입이 승인되었습니다. 로그인합니다. - + Account activation accepted. Will now login. 계정이 활성화 되었습니다. 로그인합니다. - + Number of players 플레이어 인원 - + Please enter the number of players. 최대 플레이어 인원을 입력해 주세요. - - + + Player %1 플레이어 %1 - + Load replay 리플레이 불러오기 - + About Cockatrice 코카트리스에 관하여 - + Version - + Cockatrice Webpage 코카트리스 홈페이지 - + Project Manager: 현 프로젝트 매니저: - + Past Project Managers: 전 프로젝트 매니저: - + Developers: 개발진: - + Our Developers 기여자 목록 - + Help Develop! 개발을 도와주세요! - + Translators: 번역진: - + Our Translators 번역진: - + Help Translate! 번역을 도와주세요! - + Support: 기술 지원: - + Report an Issue 문제점 보고 - + Troubleshooting 문제 해결 - + F.A.Q. 자주 묻는 질문 - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error 오류 - + Server timeout 서버 응답시간 초과 - + Failed Login 로그인 실패 - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. 잘못된 사용자명이나 비밀번호입니다. 확인 후 다시 시도해 주세요. - + There is already an active session using this user name. Please close that session first and re-login. 해당 사용자명으로 연결된 다른 세션이 있습니다. 해당 세션을 종료 한 후에 다시 시도해 주세요. - - + + You are banned until %1. %1까지 추방 당하였습니다. - - + + You are banned indefinitely. 당신은 무기한 추방 당하였습니다. - + This server requires user registration. Do you want to register now? 본 서버는 가입이 필요합니다. 지금 가입하시겠습니까? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation 계정 활성화 - + Your account has not been activated yet. You need to provide the activation token received in the activation email. 계정이 아직 활성화 되지 않았습니다. 가입 시 기입한 메일 주소에서 계정 활성화 토큰을 확인하여 주십시오. - + Server Full - + Unknown login error: %1 알 수 없는 로그인 오류: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. 대부분 클라이언트가 오래되어서 서버와 클라이언트가 말이 제대로 통하지 않을때 발생하는 문제입니다. - + Your username must respect these rules: 사용자명은 다음 조건을 만족해야 합니다: - + is %1 - %2 characters long %1에서 %2자 사이 - + can %1 contain lowercase characters 소문자 사용 %1가능 - - - - + + + + NOT - + can %1 contain uppercase characters 대문자 사용 %1가능 - + can %1 contain numeric characters 숫자 사용 %1가능 - + can contain the following punctuation: %1 다음 문장 부호를 사용 가능 : - + first character can %1 be a punctuation mark 첫 글자를 문장 부호로 입력 %1가능 - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. 사용자명에는 영어 대소문자, 숫자, _(밑줄), .(마침표)나 -(대쉬)만 사용하실 수 있습니다. - - - - - - + + + + + + Registration denied 가입 실패 - + Registration is currently disabled on this server 본 서버는 현재 가입을 받지 않습니다. - + There is already an existing account with the same user name. 이미 존재하는 사용자명 입니다. - + It's mandatory to specify a valid email address when registering. 사용 가능한 이메일 주소를 작성하셔야 합니다. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. 비밀번호가 너무 짧습니다. - + Registration failed for a technical problem on the server. 서버의 기술적 문제로 가입에 실패하였습니다. - + The connection to the server has been lost. - + Unknown registration error: %1 가입 중 알 수 없는 오류 발생: %1 - + Account activation failed 계정 활성화 실패 - + Socket error: %1 소켓 오류: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. 서버 버전이 클라이언트보다 오래되었습니다. @@ -4659,7 +5063,7 @@ Local version is %1, remote version is %2. 클라이언트 버전 %1, 서버 버전 %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. 코카트리스 클라이언트 버전이 오래되었습니다. @@ -4667,316 +5071,316 @@ Local version is %1, remote version is %2. 클라이언트 버전 %1, 서버 버전 %2. - + Connecting to %1... %1로 연결 시도 중... - + Registering to %1 as %2... 서버 %1에 %2(으)로 가입 중... - + Disconnected 연결 안됨 - + Connected, logging in at %1 연결됨, %1로 로그인 시도 중 + - Requesting forgotten password to %1 as %2... - + &Connect... 서버로 연결 - + &Disconnect 서버와의 연결 해제 - + Start &local game... 오프라인 게임 시작 - + &Watch replay... 리플레이 재생 - + &Full screen 전체 화면 - + &Register to server... 서버에 가입 - + &Restore password... - + &Settings... 환경설정 - + &Exit 끝내기 - + A&ctions 액션 - + &Cockatrice 코카트리스 - + C&ard Database - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder 커스텀 이미지 폴더 열기 - + Open custom sets folder 커스텀 셋 폴더 열기 - + Add custom sets/cards 커스텀 셋/카드 추가 - + Reload card database - + Tabs - + &Help 도움말 - + &About Cockatrice 코카트리스에 관하여 - + &Tip of the Day - + Check for Client Updates 클라이언트 업데이트 확인 - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database 카드 데이터베이스 - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No 아니오 - + Open settings 설정 열기 - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets 셋 보기 - + Welcome 환영합니다 - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information 알림 - + A card database update is already running. 이미 카드 데이터베이스 업데이트가 진행 중입니다. - + Unable to run the card database updater: 카드 데이터베이스 업데이트를 진행할 수 없습니다: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -4984,672 +5388,842 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play 전장에 - + from their graveyard - + from exile 추방 영역에 - + from their hand 자신의 손에 - + the top card of %1's library %1의 서고 맨 위의 카드 - + the top card of their library - + from the top of %1's library %1의 서고 맨 위에 - + from the top of their library - + the bottom card of %1's library %1의 서고 맨 밑의 카드 - + the bottom card of their library - + from the bottom of %1's library %1의 서고 맨 밑에 - + from the bottom of their library - + from %1's library %1의 서고에 - + from their library - + from sideboard 사이드보드에 - + from the stack 스택에 - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1이(가) %2 맨 위 카드를 공개합니다. - + %1 is not revealing the top card %2 any longer. %1이(가) %2 맨 위 카드를 더 이상 공개하지 않습니다. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. %1님이 자신의 %2에서 자신의 %3을(를) 가리켰습니다. - + %1 points from their %2 to %3's %4. %1님이 자신의 %2에서 %3님의 %4을(를) 가리켰습니다. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. %1님이 토큰 생성: %2%3. - + %1 has loaded a deck (%2). %1이(가) 덱을 불러왔습니다. (해시값: %2) - + %1 has loaded a deck with %2 sideboard cards (%3). %1이(가) %2장의 사이드보드가 있는 덱을 불러왔습니다. (해시값: %3) - + %1 destroys %2. - + a card 카드 한 장 - + %1 gives %2 control over %3. %1이(가) %3의 조종권을 %2에게 넘깁니다. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1이(가) %3서 %2을(를) 전장에 놓았습니다. - + %1 puts %2%3 into their graveyard. - + %1 exiles %2%3. %1이(가) %3 있던 %2을(를) 추방합니다. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. %1이(가) %3 있던 %2을(를) 서고 맨 밑에 넣었습니다. - + %1 plays %2%3. %1이(가) %3서 %2을(를) 플레이 하였습니다. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. 게임이 종료되었습니다. - + The game has started. 게임이 시작되었습니다. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1님이 게임에 입장했습니다. - + %1 is now watching the game. %1이(가) 관전을 시작하였습니다. - + You have been kicked out of the game. 게임에서 강제 퇴장 당하였습니다. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. %1님이 게임 시작 준비를 끝냈습니다. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. %1님이 %2을(를) 공개. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads 앞면 - + Tails 뒷면 - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. %1의 차례입니다. - + %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. %1님이 자신의 지속물을 탭. - + %1 untaps their permanents. %1님이 자신의 지속물을 언탭. - + %1 taps %2. %1님이 %2을(를) 탭. - + %1 untaps %2. %1님이 %2을(를) 언탭. - + %1 shuffles %2. %1님이 %2를 셔플했습니다. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. %1님이 %2을(를) 뗴어냈습니다. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -5657,110 +6231,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message 메세지 추가 - - + + Message: 메세지: - + Edit message - + Chat settings 대화 설정 - + Custom alert words 키워드 알림 - + Enable chat mentions 대화 중 본인의 사용자명 멘션 시 해당 문장을 강조 - + Enable mention completer 멘션 자동완성 기능 - + In-game message macros 게임 내 대화 매크로 - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users 서버에 가입하지 않은 사용자의 채널 대화 차단 - + Ignore private messages sent by unregistered users 서버에 가입하지 않은 사용자가 보낸 1:1 대화 차단 - - + + Invert text color 문장 색 반전 - + Enable desktop notifications for private messages 1:1 대화를 받을 시 데스크탑 알림 설정 - + Enable desktop notification for mentions 자신의 사용자명 멘션 시 데스크탑 알림 설정 - + Enable room message history on join 채널 입장 시 이전 대화 기록 표시 - - + + (Color is hexadecimal) (16진수 색상 코드) - + Separate words with a space, alphanumeric characters only 각 단어마다 스페이스바로 띄어써주세요. 문자 및 숫자만 가능합니다. @@ -5877,62 +6451,62 @@ Cockatrice will now reload the card database. Phase - + Unknown Phase - + Untap - + Upkeep - + Draw - + First Main - + Beginning of Combat - + Declare Attackers - + Declare Blockers - + Combat Damage - + End of Combat - + Second Main - + End/Cleanup @@ -5998,7 +6572,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages ko @@ -6007,134 +6581,134 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - + Move top cards to grave - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6142,17 +6716,17 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters - + S&ay @@ -6160,7 +6734,7 @@ Cockatrice will now reload the card database. PrintingSelector - + Display Navigation Buttons @@ -6168,22 +6742,22 @@ Cockatrice will now reload the card database. PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6199,17 +6773,17 @@ Cockatrice will now reload the card database. PrintingSelectorCardSelectionWidget - + Previous Card in Deck - + Bulk Selection - + Next Card in Deck @@ -6217,28 +6791,28 @@ Cockatrice will now reload the card database. PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -6304,37 +6878,37 @@ Cockatrice will now reload the card database. QMenuBar - + Services 서비스 - + Hide %1 %1 가리기 - + Hide Others 기타 가리기 - + Show All 모두 보기 - + Preferences... 환경설정... - + Quit %1 %1 종료 - + About %1 %1에 관하여 @@ -6342,42 +6916,42 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) 코카트리스 카드 데이터베이스 파일 (*.xml) - + All files (*.*) 모든 파일 (*.*) - + Cockatrice replays (*.cor) 코카트리스 리플레이 파일 (*.cor) - + Maindeck 메인 덱 - + Sideboard 사이드보드 - + Tokens 토큰 - + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? @@ -6385,92 +6959,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -6567,37 +7141,37 @@ Cockatrice will now reload the card database. RoomSelector - + Rooms 채널 목록 - + Joi&n 채널에 참가 - + Room 게임 - + Description 설명 - + Permissions 권한 - + Players 플레이어 - + Games 게임 @@ -6638,27 +7212,27 @@ Cockatrice will now reload the card database. SetsModel - + Enabled 활성화 - + Set type 확장판 종류 - + Set code 확장판 약자 - + Long name 확장판 이름 - + Release date 발매 일자 @@ -6666,53 +7240,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -6720,12 +7294,12 @@ Cockatrice will now reload the card database. ShortcutTreeView - + Action - + Shortcut @@ -6733,13 +7307,13 @@ Cockatrice will now reload the card database. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -6766,12 +7340,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -6779,27 +7353,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds 음향 효과 켜기 - + Current sounds theme: 현재 소리 테마: - + Test system sound engine 시스템 사운드 엔진 테스트 - + Sound settings 음향 설정 - + Master volume 주 음량 @@ -6807,48 +7381,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -7012,56 +7586,123 @@ Please check your shortcut settings! + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info 카드 정보 - + Deck - + Filters 카드 필터 - + &View &보기 - + + Card Database + + + + Printing - - - - + + + + + Visible 표시 - - - - + + + + + Floating 띄우기 - + Reset layout 화면 레이아웃 초기화 - + Deck: %1 덱 편집기: %1 @@ -7069,61 +7710,61 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + + + + Visible - - - - + + + + Floating - + Reset layout @@ -7131,22 +7772,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7154,134 +7795,134 @@ Please check your shortcut settings! TabDeckStorage - + Local file system 로컬 파일 시스템 - + Server deck storage 서버 덱 보관함 - - + + Open in deck editor 덱 편집기로 열기 - + Rename deck or folder - + Upload deck 덱 업로드 - + Download deck 덱 다운로드 + - - - + + New folder 새 폴더 + - Delete 삭제 - + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + Error - + Rename failed - - + + Invalid deck file - + Enter deck name 덱 이름 작성 - + This decklist does not have a name. Please enter a name: 이 덱리스트는 이름이 없습니다. 이름을 입력해 주세요: - + Unnamed deck 이름 없는 덱 - + Failed to upload deck to server - + Delete local file 로컬 파일 삭제 - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + Name of new folder: 새 폴더의 이름: @@ -7294,17 +7935,17 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 @@ -7345,7 +7986,7 @@ Please enter a name: - + EDHRec: @@ -7353,197 +7994,197 @@ Please enter a name: TabGame - - - + + + Replay 리플레이 - - + + Game 게임 - - + + Player List 플레이어 목록 - - + + Card Info 카드 정보 - - + + Messages 메세지 - - + + Replay Timeline 리플레이 타임라인 - + &Phases 단계 - + &Game 게임 - + Next &phase 다음 단계로 진행 - + Next phase with &action - + Next &turn 턴 넘기기 - + Reverse turn order - + &Remove all local arrows 내가 그린 화살표 제거 - + Rotate View Cl&ockwise 플레이어 위치 시계방향으로 조정 - + Rotate View Co&unterclockwise 플레이어 위치 반시계방향으로 조정 - + Game &information 게임 정보 - + Un&concede - - - + + + &Concede 항복 - + &Leave game 게임 나가기 - + C&lose replay 리플레이 닫기 - + &Focus Chat - + &Say: 말하기: - + Selected cards - + &View &보기 - - + + + - Visible 표시 - - + + + - Floating 띄우기 - + Reset layout 레이아웃 초기화 - + Concede 항복 - + Are you sure you want to concede this game? 정말 게임에서 항복하시겠습니까? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game 게임 나가기 - + Are you sure you want to leave this game? 정말 게임에서 나가시겠습니까? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. 게임에서 강제 퇴장 당하였습니다. @@ -7551,7 +8192,7 @@ Please enter a name: TabHome - + Home @@ -7559,158 +8200,158 @@ Please enter a name: TabLog - + Logs 기록 - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs 게임 기록 - + Chat Logs 대화 기록 - - + + Error 오류 - + You must select at least one filter. 최소한 한 개 이상의 필터를 선택해주세요. - + You have to select a valid number of days to locate. - + Username: 사용자명: - + IP Address: IP 주소: - + Game Name: 게임 이름: - + GameID: 게임 ID: - + Message: 메세지: - + Main Room 대화 채널 - + Game Room 게임 방 - + Private Chat 1:1 대화 - + Past X Days: ~일 전: - + Today 오늘 - + Last Hour 1시간 이내 - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. 최소한 한개 이상의 필터를 지정해주세요. 자세히 지정하시면 좀 더 세분화된 결과를 보실 수 있습니다. - + Get User Logs - + Clear Filters - + Filters 필터 - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -7756,180 +8397,180 @@ The more information you put in, the more specific your results will be. TabReplays - + Local file system 로컬 파일 시스템 - + Server replay storage 서버 리플레이 보관함 - - + + Watch replay 리플레이 보기 - + Rename - - + + New folder - - + + Delete 삭제 - + Open replays folder - + Download replay 리플레이 다운로드 - + Toggle expiration lock 자동 삭제 여부 토글 - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file 로컬 파일 삭제 - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay 서버에 저장된 리플레이 삭제 @@ -7995,30 +8636,30 @@ The more information you put in, the more specific your results will be.서버 + - Error 오류 - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -8026,93 +8667,98 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - - - Home - - - - - &Visual Deck Storage - - - - - Visual Database Display - - - Server + Archidekt - Account + Home - Deck Storage + &Visual Deck Storage - Game Replays + Visual Database Display - Administration + Server + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + Logs - + Are you sure? 확실하십니까? - + There are still open games. Are you sure you want to quit? 참가 중인 게임이 있습니다. 정말로 코카트리스를 종료하시겠습니까? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8120,32 +8766,32 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion 승급 - + You have been promoted. Please log out and back in for changes to take effect. - + Warned 경고 - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. 운영진으로부터 %1의 사유로 경고를 받았습니다. @@ -8153,7 +8799,7 @@ Please refrain from engaging in this activity or further actions may be taken ag 문의사항은 관리자에게 1:1대화로 물어보시기 바랍니다. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -8162,7 +8808,12 @@ Please refrain from engaging in this activity or further actions may be taken ag TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display @@ -8242,7 +8893,7 @@ Please refrain from engaging in this activity or further actions may be taken ag UpdateDownloader - + Could not open the file for reading. @@ -8250,206 +8901,206 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details 사용자 정보 - + Private &chat 1:1 대화 - + Show this user's &games 이 사용자의 게임 보기 - + Add to &buddy list 친구 목록에 추가 - + Remove from &buddy list 친구 목록에서 삭제 - + Add to &ignore list 차단 목록에 추가 - + Remove from &ignore list 차단 목록에서 제거 - + Kick from &game 게임에서 강제 퇴장 - + Warn user - + View user's war&n history - + Ban from &server 서버에서 추방 - + View user's &ban history - + &Promote user to moderator 사용자를 관리자로 승급 - + Dem&ote user from moderator 사용자를 관리자로부터 강등 - + Promote user to &judge - + Demote user from judge - + View admin notes - - - + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games %1의 게임 - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. 경고를 받은 적이 없는 사용자입니다. - + Failed to collect warning information. - + Failed to get admin notes. - - + + Success 성공 - + Successfully promoted user. 해당 사용자 승급에 성공하였습니다. - + Successfully demoted user. 해당 사용자 강등에 성공하였습니다. - + + - Failed 실패 - + Failed to promote user. 해당 사용자 승급에 실패하였습니다. - + Failed to demote user. 해당 사용자 강등에 실패하였습니다. - + Copy hash to clipboard - + Remove this user's messages @@ -8631,137 +9282,142 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings 일반 인터페이스 설정 - + &Double-click cards to play them (instead of single-click) 카드를 더블 클릭해서 발동 (해제시 한번만 클릭하면 발동 됨) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default 모든 대지가 아닌 카드를 발동 시에 스택으로 이동 (해제시 전장으로 바로 이동) - - - Close card view window when last card is removed - - - - - Auto focus search bar when card view window is opened - - - - - Annotate card text on tokens - 토큰에 카드 텍스트 주석 자동 추가 - - - - Use tear-off menus, allowing right click menus to persist on screen - - - - - Notifications settings - - - - - Enable notifications in taskbar - 상태 표시줄 알림 설정 - - - - Notify in the taskbar for game events while you are spectating - 관전중인 게임의 상태 표시줄 알림 설정 - - - - Notify in the taskbar when users in your buddy list connect - - - - - Animation settings - 애니메이션 설정 - - - - &Tap/untap animation - 탭/언탭 애니메이션 - - Deck editor/storage settings + Do not delete &arrows inside of subphases - Open deck in new tab by default + Close card view window when last card is removed - Use visual deck storage in game lobby + Auto focus search bar when card view window is opened - Use selection animation for Visual Deck Storage + Annotate card text on tokens + 토큰에 카드 텍스트 주석 자동 추가 + + + + Use tear-off menus, allowing right click menus to persist on screen - When adding a tag in the visual deck storage to a .txt deck: + Notifications settings + + + Enable notifications in taskbar + 상태 표시줄 알림 설정 + - do nothing + Notify in the taskbar for game events while you are spectating + 관전중인 게임의 상태 표시줄 알림 설정 + + + + Notify in the taskbar when users in your buddy list connect - ask to convert to .cod - + Animation settings + 애니메이션 설정 + + + + &Tap/untap animation + 탭/언탭 애니메이션 - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -8769,22 +9425,22 @@ Please refrain from engaging in this activity or further actions may be taken ag UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 @@ -8792,32 +9448,32 @@ Please refrain from engaging in this activity or further actions may be taken ag UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -8825,22 +9481,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match - + Mode: Includes - + Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -8848,21 +9504,49 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8888,28 +9572,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - Load from Deck + Filter by name... (Exact match) - Apply all card names in currently loaded deck as exact match name filters + Load from Deck - Load from Clipboard + Apply all card names in currently loaded deck as exact match name filters + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -8973,50 +9657,115 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - + + Sort by: + + + + + Filter by: + + + + Save and load filters - + Filter by exact card name - + Filter by card sub-type - + Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9024,46 +9773,25 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -9119,7 +9847,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9127,22 +9855,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -9150,17 +9878,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + Visual Deck Storage Settings @@ -9168,43 +9896,43 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? 어떤 종류의 경고를 보내시겠습니까? - + Redact all messages from this user in all rooms - + &OK 확인 - + &Cancel 취소 - + Warn user for misconduct 사용자의 부적절한 행동에 경고 - - + + Error 오류 - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. @@ -9212,133 +9940,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top 선택한 확장판을 맨 위로 올리기 - + Move selected set up 선택한 확장판을 한 단계 올리기 - + Move selected set down 선택한 확장판을 한 단계 내리기 - + Move selected set to the bottom 선택한 확장판을 맨 아래로 내리기 - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets 모든 확장판 활성화 - + Disable all sets 모든 확장판 비활성화 - + Enable selected set(s) 선택한 세트 적용 - + Disable selected set(s) 선택한 세트 미적용 - + Deck Editor 덱 편집기 - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art 카드 이미지 - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -9346,72 +10074,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing 닫은 후 덱 섞기 - + pile view 카드 유형별 행 정렬 @@ -9419,7 +10147,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English 한국어 (Korean) @@ -9427,12 +10155,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -9440,1005 +10168,1041 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window 메인 화면 - - + + Deck Editor 덱 편집기 - + Game Lobby 게임 로비 - + Card Counters - + Player Counters - + Power and Toughness 공격력 및 방어력 - + Game Phases 턴 단계 - + Playing Area 전장 - + Move Selected Card - + View 보기 - + Move Top Card - + Move Bottom Card - + Gameplay 게임플레이 - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - - + + Replays - + Tabs - + Check for Card Updates... - + Connect... - + Disconnect 서버와의 연결 해제 - + Exit 끝내기 - + Full screen 전체 화면 - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters - + Clear Selected Filter - + Close 덱 닫기 - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card - + Load Deck... - - + + Load Deck from Clipboard... - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck - + Open Custom Pictures Folder - + Print Deck... - + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... - + Load Remote Deck... - + Set Ready to Start - + Toggle Sideboard Lock - + Add Green Counter - + Remove Green Counter - + Set Green Counters... - + Add Red Counter - + Remove Red Counter - + Set Red Counters... - + Add Life Counter - + Show Status Bar - + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Increment all card counters - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap 언탭단 - + Upkeep 유지단 - + Draw 뽑기단 - + First Main Phase - + Start Combat - + Attack 공격자 선언단 - + Block 방어자 선언단 - + Damage 전투 피해단 - + End Combat - + Second Main Phase - + End 종료단 - + Next Phase - + Next Phase Action - + Next Turn - + Hide Card in Reveal Window - + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library - - - - + + + + Exile 추방 영역 - - - - + + + + Graveyard 무덤 - - + + + Hand - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - - Sort Hand - - - - + Library 서고 - + Sideboard 사이드보드 - + Top Cards of Library - + Bottom Cards of Library - + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede 항복 - + Roll Dice... - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan 멀리건 - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_nl.ts b/cockatrice/translations/cockatrice_nl.ts index de208b828..d42347f0b 100644 --- a/cockatrice/translations/cockatrice_nl.ts +++ b/cockatrice/translations/cockatrice_nl.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Teller instellen... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Teller instellen - + New value for counter '%1': Nieuwe waarde voor teller '%1': @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh &Verversen - + Parse Set Name and Number (if available) Interpreteer Setnaam en Nummer (indien beschikbaar) @@ -36,81 +36,83 @@ AbstractTabDeckEditor - + Open in new tab Open in nieuw tabblad - + Are you sure? Weet je het zeker? - + The decklist has been modified. Do you want to save the changes? De decklijst is gewijzigd. Wil je de wijzigingen opslaan? - - - - - - - + + + + + + Error Foutmelding - + Could not open deck at %1 Kon het deckbestand niet openen: %1 - + Could not save remote deck Kon remote deck niet opslaan - - + + The deck could not be saved. Please check that the directory is writable and try again. Het deck kon niet opgeslagen worden. Kijk na of je schrijfrechten tot de folder hebt, en probeer opnieuw. - + Save deck Deck opslaan - + The deck could not be saved. Het deck kon niet opslagen worden. - + There are no cards in your deck to be exported Er zijn geen kaarten in je deck om te exporteren. + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. - Geen deck geselecteerd om te exporteren. + + Add Analytics Panel + AdminNotesDialog - + Update Notes Update Notities - + Admin Notes for %1 Admin Notities voor %1 @@ -118,12 +120,12 @@ Kijk na of je schrijfrechten tot de folder hebt, en probeer opnieuw. AllZonesCardAmountWidget - + Mainboard Mainboard - + Sideboard Sideboard @@ -131,22 +133,22 @@ Kijk na of je schrijfrechten tot de folder hebt, en probeer opnieuw. AppearanceSettingsPage - + seconds secondes - + Error Foutmelding - + Could not create themes directory at '%1'. Kon de themes map niet aanmaken op '%1'. - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -163,7 +165,7 @@ Je moet dan de Set Manager gebruiken, die beschikbaar is via Card Database -> Weet je zeker dat je deze functie wil aanzetten? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -180,152 +182,165 @@ Je kunt ook de Set Manager gebruiken om de aangepaste sorteervolgorde voor drukk Weet je zeker dat je deze functie wilt uitschakelen? - + Confirm Change Bevestig Wijziging - + Theme settings Thema opties - + Current theme: Huidige thema: - + Open themes folder Open themafolder - + Home tab background source: Starttabblad achtergrond bron: - + Home tab background shuffle frequency: Starttabblad achtergrond verversperiode. - + Disabled Uitgeschakeld - + Menu settings Menu instellingen - + Show keyboard shortcuts in right-click menus Laat snelkoppelingen in rechtermuisknop menus zien - + + Show game filter toolbar above list in room tab + + + + Card rendering Kaartweergave - + Display card names on cards having a picture Kaartnamen altijd weergeven op kaarten met een afbeelding - + Auto-Rotate cards with sideways layout Draai kaarten samen met zijwaardse layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) Overschrijf alle kaart beelden met eigen set voorkeuren (Gedrag van voor implementatie van ProviderID) - + Bump sets that the deck contains cards from to the top in the printing selector Plaats sets van andere kaarten in het deck bovenin de drukselector - + Scale cards on mouse over Kaarten schalen met de muis - + Use rounded card corners Ronde kaarthoekjes - + Minimum overlap percentage of cards on the stack and in vertical hand Minimum overlap percentage van kaarten op de stack en in verticale handweergave - + Maximum initial height for card view window: Grootste begingrootte voor kaartbekijkvenster - - + + rows rijen - + Maximum expanded height for card view window: Grootste uitgebreide grootte voor kaartbekijkvenster - + Card counters Kaart-counters - + Counter %1 Counter %1 - + Hand layout Handweergave - + Display hand horizontally (wastes space) Hand horizontaal weergeven (gebruikt extra ruimte) - + Enable left justification Linker justificatie inschakelen - + Table grid layout Tafelindeling - + Invert vertical coordinate Verticale coördinaat omkeren - + Minimum player count for multi-column layout: Minimaal aantal spelers voor kolommenindeling: - + Maximum font size for information displayed on cards: Maximale lettergrootte voor informatie die op kaarten wordt weergegeven: + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -347,112 +362,112 @@ Weet je zeker dat je deze functie wilt uitschakelen? BanDialog - + ban &user name ban &gebruikersnaam - + ban &IP address ban &IP-adres - + ban client I&D ban gebruiker I&D - + Ban type Ban type - + &permanent ban &definitieve ban - + &temporary ban &tijdelijke ban - + &Days: &Dagen: - + &Hours: &Uur: - + &Minutes: &Minuten: - + Duration of the ban Duur van de ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Voer een reden voor de ban in. Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de verbannen persoon. - + Please enter the reason for the ban that will be visible to the banned person. Voer een reden in die zichtbaar zal zijn voor de verbannen persoon. - + Redact all messages from this user in all rooms Verwijder alle berichten van deze gebruiker in alle kamers - + &OK &OK - + &Cancel &Annuleren - + Ban user from server Ban gebruiker van server - + + - - + Error Fout - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. U moet een ban gebaseerd op IP, naam, gebruikers ID of een combinatie van de drie. - + You must have a value in the name ban when selecting the name ban checkbox. U moet een naam meegeven als u een naam gebaseerde ban wilt uitvoeren. - + You must have a value in the ip ban when selecting the ip ban checkbox. U moet een IP meegeven als u een IP gebaseerde ban wilt uitvoeren. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. U moet een gebruikers ID meegeven als u een gebruikers ID gebaseerde ban wilt uitvoeren. @@ -483,32 +498,32 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardDatabaseModel - + Name Naam - + Sets Sets - + Mana cost Manakosten - + Card type Kaarttype - + P/T P/T - + Color(s) Kleur(en) @@ -516,96 +531,101 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardFilter - + AND Logical conjunction operator used in card filter EN - + OR Logical disjunction operator used in card filter OF - + AND NOT Negated logical conjunction operator used in card filter EN NIET - + OR NOT Negated logical disjunction operator used in card filter OF NIET - + Name Naam - + + Name (Exact) + + + + Type Type - + Color Kleur - + Text Text - + Set Set - + Mana Cost Mana Kosten - + Mana Value Mana Waarde - + Rarity Zeldzaamheid - + Power Kracht - + Toughness Hardheid - + Loyalty Loyaliteit - + Format Format - + Main Type Hoofdtype - + Sub Type Subtype @@ -613,22 +633,22 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardInfoFrameWidget - + Image Plaatje - + Description Beschrijving - + Both Beide - + View transformation Bekijk gedaanteverandering @@ -636,22 +656,22 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardInfoPictureWidget - + View related cards Bekijk gerelateerde kaarten - + Add card to deck Voeg kaart aan deck toe - + Mainboard Mainboard - + Sideboard Sideboard @@ -664,12 +684,22 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v Naam: - + + Set: + + + + + Collector Number: + + + + Related cards: Gerelateerde kaarten: - + Unknown card: Onbekende kaart: @@ -677,124 +707,124 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardMenu - + Re&veal to... &Onthul aan.... - + &All players &Alle spelers - + View related cards Bekijk gerelateerde kaarten - + Token: Token: - + All tokens Alle tokens - + &Select All &Selecteer Alle - + S&elect Row S&electeer Rij - + S&elect Column S&electeer Kolom - + &Play &Speel - + &Hide &Verberg - + Play &Face Down Speel kaart &verdekt - + &Tap / Untap Turn sideways or back again &Tap / Ontap - + Toggle &normal untapping &Normal ontappen schakelen - + T&urn Over Turn face up/face down &Draai om - + &Peek at card face &Gluren naar de voorkant van kaart - + &Clone &Kloneer - + Attac&h to card... Aan de kaart &bevestigen.... - + Unattac&h &Losmaken - + &Draw arrow... &Teken pijl.... - + &Set annotation... Aantekeningen &zetten.... - + Ca&rd counters Kaart-&counters - + &Add counter (%1) Voeg counter &toe (%1) - + &Remove counter (%1) &Verwijder counter (%1) - + &Set counters (%1)... Counters &instellen (%1)... @@ -802,7 +832,7 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardSizeWidget - + Card Size Kaartgrootte @@ -810,133 +840,133 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardZoneLogic - + their hand nominative hun hand - + %1's hand nominative %1s hand - - - their library - look at zone - hun library - - - - %1's library - look at zone - %1s library - - - - of their library - top cards of zone, - van hun library - - - - of %1's library - top cards of zone - van %1s library - their library - reveal zone + look at zone hun library %1's library + look at zone + %1s library + + + + of their library + top cards of zone, + van hun library + + + + of %1's library + top cards of zone + van %1s library + + + + their library + reveal zone + hun library + + + + %1's library reveal zone %1s library - + their library shuffle hun library - + %1's library shuffle %1s library - + their library nominative hun library - + %1's library nominative %1s library - + their graveyard nominative hun kerkhof - + %1's graveyard nominative %1s kerkhof - + their exile nominative hun verbannen - + %1's exile nominative %1s verbannen - + their sideboard look at zone hun sideboard - + %1's sideboard look at zone %1s sideboard - - - their sideboard - nominative - hun sideboard - - - - %1's sideboard - nominative - %1s sideboard - + their sideboard + nominative + hun sideboard + + + + %1's sideboard + nominative + %1s sideboard + + + their custom zone '%1' nominative hun aangepaste zone '%1' - + %1's custom zone '%2' nominative %1s aangepaste zone '%2' @@ -945,7 +975,7 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CockatriceXml3Parser - + Parse error at line %1 col %2: Parseerfout op regel %1 kolom %2: @@ -953,7 +983,7 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CockatriceXml4Parser - + Parse error at line %1 col %2: Parseerfout op regel %1 kolom %2: @@ -972,6 +1002,37 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v Aangepaste zone '%1' bekijken + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -983,47 +1044,47 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) Zoek op kaartnaam (of zoekexpressies) - + Add to Deck Toevoegen aan deck - + Add to Sideboard Toevoegen aan sideboard - + Select Printing Selecteer Druk - + Show on EDHRec (Commander) Bekijk op EDHRec (Commander) - + Show on EDHRec (Card) Bekijk op EDHRec (Kaart) - + Show Related cards Gerelateerde kaarten tonen - + Add card to &maindeck Kaart aan deck &toevoegen - + Add card to &sideboard Kaart aan &sideboard toevoegen @@ -1031,87 +1092,97 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card Vaandelkaart - + Main Type Hoofdtype - + Mana Cost Mana Kosten - + Colors Kleuren - + Select Printing Selecteer Druk - + Deck Deck - + Deck &name: Deck&naam: - + Banner Card/Tags Visibility Settings Vaandelkaart/Tags zichtbaarheidsinstellingen - + Show banner card selection menu Selectiemenu van vaandelkaarten weergeven - + Show tags selection menu Selectiemenu van tags weergeven - + &Comments: &Opmerkingen: - + Group by: Groepeer op: - + + Format: + + + + Hash: Hash: - + &Increment number Ver&hoog getal - + &Decrement number Ver&laag getal - + &Remove row Ver&wijder rij - + Swap card to/from sideboard Wissel kaart van/naar sideboard @@ -1119,17 +1190,17 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckEditorFilterDockWidget - + Filters Filters - + &Clear all filters &Wis alle filters - + Delete selected Selectie verwijderen @@ -1137,114 +1208,114 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckEditorMenu - + &Deck Editor &Deck Editor - + &New deck &Nieuw deck - + &Load deck... &Laad deck... - + Load recent deck... Laad recent deck... - + Clear Wis - + &Save deck &Bewaar deck - + Save deck &as... Bewaar deck &als... - + Load deck from cl&ipboard... Laad deck van &klembord.... - + Edit deck in clipboard Deck in klembord aanpassen... - - + + Annotated Geannoteerd - - + + Not Annotated Niet Geannoteerd - + Save deck to clipboard Deck opslaan op klembord - + Annotated (No set info) Geannoteerd (Geen set info) - + Not Annotated (No set info) Niet Geannoteerd (Geen set info) - + &Print deck... &Print deck... - + Load deck from online service... Deck van online service laden... - + &Send deck to online service Deck naar online service &sturen - + Create decklist (decklist.org) Maak decklist aan (decklist.org) - + Create decklist (decklist.xyz) Maak decklist aan (decklist.xyz) - + Analyze deck (deckstats.net) Analyseer deck (deckstats.net) - + Analyze deck (tappedout.net) Analyseer deck (tappedout.net) - + &Close &Sluiten @@ -1252,7 +1323,7 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckEditorPrintingSelectorDockWidget - + Printing Selector Drukselector @@ -1260,194 +1331,227 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckEditorSettingsPage - - + + Update Spoilers Update Spoilers - - + + Success Succes - + Download URLs have been reset. Download URL's zijn gereset. - + Downloaded card pictures have been reset. Gedownloade kaartafbeeldingen's zijn gereset. - + Error Fout - + One or more downloaded card pictures could not be cleared. Een of meer gedownloade kaartafbeeldingen konden niet worden gewist. - + Add URL URL toevoegen - - + + URL: URL: - - + + Edit URL URL bewerken - + Network Cache Size: Netwerk Cache Grootte: - + Redirect Cache TTL: Redirect Cache TTL: - + How long cached redirects for urls are valid for. Hoe lang cached redirects voor urls geldig zijn - + Picture Cache Size: Afbeeldingen Cache Grootte: - + Add New URL Nieuwe URL toevoegen - + Remove URL URL verwijderen - + Day(s) Dag(en) - + Updating... Bijwerken... - + Choose path Kies locatie - + URL Download Priority URL Download Prioriteit - + Spoilers Spoilers - + Download Spoilers Automatically Spoilers Automatisch Downloaden - + Spoiler Location: Spoiler Locatie: - + Last Change Laatste Wijziging - + Spoilers download automatically on launch Spoilers downloaden automatisch bij opstarten - + Press the button to manually update without relaunching Druk op de knop om handmatig bij te werken zonder opnieuw te starten. - + Do not close settings until manual update is complete Sluit de instellingen niet af voordat de handmatige update is voltooid - + Download card pictures on the fly Kaartafbeeldingen on the fly downloaden - + How to add a custom URL Hoe een aangepaste URL toe te voegen - + Delete Downloaded Images Gedownloade Afbeeldingen Verwijderen - + Reset Download URLs Reset Download URL's - + On-disk cache for downloaded pictures Cache op schijf voor gedownloade afbeeldingen - + In-memory cache for pictures not currently on screen Cache in geheugen voor afbeeldingen die momenteel niet op het scherm staan + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count Hoeveelheid - + Set Set - + Number Nummer - + Provider ID Provider ID - + Card Kaart @@ -1455,12 +1559,12 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckLoader - + Common deck formats (%1) Algemene deckformaten (%1) - + All files (*.*) Alle bestanden (*.*) @@ -1468,17 +1572,17 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match Modus: Exacte Overeenkomst - + Mode: Includes Modus: Bevat - + Color identity filter mode (AND/OR/NOT conjunctions of filters) Kleur-identiteit filtermodus (AND/OR/NOT samenvoegingen van filters) @@ -1486,7 +1590,7 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckPreviewDeckTagsDisplayWidget - + Edit tags ... Bewerk tags ... @@ -1494,62 +1598,62 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckPreviewTagDialog - + Deck Tags Manager Deck Tags Beheren - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: Beheer je deck-tags. Vink tags aan of uit naar behoefte, of voeg nieuwe toe: - + Add a new tag (e.g., Aggro️) Nieuwe tag toevoegen (e.g., Aggro) - + Add Tag Tag Toevoegen - + Filter tags... Tags filteren... - + OK OK - + Edit default tags Standaardtags bewerken - + Cancel Annuleren - + Invalid Input Ongeldige Invoer - + Tag name cannot be empty! Naam van tag mog niet leeg zijn! - + Duplicate Tag Tag Dupliceren - + This tag already exists. Deze tag bestaat al. @@ -1562,93 +1666,151 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v Vaandelkaart - + Open in deck editor In deckbewerker openen - + Edit Tags Tags Aanpassen - + Rename Deck Deck Hernoemen - + Save Deck to Clipboard Bewaar Deck op Klembord - + Annotated Geannoteerd - + Annotated (No set info) Geannoteerd (Geen set info) - + Not Annotated Niet Geannoteerd - + Not Annotated (No set info) Niet Geannoteerd (Geen set info) - + Rename File Bestand Hernoemen - + Delete File Bestand Verwijderen - + Set Banner Card Vaandelkaart Instellen - - + + New name: Nieuwe naam: - - + + Error Foutmelding - + Rename failed Hernoemen mislukt - + Delete file Bestand Verwijderen - + Are you sure you want to delete the selected file? Weet je zeker dat je het opgeslagen bestand wilt verwijderen? - + Delete failed Verwijderen mislukt + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1666,75 +1828,75 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckViewContainer - + Load deck... Deck laden... - + Load remote deck... Laad extern deck... - + Load from clipboard... Van klembord laden.... - + Load from website... Van website laden... - + Unload deck Deck uitladen - + Ready to start Gereed voor begin - + Force start Geforceerd starten - + Sideboard unlocked Sideboard ontgrendeld - + Sideboard locked Sideboard vergrendeld - - + + Error Fout - + The selected file could not be loaded. Het geselecteerde bestand kon niet geladen worden. - + Deck is greater than maximum file size. Deck is groter dan maximum bestandsgrootte. - + Are you sure you want to force start? This will kick all non-ready players from the game. Weet je zeker dat je geforceerd beginnen wil? Dit zal alle spelers die niet gereed zijn van het spel verwijderen. - + Cockatrice Cockatrice @@ -1769,143 +1931,143 @@ Wil je het deck omzetten naar het .cod-formaat? Downloaden... - + Known Hosts Bekende Hosts - + Delete the currently selected saved server Verwijder de geselecteerde opgeslagen server - + Refresh the server list with known public servers Hernieuw de serverlijst met bekende publieke servers - + New Host Nieuwe Eigenaar - + Name: Naam: - + &Host: &Gastheer - + &Port: &Poort - + Player &name: Spelers&naam: - + P&assword: W&achtwoord: - + &Save password Wachtwoord &Opslaan - + A&uto connect A&utomatisch verbinden - + Automatically connect to the most recent login when Cockatrice opens Maak automatisch verbinding met de meest recente login wanneer Cockatrice wordt geopend. - + If you have any trouble connecting or registering then contact the server staff for help! Als u problemen ondervindt bij het aansluiten of registreren, neem dan contact op met het serverteam voor hulp! - - + + Webpage Website - + Reset Password Wachtwoord veranderen - + Forgot password? Wachtwoord vergeten? - + &Connect &Verbinden - + Server Server - + Login Login - + Server Contact Servercontact - + Connect to Server Verbinding maken met server - + Server URL Server URL - + Communication Port Communicatiepoort - + Unique Server Name Unieke Servernaam - + Connection Warning Verbindingswaarschuwing - + You need to name your new connection profile. U moet het nieuwe verbindingsprofiel een naam geven. - + Connect Warning Verbindingswaarschuwing - + The player name can't be empty. De naam van de speler mag niet leeg zijn. @@ -1913,117 +2075,122 @@ Wil je het deck omzetten naar het .cod-formaat? DlgCreateGame - + Re&member settings Instellingen &Onthouden - + &Description: &Omschrijving: - + P&layers: Spe&lers: - + General Algemeen - + Game type Spel type - + &Password: &Wachtwoord: - + Only &buddies can join Alleen &vrienden kunnen meedoen - + Only &registered users can join Alleen ge&registreerde gebruikers kunnen meedoen - + Joining restrictions Restricties voor meedoen - + &Spectators can watch Toe&schouwers kunnen meekijken - + Spectators &need a password to watch Toeschouwers hebben een wachtwoord &nodig om mee te kijken - + Spectators can &chat Toeschouwers kunnen &chatten - + Spectators can see &hands Toeschouwers kunnen &handen zien - + Create game as spectator Maak spel aan als toeschouwer - + Spectators Toeschouwers - + Starting life total: Beginnend levenstotaal: - + Open decklists in lobby Open decklijsten in lobby - + + Create game as judge + + + + Game setup options Spel opzet opties - + &Clear &Opschonen - + Create game Spel aanmaken - + Game information Spelgegevens - + Error Fout - + Server error. Server Fout @@ -2031,97 +2198,97 @@ Wil je het deck omzetten naar het .cod-formaat? DlgCreateToken - + &Name: &Naam: - + Token Token - + C&olor: Kle&ur: - + white wit - + blue blauw - + black zwart - + red rood - + green groen - + multicolor veelkleurig - + colorless kleurloos - + &P/T: &P/T: - + &Annotation: &Annotatie: - + &Destroy token when it leaves the table &Vernietig token als het de tafel verlaat - + Create face-down (Only hides name) Verdekt aanmaken (Verbergt alleen naam) - + Token data Token data - + Show &all tokens Laat &alle tokens zien - + Show tokens from this &deck Laat tokens van dit &dek zien - + Choose token from list Kies token uit lijst - + Create token Maak token @@ -2129,53 +2296,53 @@ Wil je het deck omzetten naar het .cod-formaat? DlgDefaultTagsEditor - + Edit Tags Tags Aanpassen - + Add Toevoegen - + Confirm Bevestig - + Cancel Annuleren - + Enter a tag and press Enter Vul tag in en druk Enter - - + + - + Invalid Input Ongeldige Invoer - + Tag name cannot be empty! Naam van tag mog niet leeg zijn! - + Duplicate Tag Tag Dupliceren - + This tag already exists. Deze tag bestaat al. @@ -2183,40 +2350,40 @@ Wil je het deck omzetten naar het .cod-formaat? DlgEditAvatar - - + + No image chosen. Geen afbeelding gekozen. - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. Om je avatar te veranderen, kies een nieuwe afbeelding. Om je avatar te verwijderen, accepteer zonder een nieuwe afbeelding te kiezen. - + Browse... Zoeken... - + Change avatar Verander Icoon - + Open Image Afbeelding openen - + Image Files (*.png *.jpg *.bmp) Afbeeldingen (*.png *.jpg *.bmp) - + Invalid image chosen. Geen geldige afbeelding gekozen @@ -2224,17 +2391,17 @@ Om je avatar te verwijderen, accepteer zonder een nieuwe afbeelding te kiezen. DlgEditDeckInClipboard - + Edit deck in clipboard Deck in klembord aanpassen... - + Error Foutmelding - + Invalid deck list. Niet geldige deck lijst. @@ -2242,38 +2409,38 @@ Om je avatar te verwijderen, accepteer zonder een nieuwe afbeelding te kiezen. DlgEditPassword - + Old password: Oud wachtwoord: - + New password: Nieuw wachtwoord: - + Confirm new password: Herhaal nieuw wachtwoord: - + Change password Verander wachtwoord - - + + Error Fout - + Your password is too short. Je wachtwoord is te kort. - + The new passwords don't match. Nieuwe wachtwoorden komen niet overeen. @@ -2281,93 +2448,93 @@ Om je avatar te verwijderen, accepteer zonder een nieuwe afbeelding te kiezen. DlgEditTokens - + &Name: &Naam: - + C&olor: Kle&ur: - + white wit - + blue blauw - + black zwart - + red rood - + green groen - + multicolor veelkleurig - + colorless kleurloos - + &P/T: &P/T: - + &Annotation: &Annotatie: - + Token data Token data - - + + Add token Voeg token toe - + Remove token Verwijder token - + Edit custom tokens Aangepaste tokens bewerken - + Please enter the name of the token: Gelieve de naam van de token in te voeren: - + Error Fout - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. De gekozen naam is al in gebruik door een bestaande kaart of token. @@ -2461,8 +2628,9 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo - Hide games not created by buddy - Verberg spellen die niet door vrienden zijn aangemaakt + Hide games not created by buddies + Hide games not created by buddy + @@ -2548,52 +2716,52 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo DlgForgotPasswordChallenge - + Reset Password Challenge Warning Wachtwoord Vergeten Challenge Waarschuwing - + A problem has occurred. Please try to request a new password again. Een probleem heeft zich opgetreden. Gelieve opnieuw een wachtwoord aan te vragen. - + Enter the information of the server and the account you'd like to request a new password for. Vul de gegevens in van de server en het account waar je een nieuw wachtwoord aan voor wil vragen. - + &Host: &Host: - + &Port: &Poort: - + Player &name: &Spelernaam: - + Email: E-mail: - + Reset Password Challenge Wachtwoord Vergeten Challenge - + Reset Password Challenge Error Foutmelding Wachtwoord Vergeten Challenge - + The email address can't be empty. Het e-mailadres mag niet leeg zijn. @@ -2601,37 +2769,37 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Vul de gegevens in van de server waar je een nieuw wachtwoord aan voor wil vragen. - + &Host: &Host: - + &Port: &Poort: - + Player &name: &Spelernaam: - + Reset Password Request Wachtwoord Vergeten Aanvraag - + Reset Password Error Foutmelding Wachtwoord Resetten - + The player name can't be empty. De spelernaam kan niet leeg zijn. @@ -2639,86 +2807,86 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo DlgForgotPasswordReset - + Reset Password Warning Waarschuwing Wachtwoord Resetten - + A problem has occurred. Please try to request a new password again. Een probleem heeft zich opgetreden. Gelieve opnieuw een wachtwoord aan te vragen. - + Enter the received token and the new password in order to set your new password. Vul het ontvangen token en het nieuwe wachtwoord in om je wachtwoord in te stellen. - + &Host: &Host: - + &Port: &Poort: - + Player &name: &Spelernaam: - + Token: Token: - - + + New Password: Nieuw Wachtwoord: - + Reset Password Reset Wachtwoord - + The player name can't be empty. De spelernaam kan niet leeg zijn. - - - - + + + + Reset Password Error Foutmelding Resetten Wachtwoord - + The token can't be empty. De token mag niet leeg zijn. - + The new password can't be empty. Het nieuwe wachtwoord mag niet leeg zijn. - + Error Fout - + Your password is too short. Uw wachtwoord is te kort. - + The passwords do not match. De wachtwoorden komen niet overeen. @@ -2734,17 +2902,17 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo DlgLoadDeckFromClipboard - + Load deck from clipboard Laad dek van klembord - + Error Fout - + Invalid deck list. Niet geldige deck lijst. @@ -2752,45 +2920,45 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Plak hier een link naar een decklijst site om deze te importeren. (Archidekt, Deckstats, Moxfield, en TappedOut worden ondersteund.) - - - - - + + + + + Load Deck from Website Deck van Website Laden. - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Geen lezer beschikbaar voor deze deck-leveraar. (Archidekt, Deckstats, Moxfield, en TappedOut worden ondersteund.) - + Network error: %1 Netwerk fout: %1. - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2804,7 +2972,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck Laad dek @@ -2812,37 +2980,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): Kaart naam (of zoekexpressies) - + Number of hits: Hoeveelheid resultaten: - + Auto play hits Speel resultaten automatisch - + Put top cards on stack until... Plaats bovenste kaart op stack totdat... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice Cockatrice - + Invalid filter Ongeldige filter @@ -2850,92 +3018,92 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Vul je gegevens en de gegevens van de server waar je wil registreren. Je email zal gebruikt worden om je account te controleren. - + &Host: &Eigenaar: - + &Port: &Poort: - + Player &name: &Naam van speler: - + P&assword: W&achtwoord: - + Password (again): Wachtwoord (opnieuw): - + Email: Email: - + Email (again): Email (opnieuw): - + Country: Land: - + Undefined Onbekend - + Real name: Echte naam: - + Register to server Registreer op server - - - - + + + + Registration Warning Registratie Waarschuwing - + Your password is too short. Je wachtwoord is te kort. - + Your passwords do not match, please try again. Uw wachtwoorden komen niet overeen, probeer opnieuw. - + Your email addresses do not match, please try again. Uw email adressen komen niet overeen, probeer opnieuw. - + The player name can't be empty. Spelersnaam kan niet leeg zijn. @@ -2961,40 +3129,55 @@ Je email zal gebruikt worden om je account te controleren. DlgSelectSetForCards - + Unmodified Cards: - + Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database Onbekende Fout in het lezen van de kaart database - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3011,7 +3194,7 @@ U zou oracle moeten herstarten om uw database te updaten. Zou u de locatie van uw database willen veranderen? - + Your card database version is too old. This can cause problems loading card information or images @@ -3028,7 +3211,7 @@ Dit zou mogelijk opgelost worden door oracle opnieuw te starten en je kaart data Wil je de locatie van je database veranderen? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3041,7 +3224,7 @@ Plaats een klacht op https://github.com/Cockatrice/Cockatrice/issues met uw card Wilt u uw database lokatie aanpassen? - + File Error loading your card database. Would you like to change your database location setting? @@ -3050,7 +3233,7 @@ Would you like to change your database location setting? Wilt u uw database lokatie aanpassen? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3059,7 +3242,7 @@ Would you like to change your database location setting? Wilt u uw database lokatie aanpassen? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3072,59 +3255,59 @@ Plaats een klacht op https://github.com/Cockatrice/Cockatrice/issues asltublieft Wilt u uw database lokatie aanpassen? - - - + + + Error Fout - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Het pad naar uw deck map is niet geldig. Wilt u terug gaan en de correcte pad invullen? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Het pad naar uw kaart afbeelding map is niet geldig. Wil je terug gaan en het juiste pad invullen? - + Settings Instellingen - + General Algemeen - + Appearance Weergave - + User Interface Gebruikersomgeving - + Card Sources Kaart Bronnen - + Chat Chat - + Sound Geluid - + Shortcuts Sneltoetsen @@ -3132,39 +3315,39 @@ Wilt u uw database lokatie aanpassen? DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3172,17 +3355,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next Volgende - + Previous Vorige - + Tip of the Day Tip van de Dag @@ -3190,165 +3373,165 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel Huidig releasekanaal - + Reinstall Herinstalleer - + Cancel Download Annuleer Downloaden - + Open Download Page Open de downloadpagina - + Check for Client Updates Controleren op Client-updates - - - - + + + + Error Fout - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice is niet gebouwd met SSL-ondersteuning, daarom kunt u niet automatisch updates downloaden! Ga naar de downloadpagina om handmatig bij te werken. - + Downloading update: %1 - + Checking for updates... Controleren op updates... - + Finished checking for updates Controle op updates voltooid - + No Update Available Geen Update Beschikbaar - + Cockatrice is up to date! Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. U draait al de laatste versie die beschikbaar is in het gekozen releasekanaal. - + Current version Huidige versie - + Selected release channel Geselecteerd releasekanaal - - + + Update Available Update Beschikbaar - - + + A new version of Cockatrice is available! Een nieuwe versie van Cockatrice is beschikbaar! - - + + New version Nieuwe versie - - + + Released Released - - + + Changelog Changelog - + Do you want to update now? Wilt u nu updaten? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + + Update Error Update Fout - + An error occurred while checking for updates: Er is een fout opgetreden bij het controleren op updates: - + An error occurred while downloading an update: Er is een fout opgetreden tijdens het downloaden van een update: - + Installing... Installeren... - + Cockatrice is unable to open the installer. Cockatrice kan het installatieprogramma niet openen. - + Try to update manually by closing Cockatrice and running the installer. Probeer handmatig bij te werken door Cockatrice te sluiten en het installatieprogramma uit te voeren. - + Download location Download locatie @@ -3356,21 +3539,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing Wis log bij sluiten - + Copy to clipboard Kopieer naar klembord - + Debug Log Debug Log + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3440,33 +3755,39 @@ You may have to manually download the new version. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3482,22 +3803,22 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete Bevestig Verwijderen - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. @@ -3505,22 +3826,22 @@ You may have to manually download the new version. GameEventHandler - + kicked by game host or moderator - + player left the game speler heeft het spel verlaten - + player disconnected from server - + reason unknown reden onbekend @@ -3528,226 +3849,279 @@ You may have to manually download the new version. GameSelector - - - - - - + + + + + + Error Fout - + Please join the appropriate room first. Gelieve eerst naar de juiste kamer te gaan. - + Wrong password. Verkeerd wachtwoord. - + Spectators are not allowed in this game. Toeschouwers zijn niet toegelaten in dit spel. - + The game is already full. Dit spel is reeds vol. - + The game does not exist any more. Het spel bestaat niet meer. - + This game is only open to registered users. Dit spel is alleen toegankelijk voor geregistreerde gebruikers. - + This game is only open to its creator's buddies. Dit spel staat alleen open voor de vrienden van de maker van dit spel. - + You are being ignored by the creator of this game. Je wordt genegeerd door de maker van dit spel. - + Join Game Aansluiten bij spel - + Spectate Game Spel Toeschouwen - + Game Information Spelgegevens - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game Aansluiten bij spel - + Password: Wachtwoord: - + Please join the respective room first. Gelieve eerst bij de betreffende kamer aan te sluiten. - + &Filter games &Filter spellen - + C&lear filter W&is filter - + C&reate C&reëer - + &Join &Aansluiten - + + Join as judge + + + + J&oin as spectator &Aansluiten als toeschouwer - + + Join as judge spectator + + + + Games shown: %1 / %2 Spellen weergegeven: %1 / %2 - + Games Spellen + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day >1 dag - + %1%2 hr short age in hours %1%2 uur%1%2 uur - + new nieuw - + %1%2 min short age in minutes %1%2 min%1%2 min - + password wachtwoord - + buddies only enkel vrienden - + reg. users only enkel geregistreerde gebruikers - + open decklists - - + + can chat kan praten - + see hands zie handen - + can see hands kan handen zien - + not allowed niet toegelaten - + Room Kamer - + Age Ouderdom - + Description Omschrijving - + Creator Maker - + Type Type - + Restrictions Restricties - + Players Spelers - + Spectators Toeschouwers @@ -3755,143 +4129,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths Reset alle bestandspaden - + All paths have been reset Alle bestandspaden zijn gereset - - - - - - - + + + + + + + Choose path Kies pad - + Personal settings Persoonlijke instellingen - + Language: Taal: - + Paths (editing disabled in portable mode) Locaties (bewerken uitgeschakeld in "portable mode") - + Paths Paden - + How to help with translations - + Decks directory: Map voor decks: - + Filters directory: - + Replays directory: Map voor replays: - + Pictures directory: Map voor afbeeldingen: - + Card database: Kaartdatabase: - + Custom database directory: Custom database map: - + Token database: Token-database - + Update channel Updatekanaal - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days dagen - + Notify if a feature supported by the server is missing in my client Waarschuw als een functie die is ondersteund door de server mist in mijn client - + Automatically run Oracle when running a new version of Cockatrice Oracle automatisch uitvoeren bij het opstarten van een nieuwe versie van Cockatrice - + Show tips on startup Toon tips bij het opstarten - + Last update check on %1 (%2 days ago) @@ -3899,47 +4273,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -3947,111 +4321,141 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - - &Sort hand + + Sort hand by... - - Take &mulligan + + Name - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - Reveal r&andom card to... + + + All players - - - &All players + + Reveal r&andom card to... HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + + Browse Archidekt + + + + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4253,61 +4657,61 @@ You may have to manually download the new version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. De server heeft zijn maximale gebruikerscapaciteit bereikt, controleer later nogmaals. - + There are too many concurrent connections from your address. Er zijn te veel gelijktijdige verbindingen vanaf uw adres. - + Banned by moderator Verbannen door moderator - + Expected end time: %1 Verwachte eindtijd: %1 - + This ban lasts indefinitely. Deze verbanning duurt voor onbepaalde tijd. - + Scheduled server shutdown. Geplande stillegging van de server. - - + + Invalid username. Ongeldige spelersnaam. - + You have been logged out due to logging in at another location. U bent uitgelogd omdat u op een andere locatie bent ingelogd. - + Connection closed Connectie verbroken - + The server has terminated your connection. Reason: %1 De server heeft uw verbinding beëindigd. Reden: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -4318,613 +4722,613 @@ Alle lopende spellen zullen verloren gaan. Reden voor de sluiting: %1 - + Scheduled server shutdown Geplande stillegging server - - + + Success Succesvol - + Registration accepted. Will now login. Registratie geaccepteerd. Logt nu in. - + Account activation accepted. Will now login. Account activering geaccepteerd. Logt nu in. - + Number of players Aantal spelers - + Please enter the number of players. Voer het aantal spelers in. - - + + Player %1 Speler %1 - + Load replay Laad replay - + About Cockatrice Over Cockatrice - + Version Versie - + Cockatrice Webpage Cockatrice Webpagina - + Project Manager: Project Manager: - + Past Project Managers: Vroegere projectmanagers: - + Developers: Ontwikkelaars: - + Our Developers Onze ontwikkelaars - + Help Develop! Help ontwikkelen! - + Translators: Vertalers: - + Our Translators Onze Vertalers - + Help Translate! Help Vertalen! - + Support: Steun: - + Report an Issue Een probleem melden - + Troubleshooting Probleemoplossing - + F.A.Q. F.A.Q. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error Fout - + Server timeout Time-out van de server - + Failed Login Gefaalde Login - + Your client seems to be missing features this server requires for connection. Uw client lijkt functies te missen die deze server nodig heeft voor de verbinding. - + To update your client, go to 'Help -> Check for Client Updates'. Om uw client bij te werken, gaat u naar 'Hulp -> Controleer op Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. Onjuiste gebruikersnaam of wachtwoord. Controleer uw verificatiegegevens en probeer het opnieuw. - + There is already an active session using this user name. Please close that session first and re-login. Er is al een actieve sessie bezig met deze gebruikersnaam. Gelieve die sessie eerst af te sluiten en opnieuw in te loggen. - - + + You are banned until %1. Je bent banned tot %1. - - + + You are banned indefinitely. Je bent voor onbepaalde tijd banned. - + This server requires user registration. Do you want to register now? Deze server vereist een gebruikersregistratie. Wilt u zich nu registreren? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. Deze server vereist client ID's. Uw client slaagt er niet in een ID te genereren of u heeft een aangepaste client. Gelieve uw client te sluiten en te heropenen om het opnieuw te proberen. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. Er is een interne fout opgetreden, gelieve uw client te sluiten en te heropenen alvorens het opnieuw te proberen. Als de fout zich blijft voordoen, controleer dan of u de laatste versie van de software gebruikt en neem indien nodig contact op met de softwareontwikkelaars. - + Account activation Account activering - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Uw account is nog niet geactiveerd. U moet de activeringscode die u in de activeringsmail hebt ontvangen, invullen. - + Server Full Server Vol - + Unknown login error: %1 Onbekende aanmeldingsfout: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Dit betekent meestal dat uw clientversie verouderd is en dat de server een antwoord heeft gestuurd dat uw client niet begrijpt. - + Your username must respect these rules: Uw gebruikersnaam moet aan deze regels voldoen: - + is %1 - %2 characters long is %1 - %2 karakters lang - + can %1 contain lowercase characters kan %1 kleine letters bevatten. - - - - + + + + NOT NIET - + can %1 contain uppercase characters kan %1 hoofdletters bevatten - + can %1 contain numeric characters kan %1 numerieke tekens bevatten - + can contain the following punctuation: %1 kan de volgende leestekens bevatten: %1 - + first character can %1 be a punctuation mark eerste teken kan %1 een leesteken zijn. - + no unacceptable language as specified by these server rules: note that the following lines will not be translated geen onacceptabel taalgebruik als aangegeven door deze server regels: - + can not contain any of the following words: %1 mag niet een van de volgende woorden bevatten: %1 - + can not match any of the following expressions: %1 mag niet overeenkomen met een van de volgende expressies: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. U mag alleen A-Z, a-z, 0-9, _, ., en - in uw gebruikersnaam gebruiken. - - - - - - + + + + + + Registration denied Registratie geweigerd - + Registration is currently disabled on this server Registratie is momenteel uitgeschakeld op deze server. - + There is already an existing account with the same user name. Er bestaat al een bestaand account met dezelfde gebruikersnaam. - + It's mandatory to specify a valid email address when registering. Het is verplicht om een geldig e-mailadres op te geven bij de registratie. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. Het lijkt erop dat u probeert een nieuw account te registreren op deze server, maar dat u al een account hebt geregistreerd met de verstrekte e-mail. Deze server beperkt het aantal accounts dat een gebruiker per adres kan registreren. Neem contact op met de serverbeheerder voor verdere hulp of om uw gegevens te verkrijgen. - + Password too short. Wachtwoord te kort. - + Registration failed for a technical problem on the server. De registratie is mislukt wegens een technisch probleem op de server. - + The connection to the server has been lost. - + Unknown registration error: %1 Onbekende registratiefout: %1 - + Account activation failed Account activering mislukt - + Socket error: %1 Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. U probeert verbinding te maken met een verouderde server. Gelieve uw Cockatrice versie te downgraden of verbinding te maken met een geschikte server. Lokale versie is %1, externe versie is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Uw Cockatrice klant is verouderd. Gelieve uw Cockatrice versie bij te werken. Lokale versie is %1, externe versie is %2. - + Connecting to %1... Verbinding maken met %1.... - + Registering to %1 as %2... Registreren bij %1 als %2.... - + Disconnected Verbinding verbroken - + Connected, logging in at %1 Verbonden, inloggen bij %1 + - Requesting forgotten password to %1 as %2... Aanvragen van vergeten wachtwoord tot %1 als %2... - + &Connect... &Verbinden.... - + &Disconnect V&erbinding verbreken - + Start &local game... Start &lokaal spel.... - + &Watch replay... &Bekijk replay... - + &Full screen &Volledig scherm - + &Register to server... &Registreer bij server... - + &Restore password... &Reset wachtwoord... - + &Settings... &Instellingen.... - + &Exit &Afsluiten - + A&ctions Act&ies - + &Cockatrice &Cockatrice - + C&ard Database &Kaartdatabase: - + &Manage sets... &Beheer sets... - + Edit custom &tokens... Aangepaste &tokens bewerken - + Open custom image folder Open map voor aangepaste afbeeldingen - + Open custom sets folder Open aangepaste sets map - + Add custom sets/cards Aangepaste sets/kaarten toevoegen - + Reload card database - + Tabs - + &Help &Hulp - + &About Cockatrice &Over Cockatrice - + &Tip of the Day &Tip van de Dag - + Check for Client Updates Controleren op Client-updates - + Check for Card Updates... Kijk of er kaartupdates zijn... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log Bekijk &debug log - + Open Settings Folder - + Show/Hide Tonen/Verbergen - + New Version Niewe Versie - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Gefeliciteerd met de update naar Cockatrice %1! Oracle zal nu starten om uw kaartendatabase bij te werken. - + Cockatrice installed Cockatrice geinstalleerd - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Gefeliciteerd met het installeren van Cockatrice %1! Oracle zal nu starten om uw kaartendatabase bij te werken. - + Card database Kaartdatabase - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" Cockatrice kan de kaartendatabase niet laden. - - + + Yes Ja - - + + No Nee - + Open settings Open instellingen - + New sets found Nieuwe sets gevonden - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -4935,17 +5339,17 @@ Set codes: %1 Wilt u deze inschakelen? - + View sets Bekijk sets - + Welcome Welkom - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -4954,64 +5358,64 @@ Alle sets in de kaartendatabase zijn ingeschakeld. Lees meer over het wijzigen van de setvolgorde of het uitschakelen van specifieke sets en de daaruit voortvloeiende effecten in het "Beheer sets" dialoogvenster. - - + + Information Informatie - + A card database update is already running. Een update van de kaartendatabase is al aan de gang. - + Unable to run the card database updater: Kan het updateprogramma van de kaartendatabase niet uitvoeren: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5022,673 +5426,843 @@ Dit is waarschijnlijk geen probleem, maar dit bericht kan betekenen dat er een n Om uw client bij te werken, ga naar Hulp -> Controleer op updates. - - - - - + + + + + Load sets/cards Laad sets/kaarten - + Selected file cannot be found. Geselecteerd bestand kan niet worden gevonden. - + You can only import XML databases at this time. U kunt op dit moment alleen XML-databases importeren. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. De nieuwe sets/kaarten zijn succesvol toegevoegd. Cockatrice zal nu de kaartendatabase herladen. - + Sets/cards failed to import. Sets/kaarten zijn niet geïmporteerd. - - - + + + Reset Password Reset Wachtwoord - + Your password has been reset successfully, you can now log in using the new credentials. Uw wachtwoord is succesvol gereset, u kunt nu inloggen met de nieuwe gegevens. - + Failed to reset user account password, please contact the server operator to reset your password. Het opnieuw instellen van het wachtwoord voor uw gebruikersaccount is mislukt, neem contact op met de serverbeheerder om uw wachtwoord opnieuw in te stellen. - + Activation request received, please check your email for an activation token. Activeringsaanvraag ontvangen, kijk in uw e-mail voor een activeringscode. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play vanuit spel - + from their graveyard vanuit hun graveyard. - + from exile vanuit exile - + from their hand vanuit hun hand. - + the top card of %1's library de bovenste kaart %1's library - + the top card of their library de bovenste kaart van hun library - + from the top of %1's library van de top van %1's library - + from the top of their library van de bovenkant van hun library - + the bottom card of %1's library de onderste kaart van %1's library - + the bottom card of their library de onderste kaart van hun library - + from the bottom of %1's library van de onderkant van %1's library - + from the bottom of their library van de onderkant van hun library - + from %1's library van %1's library - + from their library van hun library - + from sideboard van sideboard - + from the stack van de stack - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 houdt nu de bovenste kaart %2 onthuld. - + %1 is not revealing the top card %2 any longer. %1 geeft de bovenste kaart %2 niet meer weer. - + %1 can now look at top card %2 at any time. %1 kan nu altijd de bovenste kaart %2 bekijken. - + %1 no longer can look at top card %2 at any time. %1 kan nu niet meer de bovenste kaart %2 bekijken. - + %1 attaches %2 to %3's %4. %1 bevestigt %2 aan %3s %4. - + %1 has conceded the game. %1 heeft het spel opgegeven. - + %1 has unconceded the game. %1 is terug in het spel. - + %1 has restored connection to the game. %1 heeft de verbinding met het spel hersteld. - + %1 has lost connection to the game. %1 heeft de verbinding met het spel verloren. - + %1 points from their %2 to themselves. %1 wijst van hun %2 naar zichzelf. - + %1 points from their %2 to %3. %1 wijst van hun %2 naar %3. - + %1 points from %2's %3 to themselves. %1 wijst van %2s %3 naar zichzelf. - + %1 points from %2's %3 to %4. %1 wijst van %2s %3 naar %4. - + %1 points from their %2 to their %3. %1 wijst van hun %2 naar hun %3. - + %1 points from their %2 to %3's %4. %1 wijst van hun %2 naar %3s %4. - + %1 points from %2's %3 to their own %4. %1 wijst van %2s %3 naar hun eigen %4. - + %1 points from %2's %3 to %4's %5. %1 wijst van %2s %3 naar %4s %5. - + %1 creates a face down token. - + %1 creates token: %2%3. %1 creëert een token: %2%3. - + %1 has loaded a deck (%2). %1 heeft een deck geladen (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 heeft een deck met %2 sideboard kaarten geladen (%3). - + %1 destroys %2. %1 vernietigt %2. - + a card een kaart - + %1 gives %2 control over %3. %1 geeft %2 controle over %3. - + %1 puts %2 into play%3 face down. %1 zet %2 gesloten in het spel%3. - + %1 puts %2 into play%3. %1 zet %2 in het spel%3. - + %1 puts %2%3 into their graveyard. %1 legt %2%3 in hun graveyard. - + %1 exiles %2%3. %1 exiled %2%3. - + %1 moves %2%3 to their hand. %1 verplaatst %2%3 naar hun hand. - + %1 puts %2%3 into their library. %1 legt %2%3 in hun library. - + %1 puts %2%3 onto the bottom of their library. %1 legt %2%3 aan de onderkant van hun library. - + %1 puts %2%3 on top of their library. %1 legt %2%3 op de bovenkant van hun library. - + %1 puts %2%3 into their library %4 cards from the top. %1 plaatst %2%3 in hun library %4 kaarten vanaf de bovenkant. - + %1 moves %2%3 to sideboard. %1 verplaatst %2%3 naar het sideboard. - + %1 plays %2%3. %1 speelt %2%3. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1 probeert een kaart te nemen van een lege library - + %1 draws %2 card(s). %1 trekt een kaart%1 trekt %2 kaarten - + %1 is looking at %2. %1 is aan het kijken naar %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. %1 draait %2 om met de voorkant naar beneden. - + %1 turns %2 face-up. %1 draait %2 om met de voorkant naar boven. - + The game has been closed. Het spel is afgesloten. - + The game has started. Het spel is begonnen. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 heeft zich bij het spel aangesloten. - + %1 is now watching the game. %1 kijkt nu naar het spel. - + You have been kicked out of the game. Je bent uit het spel geschopt. - + %1 has left the game (%2). %1 heeft het spel verlaten (%2). - + %1 is not watching the game any more (%2). %1 kijkt niet meer naar het spel (%2). - + %1 is not ready to start the game any more. %1 is niet meer klaar om het spel te starten. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 schudt zijn of haar deck en raapt een nieuwe hand van %2 kaart.%1 schudt zijn of haar deck en raapt een nieuwe hand van %2 kaarten. - + %1 shuffles their deck and draws a new hand. %1 schudt zijn of haar deck en trekt een nieuwe hand. - + You are watching a replay of game #%1. Je kijkt naar een replay van spel #%1. - + %1 is ready to start the game. %1 is klaar om het spel te starten. - + cards an unknown amount of cards kaarten - + %1 card(s) a card for singular, %1 cards for plural één kaart%1 kaarten - + %1 lends %2 to %3. %1 leent %2 aan %3. - + %1 reveals %2 to %3. %1 onthult %2 naar %3. - + %1 reveals %2. %1 onthult %2. - + %1 randomly reveals %2%3 to %4. %1 onthult %2%3 aan %4 gebaseerd op willekeur. - + %1 randomly reveals %2%3. %1 onthult %2%3 gebaseerd op willekeur. - + %1 peeks at face down card #%2. %1 bekijkt verdekte kaart #%2. - + %1 peeks at face down card #%2: %3. %1 bekijkt verdekte kaart #%2: %3. - + %1 reveals %2%3 to %4. %1 onthult %2%3 aan %4. - + %1 reveals %2%3. %1 onthult %2%3. - + %1 reversed turn order, now it's %2. %1 keert de beurtvolgorde om, deze is nu %2. - + reversed omgekeerd - + normal normaal - + Heads Kop - + Tails Munt - + %1 flipped a coin. It landed as %2. %1 gooit een muntje om. Het landde %2. - + %1 rolls a %2 with a %3-sided die. %1 rolt een %2 met een %3-zijdige dobbelsteen. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 werpt %2 munten. Er zijn %3 koppen en %4 munten. - + %1 rolls a %2-sided dice %3 times: %4. %1 werpt een %2-zijdige dobbelsteen %3 keer: %4. - + %1's turn. %1s beurt. - + %1 sets annotation of %2 to %3. %1 verandert de annotatie van %2 naar %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 verandert teller %2 naar %3 (%4%5). - + %1 sets %2 to not untap normally. %1 zet %2 op niet tegelijk ontappen. - + %1 sets %2 to untap normally. %1 zet %2 op tegelijk ontappen. - + %1 removes the PT of %2. %1 verwijdert de stats van %2. - + %1 changes the PT of %2 from nothing to %4. %1 verandert de stats van %2 van niks naar %4. - + %1 changes the PT of %2 from %3 to %4. %1 verandert de stats van %2 van %3 naar %4. - + %1 has locked their sideboard. %1 heeft hun sideboard vergrendeld. - + %1 has unlocked their sideboard. %1 heeft hun sideboard ontgrendeld. - + %1 taps their permanents. %1 tapt alles. - + %1 untaps their permanents. %1 ontapt alles. - + %1 taps %2. %1 tapt %2. - + %1 untaps %2. %1 ontapt %2. - + %1 shuffles %2. %1 schudt %2. - + %1 shuffles the bottom %3 cards of %2. %1 schudt de onderste %3 kaarten van %2. - + %1 shuffles the top %3 cards of %2. %1 schudt de bovenste %3 kaarten van %2. - + %1 shuffles cards %3 - %4 of %2. %1 schudt kaarten %3 - %4 van %2. - + %1 unattaches %2. %1 maakt %2 los. - + %1 undoes their last draw. %1 legt hun laatst getrekte kaart terug. - + %1 undoes their last draw (%2). %1 legt hun laats getrekte kaart terug (%2). @@ -5696,110 +6270,110 @@ Cockatrice zal nu de kaartendatabase herladen. MessagesSettingsPage - + Word1 Word2 Word3 Woord1 Woord2 Woord3 - + Add New Message Bericht toevoegen - + Edit Message Bericht bewerken - + Remove Message Bericht Verwijderen - + Add message Bericht toevoegen - - + + Message: Bericht: - + Edit message Bericht bewerken - + Chat settings Chat instellingen - + Custom alert words Aanpasbare alerteringswoorden - + Enable chat mentions Schakel chat vermeldingen in - + Enable mention completer Vermeldings aanvuller inschakelen - + In-game message macros In-game bericht macro's - + How to use in-game message macros Uitleg gebruik van in-game bericht macro's - + Ignore chat room messages sent by unregistered users Chatroomberichten van niet-geregistreerde gebruikers negeren - + Ignore private messages sent by unregistered users Privéberichten van niet-geregistreerde gebruikers negeren - - + + Invert text color Text kleur inverteren - + Enable desktop notifications for private messages Desktopmeldingen voor privéberichten inschakelen - + Enable desktop notification for mentions Desktopmelding voor vermeldingen inschakelen - + Enable room message history on join Kamerboodschapsgeschiedenis op aansluiting inschakelen - - + + (Color is hexadecimal) (Kleur is hexadecimaal) - + Separate words with a space, alphanumeric characters only Woorden scheiden met een spatie, alleen alfanumerieke tekens @@ -5916,62 +6490,62 @@ Cockatrice zal nu de kaartendatabase herladen. Phase - + Unknown Phase Onbekende fase - + Untap Untap - + Upkeep Upkeep - + Draw Rapen - + First Main Eerste Hoofdfase - + Beginning of Combat Begin Gevecht - + Declare Attackers Aanvallers Aangeven - + Declare Blockers Verdedigers Aangeven - + Combat Damage Gevecht Schade - + End of Combat Einde Gevecht - + Second Main Tweede Hoofdfase - + End/Cleanup Einde/Opruimen @@ -6037,7 +6611,7 @@ Cockatrice zal nu de kaartendatabase herladen. PictureLoader - + en code for scryfall's language property, not available for all languages en @@ -6046,134 +6620,134 @@ Cockatrice zal nu de kaartendatabase herladen. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - + Move top cards to grave - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6181,17 +6755,17 @@ Cockatrice zal nu de kaartendatabase herladen. PlayerMenu - + Player "%1" - + &Counters - + S&ay @@ -6199,7 +6773,7 @@ Cockatrice zal nu de kaartendatabase herladen. PrintingSelector - + Display Navigation Buttons @@ -6207,22 +6781,22 @@ Cockatrice zal nu de kaartendatabase herladen. PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6238,17 +6812,17 @@ Cockatrice zal nu de kaartendatabase herladen. PrintingSelectorCardSelectionWidget - + Previous Card in Deck - + Bulk Selection - + Next Card in Deck @@ -6256,28 +6830,28 @@ Cockatrice zal nu de kaartendatabase herladen. PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -6343,37 +6917,37 @@ Cockatrice zal nu de kaartendatabase herladen. QMenuBar - + Services Diensten - + Hide %1 Verberg %1 - + Hide Others Verberg Anderen - + Show All Toon Alle - + Preferences... Voorkeuren... - + Quit %1 Stop %1 - + About %1 Over %1 @@ -6381,42 +6955,42 @@ Cockatrice zal nu de kaartendatabase herladen. QObject - + Cockatrice card database (*.xml) Cockatrice kaartendatabase (*.xml) - + All files (*.*) Alle bestanden (*.*) - + Cockatrice replays (*.cor) Cockatrice replays (*.cor) - + Maindeck Maindeck - + Sideboard Sideboard - + Tokens Tokens - + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? @@ -6424,92 +6998,92 @@ Cockatrice zal nu de kaartendatabase herladen. QPlatformTheme - + OK OK - + Save Opslaan - + Save All Alles Opslaan - + Open Open - + &Yes &Ja - + Yes to &All Ja op A&lle - + &No &Nee - + N&o to All Nee op &Alle - + Abort Afbreken - + Retry Opnieuw proberen - + Ignore Negeren - + Close Sluiten - + Cancel Annuleren - + Discard Afleggen - + Help Hulp - + Apply Toepassen - + Reset Resetten - + Restore Defaults Standaardinstellingen herstellen @@ -6606,37 +7180,37 @@ Cockatrice zal nu de kaartendatabase herladen. RoomSelector - + Rooms Kamers - + Joi&n &Deelnemen - + Room Kamer - + Description Omschrijving - + Permissions Toestemmingen - + Players Spelers - + Games Spellen @@ -6677,27 +7251,27 @@ Cockatrice zal nu de kaartendatabase herladen. SetsModel - + Enabled Ingeschakeld - + Set type Set type - + Set code Set code - + Long name Lange naam - + Release date releasedatum @@ -6705,53 +7279,53 @@ Cockatrice zal nu de kaartendatabase herladen. ShortcutSettingsPage - - + + Restore all default shortcuts Alle standaard snelkoppelingen herstellen - + Do you really want to restore all default shortcuts? Wilt u echt alle standaard snelkoppelingen herstellen? - + Clear all default shortcuts Alle standaard snelkoppelingen wissen - + Do you really want to clear all shortcuts? Wil je echt alle snelkoppelingen vrijmaken? - + Section: Sectie: - + Action: Functie: - + Shortcut: Sneltoets: - + How to set custom shortcuts Hoe aangepaste snelkoppelingen in te stellen - + Clear all shortcuts Alle sneltoetsen wissen - + Search by shortcut name @@ -6759,12 +7333,12 @@ Cockatrice zal nu de kaartendatabase herladen. ShortcutTreeView - + Action - + Shortcut @@ -6772,14 +7346,14 @@ Cockatrice zal nu de kaartendatabase herladen. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! Uw configuratiebestand bevatte ongeldige snelkoppelingen. Controleer uw snelkoppelingsinstellingen! - + The following shortcuts have been set to default: De volgende snelkoppelingen zijn op de standaardinstelling ingesteld: @@ -6807,12 +7381,12 @@ Controleer uw snelkoppelingsinstellingen! SideboardMenu - + &Sideboard - + &View sideboard @@ -6820,27 +7394,27 @@ Controleer uw snelkoppelingsinstellingen! SoundSettingsPage - + Enable &sounds &Geluiden inschakelen - + Current sounds theme: Huidige geluiden thema: - + Test system sound engine Test geluid engine van systeem - + Sound settings Geluidsinstellingen - + Master volume Hoofdvolume @@ -6848,48 +7422,48 @@ Controleer uw snelkoppelingsinstellingen! SpoilerBackgroundUpdater - + Spoilers season has ended Spoilers seizoen is afgelopen - + Deleting spoiler.xml. Please run Oracle Verwijdert spoiler.xml. Gelieve Oracle te draaien - - + + Spoilers download failed Spoilers downloaden mislukt - + No internet connection Geen internetverbinding - + Error Fout - + Spoilers already up to date Spoilers reeds up-to-date - + No new spoilers added Geen nieuwe spoilers toegevoegd - + Spoilers have been updated! Spoilers zijn bijgewerkt! - + Last change: Laatste Wijziging: @@ -7053,56 +7627,123 @@ Controleer uw snelkoppelingsinstellingen! + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info Kaart info - + Deck Deck - + Filters Filters - + &View &Bekijk - + + Card Database + + + + Printing - - - - + + + + + Visible Zichtbaar - - - - + + + + + Floating Zwevend - + Reset layout Layout resetten - + Deck: %1 Dek: %1 @@ -7110,61 +7751,61 @@ Controleer uw snelkoppelingsinstellingen! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + + + + Visible - - - - + + + + Floating - + Reset layout @@ -7172,22 +7813,22 @@ Controleer uw snelkoppelingsinstellingen! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7195,133 +7836,133 @@ Controleer uw snelkoppelingsinstellingen! TabDeckStorage - + Local file system Lokaal bestandssysteem - + Server deck storage Dekopslag op de server - - + + Open in deck editor Open dek in editor - + Rename deck or folder - + Upload deck Upload dek - + Download deck Download dek + - - - + + New folder Nieuwe folder + - Delete Verwijder - + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + Error Foutmelding - + Rename failed - - + + Invalid deck file Ongeldig deck bestand - + Enter deck name Geeft dek naam in - + This decklist does not have a name. Please enter a name: De deklijst heeft geen naam. Geef een naam in aub. - + Unnamed deck Onbenoemd dek - + Failed to upload deck to server Uploaded van deck naar server mislukt - + Delete local file Verwijder lokaal bestand - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + Name of new folder: Naam van de folder: @@ -7334,17 +7975,17 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 @@ -7385,7 +8026,7 @@ Please enter a name: - + EDHRec: @@ -7393,197 +8034,197 @@ Please enter a name: TabGame - - - + + + Replay Replay - - + + Game Spel - - + + Player List Spelerslijst - - + + Card Info Kaartinfo - - + + Messages Berichten - - + + Replay Timeline Replay Tijdslijn - + &Phases Fases - + &Game Spel - + Next &phase Volgende fase - + Next phase with &action Volgende fase met &handeling - + Next &turn Volgende beurt - + Reverse turn order Keer beurtvolgorde om - + &Remove all local arrows Verwijder alle lokale pijlen - + Rotate View Cl&ockwise Draai de weergave met de klok &mee - + Rotate View Co&unterclockwise Draai de weergave &tegen de klok in - + Game &information Spelgegevens - + Un&concede - - - + + + &Concede &Geef op - + &Leave game Verlaat spel - + C&lose replay Sluit opgeslagen spel - + &Focus Chat &Focus Chat - + &Say: Zeg: - + Selected cards - + &View &Bekijk - - + + + - Visible Zichtbaar - - + + + - Floating Zwevend - + Reset layout Reset lay-out - + Concede Opgeven - + Are you sure you want to concede this game? Ben je zeker dat je wilt opgeven voor dit spel? - + Unconcede Opgeven ongedaan maken - + You have already conceded. Do you want to return to this game? Je hebt al opgegeven. Wil je nar dit spel terugkeren? - + Leave game Verlaat spel - + Are you sure you want to leave this game? Ben je zeker dat je dit spel wilt verlaten? - + A player has joined game #%1 Een speler sluitte aan bij spel #%1 - + %1 has joined the game %1 sluitte bij het spel aan - + You have been kicked out of the game. Je bent uit het spel gegooid. @@ -7591,7 +8232,7 @@ Please enter a name: TabHome - + Home @@ -7599,158 +8240,158 @@ Please enter a name: TabLog - + Logs Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Tijd;Afzendernaam;AfzenderIP; Bericht;DoelID;DoelNaam - + Room Logs Kamerlogboeken - + Game Logs Spellogboeken - + Chat Logs Chat Logs - - + + Error Fout - + You must select at least one filter. U moet ten minste één filter selecteren. - + You have to select a valid number of days to locate. U moet een geldig aantal dagen selecteren om te zoeken. - + Username: Gebruikersnaam: - + IP Address: IP-adres: - + Game Name: Spelnaam: - + GameID: GameID: - + Message: Boodschap: - + Main Room Hoofdkamer - + Game Room Spelkamer - + Private Chat Privéchat - + Past X Days: Afgelopen X dagen: - + Today Vandaag - + Last Hour Afgelopen uur - + Maximum Results: Maximale resultaten: - + At least one filter is required. The more information you put in, the more specific your results will be. Er is minstens één filter nodig. Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn. - + Get User Logs Gebruikerslogboeken ontvangen - + Clear Filters Wis filters - + Filters Filters - + Log Locations Loglokaties - + Date Range Datumbereik - + Maximum Results Maximale resultaten - - + + Message History Bericht Geschiedenis - + Failed to collect message history information. Het is mislukt om informatie over de berichtgeschiedenis te verzamelen. - + There are no messages for the selected filters. Er zijn geen berichten voor de geselecteerde filters. @@ -7796,180 +8437,180 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn. TabReplays - + Local file system Lokaal bestandssysteem - + Server replay storage Opslag gespeelde spellen op server - - + + Watch replay Bekijk opgeslagen spel - + Rename - - + + New folder - - + + Delete Verwijder - + Open replays folder - + Download replay Download opgeslagen spel. - + Toggle expiration lock Schakel expiratieslot om - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file Verwijder lokaal opgeslagen spel - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay Verwijder op server opgeslagen spel @@ -8035,30 +8676,30 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn.Server + - Error Fout - + Failed to join the server room: it doesn't exist on the server. Aansluiten bij kamer mislukt: deze bestaat niet op de server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. De server denkt dat je in de kamer bent, maar de client is niet in staat om deze te tonen. Probeer de client opnieuw te starten. - + You do not have the required permission to join this server room. U heeft niet de vereiste toestemming om bij deze kamer aan te sluiten. - + Failed to join the server room due to an unknown error: %1. Mislukt om bij de kamer aan te sluiten door een onbekende fout: %1. @@ -8066,92 +8707,97 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - - - Home - - - - - &Visual Deck Storage - - - - - Visual Database Display - - - Server + Archidekt - Account + Home - Deck Storage + &Visual Deck Storage - Game Replays + Visual Database Display - Administration + Server + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + Logs - + Are you sure? Weet je het zeker? - + There are still open games. Are you sure you want to quit? Er staan nog spellen open. Ben je zeker dat je wilt afsluiten? - + Click to view Klik om weer te geven - + Your buddy %1 has signed on! Je maatje %1 heeft in gelogd! - + Unknown Event Onbekend Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8162,39 +8808,39 @@ Dit bericht kan betekenen dat er een nieuwe versie van Cockatrice beschikbaar is Om uw client bij te werken, ga naar Hulp -> Controleer op updates. - + Idle Timeout Inactiviteit Time-out - + You are about to be logged out due to inactivity. U staat op het punt uitgelogd te worden wegens inactiviteit. - + Promotion Promotie - + You have been promoted. Please log out and back in for changes to take effect. Je bent bevorderd. Gelieve uit te loggen en weer in te loggen om de wijzigingen in werking te laten treden. - + Warned Gewaarschuwd - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. U heeft een waarschuwing ontvangen wegens %1. Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties tegen u worden ondernomen. Als u vragen heeft, kunt u een privé-bericht sturen naar een moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) U heeft het volgende bericht van de server ontvangen. @@ -8204,7 +8850,12 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display @@ -8286,7 +8937,7 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties UpdateDownloader - + Could not open the file for reading. Kon het bestand niet openen om te lezen. @@ -8294,206 +8945,206 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties UserContextMenu - + User &details Gebruikersdetails - + Private &chat Privaat gesprek - + Show this user's &games Toon spellen van deze gebruiker - + Add to &buddy list Voeg toe aan vriendenlijst - + Remove from &buddy list Verwijder uit vriendenlijst - + Add to &ignore list Voeg toe aan negatielijst - + Remove from &ignore list Verwijder uit negatielijst - + Kick from &game Gooi uit spel - + Warn user Waarschuw gebruiker - + View user's war&n history Bekijk waarschuwings&geschiedenis van gebruiker - + Ban from &server Verban van de &server - + View user's &ban history Bekijk &bangeschiedenis van gebruiker - + &Promote user to moderator &Bevorder gebruiker tot moderator - + Dem&ote user from moderator Demoteer gebruiker van moderator - + Promote user to &judge Bevorder gebruiker tot &judge - + Demote user from judge Demoteer gebruiker van judge - + View admin notes - - - + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games %1's spellen - - - + + + Ban History Ban Geschiedenis - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Ban Tijd;Moderator;Ban Lengte; Ban Reden; Zichtbare Reden - + User has never been banned. De gebruiker is nog nooit gebant. - + Failed to collect ban information. Mislukt in het verzamelen van gegevens over ban. - - - + + + Warning History Waarschuwingsgeschiedenis - + Warning Time;Moderator;User Name;Reason Waarschuwingstijd;Moderator;Gebruikersnaam;Reden - + User has never been warned. De gebruiker is nog nooit gewaarschuwd. - + Failed to collect warning information. Mislukt in het verzamelen van gegevens over waarschuwing. - + Failed to get admin notes. - - + + Success Succesvol - + Successfully promoted user. Succesvolle promotie van gebruiker. - + Successfully demoted user. Succesvolle demotie van gebruiker. - + + - Failed Mislukt - + Failed to promote user. Mislukt in het bevorderen van gebruiker. - + Failed to demote user. De gebruiker is niet gedemoteerd. - + Copy hash to clipboard Kopieer hash naar klembord - + Remove this user's messages Verwijder de berichten van deze gebruiker @@ -8675,137 +9326,142 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties UserInterfaceSettingsPage - + General interface settings Algemene weergave-instellingen - + &Double-click cards to play them (instead of single-click) Dubbel-klik op kaarten om ze te spelen (in plaats van enkele klik) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default Speel standaard alle niet-landen op de stapel (niet op het slagveld) - - - Close card view window when last card is removed - - - - - Auto focus search bar when card view window is opened - - - - - Annotate card text on tokens - Maak aantekeningen bij de kaarttekst op tokens - - - - Use tear-off menus, allowing right click menus to persist on screen - Gebruik tear-off menus, laat rechtermuisknopmenu's op het scherm staan - - - - Notifications settings - Notificatie instellingen - - - - Enable notifications in taskbar - Activeer meldingen in de taakbalk - - - - Notify in the taskbar for game events while you are spectating - Meld spelgebeurtenissen in de taakbalk terwijl je toekijkt - - - - Notify in the taskbar when users in your buddy list connect - Notificatie in de taakbalk wanneer gebruikers in je buddy lijst verbinding maken - - - - Animation settings - Animatie-instellingen - - - - &Tap/untap animation - &Tap/Untap animatie - - Deck editor/storage settings + Do not delete &arrows inside of subphases - Open deck in new tab by default + Close card view window when last card is removed - Use visual deck storage in game lobby + Auto focus search bar when card view window is opened - Use selection animation for Visual Deck Storage - + Annotate card text on tokens + Maak aantekeningen bij de kaarttekst op tokens + + + + Use tear-off menus, allowing right click menus to persist on screen + Gebruik tear-off menus, laat rechtermuisknopmenu's op het scherm staan - When adding a tag in the visual deck storage to a .txt deck: - + Notifications settings + Notificatie instellingen + + + + Enable notifications in taskbar + Activeer meldingen in de taakbalk - do nothing - + Notify in the taskbar for game events while you are spectating + Meld spelgebeurtenissen in de taakbalk terwijl je toekijkt + + + + Notify in the taskbar when users in your buddy list connect + Notificatie in de taakbalk wanneer gebruikers in je buddy lijst verbinding maken - ask to convert to .cod - + Animation settings + Animatie-instellingen + + + + &Tap/untap animation + &Tap/Untap animatie - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -8813,22 +9469,22 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 @@ -8836,32 +9492,32 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -8869,22 +9525,22 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match - + Mode: Includes - + Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -8892,21 +9548,49 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8932,28 +9616,28 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - Load from Deck + Filter by name... (Exact match) - Apply all card names in currently loaded deck as exact match name filters + Load from Deck - Load from Clipboard + Apply all card names in currently loaded deck as exact match name filters + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -9017,50 +9701,115 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - + + Sort by: + + + + + Filter by: + + + + Save and load filters - + Filter by exact card name - + Filter by card sub-type - + Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9068,46 +9817,25 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -9163,7 +9891,7 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9171,22 +9899,22 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -9194,17 +9922,17 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + Visual Deck Storage Settings @@ -9212,43 +9940,43 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties WarningDialog - + Which warning would you like to send? Welke waarschuwing wilt u sturen? - + Redact all messages from this user in all rooms Verwijder alle berichten van deze gebruiker in alle kamers - + &OK &OK - + &Cancel &Cancel - + Warn user for misconduct Waarschuw gebruiker voor wangedrag - - + + Error Fout - + User name to send a warning to can not be blank, please specify a user to warn. Gebruikersnaam om een waarschuwing naar te sturen kan niet leeg zijn, geef een gebruiker op om te waarschuwen. - + Warning to use can not be blank, please select a valid warning to send. Te gebruiken waarschuwing kan niet leeg zijn, selecteer een geldige waarschuwing om te verzenden. @@ -9256,133 +9984,133 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties WndSets - + Move selected set to the top Verplaats de geselecteerde set naar boven - + Move selected set up Verplaats de geselecteerde set naar boven - + Move selected set down Verplaats de geselecteerde set naar beneden - + Move selected set to the bottom Verplaats de geselecteerde set naar beneden - + Search by set name, code, or type Zoeken op setnaam, -code of -type - + Default order Standaard volgorde - + Restore original art priority order Herstel oorspronkelijke volgorde prioriteit afbeeldingen - + Enable all sets Alle sets inschakelen - + Disable all sets Alle sets uitschakelen - + Enable selected set(s) Geselecteerde set(s) inschakelen - + Disable selected set(s) Geselecteerde set(s) uitschakelen - + Deck Editor Deck Editor - + Use CTRL+A to select all sets in the view. Gebruik CTRL+A om alle sets in de lijst te selecteren. - + Only cards in enabled sets will appear in the card list of the deck editor. Alleen kaarten in de ingeschakelde sets zullen verschijnen in de kaartlijst van de deck editor. - + Image priority is decided in the following order: De prioriteit voor afbeeldingen wordt in de volgende volgorde vastgesteld: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki eerst de CUSTOM Map (%1), daarna de Ingeschakelde Sets in deze dialoog (Boven naar Beneden) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art Afbeelding - + How to use custom card art Hoe aangepaste kaart afbeeldingen te gebruiken - + Hints Tips - + Note Opmerking - + Sorting by column allows you to find a set while not changing set priority. Sorteren op kolom stelt u in staat om een set te vinden zonder de ingestelde prioriteit te wijzigen. - + To enable ordering again, click the column header until this message disappears. Om de orderen weer mogelijk te maken, klikt u op de kop van de kolom totdat dit bericht verdwijnt. - + Use the current sorting as the set priority instead Gebruik de huidige sortering als de ingestelde prioriteit - + Sorts the set priority using the same column Sorteert de prioriteit van sets met behulp van dezelfde kolom - + Manage sets Beheer sets @@ -9390,72 +10118,72 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing Schudden bij afsluiten - + pile view Stapelweergave @@ -9463,7 +10191,7 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties i18n - + English Nederlands (Dutch) @@ -9471,12 +10199,12 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties main - + Connect on startup Verbinding maken bij het opstarten - + Debug to file Debug naar bestand @@ -9484,1005 +10212,1041 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties shortcutsTab - + Main Window Hoofdscherm - - + + Deck Editor Deck Editor - + Game Lobby Spel Lobby - + Card Counters Kaart-counters - + Player Counters Tellers - + Power and Toughness Kracht en Hardheid - + Game Phases Spelfasen - + Playing Area Speelruimte - + Move Selected Card Verplaats Geselecteerde Kaart - + View View - + Move Top Card Verplaats Bovenste Kaart - + Move Bottom Card Verplaats Onderste Kaart - + Gameplay Spelverloop - + Drawing Kaarten Trekken - + Chat Room Chatkamer - + Game Window Spel Venster - + Load Deck from Clipboard Laad Deck van Klembord - - + + Replays Replays - + Tabs - + Check for Card Updates... Controleren op Kaart Updates... - + Connect... Verbinden... - + Disconnect De verbinding verbreken - + Exit Verlaten - + Full screen Volledig scherm - + Register... Registreer... - + Settings... Instellingen... - + Start a Local Game... Start een Lokaal Spel... - + Watch Replay... Bekijk Replay... - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters Wis Alle Filters - + Clear Selected Filter Wis Geselecteerde Filter - + Close Sluiten - + Remove Card Verwijder Kaart - + Manage Sets... Beheer Sets... - + Edit Custom Tokens... Aangepaste Tokens Bewerken... - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card Voeg Kaart Toe - + Load Deck... Deck Inladen... - - + + Load Deck from Clipboard... Deck Inladen van Klembord... - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck Nieuw Deck - + Open Custom Pictures Folder Open Custom Afbeeldingen Map - + Print Deck... Print Deck... - + Delete Card Verwijder Kaart - - + + Reset Layout Reset Lay-out - + Save Deck Bewaar Deck - + Save Deck as... Bewaar Deck Als... - + Save Deck to Clipboard, Annotated Bewaar Deck op Klembord, Geannoteerd - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard Bewaar Deck op Klembord - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... Lokaal Deck Inladen... - + Load Remote Deck... Remote Deck Inladen... - + Set Ready to Start Gereed om te Beginnen - + Toggle Sideboard Lock Sideboard Vergrendeling Omschakelen - + Add Green Counter Groene Counters Omhoog - + Remove Green Counter Groene Counters Omlaag - + Set Green Counters... Groene Tellers Instellen... - + Add Red Counter Rode Counters Omhoog - + Remove Red Counter Rode Counters Omlaag - + Set Red Counters... Rode Counters Instellen... - + Add Life Counter Levensteller Omhoog - + Show Status Bar - + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter Levensteller Omlaag - + Set Life Counters... Levensteller Instellen... - + Add White Counter Witte Teller Omhoog - + Remove White Counter Witte Teller Omlaag - + Set White Counters... Witte Teller Instellen... - + Add Blue Counter Blauwe Teller Omhoog - + Remove Blue Counter Blauwe Teller Omlaag - + Set Blue Counters... Blauwe Teller Instellen... - + Add Black Counter Zwarte Teller Omhoog - + Remove Black Counter Zwarte Teller Omlaag - + Set Black Counters... Zwarte Teller Instellen... - + Add Colorless Counter Kleurloze Teller Omhoog - + Remove Colorless Counter Kleurloze Teller Omlaag - + Set Colorless Counters... Kleurloze Teller Instellen... - + Add Other Counter Overige Teller Omhoog - + Remove Other Counter Overige Teller Omlaag - + Set Other Counters... Overige Teller Instellen... - + Increment all card counters - + Add Power (+1/+0) Kracht Verhogen (+1/+0) - + Remove Power (-1/-0) Kracht Verlagen (-1/-0) - + Move Toughness to Power (+1/-1) Verplaats Hardheid Naar Kracht (+1/-1) - + Add Toughness (+0/+1) Hardheid Verhogen (+0/+1) - + Remove Toughness (-0/-1) Hardheid Verlagen (-0/-1) - + Move Power to Toughness (-1/+1) Verplaats Kracht Naar Hardheid (-1/+1) - + Add Power and Toughness (+1/+1) Kracht en Hardheid Verhogen (+1/+1) - + Remove Power and Toughness (-1/-1) Kracht en Hardheid Verlagen (-1/-1) - + Set Power and Toughness... Kracht en Hardheid Instellen... - + Reset Power and Toughness Kracht en Hardheid Resetten - + Untap Ontap - + Upkeep Upkeep - + Draw Trek - + First Main Phase Eerste Hoofdfase - + Start Combat Start Gevechtfase - + Attack Aanval - + Block Blokkeren - + Damage Schade - + End Combat Einde Gevechtfase - + Second Main Phase Tweede Hoofdfase - + End Einde - + Next Phase Volgende Fase - + Next Phase Action Volgende Fase Handeling - + Next Turn Volgende Beurt - + Hide Card in Reveal Window - + Tap / Untap Card Tap / Ontap Kaart - + Untap All Untap Alles - + Toggle Untap Untap Omschakelen - + Turn Card Over Draai Kaart Om - + Peek Card Bekijk Kaart - + Play Card Speel Kaart - + Attach Card... Kaart Bevestigen Aan... - + Unattach Card Kaart Losmaken - + Clone Card Copieer Kaart - + Create Token... Maak Token... - + Create All Related Tokens Maak Alle Gerelateerde Tokens - + Create Another Token Maak Nog een Token - + Set Annotation... Zet Annotatie... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library Onderkant van Library - - - - + + + + Exile Exile - - - - + + + + Graveyard Graveyard - - + + + Hand Hand - - + + Top of Library Bovenkant van Library - - - + + + Battlefield, Face Down Speelveld, Gesloten - + Battlefield Speelveld - - Sort Hand - - - - + Library Library - + Sideboard Sideboard - + Top Cards of Library Bovenste Kaarten van Library - + Bottom Cards of Library - + Close Recent View Recent Overzicht Sluiten - - + + Stack Stack - - + + Graveyard (Multiple) Graveyard (Meerdere) - - + + Exile (Multiple) Exile (Meerdere) - + Stack Until Found Stack Totdat Gevonden - + Draw Bottom Card Trek Onderste Kaart - + Draw Multiple Cards from Bottom... Trek Meerdere Kaarten van Onderkant... - + Draw Arrow... Trek Pijl... - + Remove Local Arrows Verwijder Lokale Pijlen - + Leave Game Verlaat Spel - + Concede Opgeven - + Roll Dice... Werp Dobbelstenen... - + Shuffle Library Library Schudden - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan Mulligan - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card Trek een Kaart - + Draw Multiple Cards... Trek Meerdere Kaarten... - + Undo Draw Kaart Trekken Ongedaan Maken - + Always Reveal Top Card Altijd Bovenste Kaart Onthullen - + Always Look At Top Card Altijd Bovenste Kaart Bekijken - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise Draai Weergave Met de Klok Mee - + Rotate View Counterclockwise Draai Weergave Tegen de Klok In - + Unfocus Text Box Textvak Verlaten - + Focus Chat Focus Naar Chat - + Clear Chat Wis Chat - + Refresh Verversen - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause Start/Stop - + Toggle Fast Forward Versnelling Schakelen - + Home - + Visual Deck Storage - + Deck Storage - + Server Server - + Account Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_pl.ts b/cockatrice/translations/cockatrice_pl.ts index 35ded5e99..0085d6aae 100644 --- a/cockatrice/translations/cockatrice_pl.ts +++ b/cockatrice/translations/cockatrice_pl.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Ustaw znacznik… @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Ustaw znacznik - + New value for counter '%1': Nowa wartość dla znacznika '%1' @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,79 +36,81 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. + + Add Analytics Panel AdminNotesDialog - + Update Notes Aktualności - + Admin Notes for %1 Notatki Administracyjne dla %1 @@ -116,12 +118,12 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard Mainboard - + Sideboard Sideboard @@ -129,22 +131,22 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error Błąd - + Could not create themes directory at '%1'. Nie można stworzyć folderu motywów w '%1'. - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -155,7 +157,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -166,152 +168,165 @@ Are you sure you would like to disable this feature? - + Confirm Change - + Theme settings Ustawienia motywu - + Current theme: Obecny motyw: - + Open themes folder Otwórz folder motywów - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings Ustawienia menu - + Show keyboard shortcuts in right-click menus Pokazuj skróty klawiszowe w menu kontekstowym - + + Show game filter toolbar above list in room tab + + + + Card rendering Rendering kart - + Display card names on cards having a picture Wyświetl nazwy kart na kartach z obrazkami - + Auto-Rotate cards with sideways layout Automatycznie obracaj karty horyzontalne - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector Podbijaj dodatki z których karty są w talii. - + Scale cards on mouse over Skaluj karty po najechaniu kursorem myszy - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand Minimalny procent zachodzenia kart na siebie na stosie oraz w ręce pionowej - + Maximum initial height for card view window: Maksymalna podstawowa wysokość okna podglądu karty - - + + rows rzędy - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Układ ręki - + Display hand horizontally (wastes space) Wyświetl karty na ręce poziomo (marnuje miejsce) - + Enable left justification Wyrównuj karty do lewej strony pola - + Table grid layout Układ stołu - + Invert vertical coordinate Odwróć współrzędne pionowe - + Minimum player count for multi-column layout: Minimalna liczba graczy dla widoku wielokolumnowego - + Maximum font size for information displayed on cards: Maksymalny rozmiar czcionki dla informacji wyświetlanych na kartach + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -333,112 +348,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name zbanuj nazwę &użytkownika - + ban &IP address zbanuj adres &IP - + ban client I&D ID zablokowanego konta - + Ban type Rodzaj bana - + &permanent ban ban &permanentny - + &temporary ban ban &tymczasowy - + &Days: &Dni: - + &Hours: &Godziny: - + &Minutes: &Minuty: - + Duration of the ban Czas trwania bana - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Podaj przyczynę bana. Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowanej osoby. - + Please enter the reason for the ban that will be visible to the banned person. Podaj przyczynę bana do wglądu dla banowanej osoby. - + Redact all messages from this user in all rooms Redaguj wiadomości od tego użytkownika we wszystkich pokojach - + &OK &OK - + &Cancel &Anuluj - + Ban user from server Zbanuj użytkownika z serwera - + + - - + Error Błąd - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Aby ustawić bana, musisz wybrać nazwę, IP, ID klienta lub dowolną kombinację tej trójki. - + You must have a value in the name ban when selecting the name ban checkbox. Pole imienia musi być wypełnione dla opcji ban imienia. - + You must have a value in the ip ban when selecting the ip ban checkbox. Pole ip musi być wypełnione dla opcji ban ip. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Pole id klienta musi być wypełnione dla opcji ban id klienta. @@ -469,32 +484,32 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardDatabaseModel - + Name Nazwa - + Sets Dodatki - + Mana cost Koszt many - + Card type Typ karty - + P/T S/W - + Color(s) Kolor(y) @@ -502,96 +517,101 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardFilter - + AND Logical conjunction operator used in card filter ORAZ - + OR Logical disjunction operator used in card filter LUB - + AND NOT Negated logical conjunction operator used in card filter ORAZ NIE - + OR NOT Negated logical disjunction operator used in card filter LUB NIE - + Name Imię - + + Name (Exact) + + + + Type Typ - + Color Kolor - + Text Tekst - + Set Ustaw - + Mana Cost Koszt Many - + Mana Value Wartość Many - + Rarity Rzadkość - + Power Siła - + Toughness Wytrzymałość - + Loyalty Lojalność - + Format Format - + Main Type - + Sub Type @@ -599,22 +619,22 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardInfoFrameWidget - + Image Ilustracja - + Description Opis - + Both Karta + opis - + View transformation @@ -622,22 +642,22 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -650,12 +670,22 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan Nazwa: - + + Set: + + + + + Collector Number: + + + + Related cards: Powiązane karty: - + Unknown card: Nieznana karta: @@ -663,124 +693,124 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -788,7 +818,7 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardSizeWidget - + Card Size Rozmiar Karty @@ -796,133 +826,133 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardZoneLogic - + their hand nominative - + %1's hand nominative - - - their library - look at zone - - - - - %1's library - look at zone - - - - - of their library - top cards of zone, - - - - - of %1's library - top cards of zone - - their library - reveal zone + look at zone %1's library + look at zone + + + + + of their library + top cards of zone, + + + + + of %1's library + top cards of zone + + + + + their library reveal zone - + + %1's library + reveal zone + + + + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - - - their graveyard - nominative - - - - - %1's graveyard - nominative - - - - - their exile - nominative - - - - - %1's exile - nominative - - + their graveyard + nominative + + + + + %1's graveyard + nominative + + + + + their exile + nominative + + + + + %1's exile + nominative + + + + their sideboard look at zone - + %1's sideboard look at zone - - - their sideboard - nominative - - - - - %1's sideboard - nominative - - - their custom zone '%1' + their sideboard nominative + %1's sideboard + nominative + + + + + their custom zone '%1' + nominative + + + + %1's custom zone '%2' nominative @@ -931,7 +961,7 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CockatriceXml3Parser - + Parse error at line %1 col %2: @@ -939,7 +969,7 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CockatriceXml4Parser - + Parse error at line %1 col %2: @@ -958,6 +988,37 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -969,47 +1030,47 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add to Deck - + Add to Sideboard - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1017,87 +1078,97 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing - + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + + Format: + + + + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1105,17 +1176,17 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1123,114 +1194,114 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1238,7 +1309,7 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1246,194 +1317,227 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckEditorSettingsPage - - + + Update Spoilers Uaktualnij Spoilery - - + + Success Sukces - + Download URLs have been reset. Pobrane URL zostały zresetowane. - + Downloaded card pictures have been reset. Pobrane obrazy kart zostały zresetowane - + Error Błąd - + One or more downloaded card pictures could not be cleared. Jedno lub więcej pobranych ilustracji kart nie mogło zostać wyczyszczonych. - + Add URL Dodaj URL - - + + URL: URL: - - + + Edit URL Edytuj URL - + Network Cache Size: Rozmiar sieciowej pamięci podręcznej: - + Redirect Cache TTL: TTL przekierowania w pamięci podręcznej: - + How long cached redirects for urls are valid for. Jak długo zapisane przekierowania są ważne. - + Picture Cache Size: Rozmiar Pamięci Podręcznej dla Obrazków: - + Add New URL Dodaj Nowy URL - + Remove URL Usuń URL - + Day(s) Dni - + Updating... Trwa aktualizacja... - + Choose path Wybierz ścieżkę - + URL Download Priority Priorytet Pobierania URL - + Spoilers Spoilery - + Download Spoilers Automatically Pobierz spoilery automatycznie - + Spoiler Location: Lokalizacja spoilera - + Last Change Ostatnia zmiana - + Spoilers download automatically on launch Spoilery są pobierane automatycznie w czasie premiery. - + Press the button to manually update without relaunching Wciśnij przycisk żeby ręcznie uaktualnić bez restartu. - + Do not close settings until manual update is complete Nie zamykaj ustawień dopóki aktualizacja manualna się nie skończy - + Download card pictures on the fly Pobierz ilustracje kart w locie - + How to add a custom URL Jak dodać niestandardowe URL - + Delete Downloaded Images Usuń Pobrane Ilustracje - + Reset Download URLs Resetuj Pobieranie URLi - + On-disk cache for downloaded pictures Pamięć podręczna na dysku dla pobranych obrazków - + In-memory cache for pictures not currently on screen Pamięć podręczna w pamięci fizycznej dla obrazków które nie są na ekranie + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count Ilość - + Set Dodatek - + Number Ilość - + Provider ID Provider ID - + Card Karta @@ -1441,12 +1545,12 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1454,17 +1558,17 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1472,7 +1576,7 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -1480,62 +1584,62 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -1548,93 +1652,151 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1652,74 +1814,74 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckViewContainer - + Load deck... Wczytaj talię... - + Load remote deck... Wczytaj zdalną talię... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start Gotowe do rozpoczęcia - + Force start Wymuś rozpoczęcie - + Sideboard unlocked Biblioteka poboczna odblokowana - + Sideboard locked Biblioteka poboczna zablokowana - - + + Error Błąd - + The selected file could not be loaded. Wybrany plik nie mógł zostać wczytany. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -1752,143 +1914,143 @@ This will kick all non-ready players from the game. Pobieranie... - + Known Hosts Znane Hosty - + Delete the currently selected saved server Usuń zaznaczony zapisany serwer - + Refresh the server list with known public servers Odśwież listę serwerów ze znanymi serwerami publicznymi - + New Host Nowy Host - + Name: Nazwa: - + &Host: &Host: - + &Port: &Port: - + Player &name: &Nazwa gracza: - + P&assword: H&asło: - + &Save password Zapi&sz hasło - + A&uto connect Połącz a&utomatycznie - + Automatically connect to the most recent login when Cockatrice opens Automatycznie połącz ostatnim loginem przy otwarciu Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! Jeżeli masz problem z połączeniem lub rejestracją, skontaktuj się z obsługą serwera! - - + + Webpage Strona internetowa - + Reset Password Zresetuj hasło - + Forgot password? Zapomniałeś hasła? - + &Connect &Połącz - + Server Serwer - + Login Login - + Server Contact Kontakt z Serwerem - + Connect to Server Połącz z Serwerem - + Server URL Serwer URL - + Communication Port Port Komunikacyjny - + Unique Server Name Unikalna Nazwa Serwera - + Connection Warning Ostrzeżenie o połączeniu - + You need to name your new connection profile. Musisz nazwać swój nowy profil połączenia - + Connect Warning Ostrzeżenie połączenia - + The player name can't be empty. Nazwa użytkownika nie może być pusta @@ -1896,117 +2058,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings Zapa&miętaj ustawienia - + &Description: &Opis: - + P&layers: &Liczba graczy: - + General Ogólne - + Game type Format - + &Password: &Hasło: - + Only &buddies can join Tylko dla zn&ajomych - + Only &registered users can join Tylko dla &zarejestrowanych użytkowników - + Joining restrictions Ograniczenia dostępu - + &Spectators can watch &Widzowie dozwoleni - + Spectators &need a password to watch Widzowie muszą z&nać hasło - + Spectators can &chat Widzowie mogą korzystać z &czatu - + Spectators can see &hands Widzowie mogą oglądać &ręce - + Create game as spectator Stwórz grę jako widz - + Spectators Widzowie - + Starting life total: Początkowa ilość życia: - + Open decklists in lobby - + + Create game as judge + + + + Game setup options Opcje tworzenia gry - + &Clear &Wyczyść - + Create game Stwórz grę - + Game information Informacje o grze - + Error Błąd - + Server error. Błąd serwera. @@ -2014,97 +2181,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Nazwa: - + Token Token - + C&olor: &Kolor: - + white biały - + blue niebieski - + black czarny - + red czerwony - + green zielony - + multicolor wielokolorowy - + colorless bezkolorowy - + &P/T: &S/W: - + &Annotation: &Adnotacja: - + &Destroy token when it leaves the table &Zniszcz tokena kiedy opuści stół - + Create face-down (Only hides name) - + Token data Dane tokena - + Show &all tokens Pok&azuj wszystkie tokeny - + Show tokens from this &deck Pokazuj tokeny z tej &talii - + Choose token from list Wybierz token z listy: - + Create token Tworzenie tokena @@ -2112,53 +2279,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2166,40 +2333,40 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. Nie wybrano obrazu. - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. By zmienić twojego awatara wybierz nowy obrazek. By usunąć twojego bieżącego awatara potwierdź bez wybierania nowego obrazka. - + Browse... Przeglądaj... - + Change avatar Zmień awatar - + Open Image Otwórz plik obrazu - + Image Files (*.png *.jpg *.bmp) Obrazy (*.png *.jpg *.bmp) - + Invalid image chosen. Nieprawidłowy obraz. @@ -2207,17 +2374,17 @@ By usunąć twojego bieżącego awatara potwierdź bez wybierania nowego obrazka DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2225,38 +2392,38 @@ By usunąć twojego bieżącego awatara potwierdź bez wybierania nowego obrazka DlgEditPassword - + Old password: Bieżące hasło: - + New password: Nowe hasło: - + Confirm new password: Potwierdź nowe hasło: - + Change password Zmień hasło - - + + Error Błąd - + Your password is too short. Twoje hasło jest za krótkie. - + The new passwords don't match. Hasła nie są zgodne. @@ -2264,93 +2431,93 @@ By usunąć twojego bieżącego awatara potwierdź bez wybierania nowego obrazka DlgEditTokens - + &Name: &Nazwa: - + C&olor: K&olor: - + white biały - + blue niebieski - + black czarny - + red czerwony - + green zielony - + multicolor multikolorowy - + colorless bezkolorowy - + &P/T: &S/W: - + &Annotation: &Adnotacja: - + Token data Dane tokena - - + + Add token Dodaj token - + Remove token Usuń token - + Edit custom tokens Edytuj niestandardowe żetony - + Please enter the name of the token: Wprowadź nazwę tokena: - + Error Błąd - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Wybrana nazwa koliduje z istniejącą kartą lub tokenem. @@ -2444,7 +2611,8 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami - Hide games not created by buddy + Hide games not created by buddies + Hide games not created by buddy @@ -2531,52 +2699,52 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami DlgForgotPasswordChallenge - + Reset Password Challenge Warning Ostrzeżenie w procesie przypominania hasła - + A problem has occurred. Please try to request a new password again. Wystąpił problem. Poproś o nowe hasło ponownie. - + Enter the information of the server and the account you'd like to request a new password for. Wpisz informacje serwera oraz konta, do którego chciał(a)byś stworzyć nowe hasło. - + &Host: &Host: - + &Port: &Port: - + Player &name: &Nazwa gracza: - + Email: Email: - + Reset Password Challenge Proces przypominania hasła - + Reset Password Challenge Error Błąd w procesie przypominania hasła - + The email address can't be empty. Adres email nie może być pusty. @@ -2584,37 +2752,37 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Wpisz informacje serwera na którym chciał(a)byś stworzyć nowe hasło. - + &Host: &Host: - + &Port: &Port: - + Player &name: &Nazwa gracza: - + Reset Password Request Żądanie Resetu Hasła - + Reset Password Error Błąd Resetu Hasła - + The player name can't be empty. Nazwa gracza nie może być pusta. @@ -2622,86 +2790,86 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami DlgForgotPasswordReset - + Reset Password Warning Ostrzeżenie Resetu Hasła - + A problem has occurred. Please try to request a new password again. Wystąpił problem. Poproś o nowe hasło ponownie. - + Enter the received token and the new password in order to set your new password. Wpisz otrzymany token oraz nowe hasło aby je ustawić. - + &Host: &Host: - + &Port: &Port: - + Player &name: &Nazwa gracza: - + Token: Token: - - + + New Password: Nowe Hasło: - + Reset Password Zresetuj hasło - + The player name can't be empty. Nazwa gracza nie może być pusta. - - - - + + + + Reset Password Error Błąd Resetu Hasła - + The token can't be empty. Token nie może być pusty. - + The new password can't be empty. Nowe hasło nie może być puste. - + Error Błąd - + Your password is too short. Twoje hasło jest za krótkie. - + The passwords do not match. Hasła nie są zgodne. @@ -2717,17 +2885,17 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami DlgLoadDeckFromClipboard - + Load deck from clipboard Wczytaj talię ze schowka - + Error Błąd - + Invalid deck list. Nieprawidłowa lista talii. @@ -2735,43 +2903,43 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2785,7 +2953,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck Wczytaj talię @@ -2793,37 +2961,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): Nazwa karty (lub wyrażenie wyszukiwania): - + Number of hits: Ilość trafień: - + Auto play hits Automatycznie graj trafienia - + Put top cards on stack until... Kładź karty z wierzchu na stosie dopóki... - + No cards matching the search expression exists in the card database. Proceed anyways? W bazie kart nie znaleziono kart które spełniają twoje wyrażenie. Kontynuować mimo to? - + Cockatrice Cockatrice - + Invalid filter Nieprawidłowy filtr @@ -2831,92 +2999,92 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Wpisz swoje informacje oraz informacje serwera do którego chciałbyś się zarejestrować. Twój email zostanie użyty aby zweryfikować twoje konto. - + &Host: &Host: - + &Port: &Port: - + Player &name: &Nazwa gracza: - + P&assword: H&asło: - + Password (again): Hasło (ponownie): - + Email: Email: - + Email (again): Email (ponownie): - + Country: Kraj: - + Undefined Nie wiadomo - + Real name: Prawdziwe imię: - + Register to server Zarejestruj się - - - - + + + + Registration Warning Ostrzeżenie rejestracji - + Your password is too short. Twoje hasło jest za krótkie. - + Your passwords do not match, please try again. Wpisane hasła nie są zgodne. Spróbuj ponownie. - + Your email addresses do not match, please try again. Wpisane adresy mailowe nie są zgodne. Spróbuj ponownie. - + The player name can't be empty. Nazwa użytkownika nie może być pusta @@ -2942,40 +3110,55 @@ Twój email zostanie użyty aby zweryfikować twoje konto. DlgSelectSetForCards - + Unmodified Cards: - + Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database W trakcie wczytywania bazy kart wystąpił nieznany błąd - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -2992,7 +3175,7 @@ Może istnieć potrzeba uruchomienia Oracle w celu uaktualnienia bazy kart. Czy chcesz zmienić ustawienia położenia bazy kart? - + Your card database version is too old. This can cause problems loading card information or images @@ -3009,7 +3192,7 @@ Zwykle można temu zaradzić poprzez uruchomienie narzędzia Oracle i uaktualnie Czy chcesz zmienić ustawienia położenia bazy kart? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3022,7 +3205,7 @@ Proszę wypełnić zgłoszenie błędu pod adresem https://github.com/Cockatrice Czy chcesz zmienić ustawienia położenia bazy kart? - + File Error loading your card database. Would you like to change your database location setting? @@ -3031,7 +3214,7 @@ Would you like to change your database location setting? Czy chcesz ustawić nową lokalizację bazy kart? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3040,7 +3223,7 @@ Would you like to change your database location setting? Czy chcesz ustawić nową lokalizację bazy kart? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3053,59 +3236,59 @@ Proszę wypełnić zgłoszenie błędu pod adresem https://github.com/Cockatrice Czy chcesz zmienić ustawienia położenia bazy kart? - - - + + + Error Błąd - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Ścieżka dostępu do twojego katalogu z taliami jest nieprawidłowa. Czy chcesz wrócić i ustawić poprawną ścieżkę? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Ścieżka dostępu do twojego katalogu z obrazkami jest nieprawidłowa. Czy chcesz wrócić i ustawić poprawną ścieżkę? - + Settings Ustawienia - + General Ogólne - + Appearance Wygląd - + User Interface Interfejs - + Card Sources Źródło Karty - + Chat Czat - + Sound Dźwięk - + Shortcuts Skróty @@ -3113,39 +3296,39 @@ Czy chcesz zmienić ustawienia położenia bazy kart? DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3153,17 +3336,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next Następna - + Previous Poprzednia - + Tip of the Day Wskazówka na Dziś @@ -3171,165 +3354,165 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel Kanał obecnej wersji - + Reinstall Przeinstaluj - + Cancel Download Anuluj Pobieranie - + Open Download Page Otwórz stronę pobierań - + Check for Client Updates Sprawdź aktualizacje klienta - - - - + + + + Error Błąd - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice nie wspiera SSL, dlatego automatyczne pobieranie uaktualnień nie jest możliwe! Odwiedź stronę pobierania żeby uaktualnić ręcznie. - + Downloading update: %1 - + Checking for updates... Sprawdzam aktualizacje... - + Finished checking for updates Skończyłem sprawdzać aktualizacje - + No Update Available Brak nowych aktualizacji - + Cockatrice is up to date! Cockatrice jest aktualny! - + You are already running the latest version available in the chosen release channel. Używasz obecnie najnowszej dostępnęj wersji w wybranym kanale wydania - + Current version Obecna wersja - + Selected release channel Wybierz kanał wydania - - + + Update Available Dostępna aktualizacja - - + + A new version of Cockatrice is available! Nowa wersja Cockatrice jest dostępna! - - + + New version Nowa wersja - - + + Released Wydania - - + + Changelog Zmiany - + Do you want to update now? Chcesz teraz zaktualizować? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + + Update Error Błąd aktualizacji - + An error occurred while checking for updates: Wystąpił błąd podczas sprawdzania aktualizacji: - + An error occurred while downloading an update: Wystąpił błąd podczas pobierania aktualizacji: - + Installing... Instaluję... - + Cockatrice is unable to open the installer. Cockatrice nie może otworzyć instalatora. - + Try to update manually by closing Cockatrice and running the installer. Spróbuj zaktualizować ręcznie poprzez zamknięcie Cockatrice i włączenie instalatora. - + Download location Lokalizacja pobierania @@ -3337,21 +3520,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing Wyczyść logi przy zamykaniu - + Copy to clipboard - + Debug Log Log Debugera + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3421,33 +3736,39 @@ You may have to manually download the new version. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3463,22 +3784,22 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. @@ -3486,22 +3807,22 @@ You may have to manually download the new version. GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -3509,226 +3830,279 @@ You may have to manually download the new version. GameSelector - - - - - - + + + + + + Error Błąd - + Please join the appropriate room first. Proszę najpierw dołączyć do właściwego pokoju. - + Wrong password. Nieprawidłowe hasło. - + Spectators are not allowed in this game. Ta rozgrywka nie dopuszcza widzów. - + The game is already full. Ta rozgrywka jest już pełna. - + The game does not exist any more. Ta rozgrywka już nie istnieje. - + This game is only open to registered users. Ta rozgrywka jest dostępna tylko dla zarejestrowanych użytkowników. - + This game is only open to its creator's buddies. Ta rozgrywka jest dostępna tylko dla znajomych jej twórcy. - + You are being ignored by the creator of this game. Twórca tej rozgrywki cię ignoruje. - + Join Game - + Spectate Game - + Game Information - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game Dołącz do gry - + Password: Hasło: - + Please join the respective room first. Proszę najpierw dołączyć do odpowiedniego pokoju. - + &Filter games &Filtr rozgrywek - + C&lear filter Usuń fi&ltr - + C&reate Utwó&rz - + &Join &Dołącz - + + Join as judge + + + + J&oin as spectator D&ołącz jako widz - + + Join as judge spectator + + + + Games shown: %1 / %2 Gra pokazana: %1 / %2 - + Games Gry + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day >1 dzień - + %1%2 hr short age in hours %1%2 godz%1%2 godz%1%2 godz%1%2 godz - + new Nowa - + %1%2 min short age in minutes %1%2 min%1%2 min%1%2 min%1%2 min - + password hasło - + buddies only tylko znajomi - + reg. users only tylko zarejestrowani użytkownicy - + open decklists - - + + can chat mogą rozmawiać - + see hands widzą ręce - + can see hands widzą ręce - + not allowed niedozwoleni - + Room Pokój - + Age Wiek - + Description Opis - + Creator Twórca - + Type Typ - + Restrictions Ograniczenia - + Players Gracze - + Spectators Widzowie @@ -3736,143 +4110,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths Resetuj wszystkie ścieżki - + All paths have been reset Wszystkie ścieżki zostały zresetowane - - - - - - - + + + + + + + Choose path Wybierz ścieżkę - + Personal settings Ustawienia osobiste - + Language: Język: - + Paths (editing disabled in portable mode) Scieżki (edytowanie wyłączone w trybie przenośnym) - + Paths Ścieżki - + How to help with translations - + Decks directory: Katalog z taliami: - + Filters directory: - + Replays directory: Katalog z powtórkami: - + Pictures directory: Katalog z obrazkami: - + Card database: Baza kart: - + Custom database directory: Niestandardowy folder z bazą: - + Token database: Baza tokenów: - + Update channel Aktualizuj kanał - + Check for client updates on startup Sprawdzaj aktualizacje klienta przy uruchomieniu - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client Powiadom jeśli właściwości wspierane przez serwer, brakują w kliencie - + Automatically run Oracle when running a new version of Cockatrice Automatycznie uruchom Oracle kiedy uruchomiona jest nowa wersja Cockatrice - + Show tips on startup Pokazuj wskazówki po uruchomieniu - + Last update check on %1 (%2 days ago) @@ -3880,47 +4254,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -3928,111 +4302,141 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - - &Sort hand + + Sort hand by... - - Take &mulligan + + Name - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - Reveal r&andom card to... + + + All players - - - &All players + + Reveal r&andom card to... HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + + Browse Archidekt + + + + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4234,61 +4638,61 @@ You may have to manually download the new version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Ten serwer osiągnął maksymalną liczbę użytkowników, proszę sprawdzić później. - + There are too many concurrent connections from your address. Trwa zbyt wiele równoległych połączeń z twojego adresu. - + Banned by moderator Zbanowany przez moderatora. - + Expected end time: %1 Oczekiwany czas zakończenia: %1 - + This ban lasts indefinitely. Ten ban jest bezterminowy. - + Scheduled server shutdown. Planowe wyłączenie serwera. - - + + Invalid username. Nieprawidłowa nazwa użytkownika. - + You have been logged out due to logging in at another location. Zostałeś wylogowany ze względu inny proces logowania. - + Connection closed Połączenie zakończone - + The server has terminated your connection. Reason: %1 Serwer zakończył twoje połączenie. Przyczyna: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -4303,583 +4707,583 @@ Wszystkie trwające gry zostaną utracone. Przyczyna zamknięcia: %1 - + Scheduled server shutdown Planowe wyłączenie serwera - - + + Success Operacja zakończona pomyślnie - + Registration accepted. Will now login. Rejestracja zaakceptowana. Rozpocznij logowanie. - + Account activation accepted. Will now login. Aktywacja konta zakończona pomyślnie. Rozpocznij logowanie. - + Number of players Liczba graczy - + Please enter the number of players. Wprowadź liczbę graczy. - - + + Player %1 Gracz %1 - + Load replay Wczytaj powtórkę - + About Cockatrice O Cockatrice - + Version Wersja - + Cockatrice Webpage Oficjalna strona Cocatrice - + Project Manager: Menadżer projektu: - + Past Project Managers: Poprzedni menadżerowie projektu: - + Developers: Twórcy: - + Our Developers Lista współpracowników - + Help Develop! Dołącz do grona twórców! - + Translators: Tłumacze: - + Our Translators Nasi Tłumacze: - + Help Translate! Pomóż w tłumaczeniu! - + Support: Wsparcie: - + Report an Issue Zgłoś problem - + Troubleshooting Rozwiązywanie problemów - + F.A.Q. Najczęściej zadawane pytania - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error Błąd - + Server timeout Upłynął limit czasu odpowiedzi serwera - + Failed Login Nie udało się zalogować - + Your client seems to be missing features this server requires for connection. Twój klient prawdopodobnie nie spełnia wymagań połączenia z tym serwerem. - + To update your client, go to 'Help -> Check for Client Updates'. Aby zaktualizować twojego klienta, pójdź do 'Pomoc -> Sprawdź ustawienia klienta'. - + Incorrect username or password. Please check your authentication information and try again. Nieprawidłowa nazwa użytkownika lub hasło. Sprawdź swoje dane weryfikacyjne i spróbuj ponownie. - + There is already an active session using this user name. Please close that session first and re-login. Istnieje już aktywna sesja dla tej nazwy użytkownika. Zakończ tamtą sesję i zaloguj się ponownie. - - + + You are banned until %1. Twój ban trwa do: %1. - - + + You are banned indefinitely. Zostałeś zbanowany bezterminowo. - + This server requires user registration. Do you want to register now? Ten serwer wymaga rejestracji użytkownika. Chcesz się teraz zarejestrować? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. Ten serwer potrzebuje ID klienta. Twój klient nie może wygenerować ID lub używasz zmodyfikowanego klienta. Proszę zamknij i otwórz ponownie swojego klienta aby spróbować ponownie. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. Doszło do wewnętrznego błędu, proszę zamknąć i ponownie otworzyć Cockatrice przed ponowną próbą. Jeśli błąd będzie się utrzymywać, upewnij się, że używasz najnowszej wersji programu oraz jeśli potrzebne skontaktuj się z deweloperami programu. - + Account activation Aktywacja konta - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Twoje konto nie zostało jeszcze aktywowane. Musisz podać token aktywujący, otrzymany w wiadomości email. - + Server Full Serwer pełny - + Unknown login error: %1 Nieznany błąd logowania: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. To zwykle oznacza że wersja klienta jest nieaktualna i serwer wysłał odpowiedź, której twój klient nie rozpoznaje. - + Your username must respect these rules: Twoja nazwa użytkownika musi być zgodna z poniższymi regułami: - + is %1 - %2 characters long %1 jest o %2 znaków za długi - + can %1 contain lowercase characters może zawierać 1% małych liter - - - - + + + + NOT NIE - + can %1 contain uppercase characters może zawierać %1 dużych liter - + can %1 contain numeric characters może zawierać %1 cyfr - + can contain the following punctuation: %1 Może zawierać następujące interpunkcje: %1 - + first character can %1 be a punctuation mark Pierwszy znak może zawierać %1 - + no unacceptable language as specified by these server rules: note that the following lines will not be translated zasady serwera nie pozwalają na takie słownictwo: - + can not contain any of the following words: %1 Nie można używać żadnego z podanych wyrazów: %1 - + can not match any of the following expressions: %1 Nie pasuje to żadnego z podanych wyrażeń: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Możesz użyć tylko tych znaków: A-Z, a-z, 0-9, _, ., i - w nazwie użytkownika. - - - - - - + + + + + + Registration denied Rejestracja nieudana - + Registration is currently disabled on this server Rejestracja jest obecnie wyłączona na tym serwerze. - + There is already an existing account with the same user name. Istnieje już konto o tej nazwie. - + It's mandatory to specify a valid email address when registering. Podanie poprawnego adresu email jest wymagane przy rejestracji. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. Zdaje się że próbujesz zarejestrować nowe konto na tym serwerze, posiadając już zarejestrowane konto dla tego adresu. Ten serwer ogranicza liczbę kont, które można zarejestrować dla pojedynczego adresu. Skontaktuj się z operatorem serwera w celu uzyskania pomocy lub otrzymania twoich poufnych danych. - + Password too short. Hasło jest zbyt krótkie. - + Registration failed for a technical problem on the server. Rejestracja nie powiodła się z powodów technicznych na serwerze. - + The connection to the server has been lost. - + Unknown registration error: %1 Nieznany błąd rejestracji: %1 - + Account activation failed Aktywacja konta zakończona niepowodzeniem - + Socket error: %1 Błąd gniazda: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Próbujesz połączyć się z nieaktualnym serwerem. Zainstaluj starszą wersję Cockatrice lub wybierz odpowiedni serwer. Lokalna wersja to %1, wersja zdalna to %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Twój klient Cockatrice jest nieaktualny. Uaktualnij Cockatrice do nowszej wersji. Lokalna wersja to %1, wersja zdalna to %2. - + Connecting to %1... %1 — łączenie… - + Registering to %1 as %2... Rejestrowanie do %1 jako %2... - + Disconnected Rozłączony - + Connected, logging in at %1 Połączenie ustanowione; logowanie jako %1 + - Requesting forgotten password to %1 as %2... Proszenie o zapomniane hasło do %1 jako %2... - + &Connect... &Połącz… - + &Disconnect &Rozłącz - + Start &local game... Rozpocznij &lokalną grę… - + &Watch replay... &Obejrzyj powtórkę… - + &Full screen Pełny ekra&n - + &Register to server... &Zarejestruj się... - + &Restore password... &Przywróć hasło... - + &Settings... &Ustawienia… - + &Exit &Wyjdź - + A&ctions Ak&cje - + &Cockatrice &Cockatrice - + C&ard Database B&aza kart - + &Manage sets... &Zarządzaj dodatkami... - + Edit custom &tokens... Edytuj niestandardowe %tokens... - + Open custom image folder Otwórz folder ze spersonalizowanymi obrazkami - + Open custom sets folder Otwórz folder ze spersonalizowanymi setami - + Add custom sets/cards Dodaj spersonalizowane sety/karty - + Reload card database Przeładuj bazę kart - + Tabs - + &Help Po&moc - + &About Cockatrice &O Cockatrice - + &Tip of the Day &Wskazówka na Dziś - + Check for Client Updates Sprawdź uaktualnienia klienta - + Check for Card Updates... Uaktualnij bazę kart... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log Pokaż log &debugowania - + Open Settings Folder - + Show/Hide Pokaż/Ukryj - + New Version Nowa Wersja - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Gratulacje z powodu aktualizacji do Cockatrice %1! Oracle uruchomi się teraz aby zaktualizować bazę danych kart. - + Cockatrice installed Cockatrice zainstalowany - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Gratulacje z powodu instalacji Cockatrice %1! Oracle uruchomi się teraz aby zainstalować początkowe bazy danych kart. - + Card database Baza kart - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -4888,29 +5292,29 @@ Czy chcesz teraz uaktualnić bazę kart? Jeżeli nie masz pewności lub jesteś nowym użytkownikiem, wybierz "Tak" - - + + Yes Tak - - + + No Nie - + Open settings Otwórz ustawienia - + New sets found Znaleziono nowe dodatki - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -4925,17 +5329,17 @@ Kody dodatków: %1 Czy chcesz je teraz uaktywnić? - + View sets Pokaż dodatki - + Welcome Witaj - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -4944,64 +5348,64 @@ Wszystkie dodatki w bazie kart zostały aktywowane. Przeczytaj więcej na temat zmiany kolejności czy wyłączania poszczególnych dodatków i tym podobnych w oknie "Zarządzaj Dodatkami". - - + + Information Informacja - + A card database update is already running. Aktualizacja bazy kart jest w trakcie - + Unable to run the card database updater: Nie można uruchomić aktualizacji bazy kart: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5012,673 +5416,843 @@ Prawdopodobnie nie jest to problem, ale ta wiadomośc może oznaczać że jest d Aby aktualizować klienta, otwórz okno Pomoc -> Sprawdź Aktualizacje - - - - - + + + + + Load sets/cards Wczytaj dodatki/karty - + Selected file cannot be found. Wybrany plik nie został znaleziony. - + You can only import XML databases at this time. Możesz zaimportować jedynie bazy danych XML w tym momencie. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Nowe dodatki/karty dodane pomyślnie. Cockatrice ponownie załaduje teraz bazę danych. - + Sets/cards failed to import. Dodatki/karty nie mogły być zaimportowane. - - - + + + Reset Password Zresetuj hasło - + Your password has been reset successfully, you can now log in using the new credentials. Twoje hasło zostały pomyślnie zresetowane, możesz teraz zalogować się używając nowego uwierzytelniania. - + Failed to reset user account password, please contact the server operator to reset your password. Nie udało się zresetować hasła użytkownika, skontaktuj się z operatorem serwera w celu wykonania resetu. - + Activation request received, please check your email for an activation token. Otrzymano żądanie aktywacji, odbierz token aktywujący w swojej skrzynce email. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play z gry - + from their graveyard z jego grobu - + from exile ze strefy wygnania - + from their hand z jego ręki - + the top card of %1's library karta z wierzchu biblioteki gracza %1 - + the top card of their library pierwsza karta jego biblioteki - + from the top of %1's library z wierzchu biblioteki gracza %1 - + from the top of their library z wierzchu jego biblioteki - + the bottom card of %1's library karta z wierzchu biblioteki gracza %1 - + the bottom card of their library ostatnia karta jego biblioteki - + from the bottom of %1's library z dna biblioteki gracza %1 - + from the bottom of their library z dna jego biblioteki - + from %1's library z biblioteki gracza %1 - + from their library z jego biblioteki - + from sideboard z talii pobocznej - + from the stack ze stosu - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 od teraz gra z odwróconą kartą z wierzchu %2. - + %1 is not revealing the top card %2 any longer. %1 już nie gra z odwróconą kartą z wierzchu %2. - + %1 can now look at top card %2 at any time. %1 może teraz patrzeć na kartę z wierzchu %2 w każdej chwili. - + %1 no longer can look at top card %2 at any time. %1 nie może już patrzeć na kartę z wierzchu %2 w każdej chwili. - + %1 attaches %2 to %3's %4. %1 przyłącza %2 do %4 %3 - + %1 has conceded the game. %1 poddał grę. - + %1 has unconceded the game. %1 nie poddał gry. - + %1 has restored connection to the game. %1 odzyskał połączenie z grą. - + %1 has lost connection to the game. %1 utracił połączenie z grą. - + %1 points from their %2 to themselves. %1 wskazuje z jego karty %2 na samego siebie. - + %1 points from their %2 to %3. %1 wskazuje z jego karty %2 na kartę %3. - + %1 points from %2's %3 to themselves. %1 wskazuje z karty gracza %2 %3 na samą siebie. - + %1 points from %2's %3 to %4. %1 wskazuje z karty gracza %2 %3 na kartę %4. - + %1 points from their %2 to their %3. %1 wskazuje z jego karty %2 na jego kartę %3. - + %1 points from their %2 to %3's %4. %1 wskazuje z jego karty %2 na karty %3's %4. - + %1 points from %2's %3 to their own %4. %1 wskazuje z kart %2's %3 na swoją kartę %4. - + %1 points from %2's %3 to %4's %5. %1 wskazuje z karty gracza %2 %3 na kartę gracza %4 (%5). - + %1 creates a face down token. - + %1 creates token: %2%3. %1 stworzył token: %2%3. - + %1 has loaded a deck (%2). Gracz %1 wczytał talię (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). Gracz %1 wczytał talię oraz %2 kart talii pobocznej (hash: %3). - + %1 destroys %2. %1 niszczy %2. - + a card kartę - + %1 gives %2 control over %3. %1 oddaje %3 pod kontrolę gracza %2. - + %1 puts %2 into play%3 face down. %1 zagrywa kartę %2 na pole bitwy%3 koszulką do góry. - + %1 puts %2 into play%3. %1 zagrywa kartę %2 na pole bitwy %3. - + %1 puts %2%3 into their graveyard. %1 kładzie %2 %3 na cmentarz. - + %1 exiles %2%3. %1 przenosi kartę %2 do strefy wygnania %3. - + %1 moves %2%3 to their hand. %1 bierze %2 %3 do ręki. - + %1 puts %2%3 into their library. %1 wkłada %2 %3 do biblioteki. - + %1 puts %2%3 onto the bottom of their library. %1 przenosi %2 %3 na dno swojej biblioteki. - + %1 puts %2%3 on top of their library. %1 przenosi %2 %3 na wierzch swojej bibliotek - + %1 puts %2%3 into their library %4 cards from the top. %1 kładzie %2 %3 na jego biblioteke %4 kart z góry. - + %1 moves %2%3 to sideboard. %1 przenosi kartę %2 %3 do talii pobocznej. - + %1 plays %2%3. %1 zagrywa kartę %2 %3. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1 próbuje dobrać z pustej biblioteki - + %1 draws %2 card(s). %1 dobiera %2 kartę.%1 dobiera %2 kart.%1 dobiera %2 kart.%1 dobiera %2 kart. - + %1 is looking at %2. %1 przegląda %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. %1 kładzie %2 grzbietem do góry. - + %1 turns %2 face-up. %1 kładzie %2 grzbietem do dołu. - + The game has been closed. Gra została zakończona. - + The game has started. Gra została rozpoczęta. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 dołączył do gry. - + %1 is now watching the game. %1 obserwuje grę. - + You have been kicked out of the game. Wyrzucono cię z gry. - + %1 has left the game (%2). %1 opuścił grę (%2). - + %1 is not watching the game any more (%2). %1 przestał oglądać grę (%2). - + %1 is not ready to start the game any more. %1 nie jest już gotowy do rozpoczęcia gry. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 tasuje swoją talie i dobiera nową rękę z %2 kartą.%1 tasuje swoją talie i dobiera nową rękę z %2 kartami.%1 tasuje swoją talie i dobiera nową rękę z %2 kartami.%1 tasuje swoją talie i dobiera nową rękę z %2 kartami. - + %1 shuffles their deck and draws a new hand. %1 tasuje swoją talie i dobiera nową rękę. - + You are watching a replay of game #%1. Oglądasz powtórkę gry #%1. - + %1 is ready to start the game. %1 jest gotowy do rozpoczęcia gry. - + cards an unknown amount of cards karty - + %1 card(s) a card for singular, %1 cards for plural jedną kartę%1 kart%1 kart%1 kart - + %1 lends %2 to %3. %1 pożycza %2 graczowi %3. - + %1 reveals %2 to %3. %1 pokazuję kartę %2 graczowi %3. - + %1 reveals %2. %1 pokazuję kartę %2. - + %1 randomly reveals %2%3 to %4. %1 pokazuje losowo kartę %2 %3 graczowi %4. - + %1 randomly reveals %2%3. %1 pokazuje losowo kartę %2 %3. - + %1 peeks at face down card #%2. %1 spogląda na kartę odwróconą koszulką do góry #%2. - + %1 peeks at face down card #%2: %3. %1 spogląda na kartę odwróconą koszulką do góry #%2: %3. - + %1 reveals %2%3 to %4. %1 pokazuje kartę %2 %3 graczowi %4. - + %1 reveals %2%3. %1 pokazuje kartę %2 %3. - + %1 reversed turn order, now it's %2. %1 odwrócona kolejność tur, teraz %2 - + reversed odwrócone - + normal normalne - + Heads Orzeł - + Tails Reszka - + %1 flipped a coin. It landed as %2. %1 podrzucił monetą. Wylądowała na %2. - + %1 rolls a %2 with a %3-sided die. %1 rzuca %2 kostką %3-ścienną. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 rzuca %2 monetami. Wypadło %3 orłów i %4 reszek. - + %1 rolls a %2-sided dice %3 times: %4. %1 rzuca %2-ścienną kostką %3 razy: %4. - + %1's turn. Tura gracza %1 - + %1 sets annotation of %2 to %3. %1 ustala adnotację karty %2 na %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 ustawia licznik %2 na %3 (%4%5). - + %1 sets %2 to not untap normally. %1 blokuje karcie %2 normalne odtapowywanie. - + %1 sets %2 to untap normally. %1 umożliwia karcie %2 normalne odtapowywanie. - + %1 removes the PT of %2. %1 usuwa PT %2. - + %1 changes the PT of %2 from nothing to %4. %1 zmienia PT %2 z niczego na %4 - + %1 changes the PT of %2 from %3 to %4. %1 zmienia PT %2 z %3 na %4. - + %1 has locked their sideboard. %1 zablokował jego bibliotekę poboczną - + %1 has unlocked their sideboard. %1 odblokował jego bibliotekę poboczną - + %1 taps their permanents. %1 tapuje swoje permanenty. - + %1 untaps their permanents. %1 odtapowuje swoje permanenty. - + %1 taps %2. %1 tapuje kartę %2. - + %1 untaps %2. %1 odtapowuje kartę %2. - + %1 shuffles %2. %1 tasuje %2. - + %1 shuffles the bottom %3 cards of %2. %1 tasuje %3 dolne karty z %2. - + %1 shuffles the top %3 cards of %2. %1 tasuje %3 górne karty z %2. - + %1 shuffles cards %3 - %4 of %2. %1 tasuje %3 - %4 z %2 kart. - + %1 unattaches %2. %1 odczepia kartę %2. - + %1 undoes their last draw. %1 cofa ostatnią dobraną kartę - + %1 undoes their last draw (%2). %1 cofa ostatnią dobraną kartę (%2) @@ -5686,110 +6260,110 @@ Cockatrice ponownie załaduje teraz bazę danych. MessagesSettingsPage - + Word1 Word2 Word3 Wyraz1 Wyraz2 Wyraz3 - + Add New Message Dodaj Nową Wiadomość - + Edit Message Edytuj Wiadomość - + Remove Message Usuń Wiadomość - + Add message Dodaj wiadomość - - + + Message: Wiadomość: - + Edit message Edytuj wiadomość - + Chat settings Ustawienia czatu - + Custom alert words Niestandardowy tekst alertu. - + Enable chat mentions Włącz wywołania na czacie - + Enable mention completer Włącz autouzupełnianie wywołania - + In-game message macros Makra wiadomości w trakcie gry - + How to use in-game message macros Jak korzystać z makr wiadomości w trakcie gry - + Ignore chat room messages sent by unregistered users Ignoruj wiadomości na czacie od niezarejestrowanych użytkowników - + Ignore private messages sent by unregistered users Ignoruj prywatne wiadomości od niezarejestrowanych użytkowników - - + + Invert text color Odwróć kolor tekstu - + Enable desktop notifications for private messages Włącz desktopowe notyfikacje dla prywatnych wiadomości - + Enable desktop notification for mentions Włącz desktopowe notyfikacje dla wywołań - + Enable room message history on join Włącz historię wiadomości w pokoju po dołączeniu - - + + (Color is hexadecimal) (Kolor w kodzie heksadecymalnym) - + Separate words with a space, alphanumeric characters only Rozdziel słowa spacją, tylko alfanumeryczne znaki @@ -5906,62 +6480,62 @@ Cockatrice ponownie załaduje teraz bazę danych. Phase - + Unknown Phase Nieznana Faza - + Untap Odtapuj - + Upkeep Utrzymanie - + Draw Dobieranie - + First Main Pierwsza Główna - + Beginning of Combat Początek Walki - + Declare Attackers Wyznacz Atakujących - + Declare Blockers Wyznacz Broniących - + Combat Damage Obrażenia - + End of Combat Koniec Walki - + Second Main Druga Główna - + End/Cleanup Koniec/Czyszczenie @@ -6027,7 +6601,7 @@ Cockatrice ponownie załaduje teraz bazę danych. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -6036,134 +6610,134 @@ Cockatrice ponownie załaduje teraz bazę danych. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - + Move top cards to grave - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6171,17 +6745,17 @@ Cockatrice ponownie załaduje teraz bazę danych. PlayerMenu - + Player "%1" - + &Counters - + S&ay @@ -6189,7 +6763,7 @@ Cockatrice ponownie załaduje teraz bazę danych. PrintingSelector - + Display Navigation Buttons @@ -6197,22 +6771,22 @@ Cockatrice ponownie załaduje teraz bazę danych. PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing Przypnij wersję - + Unpin Printing Odepnij wersję - + Show Related cards Pokaż powiązane karty @@ -6228,17 +6802,17 @@ Cockatrice ponownie załaduje teraz bazę danych. PrintingSelectorCardSelectionWidget - + Previous Card in Deck Poprzednia karta w talii - + Bulk Selection - + Next Card in Deck Następna karta w talii @@ -6246,28 +6820,28 @@ Cockatrice ponownie załaduje teraz bazę danych. PrintingSelectorCardSortingWidget - + Alphabetical Alfabetycznie - + Preference - + Release Date Data wydania - - + + Descending Malejąco - + Ascending Rosnąco @@ -6333,37 +6907,37 @@ Cockatrice ponownie załaduje teraz bazę danych. QMenuBar - + Services Usługi - + Hide %1 Ukryj %1 - + Hide Others Ukryj pozostałe - + Show All Pokaż wszystkie - + Preferences... Preferencje… - + Quit %1 Zakończ %1 - + About %1 O programie %1 @@ -6371,42 +6945,42 @@ Cockatrice ponownie załaduje teraz bazę danych. QObject - + Cockatrice card database (*.xml) Baza kart Cockatrice (*.xml) - + All files (*.*) Wszystkie pliki (*.*) - + Cockatrice replays (*.cor) Powtórki Cockatrice (*.cor) - + Maindeck Maindeck - + Sideboard Sideboard - + Tokens Tokeny - + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? @@ -6414,92 +6988,92 @@ Cockatrice ponownie załaduje teraz bazę danych. QPlatformTheme - + OK OK - + Save Zapisz - + Save All Zapisz Wszystko - + Open Otwórz - + &Yes Tak - + Yes to &All Tak - wszystko - + &No Nie - + N&o to All Nie - wszystko - + Abort Przerwij - + Retry Powtórz - + Ignore Ignoruj - + Close Zamknij - + Cancel Anuluj - + Discard Odrzuć - + Help Pomoc - + Apply Aplikuj - + Reset Reset - + Restore Defaults Przywróć domyślne @@ -6596,37 +7170,37 @@ Cockatrice ponownie załaduje teraz bazę danych. RoomSelector - + Rooms Pokoje - + Joi&n &Dołącz - + Room Pokój - + Description Opis - + Permissions Zezwolenia - + Players Gracze - + Games Gry @@ -6667,27 +7241,27 @@ Cockatrice ponownie załaduje teraz bazę danych. SetsModel - + Enabled Włączone - + Set type Rodzaj dodatku - + Set code Kod dodatku - + Long name Pełna nazwa - + Release date Data wydania @@ -6695,53 +7269,53 @@ Cockatrice ponownie załaduje teraz bazę danych. ShortcutSettingsPage - - + + Restore all default shortcuts Przywróć domyślne skróty - + Do you really want to restore all default shortcuts? Czy na pewno chcesz przywrócić wszystkie domyślne skróty? - + Clear all default shortcuts Wyczyść wszystkie domyślne skróty - + Do you really want to clear all shortcuts? Czy na pewno chcesz wyczyścić wszystkie skróty? - + Section: Sekcja: - + Action: Akcja: - + Shortcut: Skrót: - + How to set custom shortcuts Jak ustawić niestandardowe skróty klawiszowe - + Clear all shortcuts Wyczyść wszystkie skróty - + Search by shortcut name Szukaj po nazwie skrótu @@ -6749,12 +7323,12 @@ Cockatrice ponownie załaduje teraz bazę danych. ShortcutTreeView - + Action Akcja - + Shortcut Skrót @@ -6762,14 +7336,14 @@ Cockatrice ponownie załaduje teraz bazę danych. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! Twój plik konfiguracyjny zawierał nieprawidłowe skróty. Sprawdź ustawienia skrótów! - + The following shortcuts have been set to default: Następujące skróty zostały zamienione na domyślne: @@ -6797,12 +7371,12 @@ Sprawdź ustawienia skrótów! SideboardMenu - + &Sideboard - + &View sideboard @@ -6810,27 +7384,27 @@ Sprawdź ustawienia skrótów! SoundSettingsPage - + Enable &sounds Włącz &efekty dźwiękowe - + Current sounds theme: Aktualny zestaw dźwięków: - + Test system sound engine Testuj silnik dźwiękowy systemu - + Sound settings Ustawienia dźwięku - + Master volume Główne ustawienia głośności @@ -6838,48 +7412,48 @@ Sprawdź ustawienia skrótów! SpoilerBackgroundUpdater - + Spoilers season has ended Sezon spoilerów zakończony - + Deleting spoiler.xml. Please run Oracle Usuwanie spoiler.xml. Uruchom Oracle - - + + Spoilers download failed Pobieranie spoilerów nieudane - + No internet connection Brak połączenia z internetem - + Error Błąd - + Spoilers already up to date Spoilery są już aktualne - + No new spoilers added Nie dodano nowych spoilerów - + Spoilers have been updated! Spoilery zostały zaktualizowane. - + Last change: Ostatnia zmiana: @@ -7043,56 +7617,123 @@ Sprawdź ustawienia skrótów! Nie można aktywować użytkownika. Błąd wewnętrzny + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info Informacje o karcie - + Deck Talia - + Filters Filtry - + &View &Widok - + + Card Database + + + + Printing Wersja - - - - + + + + + Visible Widoczne - - - - + + + + + Floating Pływające - + Reset layout Resetuj układ - + Deck: %1 Talia: %1 @@ -7100,61 +7741,61 @@ Sprawdź ustawienia skrótów! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + + + + Visible - - - - + + + + Floating - + Reset layout @@ -7162,22 +7803,22 @@ Sprawdź ustawienia skrótów! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7185,134 +7826,134 @@ Sprawdź ustawienia skrótów! TabDeckStorage - + Local file system Pliki lokalne - + Server deck storage Przechowalnia talii na serwerze - - + + Open in deck editor Otwórz w edytorze talii - + Rename deck or folder - + Upload deck Wyślij talię - + Download deck Pobierz talię + - - - + + New folder Nowy folder + - Delete Usuń - + Open decks folder Otwórz folder talii - + Rename local folder - + Rename local file - + New name: - - - - + + + + Error Błąd - + Rename failed - - + + Invalid deck file Nieprawidłowy plik talii - + Enter deck name Podaj nazwę talii - + This decklist does not have a name. Please enter a name: Talia nie posiada nazwy. Wprowadź nazwę: - + Unnamed deck Nienazwana talia - + Failed to upload deck to server Nie udało się wysłać talii na serwer - + Delete local file Usuń plik lokalny - + Are you sure you want to delete the selected files? Czy na pewno chcesz usunąć wybrane pliki? - + Delete remote decks Usuń zdalną talię - + Are you sure you want to delete the selected decks? Czy na pewno chcesz usunąć wybrane talie? - - + + Name of new folder: Nazwa nowego folderu: @@ -7325,17 +7966,17 @@ Wprowadź nazwę: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 @@ -7376,7 +8017,7 @@ Wprowadź nazwę: - + EDHRec: @@ -7384,197 +8025,197 @@ Wprowadź nazwę: TabGame - - - + + + Replay Powtórka - - + + Game Gra - - + + Player List Lista Graczy - - + + Card Info Informacje o karcie - - + + Messages Wiadomości - - + + Replay Timeline Oś czasu powtórki - + &Phases &Fazy - + &Game &Gra - + Next &phase Następna &faza - + Next phase with &action Następna faza z akcją - + Next &turn Następna &tura - + Reverse turn order Odwróć kolejność tur - + &Remove all local arrows Usuń wszystkie &wskaźniki - + Rotate View Cl&ockwise Obróć widok &zgodnie z ruchem wskazówek zegara - + Rotate View Co&unterclockwise Obróć widok &przeciwnie do ruchu wskazówek zegara - + Game &information &Informacje o grze - + Un&concede Powróć do gry - - - + + + &Concede &Poddaj grę - + &Leave game &Opuść grę - + C&lose replay &Zamknij powtórkę - + &Focus Chat Skup czat - + &Say: &Powiedz: - + Selected cards - + &View &Widok - - + + + - Visible Widoczne - - + + + - Floating Pływające - + Reset layout Resetuj układ - + Concede Poddaj grę - + Are you sure you want to concede this game? Czy na pewno chcesz poddać tę grę? - + Unconcede Powróć do gry - + You have already conceded. Do you want to return to this game? Poddałeś już się. Czy chcesz powrócić do tej gry? - + Leave game Opuść grę - + Are you sure you want to leave this game? Czy na pewno chcesz zakończyć tę rozgrywkę? - + A player has joined game #%1 Gracz dołączył do gry #%1 - + %1 has joined the game %1 dołączył(a) do gry. - + You have been kicked out of the game. Wyrzucono cię z gry. @@ -7582,7 +8223,7 @@ Wprowadź nazwę: TabHome - + Home @@ -7590,158 +8231,158 @@ Wprowadź nazwę: TabLog - + Logs Logi - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs Logi Pokoju - + Game Logs Logi Gry - + Chat Logs Logi Czatu - - + + Error Błąd - + You must select at least one filter. Musisz wybrać przynajmniej jeden filtr. - + You have to select a valid number of days to locate. Musisz wybrać poprawną liczbę dni żeby zlokalizować. - + Username: Nazwa użytkownika: - + IP Address: Adres IP: - + Game Name: Nazwa rozgrywki: - + GameID: ID Gry: - + Message: Wiadomość: - + Main Room Pokój Główny - + Game Room Pokój - + Private Chat Rozmowa Prywatna - + Past X Days: Ostatnie X Dni: - + Today Dziś - + Last Hour Ostatnia godzina - + Maximum Results: Maksymalny Wynik: - + At least one filter is required. The more information you put in, the more specific your results will be. Wymagany jest co najmniej jeden filtr. Im więcej podanych informacji, tym bardziej sprecyzowany wynik. - + Get User Logs Pobierz Logi Użytkownika - + Clear Filters Wyczyść Filtry - + Filters Filtry - + Log Locations Loguj Połóżenia - + Date Range Okres - + Maximum Results Maksymalny Wynik - - + + Message History Historia wiadomości - + Failed to collect message history information. Nie udało się zebrać informacji o historii wiadomości. - + There are no messages for the selected filters. Brak wiadomości dla wybranych filtrów. @@ -7787,180 +8428,180 @@ Im więcej podanych informacji, tym bardziej sprecyzowany wynik. TabReplays - + Local file system Lokalny system plików - + Server replay storage Powtórki na serwerze - - + + Watch replay Obejrzyj powtórkę - + Rename Zmień nazwę - - + + New folder Nowy folder - - + + Delete Usuń - + Open replays folder Otwórz folder powtórek - + Download replay Pobierz powtórkę - + Toggle expiration lock Wł./Wył. ochronę przed automatycznym usunięciem - + Get replay share code - - + + Look up replay by share code - + Rename local folder Zmień nazwę lokalnego folderu - + Rename local file Zmień nazwę lokalnego pliku - + New name: Nowa nazwa: - + Error Błąd - + Rename failed Zmiana nazwy nie powiodła się - + Name of new folder: Nazwa nowego folderu: - + Delete local file Usuń plik lokalny - + Are you sure you want to delete the selected files? Czy na pewno chcesz usunąć wybrane pliki? - + Are you sure you want to delete the selected replays? Czy na pewno chcesz usunąć wybrane powtórki? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay Usuń przechowywaną powtórkę @@ -8026,30 +8667,30 @@ Im więcej podanych informacji, tym bardziej sprecyzowany wynik. Serwer + - Error Błąd - + Failed to join the server room: it doesn't exist on the server. Błąd podczas próby dołączenia do pokoju: nie istnieje na serwerze. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. Serwer myśli że jesteś w pokoju ale twój klient nie może tego wyświetlić. Spróbuj zrestartować twojego klienta. - + You do not have the required permission to join this server room. Nie masz uprawnień do dołączenia do tego pokoju. - + Failed to join the server room due to an unknown error: %1. Błąd podczas próby dołączenia do pokoju z powodu nieznanego błędu: %1 @@ -8057,92 +8698,97 @@ Im więcej podanych informacji, tym bardziej sprecyzowany wynik. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - - - Home - - - - - &Visual Deck Storage - - - - - Visual Database Display - - - Server + Archidekt - Account + Home - Deck Storage + &Visual Deck Storage - Game Replays + Visual Database Display - Administration + Server + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + Logs - + Are you sure? Czy na pewno? - + There are still open games. Are you sure you want to quit? Ciągle masz otwarte gry. Czy na pewno chcesz wyjść? - + Click to view Kliknij aby podglądnąć - + Your buddy %1 has signed on! Twój znajomy %1 zalogował się! - + Unknown Event Nieznane zdarzenie - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8153,39 +8799,39 @@ Ta wiadomośc może oznaczać że jest dostępna nowa wersja Cockatrice, lub że Aby aktualizować klienta, otwórz okno Pomoc -> Sprawdź Aktualizacje - + Idle Timeout Przekroczono czas bezczynności - + You are about to be logged out due to inactivity. Zostaniesz wylogowany ze względu na nieaktywność. - + Promotion Awans - + You have been promoted. Please log out and back in for changes to take effect. Zostałeś awansowany. Proszę wyloguj się i zaloguj ponownie aby zmiany zostały wprowadzone. - + Warned Ostrzeżony - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Otrzymałeś ostrzeżenie z powodu %1. Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. Jeżeli masz pytania, wyślij prywatną wiadomość do moderatora. - + You have received the following message from the server. (custom messages like these could be untranslated) Otrzymałeś następującą wiadomość od serwera. @@ -8195,7 +8841,12 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display @@ -8275,7 +8926,7 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. UpdateDownloader - + Could not open the file for reading. Nie można było otworzyć pliku do odczytu. @@ -8283,206 +8934,206 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. UserContextMenu - + User &details &Dane użytkownika - + Private &chat Prywatny &chat - + Show this user's &games Wyświetl &gry użytkownika - + Add to &buddy list &Dodaj do listy znajomych - + Remove from &buddy list &Usuń z listy znajomych - + Add to &ignore list Dodaj do listy &ignorowanych - + Remove from &ignore list Usuń z listy &ignorowanych - + Kick from &game Wyrzuć z &gry - + Warn user Daj ostrzeżenie użytkownikowi - + View user's war&n history Zobacz historie ostrzeżeń użytkownika - + Ban from &server Zbanuj z &serwera - + View user's &ban history Zobacz historie blokad użytkownika - + &Promote user to moderator &Daj użytkownikowi uprawnienia moderatora - + Dem&ote user from moderator &Zabierz użytkownikowi uprawnienia moderatora - + Promote user to &judge &Daj użytkownikowi uprawnienia sędziego - + Demote user from judge &Zabierz użytkownikowi uprawnienia sędziego - + View admin notes Sprawdź notatki administratorskie - - - + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games Gry gracza %1 - - - + + + Ban History Historia blokad - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Blokada Czas;Moderator;Czas Blokady;Powód Blokady;Widoczny Powód - + User has never been banned. Użytkownik nigdy nie został zablokowany - + Failed to collect ban information. Błąd podczas zbierania informacji o blokadach. - - - + + + Warning History Historia ostrzeżeń - + Warning Time;Moderator;User Name;Reason Ostrzeżenie Czas;Moderator;Nazwa Użytkownika;Powód - + User has never been warned. Użytkownik nigdy nie dostał ostrzeżenia. - + Failed to collect warning information. Błąd podczas zbierania informacji o ostrzeżeniach. - + Failed to get admin notes. Błąd podczas zbierania notatek administratorskich. - - + + Success Operacja zakończona pomyślnie - + Successfully promoted user. Pomyślny awans gracza. - + Successfully demoted user. Pomyślna degradacja gracza. - + + - Failed Wystąpił błąd - + Failed to promote user. Błąd podczas awansu gracza. - + Failed to demote user. Błąd podczas degradacji gracza. - + Copy hash to clipboard Zapisz hash do schowka - + Remove this user's messages Usuń wiadomości tego użytkownika @@ -8664,137 +9315,142 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. UserInterfaceSettingsPage - + General interface settings Ogólne ustawienia interfejsu - + &Double-click cards to play them (instead of single-click) Zagrywaj karty po&dwójnym kliknięciem (zamiast pojedynczym) - + &Clicking plays all selected cards (instead of just the clicked card) Kliknięcie zagrywa wszystkie wybrane karty (zamiast tylko klikniętej karty) - + &Play all nonlands onto the stack (not the battlefield) by default Karty nie będące lądami zagrywaj domyślnie na stos (zamiast na &pole bitwy) - - - Close card view window when last card is removed - - - - - Auto focus search bar when card view window is opened - - - - - Annotate card text on tokens - Dodaj tekst karty na tokenach - - - - Use tear-off menus, allowing right click menus to persist on screen - Użycie odczepianych menu, pozwalające na zatrzymanie menu kontekstowego na ekranie - - - - Notifications settings - Ustawienia powiadomień - - - - Enable notifications in taskbar - Włącz &powiadomienia na pasku zadań - - - - Notify in the taskbar for game events while you are spectating - Powiadomienia na pasku zadań dla gier, które &obserwujesz - - - - Notify in the taskbar when users in your buddy list connect - Powiadamiaj na pasku zadań kiedy użytkownik z twojej listy znajomych zaloguje się. - - - - Animation settings - Ustawienia animacji - - - - &Tap/untap animation - Animacja &tapowania/odtapowania - - Deck editor/storage settings + Do not delete &arrows inside of subphases - Open deck in new tab by default - Domyślnie otwórz talię w nowej karcie + Close card view window when last card is removed + - Use visual deck storage in game lobby + Auto focus search bar when card view window is opened - Use selection animation for Visual Deck Storage - + Annotate card text on tokens + Dodaj tekst karty na tokenach + + + + Use tear-off menus, allowing right click menus to persist on screen + Użycie odczepianych menu, pozwalające na zatrzymanie menu kontekstowego na ekranie - When adding a tag in the visual deck storage to a .txt deck: - + Notifications settings + Ustawienia powiadomień + + + + Enable notifications in taskbar + Włącz &powiadomienia na pasku zadań - do nothing - + Notify in the taskbar for game events while you are spectating + Powiadomienia na pasku zadań dla gier, które &obserwujesz + + + + Notify in the taskbar when users in your buddy list connect + Powiadamiaj na pasku zadań kiedy użytkownik z twojej listy znajomych zaloguje się. - ask to convert to .cod - + Animation settings + Ustawienia animacji + + + + &Tap/untap animation + Animacja &tapowania/odtapowania - always convert to .cod + Deck editor/storage settings - Default deck editor type - + Open deck in new tab by default + Domyślnie otwórz talię w nowej karcie - Classic Deck Editor + Use visual deck storage in game lobby + Use selection animation for Visual Deck Storage + + + + + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + Visual Deck Editor - + Replay settings Ustawienia powtórek - + Buffer time for backwards skip via shortcut: Buforuj czas do przeskoku w tył wywołane przez skrót: @@ -8802,22 +9458,22 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 @@ -8825,32 +9481,32 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -8858,22 +9514,22 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match - + Mode: Includes - + Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -8881,21 +9537,49 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8921,28 +9605,28 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - Load from Deck + Filter by name... (Exact match) - Apply all card names in currently loaded deck as exact match name filters + Load from Deck - Load from Clipboard + Apply all card names in currently loaded deck as exact match name filters + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -9006,50 +9690,115 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - + + Sort by: + + + + + Filter by: + + + + Save and load filters - + Filter by exact card name - + Filter by card sub-type - + Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9057,46 +9806,25 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -9152,7 +9880,7 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9160,22 +9888,22 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -9183,17 +9911,17 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + Visual Deck Storage Settings @@ -9201,43 +9929,43 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. WarningDialog - + Which warning would you like to send? Jakie ostrzeżenie chciałbyś wysłać? - + Redact all messages from this user in all rooms Redaguj wiadomości od tego użytkownika we wszystkich pokojach - + &OK &OK - + &Cancel &Anuluj - + Warn user for misconduct Ostrzeż gracza za złe zachowanie - - + + Error Błąd - + User name to send a warning to can not be blank, please specify a user to warn. Nazwa gracza do wysłania ostrzeżenia nie może być pusta, prosze podać gracza. - + Warning to use can not be blank, please select a valid warning to send. Ostrzeżenie nie może być puste, zaznacz poprawne ostrzeżenie do wysłania. @@ -9245,133 +9973,133 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. WndSets - + Move selected set to the top Przesuń zaznaczony set do góry - + Move selected set up Przesuć wybrany dodatek w górę - + Move selected set down Przesuń wybrany dodatek w dół - + Move selected set to the bottom Przesuń zaznaczony set na dół - + Search by set name, code, or type Szukaj po nazwie dodatku, kodzie lub typie - + Default order Domyślna kolejność - + Restore original art priority order Przywróć oryginalny priorytet obrazów kart - + Enable all sets Włącz wszystkie dodatki - + Disable all sets Wyłącz wszystkie dodatki - + Enable selected set(s) Włącz zaznaczony/e set(y) - + Disable selected set(s) Wyłącz zaznaczony/e set(y) - + Deck Editor Edycja Talii - + Use CTRL+A to select all sets in the view. Użyj CTRL+A aby wybrać wszystkie dodatki w widoku. - + Only cards in enabled sets will appear in the card list of the deck editor. Tylko karty z odblokowanych dodatków pojawią się w edytorze talii kart - + Image priority is decided in the following order: Priorytet obrazów jest ustalany w następujący sposób: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki najpierw folder NIESTANDARDOWYCH (%1), potem Włączone Dodatki w tym oknie dialogowym (Z góry na dół) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art Obraz karty - + How to use custom card art Jak ustawić niestandardowe obrazy do kart - + Hints Wskazówki - + Note Notatka - + Sorting by column allows you to find a set while not changing set priority. Sortowane kolumnami pozwala ci znaleźć dodatek nie zmieniając ich priorytetów. - + To enable ordering again, click the column header until this message disappears. Aby wyłączyć sortowanie klikaj ponownie na ten sam nagłówek kolumny aż zniknie ten komunikat. - + Use the current sorting as the set priority instead Użyj tymczasowego sortowania jako priorytet dodatków - + Sorts the set priority using the same column Sortuje priorytet dodatków korzystając z tej samej kolumny - + Manage sets Zarządzaj dodatkami @@ -9379,72 +10107,72 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped Niepogrupowane - + Group by Type Grupuj po typie - + Group by Mana Value Grupuj po wartości many - + Group by Color Grupuj po kolorze - + Unsorted Nieposortowane - + Sort by Name Sortuj według nazwy - + Sort by Type Sortuj według typu - + Sort by Mana Cost Sortuj według wartości many - + Sort by Colors Sortuj według koloru - + Sort by P/T Sortuj według P/T - + Sort by Set Sortuj według dodatku - + shuffle when closing przetasuj przy zamykaniu - + pile view widok sterty @@ -9452,7 +10180,7 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. i18n - + English Polski (Polish) @@ -9460,12 +10188,12 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. main - + Connect on startup Zaloguj po uruchowmieniu - + Debug to file Debuguj do pliku @@ -9473,1005 +10201,1041 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. shortcutsTab - + Main Window Główne okno - - + + Deck Editor Edycja Talii - + Game Lobby Poczekalnia gier - + Card Counters Znaczniki kart - + Player Counters Znaczniki graczy - + Power and Toughness Siła i wytrzymałość - + Game Phases Fazy gry - + Playing Area Teren gry - + Move Selected Card Przenieś wybraną kartę - + View Widok - + Move Top Card Przenieś kartę z wierzchu - + Move Bottom Card Przenieś kartę z dołu - + Gameplay Rozgrywka - + Drawing Dobieranie - + Chat Room Pokój czatu - + Game Window Okno gry - + Load Deck from Clipboard Wczytaj talię ze schowka - - + + Replays Powtórki - + Tabs - + Check for Card Updates... Uaktualnij bazę kart... - + Connect... Połącz… - + Disconnect Rozłącz - + Exit Wyjście - + Full screen Pełny ekran - + Register... Zarejestruj się... - + Settings... Ustawienia... - + Start a Local Game... Rozpocznij lokalną grę… - + Watch Replay... Obejrzyj powtórkę… - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters Wyczyść wszystkie filtry - + Clear Selected Filter Wyczyść zaznaczone filtry - + Close Zamknij - + Remove Card Usuń kartę - + Manage Sets... Zarządzaj dodatkami... - + Edit Custom Tokens... Edytuj niestandardowe tokeny... - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card Dodaj kartę - + Load Deck... Wczytaj talię... - - + + Load Deck from Clipboard... Wczytaj talię ze schowka… - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck Nowa talia - + Open Custom Pictures Folder Otwórz folder z niestandardowymi obrazkami - + Print Deck... Drukuj talię - + Delete Card Usuń kartę - - + + Reset Layout Zresetuj wygląd - + Save Deck Zapisz talię - + Save Deck as... Zapisz talię jako… - + Save Deck to Clipboard, Annotated Zapisz talię, anotowaną, do schowka - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard Zapisz talię do schowka - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... Wczytaj talię lokalną... - + Load Remote Deck... Wczytaj zdalną talię... - + Set Ready to Start Ustaw gotowość do rozpoczęcia - + Toggle Sideboard Lock Przełącz blokadę sideboard - + Add Green Counter Dodaj Zielony Znacznik - + Remove Green Counter Odejmij Zielony Znacznik - + Set Green Counters... Ustaw Zielony Znacznik... - + Add Red Counter Dodaj Czerwony Znacznik - + Remove Red Counter Odejmij Czerwony Znacznik - + Set Red Counters... Ustaw Czerwony Znacznik - + Add Life Counter Dodaj do Znacznika Życia - + Show Status Bar - + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter Odejmij od Znacznika Życia - + Set Life Counters... Ustaw Znacznik Życia... - + Add White Counter Dodaj Biały Znacznik - + Remove White Counter Odejmij Biały Znacznik - + Set White Counters... Ustaw Biały Znacznik... - + Add Blue Counter Dodaj Niebieski Znacznik - + Remove Blue Counter Odejmij Niebieski Znacznik - + Set Blue Counters... Ustaw Niebieski Znacznik... - + Add Black Counter Dodaj Czarny Znacznik - + Remove Black Counter Odejmij Czarny Znacznik - + Set Black Counters... Ustaw Czarny Znacznik... - + Add Colorless Counter Dodaj Bezkolorowy Znacznik - + Remove Colorless Counter Odejmij Bezkolorowy Znacznik - + Set Colorless Counters... Ustaw Bezkolorowy Znacznik... - + Add Other Counter Dodaj Inny Znacznik - + Remove Other Counter Odejmij Inny Znacznik - + Set Other Counters... Ustaw Inny Znacznik... - + Increment all card counters - + Add Power (+1/+0) Dodaj Power (+1/+0) - + Remove Power (-1/-0) Odejmij Power (-1/-0) - + Move Toughness to Power (+1/-1) Przesuń Toughness do Power (+1/-1) - + Add Toughness (+0/+1) Dodaj Toughness (+0/+1) - + Remove Toughness (-0/-1) Odejmij Toughness (-0/-1) - + Move Power to Toughness (-1/+1) Przesuń Power do Toughness (-1/+1) - + Add Power and Toughness (+1/+1) Dodaj Power i Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) Odejmij Power i Toughness (-1/-1) - + Set Power and Toughness... Ustaw Power i Toughness... - + Reset Power and Toughness Resetuj Power i Toughness - + Untap Odtapuj - + Upkeep Utrzymanie - + Draw Dobranie - + First Main Phase Pierwsza Faza Główna - + Start Combat Początek Walki - + Attack Atak - + Block Blok - + Damage Obrażenia - + End Combat Koniec Walki - + Second Main Phase Druga Faza Główna - + End Koniec - + Next Phase Następna Faza - + Next Phase Action Następna Akcja Fazowa - + Next Turn Następna Tura - + Hide Card in Reveal Window - + Tap / Untap Card Zatapuj / Odtapuj kartę - + Untap All Odtapuj Wszystkie - + Toggle Untap Odtapuj - + Turn Card Over Obróć Kartę - + Peek Card Podglądnij Kartę - + Play Card Zagraj Kartę - + Attach Card... Dołącz Kartę.. - + Unattach Card Odłącz Kartę - + Clone Card Sklonuj Kartę - + Create Token... Utwórz token… - + Create All Related Tokens Stwórz Wszystkie Pokrewne Tokeny - + Create Another Token Stwórz Kolejny Token - + Set Annotation... Ustaw Adnotację… - + Select All Cards in Zone Zaznacz Wszystkie Karty w Strefie - + Select All Cards in Row Zaznacz Wszystkie Karty w Rzędzie - + Select All Cards in Column Zaznacz Wszystkie Karty w Kolumnie - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library Dno biblioteki - - - - + + + + Exile Wygnanie - - - - + + + + Graveyard Grób - - + + + Hand Ręka - - + + Top of Library Wierzch biblioteki - - - + + + Battlefield, Face Down Pole Bitwy, Koszulką do Góry - + Battlefield Pole Bitwy - - Sort Hand - - - - + Library Biblioteka - + Sideboard Biblioteka poboczna - + Top Cards of Library Karty z Wierzchu Biblioteki - + Bottom Cards of Library - + Close Recent View Zamknij Ostatni Widok - - + + Stack Stos - - + + Graveyard (Multiple) Cmentarz (Wiele) - - + + Exile (Multiple) Wygnanie (Wiele) - + Stack Until Found Stos aż do Znalezienia - + Draw Bottom Card Dobierz Kartę z Dna - + Draw Multiple Cards from Bottom... Dobierz Wiele Kart z Dna... - + Draw Arrow... Narysuj wskaźnik... - + Remove Local Arrows Usuń Lokalne Wskaźniki - + Leave Game Opuść Grę - + Concede Poddanie - + Roll Dice... Rzuć Kośćmi... - + Shuffle Library Potasuj Bibliotekę - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan Mulligan - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card Dobierz Kartę - + Draw Multiple Cards... Dobierz Wiele Kart... - + Undo Draw Cofnij Dobranie - + Always Reveal Top Card Zawsze Odkrywaj Górną Kartę - + Always Look At Top Card Zawsze Patrz na Górną Kartę - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise Obróć widok zgodnie z ruchem wskazówek zegara - + Rotate View Counterclockwise Obróć widok przeciwnie do ruchu wskazówek zegara - + Unfocus Text Box Nie skupiaj Tekstu - + Focus Chat Skup Czat - + Clear Chat Wyczyść czat - + Refresh Odśwież - + Skip Forward Przeskocz do Przodu - + Skip Backward Przeskocz do Tyłu - + Skip Forward by a lot Bardzo Przeskocz do Przodu - + Skip Backward by a lot Bardzo Przeskocz do Tyłu - + Play/Pause Odtwórz/Pauza - + Toggle Fast Forward Przełącz przyspieszenie - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_pt.ts b/cockatrice/translations/cockatrice_pt.ts index 7c0a03033..c2624e6d5 100644 --- a/cockatrice/translations/cockatrice_pt.ts +++ b/cockatrice/translations/cockatrice_pt.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... Definir marcador @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter - + New value for counter '%1': @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,79 +36,81 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. + + Add Analytics Panel AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -116,12 +118,12 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard - + Sideboard @@ -129,22 +131,22 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error - + Could not create themes directory at '%1'. - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -155,7 +157,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -166,152 +168,165 @@ Are you sure you would like to disable this feature? - + Confirm Change - + Theme settings Definições do Tema - + Current theme: Tema actual: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + + Show game filter toolbar above list in room tab + + + + Card rendering Rendering da carta - + Display card names on cards having a picture Mostrar o nome em cartas com imagem - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Aumentar as cartas ao passar o rato - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Disposição da Mão - + Display hand horizontally (wastes space) Mostrar mão horizontalmente (desperdiça espaço) - + Enable left justification Permitir justificação de texto à esquerda - + Table grid layout Esquema da mesa - + Invert vertical coordinate Inverter coordenada vertical - + Minimum player count for multi-column layout: Número mínimo de kogadores para layout com múltiplas colunas: - + Maximum font size for information displayed on cards: + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -333,112 +348,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name Banir nome de &utilizador - + ban &IP address Banir endereço de &IP - + ban client I&D banir I&D do cliente - + Ban type Tipo de Ban - + &permanent ban Ban &permanente - + &temporary ban Ban &temporário - + &Days: &Dias: - + &Hours: &Horas: - + &Minutes: &Minutos: - + Duration of the ban Duração do ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Por favor introduza o motivo do ban. Isto apenas é guardado para os moderadores e não é visível para a pessoa banida. - + Please enter the reason for the ban that will be visible to the banned person. Por favor introduza o motivo do ban que será visível à pessoa banida. - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Cancelar - + Ban user from server Banir utilizador do servidor - + + - - + Error Erro - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Tem de seleccionar um banimento baseado no nome, no IP ou ambos. - + You must have a value in the name ban when selecting the name ban checkbox. Deve preencher o campo de nome a banir quando selecciona a caixa de verificação para banir um nome. - + You must have a value in the ip ban when selecting the ip ban checkbox. Deve preencher o campo de IP a banir quando selecciona a caixa de verificação para banir um IP. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Deve preencher o campo de identificação de cliente quando selecciona a caixa de verificação para banir uma identificação de cliente. @@ -469,32 +484,32 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardDatabaseModel - + Name Nome - + Sets Expansões - + Mana cost Custo de mana - + Card type Tipo de carta - + P/T P/R - + Color(s) Cor(es) @@ -502,96 +517,101 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardFilter - + AND Logical conjunction operator used in card filter - + OR Logical disjunction operator used in card filter - + AND NOT Negated logical conjunction operator used in card filter - + OR NOT Negated logical disjunction operator used in card filter - + Name - + + Name (Exact) + + + + Type - + Color - + Text - + Set - + Mana Cost - + Mana Value - + Rarity - + Power - + Toughness - + Loyalty - + Format - + Main Type - + Sub Type @@ -599,22 +619,22 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardInfoFrameWidget - + Image - + Description - + Both - + View transformation @@ -622,22 +642,22 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -650,12 +670,22 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban - + + Set: + + + + + Collector Number: + + + + Related cards: - + Unknown card: @@ -663,124 +693,124 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -788,7 +818,7 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardSizeWidget - + Card Size @@ -796,133 +826,133 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardZoneLogic - + their hand nominative - + %1's hand nominative - - - their library - look at zone - - - - - %1's library - look at zone - - - - - of their library - top cards of zone, - - - - - of %1's library - top cards of zone - - their library - reveal zone + look at zone %1's library + look at zone + + + + + of their library + top cards of zone, + + + + + of %1's library + top cards of zone + + + + + their library reveal zone - + + %1's library + reveal zone + + + + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - - - their graveyard - nominative - - - - - %1's graveyard - nominative - - - - - their exile - nominative - - - - - %1's exile - nominative - - + their graveyard + nominative + + + + + %1's graveyard + nominative + + + + + their exile + nominative + + + + + %1's exile + nominative + + + + their sideboard look at zone - + %1's sideboard look at zone - - - their sideboard - nominative - - - - - %1's sideboard - nominative - - - their custom zone '%1' + their sideboard nominative + %1's sideboard + nominative + + + + + their custom zone '%1' + nominative + + + + %1's custom zone '%2' nominative @@ -931,7 +961,7 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CockatriceXml3Parser - + Parse error at line %1 col %2: @@ -939,7 +969,7 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CockatriceXml4Parser - + Parse error at line %1 col %2: @@ -958,6 +988,37 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -969,47 +1030,47 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add to Deck - + Add to Sideboard - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1017,87 +1078,97 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing - + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + + Format: + + + + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1105,17 +1176,17 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1123,114 +1194,114 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1238,7 +1309,7 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1246,194 +1317,227 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckEditorSettingsPage - - + + Update Spoilers - - + + Success - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + Add URL - - + + URL: - - + + Edit URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL - + Remove URL - + Day(s) - + Updating... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count - + Set - + Number Número - + Provider ID - + Card Carta @@ -1441,12 +1545,12 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1454,17 +1558,17 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1472,7 +1576,7 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -1480,62 +1584,62 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -1548,93 +1652,151 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1652,74 +1814,74 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckViewContainer - + Load deck... &Carregar deck... - + Load remote deck... Carregar deck remoto... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start - + Force start - + Sideboard unlocked - + Sideboard locked - - + + Error Erro - + The selected file could not be loaded. O ficheiro seleccionado não pôde ser carregado. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -1752,143 +1914,143 @@ This will kick all non-ready players from the game. - + Known Hosts - + Delete the currently selected saved server - + Refresh the server list with known public servers - + New Host Novo anfitrião - + Name: - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: &Nome do jogador: - + P&assword: P&assword: - + &Save password &Guardar password - + A&uto connect Conectar a&utomaticamente no arranque - + Automatically connect to the most recent login when Cockatrice opens Conectar automaticamente ao mais recente login ao abrir o Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + Forgot password? - + &Connect - + Server Servidor - + Login Login - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning - + You need to name your new connection profile. - + Connect Warning Aviso de conexão - + The player name can't be empty. O nome de utilizador tem de ser inserido. @@ -1896,117 +2058,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings Re&lembrar definições. - + &Description: &Descrição: - + P&layers: &Jogadores: - + General - + Game type Tipo de jogo - + &Password: &Password: - + Only &buddies can join Apenas &amigos podem entrar - + Only &registered users can join Apenas utilizadores &registados podem entrar - + Joining restrictions Restrições para ligar - + &Spectators can watch E&spectadores podem ver - + Spectators &need a password to watch Espectadores &necessitam de palavra-passe - + Spectators can &chat Espectadores podem c&onversar - + Spectators can see &hands &Espectadores podem ver as mãos - + Create game as spectator - + Spectators Espectadores - + Starting life total: - + Open decklists in lobby - + + Create game as judge + + + + Game setup options - + &Clear &Limpar - + Create game Criar jogo - + Game information Informação do jogo - + Error Erro - + Server error. Erro do servidor. @@ -2014,97 +2181,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Nome: - + Token Ficha - + C&olor: C&or: - + white branco - + blue azul - + black preto - + red vermelho - + green verde - + multicolor multicolor - + colorless incolor - + &P/T: &P/R: - + &Annotation: &Nota: - + &Destroy token when it leaves the table &Destruir ficha quando ela deixar a mesa - + Create face-down (Only hides name) - + Token data Informação de ficha - + Show &all tokens Mostrar &todas as fichas - + Show tokens from this &deck Mostrar &fichas deste deck - + Choose token from list Escolha ficha da lista - + Create token Criar ficha @@ -2112,53 +2279,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2166,40 +2333,40 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. Não escolheu nenhuma imagem. - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. Para alterar o seu avatar, escolha uma nova imagem. Para remover o seu avatar actual, confirme SEM escolher uma nova imagem. - + Browse... Procurar... - + Change avatar Alterar avatar - + Open Image Abrir Imagem - + Image Files (*.png *.jpg *.bmp) Ficheiros de Imagem (*.png *.jpg *.bmp) - + Invalid image chosen. Escolheu uma imagem inválida. @@ -2207,17 +2374,17 @@ Para remover o seu avatar actual, confirme SEM escolher uma nova imagem. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2225,38 +2392,38 @@ Para remover o seu avatar actual, confirme SEM escolher uma nova imagem. DlgEditPassword - + Old password: Password antiga: - + New password: Nova password: - + Confirm new password: Confirme a nova password: - + Change password Alterar a password - - + + Error Erro - + Your password is too short. - + The new passwords don't match. As novas passwords que digitou não coincidem. @@ -2264,93 +2431,93 @@ Para remover o seu avatar actual, confirme SEM escolher uma nova imagem. DlgEditTokens - + &Name: &Nome: - + C&olor: C&or: - + white branco - + blue azul - + black preto - + red vermelho - + green verde - + multicolor multicolor - + colorless incolor - + &P/T: &P/R: - + &Annotation: &Nota: - + Token data Informação de ficha - - + + Add token Adicionar ficha - + Remove token Remover ficha - + Edit custom tokens - + Please enter the name of the token: Por favor introduza o nome da ficha: - + Error Erro - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. @@ -2443,7 +2610,8 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games not created by buddy + Hide games not created by buddies + Hide games not created by buddy @@ -2530,52 +2698,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: - + &Port: - + Player &name: - + Email: - + Reset Password Challenge - + Reset Password Challenge Error - + The email address can't be empty. @@ -2583,37 +2751,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: - + &Port: - + Player &name: - + Reset Password Request - + Reset Password Error - + The player name can't be empty. @@ -2621,86 +2789,86 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: - + &Port: - + Player &name: - + Token: - - + + New Password: - + Reset Password - + The player name can't be empty. - - - - + + + + Reset Password Error - + The token can't be empty. - + The new password can't be empty. - + Error - + Your password is too short. - + The passwords do not match. @@ -2716,17 +2884,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard Carregar baralho da memória - + Error Erro - + Invalid deck list. Lista de deck inválida. @@ -2734,43 +2902,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2784,7 +2952,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck Carregar baralho @@ -2792,37 +2960,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -2830,91 +2998,91 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: &Nome do jogador: - + P&assword: P&assword: - + Password (again): Password (novamente): - + Email: Email: - + Email (again): Email (novamente): - + Country: País: - + Undefined Indefinido - + Real name: Nome real: - + Register to server Registe-se no server - - - - + + + + Registration Warning Registo recusado - + Your password is too short. - + Your passwords do not match, please try again. As passwords que digitou não coincidem. - + Your email addresses do not match, please try again. Os emails que digitou não coincidem. - + The player name can't be empty. Tem de escolher um nome de utilizador. @@ -2940,40 +3108,55 @@ Your email will be used to verify your account. DlgSelectSetForCards - + Unmodified Cards: - + Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database Erro desconhecido ao carregar a base de dados das cartas - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -2990,7 +3173,7 @@ Poderá necessitar de correr o Oracle para actualizar a base de dados. Gostaria de alterar a localização da base de dados? - + Your card database version is too old. This can cause problems loading card information or images @@ -3007,7 +3190,7 @@ Pode ser reparado correndo o Oracle de novo para actualizar a base de dados das Gostaria de alterar a localização da base de dados? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3016,7 +3199,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3025,7 +3208,7 @@ Would you like to change your database location setting? Gostaria de alterar a localização da base de dados? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3034,7 +3217,7 @@ Would you like to change your database location setting? Gostaria de alterar a localização da base de dados? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3043,59 +3226,59 @@ Would you like to change your database location setting? - - - + + + Error Erro - + The path to your deck directory is invalid. Would you like to go back and set the correct path? O directório do seu deck é inválido. Gostaria de voltar atrás e corrigir o directório? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? O directório das imagens das cartas é inválido. Gostaria de voltar atrás e corrigir o directório? - + Settings Definições - + General Geral - + Appearance Aparência - + User Interface Interface do utilizador - + Card Sources - + Chat Chat - + Sound Som - + Shortcuts Atalhos @@ -3103,39 +3286,39 @@ Would you like to change your database location setting? DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3143,17 +3326,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next - + Previous - + Tip of the Day @@ -3161,164 +3344,164 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel - + Reinstall - + Cancel Download - + Open Download Page Abrir a página de downloads - + Check for Client Updates - - - - + + + + Error Erro - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - + Downloading update: %1 - + Checking for updates... A procurar actualizações... - + Finished checking for updates - + No Update Available - + Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. - + Current version - + Selected release channel + + + + Update Available + + + + + + A new version of Cockatrice is available! + + + + + + New version + + - Update Available - - - - - - A new version of Cockatrice is available! - - - - - - New version - - - - - Released - - + + Changelog - + Do you want to update now? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + + Update Error Erro na actualização - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... A instalar... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -3326,21 +3509,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing - + Copy to clipboard - + Debug Log Registo de erros + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3410,33 +3725,39 @@ You may have to manually download the new version. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3452,22 +3773,22 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. @@ -3475,22 +3796,22 @@ You may have to manually download the new version. GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -3498,226 +3819,279 @@ You may have to manually download the new version. GameSelector - - - - - - + + + + + + Error Erro - + Please join the appropriate room first. Por favor entre na sala apropriada primeiro. - + Wrong password. Password incorrecta. - + Spectators are not allowed in this game. Não são permitidos espectadores neste jogo. - + The game is already full. O jogo já se encontra cheio. - + The game does not exist any more. O jogo já não existe. - + This game is only open to registered users. Este jogo só está aberto a utilizadores registados. - + This game is only open to its creator's buddies. Este jogo só está aberto aos amigos do seu criador. - + You are being ignored by the creator of this game. Você está a ser ignorado pelo criador deste jogo. - + Join Game - + Spectate Game - + Game Information - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game Entrar no jogo - + Password: Password: - + Please join the respective room first. Por favor entre na sala respectiva primeiro. - + &Filter games &Filtrar jogos - + C&lear filter &Limpar filtros - + C&reate C&riar - + &Join &Entrar - + + Join as judge + + + + J&oin as spectator Entrar como &espectador - + + Join as judge spectator + + + + Games shown: %1 / %2 - + Games Jogos + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password senha - + buddies only amigos apenas - + reg. users only utilizadores registados apenas - + open decklists - - + + can chat pode falar - + see hands ver mãos - + can see hands pode ver as mãos - + not allowed não permitidos - + Room Sala - + Age Idade - + Description Descrição - + Creator Criador - + Type Tipo - + Restrictions Restrições - + Players Jogadores - + Spectators Espectadores @@ -3725,143 +4099,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path Escolher directório - + Personal settings Defenições pessoais - + Language: Língua: - + Paths (editing disabled in portable mode) Diretórios (edição desativada em modo portátil) - + Paths Directórios - + How to help with translations - + Decks directory: Directório dos decks: - + Filters directory: - + Replays directory: Directório de replays: - + Pictures directory: Directório das imagens: - + Card database: Base de dados das cartas: - + Custom database directory: - + Token database: Base de dados das fichas: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup - + Last update check on %1 (%2 days ago) @@ -3869,47 +4243,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -3917,111 +4291,141 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - - &Sort hand + + Sort hand by... - - Take &mulligan + + Name - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - Reveal r&andom card to... + + + All players - - - &All players + + Reveal r&andom card to... HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + + Browse Archidekt + + + + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4223,61 +4627,61 @@ You may have to manually download the new version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. O servidor atingiu a máxima capacidade de utilizadores, por favor verifique de novo mais tarde. - + There are too many concurrent connections from your address. Há demasiadas ligações concorrentes do seu endereço. - + Banned by moderator Banido por moderador - + Expected end time: %1 Tempo previsto para o final:%1 - + This ban lasts indefinitely. Este banimento dura indefinidamente. - + Scheduled server shutdown. Encerramento do servidor agendado. - - + + Invalid username. Nome de utilizador invalido. - + You have been logged out due to logging in at another location. Você deslogado por ter entrado numa outra localização. - + Connection closed Ligação terminada - + The server has terminated your connection. Reason: %1 O servidor terminou a sua ligação. Motivo: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -4290,579 +4694,579 @@ Todos os jogos a decorrer serão perdidos. Motivo para o encerramento: %1 - + Scheduled server shutdown Encerramento do servidor agendado - - + + Success Sucesso - + Registration accepted. Will now login. Registo aceite.%s Entrará agora. - + Account activation accepted. Will now login. Activação da conta aceite. %s Entrará agora. - + Number of players Número de jogadores - + Please enter the number of players. Por favor introduza o número de jogadores. - - + + Player %1 Jogador %1 - + Load replay Carregar replay - + About Cockatrice Sobre o Cockatrice - + Version - + Cockatrice Webpage Website Cockatrice - + Project Manager: Gestor do projecto: - + Past Project Managers: Anteriores gestores do projecto: - + Developers: Desenvolvedores: - + Our Developers Os nossos desenvolvedores - + Help Develop! Ajuda Desenvolvimento! - + Translators: Tradutores: - + Our Translators - + Help Translate! Ajude a traduzir! - + Support: Ajuda técnica: - + Report an Issue Reportar um Problema - + Troubleshooting Solução de problemas - + F.A.Q. Perguntas frequentes - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error Erro - + Server timeout Tempo do servidor esgotado - + Failed Login Tentativa falhada de entrada - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. Nome de utilizador ou palavra-passe incorrecta. Por favor verifique as suas informações e tente de novo. - + There is already an active session using this user name. Please close that session first and re-login. Já existe uma sessão activa com este nome de utilizador. Por favor termine essa sessão e volte a ligar-se. - - + + You are banned until %1. Está banido até %1. - - + + You are banned indefinitely. Está banido indefinidamente. - + This server requires user registration. Do you want to register now? Este servidor requer registo de utilizador. Gostaria de se registar agora? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation Activação de conta - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full - + Unknown login error: %1 Erro de login desconhecido:%1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Normalmente isto significa que a sua versão de cliente está desactualizada e que o servidor enviou uma resposta que o seu cliente não percebe. - + Your username must respect these rules: O seu nome de utilizador deve respeitar as seguintes regras: - + is %1 - %2 characters long tem %1 - %2 caracteres - + can %1 contain lowercase characters %1 pode conter caracteres em minúsculas - - - - + + + + NOT NÃO - + can %1 contain uppercase characters %1 pode conter caracteres em maiúsculas - + can %1 contain numeric characters %1 pode conter caracteres numéricos - + can contain the following punctuation: %1 pode conter a seguinte pontuação: %1 - + first character can %1 be a punctuation mark o primeiro caracter %1 pode ser pontuação - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Apenas A-Z, a-z, 0-9, _, ., e - são permitidos. - - - - - - + + + + + + Registration denied Registo recusado - + Registration is currently disabled on this server Os registos encontram-se indisponívels para este servidor de momento. - + There is already an existing account with the same user name. Já existe uma conta com o mesmo nome de utilizador. - + It's mandatory to specify a valid email address when registering. É obrigatório especificar um endereço de e-mail válido quando se regista. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. A password que inseriu é demasiado curta. - + Registration failed for a technical problem on the server. Falha no registo devido a um problema técnico com o servidor - + The connection to the server has been lost. - + Unknown registration error: %1 Erro de registo desconhecido: %1 - + Account activation failed Falha na activação de conta - + Socket error: %1 Erro de ligação:%1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Está a tentar ligar-se a um servidor obsoleto. Por favor faça downgrade à sua versão do Cockatrice ou ligue-se a servidor adequado. Versão local é %1, versão remota é %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. A sua versão do Cockatrice é obsoleta. Por favor actualize-a. Versão local é %1, versão remota é %2. - + Connecting to %1... Ligando a %1... - + Registering to %1 as %2... Registando em %1 como %2... - + Disconnected Desligado - + Connected, logging in at %1 Conectado, logando em %1 + - Requesting forgotten password to %1 as %2... - + &Connect... &Ligar... - + &Disconnect &Desligar - + Start &local game... Começar &jogo local... - + &Watch replay... &Ver replay... - + &Full screen Ecrã &inteiro - + &Register to server... &A registar no servidor... - + &Restore password... - + &Settings... &Configurações... - + &Exit &Sair - + A&ctions A&cções - + &Cockatrice &Cockatrice - + C&ard Database B&ase de dados de cartas - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder Abrir a pasta de imagens personalizadas - + Open custom sets folder Abrir a pasta de expansões personalizadas - + Add custom sets/cards Adicione cartas/expansões personalizadas - + Reload card database - + Tabs - + &Help &Ajuda - + &About Cockatrice S&obre o Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database Base de dados de cartas - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -4871,110 +5275,110 @@ Deseja actualizar a sua base de dados agora?%s Se não tem a certeza ou é a primeira vez que está a utilizar o Cockatrice, escolha "Sim" - - + + Yes Sim - - + + No Não - + Open settings Abrir definições - + New sets found Novas expansões foram encontradas - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets Ver expansões - + Welcome Bem-vindo/a - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information Informação - + A card database update is already running. A actualização da base de dados das cartas já está a correr. - + Unable to run the card database updater: Incapaz de correr o actualizador da base de dados das cartas: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -4982,673 +5386,843 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards Carregar expansões/cartas - + Selected file cannot be found. O ficheiro escolhido não foi encontrado. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Actualização completa com sucesso. %s O Cockatrice irá agora recarregar a base de dados das cartas. - + Sets/cards failed to import. Falha na importação de expansões/cartas. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play do jogo - + from their graveyard do seu cemitério - + from exile vindo do exílio - + from their hand da sua mão - + the top card of %1's library a carta do topo do grimório de %1 - + the top card of their library a carta do topo dos seus grimórios - + from the top of %1's library do topo do grimório de %1 - + from the top of their library do topo dos seus grimórios - + the bottom card of %1's library a carta do fundo do grimório de %1 - + the bottom card of their library a carta do fundo dos seus grimórios - + from the bottom of %1's library do fundo do grimório de %1 - + from the bottom of their library do fundo dos seus grimórios - + from %1's library do grimório de %1 - + from their library dos seus grimórios - + from sideboard do sideboard - + from the stack da pilha - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 está agora a manter a carta do topo %2 revelada. - + %1 is not revealing the top card %2 any longer. %1 já não está a manter a carta do topo %2 revelada. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 anexa %2 a %4 de %3. - + %1 has conceded the game. %1 concedeu o jogo. - + %1 has unconceded the game. - + %1 has restored connection to the game. %1 restabeleceu a ligação ao jogo. - + %1 has lost connection to the game. %1 perdeu a ligação ao jogo. - + %1 points from their %2 to themselves. %1 aponta do seu %2 para si próprio. - + %1 points from their %2 to %3. %1 aponta do seu %2 para %3. - + %1 points from %2's %3 to themselves. %1 aponta do %3 de %2 para si própria. - + %1 points from %2's %3 to %4. %1 aponta de %3 de %2 para %4. - + %1 points from their %2 to their %3. %1 aponta do seu %2 para %3. - + %1 points from their %2 to %3's %4. %1 aponta do seu %2 para o %4 de %3. - + %1 points from %2's %3 to their own %4. %1 aponta de %3 de %2 para o seu %4. - + %1 points from %2's %3 to %4's %5. %1 aponta de %3 de %2 para %5 de %4. - + %1 creates a face down token. - + %1 creates token: %2%3. %1 cria ficha: %2%3. - + %1 has loaded a deck (%2). %1 carregou um deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 carregou um deck com %2 cartas na sideboard (%3). - + %1 destroys %2. %1 destrói %2. - + a card uma carta - + %1 gives %2 control over %3. %1 dá controlo sobre %3 a %2. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 coloca %2 em jogo %3. - + %1 puts %2%3 into their graveyard. %1 coloca %2%3 no seu grimório. - + %1 exiles %2%3. %1 exila %2%3. - + %1 moves %2%3 to their hand. %1 move %2%3 para a sua mão. - + %1 puts %2%3 into their library. %1 coloca %2%3 no seu grimório. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. %1 coloca %2%3 no topo do seu grimório. - + %1 puts %2%3 into their library %4 cards from the top. %1 põe %2%3 no seu grimório, %4 cartas a contar do topo. - + %1 moves %2%3 to sideboard. %1 move %2%3 para o sideboard. - + %1 plays %2%3. %1 joga %2%3. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. %1 está a olhar para %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. Este jogo foi encerrado. - + The game has started. O jogo começou. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 entrou no jogo. - + %1 is now watching the game. %1 está agora a ver o jogo. - + You have been kicked out of the game. Você foi expulso do jogo. - + %1 has left the game (%2). %1 abandonou o jogo (%2). - + %1 is not watching the game any more (%2). %1 não está mais a observar o jogo (%2). - + %1 is not ready to start the game any more. %1 já não está pronto a começar o jogo. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. Está a ver um replay do jogo #%1. - + %1 is ready to start the game. %1 está pronto a começar o jogo. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 revela %2 a %3. - + %1 reveals %2. %1 revela %2. - + %1 randomly reveals %2%3 to %4. %1 revela aleatoreamente %2%3. a %4. - + %1 randomly reveals %2%3. %1 revela aleatoreamente %2%3. - + %1 peeks at face down card #%2. %1 espreita a carta virada para baixo #%2. - + %1 peeks at face down card #%2: %3. %1 espreita a carta virada para baixo #%2: %3. - + %1 reveals %2%3 to %4. %1 revela %2%3 a %4. - + %1 reveals %2%3. %1 revela %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads Cara - + Tails Coroa - + %1 flipped a coin. It landed as %2. %1 atirou uma moeda ao ar. Calhou %2. - + %1 rolls a %2 with a %3-sided die. %1 obteve %2 com um dado de %3 faces. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. Turno de %1. - + %1 sets annotation of %2 to %3. %1 coloca uma nota de %2 em%3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 altera o número de marcadores %2 para %3(%4%5). - + %1 sets %2 to not untap normally. %1 define %2 para não desvirar normalmente. - + %1 sets %2 to untap normally. %1 define %2 para desvirar normalmente. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. %1 bloqueou o seu sideboard. - + %1 has unlocked their sideboard. %1 desbloqueou o seu sideboard. - + %1 taps their permanents. %1 vira as suas permanentes. - + %1 untaps their permanents. %1 desvira as suas permanentes. - + %1 taps %2. %1 vira %2. - + %1 untaps %2. %1 desvira %2. - + %1 shuffles %2. %1 baralha %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. %1 desanexa %2. - + %1 undoes their last draw. %1 desfaz a sua última compra. - + %1 undoes their last draw (%2). %1 desfaz a sua última compra (%2). @@ -5656,110 +6230,110 @@ O Cockatrice irá agora recarregar a base de dados das cartas. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Adicionar mensagem - - + + Message: Mensagem: - + Edit message - + Chat settings Definições do Chat - + Custom alert words Palavras alerta personalizáveis - + Enable chat mentions Permitir menções no chat - + Enable mention completer Permitir complementação de menções - + In-game message macros Macros de mensagem no decorrer do jogo - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users Ignorar mensagens de chat enviadas por utilizadores não registados - + Ignore private messages sent by unregistered users Ignorar mensagens privadas enviadas por utilizadores não registados - - + + Invert text color Inverter cor do texto - + Enable desktop notifications for private messages Permitir notificações de mensagens privadas no Ambiente de Trabalho - + Enable desktop notification for mentions Permitir notificações de menções no ambiente de trabalho - + Enable room message history on join Permitir o histórico de mensagens de uma sala ao entrar - - + + (Color is hexadecimal) (A cor é hexadecimal) - + Separate words with a space, alphanumeric characters only Palavras separadas com espaço, apenas caracteres alfanuméricos @@ -5876,62 +6450,62 @@ O Cockatrice irá agora recarregar a base de dados das cartas. Phase - + Unknown Phase - + Untap - + Upkeep - + Draw - + First Main - + Beginning of Combat - + Declare Attackers - + Declare Blockers - + Combat Damage - + End of Combat - + Second Main - + End/Cleanup @@ -5997,7 +6571,7 @@ O Cockatrice irá agora recarregar a base de dados das cartas. PictureLoader - + en code for scryfall's language property, not available for all languages pt @@ -6006,134 +6580,134 @@ O Cockatrice irá agora recarregar a base de dados das cartas. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - + Move top cards to grave - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6141,17 +6715,17 @@ O Cockatrice irá agora recarregar a base de dados das cartas. PlayerMenu - + Player "%1" - + &Counters - + S&ay @@ -6159,7 +6733,7 @@ O Cockatrice irá agora recarregar a base de dados das cartas. PrintingSelector - + Display Navigation Buttons @@ -6167,22 +6741,22 @@ O Cockatrice irá agora recarregar a base de dados das cartas. PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6198,17 +6772,17 @@ O Cockatrice irá agora recarregar a base de dados das cartas. PrintingSelectorCardSelectionWidget - + Previous Card in Deck - + Bulk Selection - + Next Card in Deck @@ -6216,28 +6790,28 @@ O Cockatrice irá agora recarregar a base de dados das cartas. PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -6303,37 +6877,37 @@ O Cockatrice irá agora recarregar a base de dados das cartas. QMenuBar - + Services Serviços - + Hide %1 Ocultar %1 - + Hide Others Ocultar outros - + Show All Mostrar tudo - + Preferences... Preferências... - + Quit %1 Sair %1 - + About %1 Cerca %1 @@ -6341,42 +6915,42 @@ O Cockatrice irá agora recarregar a base de dados das cartas. QObject - + Cockatrice card database (*.xml) Base de dados de cartas Cockatrice (*.xml) - + All files (*.*) Todos os ficheiros (*.*) - + Cockatrice replays (*.cor) Replays do Cockatrice (*.cor) - + Maindeck Baralho - + Sideboard Sideboard - + Tokens Tokens - + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? @@ -6384,92 +6958,92 @@ O Cockatrice irá agora recarregar a base de dados das cartas. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -6566,37 +7140,37 @@ O Cockatrice irá agora recarregar a base de dados das cartas. RoomSelector - + Rooms Salas - + Joi&n E&ntrar - + Room Sala - + Description Descrição - + Permissions Permissões - + Players Jogadores - + Games Jogos @@ -6637,27 +7211,27 @@ O Cockatrice irá agora recarregar a base de dados das cartas. SetsModel - + Enabled Activado - + Set type Tipo de expansão - + Set code Código da expansão - + Long name Nome longo - + Release date Data de lançamento @@ -6665,53 +7239,53 @@ O Cockatrice irá agora recarregar a base de dados das cartas. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -6719,12 +7293,12 @@ O Cockatrice irá agora recarregar a base de dados das cartas. ShortcutTreeView - + Action - + Shortcut @@ -6732,13 +7306,13 @@ O Cockatrice irá agora recarregar a base de dados das cartas. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -6765,12 +7339,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -6778,27 +7352,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Permitir &sons - + Current sounds theme: Tema sonoro actual: - + Test system sound engine Teste motor de sistema de som - + Sound settings Definições de Som - + Master volume Volume mestre @@ -6806,48 +7380,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -7011,56 +7585,123 @@ Please check your shortcut settings! + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info Informação da carta - + Deck Baralho - + Filters Filtros - + &View &Vista - + + Card Database + + + + Printing - - - - + + + + + Visible Visível - - - - + + + + + Floating Flutuando - + Reset layout Repor disposição - + Deck: %1 Deck:%1 @@ -7068,61 +7709,61 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + + + + Visible - - - - + + + + Floating - + Reset layout @@ -7130,22 +7771,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7153,134 +7794,134 @@ Please check your shortcut settings! TabDeckStorage - + Local file system Ficheiros locais - + Server deck storage Decks armazenados no servidor - - + + Open in deck editor Abrir no editor de decks - + Rename deck or folder - + Upload deck Upload do deck - + Download deck Download do deck + - - - + + New folder Nova pasta + - Delete Apagar - + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + Error - + Rename failed - - + + Invalid deck file - + Enter deck name Introduza o nome do deck - + This decklist does not have a name. Please enter a name: Este deck nao tem um nome Por favor introduza um nome: - + Unnamed deck Deck sem nome - + Failed to upload deck to server - + Delete local file Apagar ficheiro local - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + Name of new folder: Nome da nova pasta: @@ -7293,17 +7934,17 @@ Por favor introduza um nome: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 @@ -7344,7 +7985,7 @@ Por favor introduza um nome: - + EDHRec: @@ -7352,197 +7993,197 @@ Por favor introduza um nome: TabGame - - - + + + Replay Repetição - - + + Game Jogo - - + + Player List Jogadores - - + + Card Info Informação da carta - - + + Messages Mensagens - - + + Replay Timeline Repetir Cronologia - + &Phases Fa&ses - + &Game &Jogo - + Next &phase Próxima &fase - + Next phase with &action - + Next &turn Próximo &turno - + Reverse turn order - + &Remove all local arrows &Remover todas as setas locais - + Rotate View Cl&ockwise Rodar a vista no se&ntido dos ponteiros - + Rotate View Co&unterclockwise rodar a vista no se&ntido contrário aos ponteiros - + Game &information &Informação do jogo - + Un&concede - - - + + + &Concede &Conceder - + &Leave game Sair do &jogo - + C&lose replay &Fechar replay - + &Focus Chat - + &Say: &Dizer: - + Selected cards - + &View &Vista - - + + + - Visible Visível - - + + + - Floating Flutuando - + Reset layout Repor disposição - + Concede Conceder - + Are you sure you want to concede this game? Tem a certeza que deseja conceder este jogo? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game Sair do jogo - + Are you sure you want to leave this game? Tem a certeza que deseja sair deste jogo? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. Foi expulso do jogo. @@ -7550,7 +8191,7 @@ Por favor introduza um nome: TabHome - + Home @@ -7558,158 +8199,158 @@ Por favor introduza um nome: TabLog - + Logs Registos - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Tempo;NomeEnviador;IPEnviador;Mensagem;IDAlvo;NomeAlvo - + Room Logs Registos das salas - + Game Logs Registos dos jogos - + Chat Logs Registos das conversas - - + + Error Erro - + You must select at least one filter. Deve seleccionar pelo menos um filtro - + You have to select a valid number of days to locate. Deve seleccionar um número válido de dias para localizar. - + Username: Nome de utilizador: - + IP Address: Morada IP: - + Game Name: Nome do Jogo: - + GameID: ID do Jogo - + Message: Mensagem: - + Main Room Sala principal - + Game Room Sala de Jogo - + Private Chat Chat Privado - + Past X Days: Últimos X Dias: - + Today Hoje - + Last Hour Última Hora - + Maximum Results: Resultados Máximos: - + At least one filter is required. The more information you put in, the more specific your results will be. É requerido pelo menos um filtro. %s Quanta mais informação der, mais específicos serão os resultados. - + Get User Logs Obter registos dos utilizadores - + Clear Filters Limpar Filtros - + Filters Filtros: - + Log Locations Localização dos Registos - + Date Range Alcance de dados - + Maximum Results Resultados máximos - - + + Message History Histórico de mensagens - + Failed to collect message history information. Falha na obtenção do histórico de mensagens - + There are no messages for the selected filters. Não há mensagens para os filtros seleccionados @@ -7755,180 +8396,180 @@ Quanta mais informação der, mais específicos serão os resultados. TabReplays - + Local file system Ficheiros locais - + Server replay storage Armazenamento de replays no servidor - - + + Watch replay Ver replay - + Rename - - + + New folder - - + + Delete Apagar - + Open replays folder - + Download replay Download replay - + Toggle expiration lock Alternar bloqueio de expiração - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file Apagar ficheiro local - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay Apagar replay remoto @@ -7994,30 +8635,30 @@ Quanta mais informação der, mais específicos serão os resultados.Servidor + - Error Erro - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -8025,92 +8666,97 @@ Quanta mais informação der, mais específicos serão os resultados. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - - - Home - - - - - &Visual Deck Storage - - - - - Visual Database Display - - - Server + Archidekt - Account + Home - Deck Storage + &Visual Deck Storage - Game Replays + Visual Database Display - Administration + Server + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + Logs - + Are you sure? Tem a certeza? - + There are still open games. Are you sure you want to quit? Ainda estão a decorrer alguns jogos. Tem a certeza que quer sair? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8118,39 +8764,39 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion Promoção - + You have been promoted. Please log out and back in for changes to take effect. - + Warned Avisado - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Recebeu um aviso devido a %1. %s Por favor evite continuar essa actividade ou acções serão tomadas contra si futuramente. Se tem alguma questão, por favor envie uma mensagem privada a um moderador. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -8159,7 +8805,12 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display @@ -8239,7 +8890,7 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f UpdateDownloader - + Could not open the file for reading. Incapaz de abrir o ficheiro para leitura. @@ -8247,206 +8898,206 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f UserContextMenu - + User &details Detalhes do &utilizador - + Private &chat Chat &privado - + Show this user's &games Mostrar os &jogos deste utilizador - + Add to &buddy list Adicionar à &lista de amigos - + Remove from &buddy list Remover da lista de &amigos - + Add to &ignore list Adicionar a lista a i&gnorar - + Remove from &ignore list Remover da lista a &ignorar - + Kick from &game Expulsar do &jogo - + Warn user Avisar utilizador - + View user's war&n history Ver o histórico de avi&sos do utilizador - + Ban from &server Banir do &servidor - + View user's &ban history Ver o histórico de &bans do utilizador - + &Promote user to moderator &Promover utilizador a moderador - + Dem&ote user from moderator &Despromover utilizador do cargo de moderador - + Promote user to &judge - + Demote user from judge - + View admin notes - - - + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games jogos de %1 - - - + + + Ban History Histórico de banições - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Hora de Banição; Moderador; Duração da Banição; Razão da Banição; Razão visível - + User has never been banned. O utilizador nunca foi banido. - + Failed to collect ban information. - - - + + + Warning History Histórico de avisos - + Warning Time;Moderator;User Name;Reason Hora do aviso; Moderador; Nome do Utilizador; Razão - + User has never been warned. O utilizador nunca foi avisado. - + Failed to collect warning information. - + Failed to get admin notes. - - + + Success Sucesso - + Successfully promoted user. Utilizador promovido com sucesso. - + Successfully demoted user. Utilizador despromovido com sucesso. - + + - Failed Falhou - + Failed to promote user. Falha na promoção do utilizador. - + Failed to demote user. Falha na despromoção do utilizador. - + Copy hash to clipboard - + Remove this user's messages @@ -8628,137 +9279,142 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f UserInterfaceSettingsPage - + General interface settings Configurações gerais da interface - + &Double-click cards to play them (instead of single-click) Clicar &duas vezes nas cartas para as jogar (ao invés de clicar apenas uma vez) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default &Jogar todos os não-terrenos para a pilha (em vez de para o campo de batalha) por definição - - - Close card view window when last card is removed - - - - - Auto focus search bar when card view window is opened - - - - - Annotate card text on tokens - Permitir anotações em tokens - - - - Use tear-off menus, allowing right click menus to persist on screen - - - - - Notifications settings - - - - - Enable notifications in taskbar - Permitir notificações na barra de tarefas - - - - Notify in the taskbar for game events while you are spectating - Notificar eventos de jogos na barra de tarefas - - - - Notify in the taskbar when users in your buddy list connect - - - - - Animation settings - Configurações de Animações - - - - &Tap/untap animation - Animação de &virar/desvirar - - Deck editor/storage settings + Do not delete &arrows inside of subphases - Open deck in new tab by default + Close card view window when last card is removed - Use visual deck storage in game lobby + Auto focus search bar when card view window is opened - Use selection animation for Visual Deck Storage + Annotate card text on tokens + Permitir anotações em tokens + + + + Use tear-off menus, allowing right click menus to persist on screen - When adding a tag in the visual deck storage to a .txt deck: + Notifications settings + + + Enable notifications in taskbar + Permitir notificações na barra de tarefas + - do nothing + Notify in the taskbar for game events while you are spectating + Notificar eventos de jogos na barra de tarefas + + + + Notify in the taskbar when users in your buddy list connect - ask to convert to .cod - + Animation settings + Configurações de Animações + + + + &Tap/untap animation + Animação de &virar/desvirar - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -8766,22 +9422,22 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 @@ -8789,32 +9445,32 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -8822,22 +9478,22 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match - + Mode: Includes - + Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -8845,21 +9501,49 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8885,28 +9569,28 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - Load from Deck + Filter by name... (Exact match) - Apply all card names in currently loaded deck as exact match name filters + Load from Deck - Load from Clipboard + Apply all card names in currently loaded deck as exact match name filters + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -8970,50 +9654,115 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - + + Sort by: + + + + + Filter by: + + + + Save and load filters - + Filter by exact card name - + Filter by card sub-type - + Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9021,46 +9770,25 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -9116,7 +9844,7 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9124,22 +9852,22 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -9147,17 +9875,17 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + Visual Deck Storage Settings @@ -9165,43 +9893,43 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f WarningDialog - + Which warning would you like to send? Que aviso gostaria de enviar? - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Cancelar - + Warn user for misconduct Aviso por má conduta - - + + Error Erro - + User name to send a warning to can not be blank, please specify a user to warn. O nome de utilizador não se pode encontrar vazio, por favor especifique o utilizador que deseja avisar. - + Warning to use can not be blank, please select a valid warning to send. O aviso a enviar não se pode encontrar vazio, por favor seleccione um aviso válido para enviar. @@ -9209,133 +9937,133 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f WndSets - + Move selected set to the top Mover expansão seleccionada para o topo - + Move selected set up Mover expansão seleccionada para cima - + Move selected set down Mover expansão seleccionada para baixo - + Move selected set to the bottom Mover expansão seleccionada para o fundo - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets Permitir todas as expansões - + Disable all sets Inactivar todas as expansões - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -9343,72 +10071,72 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing baralhar quando terminar - + pile view vista em pilha @@ -9416,7 +10144,7 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f i18n - + English Português (Portuguese) @@ -9424,12 +10152,12 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f main - + Connect on startup - + Debug to file @@ -9437,1005 +10165,1041 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f shortcutsTab - + Main Window Janela Principal - - + + Deck Editor Editor de Baralhos - + Game Lobby Lobby de jogo - + Card Counters - + Player Counters - + Power and Toughness Poder e Resistência - + Game Phases Etapas de jogo - + Playing Area Área de jogo - + Move Selected Card - + View Ver - + Move Top Card - + Move Bottom Card - + Gameplay Área de jogo - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - - + + Replays - + Tabs - + Check for Card Updates... - + Connect... - + Disconnect Desconectar - + Exit Sair - + Full screen Ecrã Inteiro - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters - + Clear Selected Filter - + Close Fechar - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card - + Load Deck... - - + + Load Deck from Clipboard... - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck - + Open Custom Pictures Folder - + Print Deck... - + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... - + Load Remote Deck... - + Set Ready to Start - + Toggle Sideboard Lock - + Add Green Counter - + Remove Green Counter - + Set Green Counters... - + Add Red Counter - + Remove Red Counter - + Set Red Counters... - + Add Life Counter - + Show Status Bar - + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Increment all card counters - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap Desvirar - + Upkeep Etapa de Upkeep - + Draw Etapa de compra de cartas - + First Main Phase - + Start Combat - + Attack Etapa de Ataque - + Block Etapa de Bloqueio - + Damage Etapa de atribuição de dano - + End Combat - + Second Main Phase - + End Fim de turno - + Next Phase - + Next Phase Action - + Next Turn - + Hide Card in Reveal Window - + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library - - - - + + + + Exile Exílio - - - - + + + + Graveyard Cemitério - - + + + Hand Mão - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - - Sort Hand - - - - + Library Baralho - + Sideboard Sideboard - + Top Cards of Library - + Bottom Cards of Library - + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede Desistir - + Roll Dice... - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan Mulligan - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_pt_BR.ts b/cockatrice/translations/cockatrice_pt_BR.ts index 765d8491b..4a2548812 100644 --- a/cockatrice/translations/cockatrice_pt_BR.ts +++ b/cockatrice/translations/cockatrice_pt_BR.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Definir marcador... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Definir marcador - + New value for counter '%1': Novo valor para marcador '%1': @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh &Atualizar - + Parse Set Name and Number (if available) Analisar nome e número do conjunto (se disponível) @@ -36,81 +36,83 @@ AbstractTabDeckEditor - + Open in new tab Abrir em uma nova aba - + Are you sure? Você tem certeza? - + The decklist has been modified. Do you want to save the changes? A lista foi modificada. Gostaria de salvar as alterações? - - - - - - - + + + + + + Error Erro - + Could not open deck at %1 Não foi possível abrir o deck em %1 - + Could not save remote deck O deck não pode ser salvo remotamente. - - + + The deck could not be saved. Please check that the directory is writable and try again. O deck não pôde ser salvo. Por favor, verifique se a pasta não é somente leitura e tente novamente. - + Save deck Salvar deck - + The deck could not be saved. O deck não pôde ser salvo. - + There are no cards in your deck to be exported Não há cartas em seu deck a serem exportadas + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. - Nenhum deck selecionado para ser exportado. + + Add Analytics Panel + AdminNotesDialog - + Update Notes Notas de Atualização - + Admin Notes for %1 Notas do Administrador para %1 @@ -118,12 +120,12 @@ Por favor, verifique se a pasta não é somente leitura e tente novamente. AllZonesCardAmountWidget - + Mainboard Main - + Sideboard Side @@ -131,22 +133,22 @@ Por favor, verifique se a pasta não é somente leitura e tente novamente. AppearanceSettingsPage - + seconds - + segundos - + Error Erro - + Could not create themes directory at '%1'. Não foi possível criar o diretório de temas em '%1'. - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -154,10 +156,16 @@ You will not be able to manage printing preferences on a per-deck basis, or see You will have to use the Set Manager, available through Card Database -> Manage Sets. Are you sure you would like to enable this feature? - + Habilitar essa funcionalidade desabilita o uso do Selecionador de Impressões. + +Você não será capaz de gerenciar preferências de impressões por decks e nem ver impressões que outros jogadores escolheram para os decks deles. + +Você deverá usar o Gerenciador de Coleção, disponível via Banco de Dados de Cartas -> Gerenciar Coleções. + +Tem certeza de que quer habilitar essa funcionalidade? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -168,279 +176,292 @@ Are you sure you would like to disable this feature? - + Confirm Change - + Confirmar Alteração - + Theme settings Configurações de Tema - + Current theme: Tema atual: - + Open themes folder Abra a pasta de temas - - - Home tab background source: - - - - - Home tab background shuffle frequency: - - - - - Disabled - - + Home tab background source: + Fonte do background da tab Home: + + + + Home tab background shuffle frequency: + Frequência de escolha aleatória de background da tab Home: + + + + Disabled + Desabilitado + + + Menu settings Configurações do Menu - + Show keyboard shortcuts in right-click menus Mostrar atalhos de teclado nos menus de clique com o botão direito - + + Show game filter toolbar above list in room tab + Mostrar o filtro de jogos acima da lista da tab de sala + + + Card rendering Renderização das cartas - + Display card names on cards having a picture Mostrar nome nas cartas que possuem imagem - + Auto-Rotate cards with sideways layout Rotacionar automaticamente cartas com layout horizontal - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector Priorizar conjuntos que o deck contém no topo do seletor de impressão - + Scale cards on mouse over Redimensionar cartas ao passar o mouse - + Use rounded card corners Usar cantos de cartas arredondados - + Minimum overlap percentage of cards on the stack and in vertical hand Porcentagem mínima de sobreposição de cartas na pilha e na mão vertical - + Maximum initial height for card view window: Altura inicial máxima para a janela de visualização de cartas: - - + + rows linhas - + Maximum expanded height for card view window: Altura expandida máxima para a janela de visualização de cartas: - + Card counters - + Marcadores de carta - + Counter %1 - + Marcador %1 - + Hand layout Organização da mão - + Display hand horizontally (wastes space) Mostrar a mão na horizontal (desperdiça espaço) - + Enable left justification Habilitada justificação à esquerda - + Table grid layout Organização do campo de batalha - + Invert vertical coordinate Inverter a coordenada vertical - + Minimum player count for multi-column layout: Número mínimo de jogadores para o layout de multi-colunas - + Maximum font size for information displayed on cards: Tamanho máximo da fonte para informações nas cartas: + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + Abrir no Editor de Decks + + BackgroundSources Theme - + Tema Art crop of random card - + Arte de uma carta aleatória Art crop of background.cod deck file - + Arte de um arquivo de deck backround.cod BanDialog - + ban &user name banir &usuário pelo nome - + ban &IP address banir endereço &IP - + ban client I&D banir cliente por I&D: - + Ban type Tipo de banimento - + &permanent ban &banir permanentemente - + &temporary ban &banir temporariamente - + &Days: &Dias: - + &Hours: &Horas: - + &Minutes: &Minutos: - + Duration of the ban Duração do banimento - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Por favor, entre com o motivo do banimento. Isto será visto somente por moderadores e não pode ser visto pela pessoa banida. - + Please enter the reason for the ban that will be visible to the banned person. Por favor, entre com o motivo para o banimento que será visível pela pessoa banida. - + Redact all messages from this user in all rooms Remover todas mensagens deste usuário em todas as salas - + &OK &OK - + &Cancel &Cancelar - + Ban user from server Banir usuário do servidor - + + - - + Error Erro - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Você deve selecionar um nome, IP, cliente ou alguma combinação desses três itens para banir. - + You must have a value in the name ban when selecting the name ban checkbox. Você têm que colocar um nome válido quando seleciona a caixa de banimento por nome. - + You must have a value in the ip ban when selecting the ip ban checkbox. Você têm que colocar um IP válido quando seleciona a caixa de banimento por IP. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Você têm que colocar um ID de cliente válido quando seleciona a caixa de banimento por ID de cliente. @@ -471,32 +492,32 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid CardDatabaseModel - + Name Nome - + Sets Expansões - + Mana cost Custo de mana - + Card type Tipo de carta - + P/T P/R - + Color(s) Cor(es) @@ -504,119 +525,124 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid CardFilter - + AND Logical conjunction operator used in card filter E - + OR Logical disjunction operator used in card filter OU - + AND NOT Negated logical conjunction operator used in card filter E NÃO - + OR NOT Negated logical disjunction operator used in card filter OU NÃO - + Name Nome - + + Name (Exact) + Nome (Exatamente) + + + Type Tipo - + Color Cor - + Text Texto - + Set Coleção - + Mana Cost Custo de Mana - + Mana Value Valor de Mana - + Rarity Raridade - + Power Poder - + Toughness Resistência - + Loyalty Lealdade - + Format Formato - + Main Type - + Tipo Principal - + Sub Type - + Sub-Tipo CardInfoFrameWidget - + Image Imagem - + Description Descrição - + Both Ambos - + View transformation Ver transformação @@ -624,22 +650,22 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid CardInfoPictureWidget - + View related cards Visualizar cartas relacionadas - + Add card to deck Adicionar carta ao deck - + Mainboard Main - + Sideboard Side @@ -652,12 +678,22 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid Nome: - + + Set: + Coleção: + + + + Collector Number: + Número de Colecionador: + + + Related cards: Cartas relacionadas: - + Unknown card: Carta desconhecida: @@ -665,132 +701,132 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid CardMenu - + Re&veal to... - + Revelar para... - + &All players - + Todos os &jogadores - + View related cards - + Visualizar cartas relacionadas - + Token: - + Ficha: - + All tokens - + Todas as fichas - + &Select All - + &Marque todos - + S&elect Row - + Selecionar Linha - + S&elect Column - + Selecionar Coluna - + &Play - + &Jogar - + &Hide - + &Ocultar - + Play &Face Down - + Jogue &virada para baixo - + &Tap / Untap Turn sideways or back again - + &Virar / Desvirar - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Virar - + &Peek at card face - + &Olhar a face da carta - + &Clone - - - - - Attac&h to card... - - - - - Unattac&h - - - - - &Draw arrow... - - - - - &Set annotation... - - - - - Ca&rd counters - - - - - &Add counter (%1) - + &Clonar - &Remove counter (%1) - + Attac&h to card... + Ane&xar na carta... + + + + Unattac&h + De&sanexar + + + + &Draw arrow... + &Desenhar seta... + &Set annotation... + Colocar &nota... + + + + Ca&rd counters + Marcadores de carta + + + + &Add counter (%1) + &Adicionar marcador(%1) + + + + &Remove counter (%1) + &Remover marcador (%1) + + + &Set counters (%1)... - + &Definir marcadores (%1)... CardSizeWidget - + Card Size Tamanho da Carta @@ -798,152 +834,152 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid CardZoneLogic - + their hand nominative - + sua mão - + %1's hand nominative - - - - - their library - look at zone - - - - - %1's library - look at zone - - - - - of their library - top cards of zone, - - - - - of %1's library - top cards of zone - + mão de %1 their library - reveal zone - + look at zone + seu grimório %1's library + look at zone + grimório de %1 + + + + of their library + top cards of zone, + do seu grimório + + + + of %1's library + top cards of zone + do grimório de %1 + + + + their library reveal zone - + seu grimório - + + %1's library + reveal zone + grimório de %1 + + + their library shuffle - + seu grimório - + %1's library shuffle - + grimório de %1 - + their library nominative - + seu grimório - + %1's library nominative - - - - - their graveyard - nominative - - - - - %1's graveyard - nominative - - - - - their exile - nominative - - - - - %1's exile - nominative - + grimório de %1 + their graveyard + nominative + seu cemitério + + + + %1's graveyard + nominative + cemitério de %1 + + + + their exile + nominative + seu exílio + + + + %1's exile + nominative + exílio de %1 + + + their sideboard look at zone - + seu sideboard - + %1's sideboard look at zone - - - - - their sideboard - nominative - - - - - %1's sideboard - nominative - + sideboard de %1 - their custom zone '%1' + their sideboard nominative - + seu sideboard + %1's sideboard + nominative + sideboard de %1 + + + + their custom zone '%1' + nominative + sua zona customizada '%1' + + + %1's custom zone '%2' nominative - + zona customizada '%2' de %1 CockatriceXml3Parser - + Parse error at line %1 col %2: - + Erro na linha %1 coluna %2: CockatriceXml4Parser - + Parse error at line %1 col %2: - + Erro na linha %1 coluna %2: @@ -951,13 +987,44 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid C&ustom Zones - + Zonas Customizadas View custom zone '%1' - + Ver zona customizada '%1' + + + + DeckAnalyticsWidget + + + Add Panel + Adicionar Painel + + + + Remove Panel + Remover Painel + + + + Save Layout + Salvar Layout + + + + Load Layout + Carregar Layout + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + Banco de Dados de Cartas @@ -971,47 +1038,47 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) Pesquisar pelo nome da carta (ou expressões de busca) - + Add to Deck Adicionar ao deck - + Add to Sideboard Adicionar ao Sideboard - + Select Printing Escolher Impressão - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards Mostrar cartas Relacionadas - + Add card to &maindeck Adicionar carta ao &baralho principal - + Add card to &sideboard Adicionar carta ao &sideboard @@ -1019,87 +1086,97 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card Carta em Destaque - + Main Type - + Mana Cost - + Colors - + Select Printing Escolher Impressão - + Deck Deck - + Deck &name: &Nome do Deck: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: &Comments: - + Group by: - + + Format: + + + + Hash: Hash: - + &Increment number &Aumentar número - + &Decrement number &Diminuir número - + &Remove row &Remover linha - + Swap card to/from sideboard Mover carta para/do Side @@ -1107,17 +1184,17 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckEditorFilterDockWidget - + Filters Filtros - + &Clear all filters &Limpar todos os filtros - + Delete selected Apagar selecionados @@ -1125,114 +1202,114 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckEditorMenu - + &Deck Editor &Editor de Decks - + &New deck &Novo deck - + &Load deck... &Carregar deck... - + Load recent deck... Carregar deck recente... - + Clear Limpar - + &Save deck &Salvar deck - + Save deck &as... Salvar deck &como... - + Load deck from cl&ipboard... Carregar deck da área de &transferência... - + Edit deck in clipboard Editar deck na área de transferência - - + + Annotated Anotado - - + + Not Annotated Não anotado - + Save deck to clipboard Salvar deck para a área de transferência - + Annotated (No set info) Anotado (Sem informação de expansão) - + Not Annotated (No set info) Não Anotado (Sem informação de expansão) - + &Print deck... &Imprimir deck... - + Load deck from online service... - + &Send deck to online service &Enviar deck para serviço online - + Create decklist (decklist.org) Criar decklist (decklist.org) - + Create decklist (decklist.xyz) Criar decklist (decklist.xyz) - + Analyze deck (deckstats.net) &Analisar deck (deckstats.net) - + Analyze deck (tappedout.net) &Analisar deck (tappedout.net) - + &Close &Fechar @@ -1240,7 +1317,7 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckEditorPrintingSelectorDockWidget - + Printing Selector Seletor de Impressão @@ -1248,194 +1325,227 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckEditorSettingsPage - - + + Update Spoilers Atualizar Spoilers - - + + Success Sucesso - + Download URLs have been reset. As URLs de download foram redefinidas. - + Downloaded card pictures have been reset. As imagens de cartas baixadas foram redefinidas. - + Error Erro - + One or more downloaded card pictures could not be cleared. Uma ou mais imagens de cartas baixadas não puderam ser apagadas. - + Add URL Adicionar URL - - + + URL: URL: - - + + Edit URL Editar URL - + Network Cache Size: Tamanho do Cache de Rede: - + Redirect Cache TTL: TTL do Cache de Redirecionamento: - + How long cached redirects for urls are valid for. Por quanto tempo os redirecionamentos em cache para URLs são válidos. - + Picture Cache Size: Tamanho do Cache de Imagens: - + Add New URL Adicionar nova URL - + Remove URL Remover URL - + Day(s) Dia(s) - + Updating... Atualizando... - + Choose path Selecione o caminho - + URL Download Priority Prioridade de download de URL - + Spoilers 'Spoilers' - + Download Spoilers Automatically Baixar 'spoilers' automaticamente - + Spoiler Location: Localização do 'spoiler': - + Last Change Última Alteração - + Spoilers download automatically on launch Baixar automaticamente 'spoilers' ao iniciar - + Press the button to manually update without relaunching Pressione o botão para atualizar manualmente sem reinicializar - + Do not close settings until manual update is complete Não feche as configurações até que a atualização manual termine - + Download card pictures on the fly Baixar imagens de cartas em tempo real - + How to add a custom URL Como adicionar uma URL customizada - + Delete Downloaded Images Deletar imagens baixadas - + Reset Download URLs Resetar URLs de download - + On-disk cache for downloaded pictures Cache em disco para imagens baixadas - + In-memory cache for pictures not currently on screen Cache em memória para imagens que não estão atualmente na tela + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count Contagem - + Set Conjunto - + Number Número - + Provider ID ID do Provedor - + Card Card @@ -1443,12 +1553,12 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckLoader - + Common deck formats (%1) Formatos de decks comuns (%1) - + All files (*.*) Todos arquivos (*.*) @@ -1456,17 +1566,17 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match Modo: Correspondência Exata - + Mode: Includes Modo: Inclui - + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1474,7 +1584,7 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckPreviewDeckTagsDisplayWidget - + Edit tags ... Editar tags ... @@ -1482,62 +1592,62 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckPreviewTagDialog - + Deck Tags Manager Gerenciador de Tags de Deck - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: Gerencie suas tags de deck. Marque ou desmarque as tags conforme necessário ou adicione novas: - + Add a new tag (e.g., Aggro️) Adicionar uma nova tag (ex.: Aggro) - + Add Tag Adicionar Tag - + Filter tags... Filtrar tags... - + OK OK - + Edit default tags - + Cancel Cancelar - + Invalid Input Entrada Inválida - + Tag name cannot be empty! O nome da tag não pode estar vazio! - + Duplicate Tag Tag Duplicada - + This tag already exists. Essa tag já existe. @@ -1550,93 +1660,151 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid Carta em Destaque - + Open in deck editor Abrir no editor de decks - + Edit Tags Editar Tags - + Rename Deck Renomear Deck - + Save Deck to Clipboard Salvar deck na área de transferência - + Annotated Anotado - + Annotated (No set info) Anotado (Sem informação de expansão) - + Not Annotated Não Anotado - + Not Annotated (No set info) Não Anotado (Sem informação de expansão) - + Rename File Renomear Arquivo - + Delete File Deletar Arquivo - + Set Banner Card Escolher Carta em Destaque - - + + New name: Novo nome: - - + + Error Erro - + Rename failed Falha ao renomear - + Delete file Deletar Arquivo - + Are you sure you want to delete the selected file? Tem certeza de que deseja excluir o arquivo selecionado? - + Delete failed Falha ao deletar + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1654,76 +1822,76 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckViewContainer - + Load deck... Carregar deck... - + Load remote deck... Carregar deck remoto... - + Load from clipboard... - + Load from website... - + Unload deck Descarregar deck - + Ready to start Preparadx para começar - + Force start Forçar início - + Sideboard unlocked Sideboard aberto - + Sideboard locked Sideboard fechado - - + + Error Erro - + The selected file could not be loaded. O arquivo selecionado não pode ser carregado - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice - + Cockatrice @@ -1756,143 +1924,143 @@ Deseja converter o deck para .cod? Baixando... - + Known Hosts Servidores conhecidos - + Delete the currently selected saved server Excluir o servidor salvo atualmente selecionado - + Refresh the server list with known public servers Atualizar a lista de servidores com servidores públicos conhecidos - + New Host Novo servidor - + Name: Nome: - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: &Nome do jogador: - + P&assword: S&enha: - + &Save password &Salvar senha - + A&uto connect &Conectar automaricamente - + Automatically connect to the most recent login when Cockatrice opens Conectar automaticamente com o login mais recente quando Cockatrice for iniciado - + If you have any trouble connecting or registering then contact the server staff for help! Se você tiver problemas ao se conectar ou registar entre em contato com os responsáveis pelo servidor! - - + + Webpage Página da Web - + Reset Password Resetar Senha - + Forgot password? Esqueceu a senha? - + &Connect &Conectar - + Server Servidor - + Login Login - + Server Contact Salvar Contato - + Connect to Server Conectar ao Servidor - + Server URL URL do Servidor - + Communication Port Porta de comunicação - + Unique Server Name Nome Único de Servidor - + Connection Warning Aviso de conexão - + You need to name your new connection profile. Você deve nomear seu novo perfil de conexão - + Connect Warning Aviso de conexão - + The player name can't be empty. O nome do jogador é obrigatório. @@ -1900,117 +2068,122 @@ Deseja converter o deck para .cod? DlgCreateGame - + Re&member settings Le&mbrar configurações. - + &Description: &Descrição: - + P&layers: &Jogadores: - + General Geral - + Game type Tipo de jogo - + &Password: &Senha: - + Only &buddies can join Apenas &amigos podem entrar - + Only &registered users can join Apenas usuários &registrados podem entrar - + Joining restrictions Restrições para entrar - + &Spectators can watch &Espectadores podem assistir - + Spectators &need a password to watch &Espectadores precisam de uma senha para assistir - + Spectators can &chat Visitantes podem &conversar - + Spectators can see &hands Espectadores podem ver &mãos - + Create game as spectator Criar jogo como espectador - + Spectators Visitantes - + Starting life total: Total de vida inicial: - + Open decklists in lobby - + + Create game as judge + + + + Game setup options Opções de configuração do jogo - + &Clear &Limpar - + Create game Criar jogo - + Game information Informação de jogo - + Error Erro - + Server error. Erro do servidor. @@ -2018,97 +2191,97 @@ Deseja converter o deck para .cod? DlgCreateToken - + &Name: &Nome: - + Token Ficha - + C&olor: C&or: - + white branco - + blue azul - + black preto - + red vermelho - + green verde - + multicolor multicolorido - + colorless incolor - + &P/T: &P/R: - + &Annotation: &Nota: - + &Destroy token when it leaves the table Destruir a &ficha quando ela sair do campo de batalha - + Create face-down (Only hides name) - + Token data Informação de ficha - + Show &all tokens Mostre &todas fichas - + Show tokens from this &deck Mostre fichas deste &deck - + Choose token from list Mostre fichas da lista - + Create token Criar ficha @@ -2116,53 +2289,53 @@ Deseja converter o deck para .cod? DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2170,40 +2343,40 @@ Deseja converter o deck para .cod? DlgEditAvatar - - + + No image chosen. Nenhuma imagem selecionada. - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. Para mudar seu avatar, escolha uma nova imagem Para remover o avatar atual, confirme sem escolher uma nova imagem. - + Browse... Procurar... - + Change avatar Mudar avatar - + Open Image Abrir imagem - + Image Files (*.png *.jpg *.bmp) Arquivos de imagem (*.png *.jpg *.bmp) - + Invalid image chosen. Imagem inválida. @@ -2211,17 +2384,17 @@ Para remover o avatar atual, confirme sem escolher uma nova imagem. DlgEditDeckInClipboard - + Edit deck in clipboard Editar deck na área de transferência - + Error Erro - + Invalid deck list. Decklist inválida. @@ -2229,38 +2402,38 @@ Para remover o avatar atual, confirme sem escolher uma nova imagem. DlgEditPassword - + Old password: Senha antiga: - + New password: Nova senha: - + Confirm new password: Confirmar senha: - + Change password Trocar senha: - - + + Error Erro - + Your password is too short. Sua senha é muito curta. - + The new passwords don't match. As novas senhas não coincidem. @@ -2268,93 +2441,93 @@ Para remover o avatar atual, confirme sem escolher uma nova imagem. DlgEditTokens - + &Name: &Nome: - + C&olor: &Cor: - + white branco - + blue azul - + black preto - + red vermelho - + green verde - + multicolor multicor - + colorless incolor - + &P/T: &P/R: - + &Annotation: &Anotação - + Token data Informação da ficha - - + + Add token Adicionar ficha - + Remove token Remover ficha - + Edit custom tokens Editar fichas personalizadas - + Please enter the name of the token: Por favor, entre com o nome da ficha: - + Error Erro - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. O nome escolhido está em conflito com uma carta ou ficha existente. @@ -2448,7 +2621,8 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp - Hide games not created by buddy + Hide games not created by buddies + Hide games not created by buddy @@ -2535,52 +2709,52 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp DlgForgotPasswordChallenge - + Reset Password Challenge Warning Aviso de redefinição de desafio de senha - + A problem has occurred. Please try to request a new password again. Houve um problema. Por favor, tente solicitar uma nova senha novamente. - + Enter the information of the server and the account you'd like to request a new password for. Coloque a informação do servidor e a conta que você gostaria de solicitar uma nova senha. - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: Jogador &nome - + Email: Email: - + Reset Password Challenge Desafio de redefinir senha - + Reset Password Challenge Error Erro no desafio de redefinir senha - + The email address can't be empty. O endereço de email não pode estar vazio. @@ -2588,37 +2762,37 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Coloque a informação do servidor que você gostaria de solicitar uma nova senha. - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: Nome do &jogador - + Reset Password Request Pedido de redefinição de senha. - + Reset Password Error Erro de redefinição de senha - + The player name can't be empty. O nome do jogador não pode ficar vazio. @@ -2626,86 +2800,86 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp DlgForgotPasswordReset - + Reset Password Warning Aviso de Redefinição de Senha - + A problem has occurred. Please try to request a new password again. Um problema ocorreu. Por favor tente requisitar uma nova senha novamente. - + Enter the received token and the new password in order to set your new password. Insira o código recebido e a nova senha para definir sua nova senha. - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: Jogador &nome: - + Token: Ficha: - - + + New Password: Nova senha: - + Reset Password Redefinir Senha - + The player name can't be empty. O nome do jogador não pode estar vazio. - - - - + + + + Reset Password Error Erro de Redefinição de Senha - + The token can't be empty. O código não pode estar vazia. - + The new password can't be empty. A nova senha não pode estar vazia. - + Error Erro - + Your password is too short. Sua senha é muito curta. - + The passwords do not match. As senhas não correspondem. @@ -2721,17 +2895,17 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp DlgLoadDeckFromClipboard - + Load deck from clipboard Carregar deck da área de transferência - + Error Erro - + Invalid deck list. Lista de deck inválida. @@ -2739,43 +2913,43 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2789,7 +2963,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck Carregar deck @@ -2797,37 +2971,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): Nome da carta (ou expressões de busca): - + Number of hits: Número de resultados: - + Auto play hits Reproduzir resultados automaticamente - + Put top cards on stack until... Colocar as cartas do topo na pilha até... - + No cards matching the search expression exists in the card database. Proceed anyways? Nenhuma carta correspondente à expressão de busca existe no banco de dados de cartas. Deseja continuar assim mesmo? - + Cockatrice Cockatrice - + Invalid filter Filtro inválido @@ -2835,92 +3009,92 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Insira seus dados e os do servidor que você gostaria de se registrar. Seu e-mail será utilizado para verificar sua conta. - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: Nome do &jogador: - + P&assword: S&enha: - + Password (again): Senha (novamente): - + Email: Email: - + Email (again): Email (novamente): - + Country: País: - + Undefined Indefinido - + Real name: Nome real: - + Register to server Registrar ao servidor: - - - - + + + + Registration Warning Aviso de Registro - + Your password is too short. Sua senha é muito curta. - + Your passwords do not match, please try again. Suas senhas não coincidem, por favor tente novamente. - + Your email addresses do not match, please try again. Seus endereços de email não coincidem, por favor tente novamente. - + The player name can't be empty. O nome de jogador não pode estar vazio. @@ -2946,40 +3120,55 @@ Seu e-mail será utilizado para verificar sua conta. DlgSelectSetForCards - + Unmodified Cards: - + Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database Erro desconhecido ao carregar bando de dados de cartas - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -2996,7 +3185,7 @@ Você pode precisar rodar o Oracle novamente para atualizar o banco de dados de Você gostaria de mudar o local da configuração de seu banco de dados? - + Your card database version is too old. This can cause problems loading card information or images @@ -3013,7 +3202,7 @@ Usualmente isto pode ser consertado rodando novamente o Oracle para atualizar se Você gostaria de alterar o local da configuração de seu banco de dados? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3026,7 +3215,7 @@ Por favor, envie um ticket para https://github.com/Cockatrice/Cockatrice/issues Gostaria de mudar a definição da localização da sua base de dados? - + File Error loading your card database. Would you like to change your database location setting? @@ -3035,7 +3224,7 @@ Would you like to change your database location setting? Você gostaria de alterar seu local de configuração de seu banco de dados? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3044,7 +3233,7 @@ Would you like to change your database location setting? Você gostaria de alterar seu local de configuração de banco de dados? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3057,59 +3246,59 @@ Por favor, crie um ticket em https://github.com/Cockatrice/Cockatrice/issues Você gostaria de alterar seu local de configuração do banco de dados? - - - + + + Error Erro - + The path to your deck directory is invalid. Would you like to go back and set the correct path? O caminho para a sua pasta de decks é inválido. Você gostaria de voltar e corrigir o caminho? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? O caminho para a sua pasta de imagens de cards é inválido. Você gostaria de voltar e corrigir o caminho? - + Settings Configurações - + General Geral - + Appearance Aparência - + User Interface Interface do usuário - + Card Sources Fonte de Cartas - + Chat Conversa - + Sound Som - + Shortcuts Atalhos @@ -3117,39 +3306,39 @@ Você gostaria de alterar seu local de configuração do banco de dados? DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3157,17 +3346,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next Próximo - + Previous Anterior - + Tip of the Day Dica do Dia @@ -3175,165 +3364,165 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel Canal de lançamento atual - + Reinstall Reinstalar - + Cancel Download Cancelar Download - + Open Download Page Abrir página de download - + Check for Client Updates Procurar por atualizações do cliente - - - - + + + + Error Erro - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice não possui suporte a SSL, por causa disso você não pode baixar updates automaticamente! Por favor visite a página de downloads para atualizar manualmente.  - + Downloading update: %1 Baixando atualização: %1 - + Checking for updates... Verificando atualizações... - + Finished checking for updates Busca por atualizações concluída - + No Update Available Sem atualização disponível - + Cockatrice is up to date! Cockatrice está atualizado! - + You are already running the latest version available in the chosen release channel. Você já está executando a última versão disponível no canal de lançamento escolhido. - + Current version Versão atual - + Selected release channel Canal de lançamento selecionado - - + + Update Available Atualização disponível - - + + A new version of Cockatrice is available! Uma nova versão do Cockatrice está disponível! - - + + New version Nova versão - - + + Released Lançado - - + + Changelog Log de mudanças - + Do you want to update now? Você quer atualizar agora? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. Infelizmente, o atualizador automática não encontrou um download compatível. Talvez você tenha que baixar a nova versão manualmente. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. Por favor confira a <a href="%1">página de releases</a> no nosso repositório do Github e baixe o programa para o seu sistema. - - - + + + Update Error Erro na atualização - + An error occurred while checking for updates: Ocorreu um erro enquanto procurávamos por atualizações: - + An error occurred while downloading an update: Um erro ocorreu enquanto uma atualização era baixada: - + Installing... Instalando... - + Cockatrice is unable to open the installer. Cockatrice está incapaz de abrir o instalador. - + Try to update manually by closing Cockatrice and running the installer. Tente atualizar o Cockatrice manualmente fechando-o e rodando o instalador. - + Download location Localização do download @@ -3341,21 +3530,153 @@ Talvez você tenha que baixar a nova versão manualmente. DlgViewLog - + Clear log when closing Limpar log ao fechar - + Copy to clipboard - + Debug Log Registro de Depuração + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3425,33 +3746,39 @@ Talvez você tenha que baixar a nova versão manualmente. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: Sal: @@ -3467,22 +3794,22 @@ Talvez você tenha que baixar a nova versão manualmente. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. @@ -3490,22 +3817,22 @@ Talvez você tenha que baixar a nova versão manualmente. GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -3513,226 +3840,279 @@ Talvez você tenha que baixar a nova versão manualmente. GameSelector - - - - - - + + + + + + Error Erro - + Please join the appropriate room first. Por favor, entre na sala apropriada primeiramente - + Wrong password. Senha incorreta. - + Spectators are not allowed in this game. Não são permitidos visitantes neste jogo. - + The game is already full. O jogo está cheio. - + The game does not exist any more. O jogo não existe mais. - + This game is only open to registered users. Este jogo é aberto apenas para usuários registrados. - + This game is only open to its creator's buddies. Este jogo é aberto apenas para os amigos de quem criou o jogo. - + You are being ignored by the creator of this game. Você está sendo ignorado pelo criador deste jogo. - + Join Game Entrar no jogo - + Spectate Game Assistir ao jogo - + Game Information Informações do jogo - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game Entrar no jogo - + Password: Senha: - + Please join the respective room first. Por favor, entre na respectiva sala primeiro. - + &Filter games &Filtrar jogos. - + C&lear filter L&impar Filtros - + C&reate C&riar - + &Join &Entrar - + + Join as judge + + + + J&oin as spectator E&ntrar como visitante - + + Join as judge spectator + + + + Games shown: %1 / %2 Jogos mostrados: %1 / %2 - + Games Jogos + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day >1 dia - + %1%2 hr short age in hours %1%2 h%1%2 h%1%2 h - + new novo - + %1%2 min short age in minutes %1%2 min%1%2 min%1%2 min - + password senha - + buddies only apenas amigos - + reg. users only usuários reg. apenas - + open decklists - - + + can chat pode conversar - + see hands ver mãos - + can see hands pode ver mãos - + not allowed não permitidos - + Room Sala - + Age Idade - + Description Descrição - + Creator Criador - + Type Tipo - + Restrictions Restrições - + Players Jogadores - + Spectators Visitantes @@ -3740,143 +4120,143 @@ Talvez você tenha que baixar a nova versão manualmente. GeneralSettingsPage - + Reset all paths Redefinir todos os diretórios - + All paths have been reset Todos os caminhos para diretórios foram redefinidos - - - - - - - + + + + + + + Choose path Escolher caminho - + Personal settings Configurações pessoais - + Language: Idioma: - + Paths (editing disabled in portable mode) Caminhos (edição desabilitada no modo portátil) - + Paths Caminhos - + How to help with translations Como ajudar com as traduções - + Decks directory: Pasta de decks: - + Filters directory: - + Replays directory: Pasta de replays: - + Pictures directory: Pasta de imagens: - + Card database: Banco de dados de cartas: - + Custom database directory: Diretório do banco de dados de cartas customizadas - + Token database: Banco de dados de fichas: - + Update channel Canal de atualização - + Check for client updates on startup Verificar atualizações do cliente ao iniciar - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client Notificar se uma função suportada pelo servidor está faltando em meu programa. - + Automatically run Oracle when running a new version of Cockatrice Rodar Oracle automaticamente quando rodar uma nova versão de Cockatrice - + Show tips on startup Exibir dicas ao iniciar - + Last update check on %1 (%2 days ago) @@ -3884,47 +4264,47 @@ Talvez você tenha que baixar a nova versão manualmente. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -3932,111 +4312,141 @@ Talvez você tenha que baixar a nova versão manualmente. HandMenu - + &Hand - + &View hand - - &Sort hand + + Sort hand by... - - Take &mulligan + + Name - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - Reveal r&andom card to... + + + All players - - - &All players + + Reveal r&andom card to... HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + + Browse Archidekt + + + + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4238,61 +4648,61 @@ Talvez você tenha que baixar a nova versão manualmente. MainWindow - - + + The server has reached its maximum user capacity, please check back later. O servidor atingiu a capacidade maxima de usuarios, tente novamente mais tarde. - + There are too many concurrent connections from your address. Há conexões concorrentes demais vinds do seu endereço. - + Banned by moderator Banido pelo moderador - + Expected end time: %1 Esperado tempo para fim: %1 - + This ban lasts indefinitely. Este banimento dura indefinidamente. - + Scheduled server shutdown. Servidor de eventos fora do ar. - - + + Invalid username. Usuário inválido - + You have been logged out due to logging in at another location. Você foi desconectado devido a um acesso em outra localidade. - + Connection closed Conexão fechada - + The server has terminated your connection. Reason: %1 O servidor terminou sua conexão. Razão: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -4305,584 +4715,584 @@ Todos os jogos serão perdidos. Motivo para desligar: %1 - + Scheduled server shutdown Servidor de eventos fora do ar - - + + Success Sucesso - + Registration accepted. Will now login. Registro aceito Irá entrar agora - + Account activation accepted. Will now login. Ativação da conta aceita. Irá entrar agora. - + Number of players Número de jogadores - + Please enter the number of players. Por favor, entre o número de jogadores. - - + + Player %1 Jogador %1 - + Load replay Carregar replay - + About Cockatrice Sobre o Cockatrice - + Version Versão - + Cockatrice Webpage Pagina da Web do Cockatrice - + Project Manager: Diretor do projeto: - + Past Project Managers: Antigos diretores do projeto: - + Developers: Desenvolvedores: - + Our Developers Nossos desenvolvedores - + Help Develop! Ajude a desenvolver! - + Translators: Tradutores: - + Our Translators Nossos tradutores. - + Help Translate! Ajude a traduzir! - + Support: Suporte: - + Report an Issue Reporte um problema: - + Troubleshooting Solução de problemas - + F.A.Q. F.A.Q. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error Erro - + Server timeout Tempo esgotado do servidor - + Failed Login Login falhou - + Your client seems to be missing features this server requires for connection. Seu cliente parece estar faltando recursos que este servidor requer para conexão. - + To update your client, go to 'Help -> Check for Client Updates'. Para atualizar seu cliente, vá para 'Ajuda -> Procurar por atualizações do cliente'. - + Incorrect username or password. Please check your authentication information and try again. Usuário ou senha incorretos. Por favor, verifique suas informações de autenticação e tente novamente. - + There is already an active session using this user name. Please close that session first and re-login. Já existe uma sessão ativa usando este nome de usuário. Por favor, feche a sessão primeiro e logue novamente. - - + + You are banned until %1. Você está banido até %1. - - + + You are banned indefinitely. Você está banido indefinidamente. - + This server requires user registration. Do you want to register now? Este servidor exige registro de usuário. Você gostaria de registrar agora? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. Este servidor requer ID de cliente. Seu cliente ou não está conseguindo gerar um ID ou você está executando um cliente modificado. Por favor feche e abra seu cliente para tentar novamente. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. Ocorreu um erro interno, por favor, feche e reabra o Cockatrice antes de tentar novamente. Se o erro persistir, verifique se você está executando a versão mais recente do software e, se necessário, entre em contato com os desenvolvedores de software. - + Account activation Ativação da conta - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Sua conta não foi ativada ainda Você precisa fornecer o código de ativação recebido por email. - + Server Full Servidor cheio. - + Unknown login error: %1 Erro de login desconhecido: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Isso geralmente significa que seu cliente está obsoleto, e o servidor enviou uma resposta que seu cliente não entende. - + Your username must respect these rules: Seu nome de usuário precesia seguir essas regras: - + is %1 - %2 characters long tem entre %1 e %2 caracteres - + can %1 contain lowercase characters %1 pode conter caracteres minúsculos. - - - - + + + + NOT NÃO - + can %1 contain uppercase characters %1 pode conter caracteres maiúsculos. - + can %1 contain numeric characters %1 pode conter caracteres numéricos. - + can contain the following punctuation: %1 Pode conter a seguinte pontuação: %1 - + first character can %1 be a punctuation mark Primeiro caractere pode %1 ser um sinal de pontuação - + no unacceptable language as specified by these server rules: note that the following lines will not be translated Nenhuma linguagem inaceitável, conforme especificado por estas regras do servidor: - + can not contain any of the following words: %1 Não pode conter nenhuma das seguintes palavras: %1 - + can not match any of the following expressions: %1 Não pode corresponder a nenhuma das seguintes expressões: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Você pode usar apenas A-Z, a-z, 0-9, _, ., e - em seu nome de usuário. - - - - - - + + + + + + Registration denied Registro negado - + Registration is currently disabled on this server Registro está atualmente desabilitado neste servidor - + There is already an existing account with the same user name. Já existe uma conta com este nome de usuário. - + It's mandatory to specify a valid email address when registering. É obrigatório especificar um endereço de email válido para registrar. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. Aparentemente você está tentando registrar uma nova conta nesse servidor, porém você já tem uma conta registrada com o e-mail fornecido. Este servidor restringe a quantidade de contas que podem ser registradas por endereço. Por favor contate o operador do servidor para assistência posterior ou para obter informações das suas credenciais. - + Password too short. Senha muito curta - + Registration failed for a technical problem on the server. Registro falhou devido a um problema tecnico no servidor. - + The connection to the server has been lost. - + Unknown registration error: %1 Erro de registro desconhecido: %1 - + Account activation failed A ativação de conta falhou - + Socket error: %1 Erro de ligação:%1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Você está tentando conectar a um servidor obsoleto. Por favor, faça um downgrade na versão do seu Cockatrice ou conecte-se ao servidor correto. A versão local é %1 e a versão remota é %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. A versão do seu Cockatrice é obsoleta. Por favor, atualize a sua versão. A versão local é %1 e a versão remota é %2. - + Connecting to %1... Conectando a %1... - + Registering to %1 as %2... Registrando em %1 como %2... - + Disconnected Desconectado - + Connected, logging in at %1 Conectado, logando em %1 + - Requesting forgotten password to %1 as %2... Solicitando senha esquecida para %1 como %2... - + &Connect... &Conectar... - + &Disconnect &Desconectar - + Start &local game... Iniciar jogo &local... - + &Watch replay... &Assistir replay... - + &Full screen Tela &cheia - + &Register to server... &Registrar ao servidor: - + &Restore password... &Recuperar senha... - + &Settings... &Configurações... - + &Exit &Sair - + A&ctions Açõ&es - + &Cockatrice &Cockatrice - + C&ard Database Base de Cartas - + &Manage sets... &Gerenciar expansões... - + Edit custom &tokens... Editar &fichas personalizadas - + Open custom image folder Abrir pasta customizada de imagens. - + Open custom sets folder Abrir pasta de sets personalizados - + Add custom sets/cards Adicionar sets/cartas personalizadas - + Reload card database Recarregar banco de dados de cartas - + Tabs Abas - + &Help &Ajuda - + &About Cockatrice So&bre o Cockatrice - + &Tip of the Day &Dica do Dia - + Check for Client Updates Procurar por atualizações do cliente - + Check for Card Updates... Procurar por atualizações de cartas... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log Ver registro de &depuração - + Open Settings Folder Abrir Pasta de Configurações - + Show/Hide Mostrar/Ocultar - + New Version Nova Versão - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Parabéns por atualizar o Cockatrice %1! Oracle será inicializado agora para atualizar sua base de cartas - + Cockatrice installed Cockatrice instalado - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Parabéns pela instalação do Cockatrice %1! O Oracle agora será iniciado para instalar o banco de dados inicial de cartas. - + Card database Base de cartas - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -4891,29 +5301,29 @@ Gostaria de atualizar seu banco de dados de cartas agora? Se estiver em dúvida ou for um novo usuário selecione "Sim" - - + + Yes Sim - - + + No Não - + Open settings Abrir configurações - + New sets found Novas expansões encontradas - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -4926,17 +5336,17 @@ Códigos das coleções: %1 Deseja ativá-las? - + View sets Visualizar expansões - + Welcome Bem vindo - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -4945,64 +5355,64 @@ Todas as expansões do banco de dados de cartas foram habilitadas. Leia mais sobre mudar a ordem das expansões ou desabilitando expansões específicas na aba "Gerenciar Expansões". - - + + Information Informação - + A card database update is already running. Uma atualização da base de dados de cartas já está em andamento. - + Unable to run the card database updater: Não foi possível executar a atualização da base de dados de cartas. - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. Falha ao começar. O arquivo pode não existir ou as permissões estão incorretas. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5013,673 +5423,843 @@ Provavelmente não é um problema, mas esta mensagem pode significar que existe Para atualizar seu cliente, vá para Ajuda -> Verificar atualizações. - - - - - + + + + + Load sets/cards Carregar expansões/cards - + Selected file cannot be found. Arquivo selecionado não pode ser encontrado - + You can only import XML databases at this time. Você só pode importar bancos de dados XML neste momento. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. As novas cartas/coleções foram adicionadas com sucesso. O Cockatrice reiniciará imediatamente o banco de dados de cartas. - + Sets/cards failed to import. Falha ao importar expansões/cartas. - - - + + + Reset Password Redefinir Senha - + Your password has been reset successfully, you can now log in using the new credentials. Sua senha foi alterada com sucesso, você pode se conectar usando suas novas credenciais - + Failed to reset user account password, please contact the server operator to reset your password. Falha ao redefinir a senha da conta do usuário, entre em contato com o operador do servidor para redefinir sua senha. - + Activation request received, please check your email for an activation token. Solicitação de ativação recebida, verifique seu e-mail para um token de ativação. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play do jogo - + from their graveyard do seu cemitério - + from exile vindo do exílio - + from their hand da sua mão - + the top card of %1's library a carta do topo do grimório de %1 - + the top card of their library a carta do topo do seu grimório - + from the top of %1's library do topo do grimório de %1 - + from the top of their library do topo do seu grimório - + the bottom card of %1's library a carta do fundo do grimório de %1 - + the bottom card of their library a carta do fundo do seu grimório - + from the bottom of %1's library do fundo do grimório de %1 - + from the bottom of their library do fundo do seu grimório - + from %1's library do grimório de %1 - + from their library do seu grimório - + from sideboard vindo do sideboard - + from the stack vindo da pilha - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 está mantendo as carta do topo %2 reveladas. - + %1 is not revealing the top card %2 any longer. %1 não está mais revelando as cartas do topo %2. - + %1 can now look at top card %2 at any time. %1 agora pode olhar a carta do topo de %2 a qualquer momento. - + %1 no longer can look at top card %2 at any time. %1 não pode mais olhar a carta do topo de %2 a qualquer momento. - + %1 attaches %2 to %3's %4. %1 vincula %2 a %4 de %3. - + %1 has conceded the game. %1 concedeu o jogo. - + %1 has unconceded the game. %1 desfez a concessão do jogo - + %1 has restored connection to the game. %1 restaurou a conexão com o jogo. - + %1 has lost connection to the game. %1 perdeu conexão com o jogo. - + %1 points from their %2 to themselves. %1 aponta de sua %2 para si - + %1 points from their %2 to %3. %1 aponta de sua %2 para %3. - + %1 points from %2's %3 to themselves. %1 aponta da %3 de %2 para si - + %1 points from %2's %3 to %4. %1 aponta da %3 de %2 para %4 - + %1 points from their %2 to their %3. %1 aponta da sua %2 para sua %3 - + %1 points from their %2 to %3's %4. %1 aponta de sua %2 para %4 de %3 - + %1 points from %2's %3 to their own %4. %1 aponta da %3 de %2 para seu próprio %4. - + %1 points from %2's %3 to %4's %5. %1 aponta da %3 de %2 para a %5 de %4. - + %1 creates a face down token. - + %1 creates token: %2%3. %1 cria uma ficha: %2%3. - + %1 has loaded a deck (%2). %1 carregou um deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 carregou um deck com %2 cartas no sideboard (%3). - + %1 destroys %2. %1 destrói %2. - + a card um card - + %1 gives %2 control over %3. %1 dá controle para %2 sobre %3. - + %1 puts %2 into play%3 face down. %1 coloca %2 em jogo%3 com a face voltada para baixo. - + %1 puts %2 into play%3. %1 põe %2 no campo de batalha %3. - + %1 puts %2%3 into their graveyard. %1 coloca %2%3 em seu cemitério. - + %1 exiles %2%3. %1 exila %2%3. - + %1 moves %2%3 to their hand. %1 moveu %2%3 para sua mão. - + %1 puts %2%3 into their library. %1 coloca %2%3 em seu grimório - + %1 puts %2%3 onto the bottom of their library. %1 coloca %2%3 no fundo de seu grimório. - + %1 puts %2%3 on top of their library. %1 coloca %2%3 no topo de seu grimório. - + %1 puts %2%3 into their library %4 cards from the top. %1 coloca %2%3 do seu grimório %4 cartas abaixo do topo. - + %1 moves %2%3 to sideboard. %1 move %2%3 para o sideboard. - + %1 plays %2%3. %1 põe %2 na pilha%3. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1 tenta comprar de uma biblioteca vazia - + %1 draws %2 card(s). %1 comprou %2 carta(s).%1 comprou %2 carta(s).%1 comprou %2 carta(s). - + %1 is looking at %2. %1 está olhando para %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 está olhando para a(s) %3 carta(s) %4 %2.%1 está olhando para a(s) %3 carta(s) %4 %2.%1 está olhando para a(s) %3 carta(s) %4 %2. - + bottom de fundo - + top de topo - + %1 turns %2 face-down. %1 turnos %2 cartas viradas para baixo. - + %1 turns %2 face-up. %1 turnos %2 cartas viradas para cima. - + The game has been closed. O jogo foi fechado. - + The game has started. O jogo começou. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 entrou no jogo. - + %1 is now watching the game. %1 está assistindo o jogo agora. - + You have been kicked out of the game. Você foi 'chutado' do jogo. - + %1 has left the game (%2). %1 deixou o jogo (%2) - + %1 is not watching the game any more (%2). %1 não está mais assistindo o jogo (%2). - + %1 is not ready to start the game any more. %1 não está mais pronto para inciar o jogo. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 embaralhou seu grimório e comprou uma nova mão com %2 card.%1 embaralhou seu grimório e comprou uma nova mão com %2 cards.%1 embaralhou seu grimório e comprou uma nova mão com %2 cards. - + %1 shuffles their deck and draws a new hand. %1 embaralhou seu grimório e comprou uma nova mão. - + You are watching a replay of game #%1. Você está assistindo o replay do jogo #%1. - + %1 is ready to start the game. %1 está pronto para inciar o jogo. - + cards an unknown amount of cards cartas - + %1 card(s) a card for singular, %1 cards for plural %1 carta%1 carta(s)%1 carta(s) - + %1 lends %2 to %3. %1 empresta %2 para %3. - + %1 reveals %2 to %3. %1 revela %2 para %3. - + %1 reveals %2. %1 revela %2. - + %1 randomly reveals %2%3 to %4. %1 revela aleatoriamente %2%3 para %4. - + %1 randomly reveals %2%3. %1 revela aleatoriamente %2%3. - + %1 peeks at face down card #%2. %1 olha a carta virada para baixo #%2. - + %1 peeks at face down card #%2: %3. %1 espia a face para baixo da carta #%2: %3. - + %1 reveals %2%3 to %4. %1 revela %2 %3 para %4. - + %1 reveals %2%3. %1 revela %2%3. - + %1 reversed turn order, now it's %2. %1 reverteu a ordem dos turnos, agora é a vez de %2. - + reversed revertido - + normal normal - + Heads Cara - + Tails Coroa - + %1 flipped a coin. It landed as %2. %1 jogou uma moeda. Saiu %2. - + %1 rolls a %2 with a %3-sided die. %1 tirou %2 em um D%3. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 joga %2 moedas. São %3 caras e %4 coroas. - + %1 rolls a %2-sided dice %3 times: %4. %1 rola um dado de %2 lados %3 vezes: %4. - + %1's turn. Turno de %1. - + %1 sets annotation of %2 to %3. %1 altera a anotação de %2 para %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 ajusta o marcador %2 para %3 (%4%5). - + %1 sets %2 to not untap normally. %1 determina que %2 não desvira normalmente. - + %1 sets %2 to untap normally. %1 determina que %2 desvira normalmente. - + %1 removes the PT of %2. %1 remove o P/R de %2 - + %1 changes the PT of %2 from nothing to %4. %1 alterar o PR de %2 de nada para %4. - + %1 changes the PT of %2 from %3 to %4. %1 alterar o P/R de %2 de %3 para %4. - + %1 has locked their sideboard. %1 bloqueou seu sideboard - + %1 has unlocked their sideboard. %1 desbloqueou seu sideboard - + %1 taps their permanents. %1 vira suas permanentes. - + %1 untaps their permanents. %1 desvira suas permanentes. - + %1 taps %2. %1 vira %2 - + %1 untaps %2. %1 desvira %2. - + %1 shuffles %2. %1 embaralha %2 - + %1 shuffles the bottom %3 cards of %2. %1 embaralha as %3 cartas do fundo de %2 - + %1 shuffles the top %3 cards of %2. %1 embaralha as %3 cartas do topo de %2 - + %1 shuffles cards %3 - %4 of %2. %1 embaralha as cartas %3 - %4 de %2 - + %1 unattaches %2. %1 desanexa %2. - + %1 undoes their last draw. %1 desfaz sua última compra. - + %1 undoes their last draw (%2). %1 desfaz sua última compra (%2). @@ -5687,110 +6267,110 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. MessagesSettingsPage - + Word1 Word2 Word3 Palavra1 Palavra2 Palavra3 - + Add New Message Adicionar nova mensagem - + Edit Message Editar mensagem - + Remove Message Remover mensagem - + Add message Adicionar mensagem - - + + Message: Mensagem: - + Edit message Editar mensagem - + Chat settings Configurações de Conversa - + Custom alert words Palavras de alertas customizadas. - + Enable chat mentions Habilitar menções em conversa - + Enable mention completer Habilitar completador de menções. - + In-game message macros Mensagem no jogo - + How to use in-game message macros Como usar macros de mensagens no jogo - + Ignore chat room messages sent by unregistered users Ignorar mensagens na sala enviadas por usuários não registrados - + Ignore private messages sent by unregistered users Ignorar mensagens privadas enviadas por usuários não registrados - - + + Invert text color Inverter cor do texto - + Enable desktop notifications for private messages Ativar notificações da área de trabalho para mensagens privadas. - + Enable desktop notification for mentions Habilitar notificações de desktop para menções. - + Enable room message history on join Ativar histórico de mensagens da sala ao entrar - - + + (Color is hexadecimal) (Cor em hexadecimal) - + Separate words with a space, alphanumeric characters only Separar palavras com um espaço, apenas caracteres alfanuméricos @@ -5907,62 +6487,62 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. Phase - + Unknown Phase Frase desconhecida - + Untap Desvirar - + Upkeep Manutenção - + Draw Compra - + First Main Primeira fase principal - + Beginning of Combat Início do combate - + Declare Attackers Declaração de atacantes - + Declare Blockers Declaração de bloqueadores - + Combat Damage Dano de combate - + End of Combat Fim do combate - + Second Main Segunda fase principal - + End/Cleanup Fim do turno @@ -6028,7 +6608,7 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. PictureLoader - + en code for scryfall's language property, not available for all languages pt @@ -6037,134 +6617,134 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - + Move top cards to grave - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6172,17 +6752,17 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. PlayerMenu - + Player "%1" - + &Counters - + S&ay @@ -6190,7 +6770,7 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. PrintingSelector - + Display Navigation Buttons @@ -6198,22 +6778,22 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. PrintingSelectorCardOverlayWidget - + Preference Preferência - + Pin Printing Fixar Impressão - + Unpin Printing Desafixar Impressão - + Show Related cards Mostrar Cartas Relacionadas @@ -6229,17 +6809,17 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. PrintingSelectorCardSelectionWidget - + Previous Card in Deck Carta Anterior no Deck - + Bulk Selection - + Next Card in Deck Próxima Carta no Deck @@ -6247,28 +6827,28 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. PrintingSelectorCardSortingWidget - + Alphabetical Alfabética - + Preference Preferência - + Release Date Data de Lançamento - - + + Descending Decrescente - + Ascending Crescente @@ -6334,37 +6914,37 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. QMenuBar - + Services Serviços - + Hide %1 Esconder %1 - + Hide Others Esconder outros - + Show All Mostrar tudo - + Preferences... Preferências - + Quit %1 Sair %1 - + About %1 Sobre %1 @@ -6372,42 +6952,42 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. QObject - + Cockatrice card database (*.xml) Banco de dados de cartas do Cockatrice (*.xml) - + All files (*.*) Todos arquivos (*.*) - + Cockatrice replays (*.cor) Cockatrice replays (*.cor) - + Maindeck Main - + Sideboard Side - + Tokens Fichas - + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? @@ -6415,92 +6995,92 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. QPlatformTheme - + OK OK - + Save Salvar - + Save All Salvar Todos - + Open Abrir - + &Yes &Sim - + Yes to &All Sim para &Todos - + &No &Não - + N&o to All Não para &Todos - + Abort Abortar - + Retry Tentar Novamente - + Ignore Ignorar - + Close Fechar - + Cancel Cancelar - + Discard Descartar - + Help Ajuda - + Apply Aplicar - + Reset Resetar - + Restore Defaults Restaurar Padrão @@ -6597,37 +7177,37 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. RoomSelector - + Rooms Salas - + Joi&n &Entrar - + Room Sala - + Description Descrição - + Permissions Permissões - + Players Jogadores - + Games Jogos @@ -6668,27 +7248,27 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. SetsModel - + Enabled Ativado - + Set type Selecione tipo - + Set code Selecione código - + Long name Nome longo - + Release date Data lançamento @@ -6696,53 +7276,53 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. ShortcutSettingsPage - - + + Restore all default shortcuts Restaurar todos atalhos padrões - + Do you really want to restore all default shortcuts? Você tem certeza que deseja restaurar todos atalhos padrões? - + Clear all default shortcuts Limpar todos os atalhos padrão - + Do you really want to clear all shortcuts? Você realmente quer limpar todos os atalhos? - + Section: Seção: - + Action: Ação: - + Shortcut: Atalho: - + How to set custom shortcuts Como definir atalhos personalizados - + Clear all shortcuts Limpar todos os atalhos - + Search by shortcut name Pesquisar pelo nome do atalho @@ -6750,12 +7330,12 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. ShortcutTreeView - + Action Ação - + Shortcut Atalho @@ -6763,14 +7343,14 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! Seu arquivo de configurações contém atalhos inválidos. Por favor, verifique suas configurações de atalho! - + The following shortcuts have been set to default: Os seguintes atalhos foram configurados como padrão: @@ -6798,12 +7378,12 @@ Por favor, verifique suas configurações de atalho! SideboardMenu - + &Sideboard - + &View sideboard @@ -6811,27 +7391,27 @@ Por favor, verifique suas configurações de atalho! SoundSettingsPage - + Enable &sounds Habilitar &sons - + Current sounds theme: Tema sonoro atual: - + Test system sound engine Testar sistema de som - + Sound settings Configurações de som - + Master volume Volume principal @@ -6839,48 +7419,48 @@ Por favor, verifique suas configurações de atalho! SpoilerBackgroundUpdater - + Spoilers season has ended Temporada de 'spoilers' encerrada - + Deleting spoiler.xml. Please run Oracle Deletando spoiler.xml. Favor rodar o Oracle - - + + Spoilers download failed Falha no download de 'spoilers' - + No internet connection Sem conexão com a internet - + Error Erro - + Spoilers already up to date 'Spoilers' já atualizados - + No new spoilers added Nenhum 'spoiler' novo adicionado - + Spoilers have been updated! 'Spoilers' foram adicionados! - + Last change: Última alteração: @@ -7044,56 +7624,123 @@ Por favor, verifique suas configurações de atalho! Não foi possível ativar o usuário. Erro interno + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info Informação da carta. - + Deck Deck - + Filters Filtros - + &View &Visualizar - + + Card Database + + + + Printing Impressão - - - - + + + + + Visible Visível - - - - + + + + + Floating Flutuante - + Reset layout Reiniciar layout - + Deck: %1 Deck: %1 @@ -7101,61 +7748,61 @@ Por favor, verifique suas configurações de atalho! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + + + + Visible - - - - + + + + Floating - + Reset layout @@ -7163,22 +7810,22 @@ Por favor, verifique suas configurações de atalho! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7186,134 +7833,134 @@ Por favor, verifique suas configurações de atalho! TabDeckStorage - + Local file system Sistema de arquivos local - + Server deck storage Armazenamento de decks no servidor - - + + Open in deck editor Abrir no editor de decks - + Rename deck or folder - + Upload deck Upload do deck - + Download deck Download do deck + - - - + + New folder Nova pasta + - Delete Apagar - + Open decks folder Abrir pasta de decks - + Rename local folder - + Rename local file - + New name: - - - - + + + + Error Error - + Rename failed - - + + Invalid deck file Arquivo de deck inválido - + Enter deck name Entre o nome do deck - + This decklist does not have a name. Please enter a name: Este deck não tem um nome. Por favor, entre um nome: - + Unnamed deck Deck sem nome - + Failed to upload deck to server Falha ao enviar deck ao servidor - + Delete local file Excluir arquivo local - + Are you sure you want to delete the selected files? Tem certeza de que deseja excluir os arquivos selecionados? - + Delete remote decks Excluir decks remotos - + Are you sure you want to delete the selected decks? Tem certeza de que deseja excluir os decks selecionados? - - + + Name of new folder: Nome da nova pasta: @@ -7326,17 +7973,17 @@ Por favor, entre um nome: TabDeckStorageVisual - + Visual Deck Storage Decks Visuais - + Error - + Could not open deck at %1 @@ -7377,7 +8024,7 @@ Por favor, entre um nome: - + EDHRec: @@ -7385,197 +8032,197 @@ Por favor, entre um nome: TabGame - - - + + + Replay Replay - - + + Game Jogo - - + + Player List Lista de jogadores. - - + + Card Info Informação da carta - - + + Messages Mensagens - - + + Replay Timeline Linha do tempo do Replay - + &Phases &Etapas - + &Game &Jogo - + Next &phase Próxima &etapa - + Next phase with &action Próxima etapa com &acao - + Next &turn Próximo &turno - + Reverse turn order Ordem dos turnos revertida - + &Remove all local arrows &Apagar todas as setas locais - + Rotate View Cl&ockwise Girar visualização em sentido h&orário - + Rotate View Co&unterclockwise Girar visualização em sentido a&nti-horário - + Game &information &Informação de jogo - + Un&concede Cancelar Rendição - - - + + + &Concede &Conceder - + &Leave game &Sair do jogo - + C&lose replay &Fechar replay - + &Focus Chat &Focar no chat - + &Say: &Falar: - + Selected cards - + &View &Visualizar - - + + + - Visible Visível - - + + + - Floating Flutuante - + Reset layout Reiniciar layout - + Concede Conceder - + Are you sure you want to concede this game? Você tem certeza que deseja conceder este jogo? - + Unconcede Defazer concessão - + You have already conceded. Do you want to return to this game? Você já concedeu. Quer retornar a esse jogo? - + Leave game Sair do jogo - + Are you sure you want to leave this game? Você tem certeza que deseja sair deste jogo? - + A player has joined game #%1 Um jogador entrou no jogo #%1 - + %1 has joined the game %1 entrou no jogo - + You have been kicked out of the game. Você foi 'chutado' do jogo. @@ -7583,7 +8230,7 @@ Por favor, entre um nome: TabHome - + Home @@ -7591,158 +8238,158 @@ Por favor, entre um nome: TabLog - + Logs Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Tempo;RemetenteNome;RemetenteIP;Mensagem;AlvoID;AlvoNome - + Room Logs Logs de salas - + Game Logs Logs de jogos - + Chat Logs Logs de conversas - - + + Error Erro - + You must select at least one filter. Você precisa selecionar pelo menos um filtro. - + You have to select a valid number of days to locate. Você precisa selecionar um número de dias válido para localizar - + Username: Nome de usuário. - + IP Address: Endereço de IP: - + Game Name: Nome do jogo: - + GameID: ID do Jogo: - + Message: Mensagem: - + Main Room Sala Principal - + Game Room Sala de Jogo - + Private Chat Chat Privado. - + Past X Days: X dias anteriores: - + Today Hoje - + Last Hour Última hora - + Maximum Results: Máximo de resultados: - + At least one filter is required. The more information you put in, the more specific your results will be. Ao menos um filtro deve ser selecionado. Quanto mais informação você inserir, mais específicos seus resultados serão. - + Get User Logs Obter logs de usuário - + Clear Filters Limpar Filtros - + Filters Filtros - + Log Locations Logs de localização - + Date Range Intervalo de data - + Maximum Results Máximo de resultados - - + + Message History Histórico de Mensagens - + Failed to collect message history information. Falha ao coletar informação do histórico de mensagens. - + There are no messages for the selected filters. Não existe mensagem que corresponda aos filtros selecionados. @@ -7788,180 +8435,180 @@ Quanto mais informação você inserir, mais específicos seus resultados serão TabReplays - + Local file system Sistema de arquivos local - + Server replay storage Servidor de armazenamento de replays - - + + Watch replay Assistir replay - + Rename Renomear - - + + New folder Nova pasta - - + + Delete Excluir - + Open replays folder Abrir pasta de repetições - + Download replay Baixar replay - + Toggle expiration lock Trocar expiração da trava - + Get replay share code - - + + Look up replay by share code - + Rename local folder Renomear pasta local - + Rename local file Renomear arquivo local - + New name: Novo nome: - + Error Erro - + Rename failed Falha ao renomear - + Name of new folder: Nome da nova pasta: - + Delete local file Excluir arquivo local - + Are you sure you want to delete the selected files? Tem certeza de que deseja excluir os arquivos selecionados? - + Are you sure you want to delete the selected replays? Tem certeza de que deseja excluir as repetições selecionadas? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay Excluir replay remoto @@ -8027,30 +8674,30 @@ Quanto mais informação você inserir, mais específicos seus resultados serão Servidor + - Error Erro - + Failed to join the server room: it doesn't exist on the server. Falha ao entrar na sala: ela não existe no servidor. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. O servidor pensa que você está na sala, mas seu cliente não consegue exibi-la. Tente reiniciar o seu cliente. - + You do not have the required permission to join this server room. Você não tem permissão para entrar nesta sala. - + Failed to join the server room due to an unknown error: %1. Falha ao entrar na sala devido a um erro desconhecido: %1. @@ -8058,92 +8705,97 @@ Quanto mais informação você inserir, mais específicos seus resultados serão TabSupervisor - + Deck Editor Editor de Deck - + Visual Deck Editor - + EDHRec - + + Archidekt + + + + Home - + &Visual Deck Storage &Decks Visuais - + Visual Database Display - + Server Servidor - + Account Conta - + Deck Storage Armazenamento de Deck - + Game Replays Repetições de jogo - + Administration Administração - + Logs Registros - + Are you sure? Você tem certeza? - + There are still open games. Are you sure you want to quit? Ainda existem jogos abertos. Você tem certeza que deseja sair? - + Click to view Clique para visualizar - + Your buddy %1 has signed on! Seu amigo %1 entrou no servidor! - + Unknown Event Evento desconhecido. - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8154,39 +8806,39 @@ Esta mensagem pode significar que existe uma nova versão do Cockatrice disponí Para atualizar seu cliente, vá para Ajuda -> Verificar atualizações. - + Idle Timeout Tempo esgotado de inatividade - + You are about to be logged out due to inactivity. Você irá ser desconectado por inatividade - + Promotion Promoção - + You have been promoted. Please log out and back in for changes to take effect. Você foi promovido a moderador(a). Por favor faça logout e depois acesse novamente para que as mudanças sejam efetivadas. - + Warned Advertido - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Você recebeu uma advertência por %1. Por favor evite tais ações ou outras medidas poderão ser tomadas contra você. Se você tem qualquer dúvida, por favor envie uma mensagem privada um dos moderadores. - + You have received the following message from the server. (custom messages like these could be untranslated) Você recebeu a seguinte mensagem do servidor. @@ -8196,7 +8848,12 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display @@ -8278,7 +8935,7 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você UpdateDownloader - + Could not open the file for reading. Não foi possível abrir o arquivo para leitura. @@ -8286,206 +8943,206 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você UserContextMenu - + User &details &Detalhes do usuário - + Private &chat Conversa privada - + Show this user's &games Mostrar este usuário de jogo - + Add to &buddy list Adicionar à &lista de amigos - + Remove from &buddy list Remover da li&sta de amigos - + Add to &ignore list Adicionar à li&sta dos ignorados - + Remove from &ignore list Remover da lista dos i&gnorados - + Kick from &game C&hutar do jogo - + Warn user Advertir usuário - + View user's war&n history Visualizar o histórico de advertê&ncias do usuário. - + Ban from &server Ban&ir do servidor - + View user's &ban history Visualizar o histórico de %banimento do usuário. - + &Promote user to moderator &Promover usuário a moderador - + Dem&ote user from moderator Rev&ogar as permissões de moderação do usuário - + Promote user to &judge Promover usuário a &juiz - + Demote user from judge Remover usuário da posição de juiz - + View admin notes Visualizar notas do administrador - - - + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games Jogos de %1 - - - + + + Ban History Histórico de banimento - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Tempo de banimento;Moderador;Duração do Banimento;Razão do Banimento;Razão Visível - + User has never been banned. O usuário nunca foi banido. - + Failed to collect ban information. Falha ao coletar informações de banidas. - - - + + + Warning History Histórico de advertências - + Warning Time;Moderator;User Name;Reason Tempo de advertência;Moderador;Nome de Usuário;Razão - + User has never been warned. Usuário nunca foi advertido. - + Failed to collect warning information. Falha ao coletar informações de aviso. - + Failed to get admin notes. Falha ao obter as notas do administrador. - - + + Success Sucesso - + Successfully promoted user. Usuário promovido com sucesso. - + Successfully demoted user. Permissões de moderação revogadas com sucesso. - + + - Failed Falha - + Failed to promote user. Falha ao promover usuário. - + Failed to demote user. Falha ao revogar permissões de moderação do usuário. - + Copy hash to clipboard Copiar hash para área de transferência - + Remove this user's messages Remover mensagens deste usuário @@ -8667,137 +9324,142 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você UserInterfaceSettingsPage - + General interface settings Configurações gerais de interface - + &Double-click cards to play them (instead of single-click) &Duplo clique nos cards para jogá-los (ao invés de clique simples) - + &Clicking plays all selected cards (instead of just the clicked card) Clicar reproduz todas as cartas selecionadas (em vez de apenas a carta clicada) - + &Play all nonlands onto the stack (not the battlefield) by default &Jogar todos não terrenos na pilha (não campo de batalha) por padrão - + + Do not delete &arrows inside of subphases + + + + Close card view window when last card is removed Fechar janela de visualização ao remover a última carta - + Auto focus search bar when card view window is opened - + Annotate card text on tokens Anotar texto de card nas fichas - + Use tear-off menus, allowing right click menus to persist on screen Usar menus tear-off, permitindo que menus de clique com botão direito persistam na tela - + Notifications settings Configurações de Notificações - + Enable notifications in taskbar Habilitar notificações na barra de tarefas - + Notify in the taskbar for game events while you are spectating Notificar na barra de tarefas para eventos do jogo enquanto você está como espectador - + Notify in the taskbar when users in your buddy list connect Notificar na barra de tarefas quando usuários na sua lista de amigos se conectar - + Animation settings Configurações de animação - + &Tap/untap animation Animação de &virar/desvirar - + Deck editor/storage settings Configurações do editor/armazenamento de decks - + Open deck in new tab by default Abrir deck em uma nova aba por padrão - + Use visual deck storage in game lobby Usar decks visuais no lobby do jogo - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - + do nothing - + ask to convert to .cod - + always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + Replay settings Configurações de repetição - + Buffer time for backwards skip via shortcut: Tempo de buffer para retroceder usando atalho: @@ -8805,22 +9467,22 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você UserListWidget - + Users connected to server: %1 Usuários conectados ao servidor: %1 - + Users in this room: %1 Usuários nesta sala: %1 - + Buddies online: %1 / %2 Amigos online: %1 / %2 - + Ignored users online: %1 / %2 Usuários ignorados online: %1 / %2 @@ -8828,32 +9490,32 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -8861,22 +9523,22 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match - + Mode: Includes - + Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -8884,21 +9546,49 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8924,28 +9614,28 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - Load from Deck + Filter by name... (Exact match) - Apply all card names in currently loaded deck as exact match name filters + Load from Deck - Load from Clipboard + Apply all card names in currently loaded deck as exact match name filters + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -9009,50 +9699,115 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - + + Sort by: + + + + + Filter by: + + + + Save and load filters - + Filter by exact card name - + Filter by card sub-type - + Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9060,46 +9815,25 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage Decks Visuais @@ -9155,7 +9889,7 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9163,22 +9897,22 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) Ordenar Alfabeticamente (Nome do Deck) - + Sort Alphabetically (Filename) Ordenar Alfabeticamente (Nome do Arquivo) - + Sort by Last Modified Ordenar por Última Modificação - + Sort by Last Loaded Ordenar por Último Carregamento @@ -9186,17 +9920,17 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDeckStorageWidget - + Loading database ... Carregando banco de dados ... - + Refresh loaded files - + Visual Deck Storage Settings @@ -9204,43 +9938,43 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você WarningDialog - + Which warning would you like to send? Qual advertência você gostaria de enviar? - + Redact all messages from this user in all rooms Remover todas mensagens deste usuário em todas salas - + &OK %OK - + &Cancel &Cancelar - + Warn user for misconduct Advertir usuario por mau comportamento. - - + + Error Erro - + User name to send a warning to can not be blank, please specify a user to warn. O campo "nome de usuário" ao qual se deseja enviar uma advertência não pode permanecer em branco, por favor especifique um usuário para ser advertido. - + Warning to use can not be blank, please select a valid warning to send. O campo "advertência" não pode permanecer em branco, por favor selecione uma advertência para ser enviada. @@ -9248,133 +9982,133 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você WndSets - + Move selected set to the top Mover o selecionado para o topo - + Move selected set up Mover o selecionado para cima - + Move selected set down Mover o selecionado para baixo - + Move selected set to the bottom Mover o selecionado para o fundo - + Search by set name, code, or type Procurar por expansão, código ou tipo - + Default order Ordem padrão - + Restore original art priority order Restaurar prioridade da ordem de arte original - + Enable all sets Habilitar todas expansões - + Disable all sets Desabilitar todas expansões - + Enable selected set(s) Habilitar expansão/expansões selecionada(s) - + Disable selected set(s) Desabilitar expansão/expansões selecionada(s) - + Deck Editor Editor de Deck - + Use CTRL+A to select all sets in the view. Use CTRL+A para selecionar todas expansões visíveis. - + Only cards in enabled sets will appear in the card list of the deck editor. Apenas cartas em expansões ativadas irão aparecer na lista de cartas do editor de deck. - + Image priority is decided in the following order: Prioridade da imagem é decidida na seguinte ordem: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki primeiro a pasta de PERSONALIDADAS (%1), depois as expansões habilitadas neste diálogo (de cima para baixo) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art Arte da Carta - + How to use custom card art Como usar arte de carta personalizada - + Hints Dicas - + Note Nota - + Sorting by column allows you to find a set while not changing set priority. Classificar por coluna te permite encontrar um set sem mudar a prioridade de sets - + To enable ordering again, click the column header until this message disappears. Para reabilitar a ordenação, clique no cabeçalho da coluna até que esta mensagem desapareça - + Use the current sorting as the set priority instead Usar a classificação atual como a prioridade de sets - + Sorts the set priority using the same column Classificar a prioridade de sets usando a mesma coluna - + Manage sets Gerenciar expansões @@ -9382,72 +10116,72 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped Não agrupado - + Group by Type Agrupar por Tipo - + Group by Mana Value Agrupar por Valor de Mana - + Group by Color Agrupar por Cor - + Unsorted Não ordenado - + Sort by Name Ordenar por Nome - + Sort by Type Ordenar por Tipo - + Sort by Mana Cost Ordenar por Custo de Mana - + Sort by Colors Ordenar por Cores - + Sort by P/T Ordenar por P/T - + Sort by Set Ordenar por Conjunto - + shuffle when closing embaralhar quando fechar - + pile view visualização da pilha @@ -9455,7 +10189,7 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você i18n - + English Português do Brasil (Brazilian Portuguese) @@ -9463,12 +10197,12 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você main - + Connect on startup Conectar ao iniciar - + Debug to file Depurar para um arquivo @@ -9476,1005 +10210,1041 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você shortcutsTab - + Main Window Janela Principal - - + + Deck Editor Editor de Deck - + Game Lobby Saguão de jogo - + Card Counters Marcadores de carta - + Player Counters Marcadores de jogador - + Power and Toughness Poder e Resistência - + Game Phases Etapas de Jogo - + Playing Area Área de jogo - + Move Selected Card Mover Carta Selecionada - + View Visualizar - + Move Top Card Mover A Carta Do Topo - + Move Bottom Card Mover A Carta Do Fundo - + Gameplay Jogabilidade - + Drawing Comprando - + Chat Room Sala de Conversa - + Game Window Janela de Jogo - + Load Deck from Clipboard Carregar deck da área de transferência - - + + Replays Repetições - + Tabs Abas - + Check for Card Updates... Procurar por atualizações de cartas... - + Connect... Conectar... - + Disconnect Desconectar - + Exit Sair - + Full screen Tela cheia - + Register... Registrar... - + Settings... Configurações... - + Start a Local Game... Iniciar um Jogo Local... - + Watch Replay... Assistir Replay... - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters Limpar todos os FIltros - + Clear Selected Filter Limpar Filtro Selecionado - + Close Fechar - + Remove Card Remover Carta - + Manage Sets... Gerenciar expansões... - + Edit Custom Tokens... Editar fichas customizadas... - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card Adicionar Carta - + Load Deck... Carregar Deck... - - + + Load Deck from Clipboard... Carregar deck da área de transferência... - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck Novo Deck - + Open Custom Pictures Folder Abrir pasta de imagens customizadas - + Print Deck... Imprimir Deck... - + Delete Card Deletar carta - - + + Reset Layout Redefinir layout - + Save Deck Salvar Deck - + Save Deck as... Salvar Deck Como... - + Save Deck to Clipboard, Annotated Salvar deck na área de transferência, anotado - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard Salvar deck na área de transferência - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... Carregar deck local... - + Load Remote Deck... Carregar deck remoto... - + Set Ready to Start Preparade para começar - + Toggle Sideboard Lock Alternar trava de sideboard - + Add Green Counter Adicionar marcador verde - + Remove Green Counter Remover marcador verde - + Set Green Counters... Definir marcadores verde... - + Add Red Counter Adicionar marcador vermelho - + Remove Red Counter Remover marcador vermelho - + Set Red Counters... Definir marcadores vermelhos - + Add Life Counter Adicionar ponto de vida - + Show Status Bar - + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter Remover ponto de vida - + Set Life Counters... Definir pontos de vida... - + Add White Counter Adicionar marcador branco - + Remove White Counter Remover marcador branco - + Set White Counters... Definir marcadores brancos... - + Add Blue Counter Adicionar marcador azul - + Remove Blue Counter Remover marcador azul - + Set Blue Counters... Definir marcadores azul - + Add Black Counter Adicionar marcador preto - + Remove Black Counter Remover marcador preto - + Set Black Counters... Definir marcadores pretos... - + Add Colorless Counter Adicionar marcador incolor - + Remove Colorless Counter Remover marcador incolor - + Set Colorless Counters... Definir marcadores incolor... - + Add Other Counter Adicionar outro marcador - + Remove Other Counter Remover outro marcador - + Set Other Counters... Definir outros marcadores... - + Increment all card counters - + Add Power (+1/+0) adicionar Poder (+1/+0) - + Remove Power (-1/-0) Remover Poder (-1/-0) - + Move Toughness to Power (+1/-1) Mover Resistência Para Poder (+1/-1) - + Add Toughness (+0/+1) Adicionar Resistência (+0/+1) - + Remove Toughness (-0/-1) Remover Resistência (-0/-1) - + Move Power to Toughness (-1/+1) Mover Poder Para Resistência (-1/+1) - + Add Power and Toughness (+1/+1) Adicionar Poder e Resistência (+1/+1) - + Remove Power and Toughness (-1/-1) Remover Poder e Resistência (-1/-1) - + Set Power and Toughness... Definir poder e resistência... - + Reset Power and Toughness Redefinir poder e resistência - + Untap Desvirar - + Upkeep Manutenção - + Draw Compra - + First Main Phase Primeira Fase Principal - + Start Combat Iniciar Combate - + Attack Atacar - + Block Bloquear - + Damage Dano - + End Combat Terminar Combate - + Second Main Phase Segunda Fase Principal - + End Fim - + Next Phase Próxima Fase - + Next Phase Action Ação de Próxima Etapa - + Next Turn Próximo Turno - + Hide Card in Reveal Window Ocultar Carta na Janela de Revelação - + Tap / Untap Card Virar / Desvirar Carta - + Untap All Desvirar Tudo - + Toggle Untap Alternar modo de desvirar - + Turn Card Over Virar carta para cima - + Peek Card Olhar carta - + Play Card Jogar Carta - + Attach Card... Anexar carta... - + Unattach Card Desanexar carta... - + Clone Card Duplicar Carta - + Create Token... Criar Ficha - + Create All Related Tokens Criar todas as fichas relacionadas - + Create Another Token Criar Outra Ficha - + Set Annotation... Definir anotação - + Select All Cards in Zone Selecionar Todas as Cartas na Zona - + Select All Cards in Row Selecionar Todas as Cartas na Linha - + Select All Cards in Column Selecionar Todas as Cartas na Coluna - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library Fundo do Grimório - - - - + + + + Exile Exilar - - - - + + + + Graveyard Cemitério - - + + + Hand Mão - - + + Top of Library Topo do Grimório - - - + + + Battlefield, Face Down Campo de batalha, Voltada para baixo - + Battlefield Campo de Batalha - - Sort Hand - - - - + Library Grimório - + Sideboard Sideboard - + Top Cards of Library Cartas do topo do grimório - + Bottom Cards of Library Cartas do Fundo da Biblioteca - + Close Recent View Fechar visualização recente - - + + Stack Pilha - - + + Graveyard (Multiple) Cemitério (Vários) - - + + Exile (Multiple) Exílio (Vários) - + Stack Until Found Empilhar Até Encontrar - + Draw Bottom Card Comprar carta do fundo - + Draw Multiple Cards from Bottom... Comprar várias cartas do fundo... - + Draw Arrow... Desenhar seta... - + Remove Local Arrows Remover setas locais - + Leave Game Sair do jogo - + Concede Conceder - + Roll Dice... Rolar dados... - + Shuffle Library Embaralhar grimório - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan Mulligan - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card Comprar uma carta - + Draw Multiple Cards... Comprar várias cartas - + Undo Draw Desfazer compra - + Always Reveal Top Card Sempre revelar a carta do topo do grimório - + Always Look At Top Card Sempre olhar para a carta do topo do grimório - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise Rotacionar visão no sentido horário - + Rotate View Counterclockwise Rotacionar visão no sentido anti-horário - + Unfocus Text Box Desfocar caixa de texto - + Focus Chat Focar no chat - + Clear Chat Limpar chat - + Refresh Atualizar - + Skip Forward Avançar - + Skip Backward Retroceder - + Skip Forward by a lot Avançar Bastante - + Skip Backward by a lot Retroceder Bastante - + Play/Pause Reproduzir/Pausar - + Toggle Fast Forward Ativar/Desativar Avanço Rápido - + Home - + Visual Deck Storage Decks Visuais - + Deck Storage Armazenamento de Deck - + Server Servidor - + Account Servidor - + Administration Administração - + Logs Registros diff --git a/cockatrice/translations/cockatrice_ru.ts b/cockatrice/translations/cockatrice_ru.ts index 319905695..8f062c706 100644 --- a/cockatrice/translations/cockatrice_ru.ts +++ b/cockatrice/translations/cockatrice_ru.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Установить жетоны... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Установить жетоны - + New value for counter '%1': Новое значение жетонов '%1': @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh Обновить - + Parse Set Name and Number (if available) Распознать имя сета и номер (если есть) @@ -36,81 +36,83 @@ AbstractTabDeckEditor - + Open in new tab Открыть в новой вкладке - + Are you sure? Вы уверены? - + The decklist has been modified. Do you want to save the changes? Деклист был изменен. Вы хотите сохранить изменения? - - - - - - - + + + + + + Error Ошибка - + Could not open deck at %1 Не удалось открыть колоду по пути «%1» - + Could not save remote deck Не удалось сохранить сетевую колоду - - + + The deck could not be saved. Please check that the directory is writable and try again. Колода не может быть сохранена. Проверьте что директория доступна для записи и попробуйте снова. - + Save deck Сохранить колоду - + The deck could not be saved. Не удалось сохранить колоду. - + There are no cards in your deck to be exported В колоде нет карт для экспорта + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. - Не выбрана колода для экспорта. + + Add Analytics Panel + AdminNotesDialog - + Update Notes Список изменений - + Admin Notes for %1 Примечания администратора для %1 @@ -118,12 +120,12 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard Основная колода - + Sideboard Сайдборд @@ -131,22 +133,22 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds секунд - + Error Ошибка - + Could not create themes directory at '%1'. Не удалость создать папку для тем в '%1'. - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -163,7 +165,7 @@ Are you sure you would like to enable this feature? Уверены что хотите включить эту опцию? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -180,152 +182,165 @@ Are you sure you would like to disable this feature? Уверены что хотите выключить эту опцию? - + Confirm Change Подтвердить изменения - + Theme settings Настройки темы - + Current theme: Текущая тема: - + Open themes folder Открыть папку с темами - + Home tab background source: Путь до обоев на домашней вкладке: - + Home tab background shuffle frequency: Частота смены обоев: - + Disabled Отключено - + Menu settings Настройки меню - + Show keyboard shortcuts in right-click menus Отображать сочетания клавиш в контекстном меню - + + Show game filter toolbar above list in room tab + + + + Card rendering Отрисовка карт - + Display card names on cards having a picture Отображать название карты поверх изображения - + Auto-Rotate cards with sideways layout Автоматически поворачивать карты альбомной ориентации - + Override all card art with personal set preference (Pre-ProviderID change behavior) Перезаписать все арты в соответствии с выбранными изданиями (Pre-ProviderID меняет это поведение) - + Bump sets that the deck contains cards from to the top in the printing selector Перемещать в начало списка выбора издания те выпуски, из которых в колоде есть карты. - + Scale cards on mouse over Увеличивать карты при наведении мыши - + Use rounded card corners Использовать скругленные углы карт - + Minimum overlap percentage of cards on the stack and in vertical hand Минимальный процент наложения карт в стеке и вертикальной руке - + Maximum initial height for card view window: Максимальная начальная высота для окна просмотра карты - - + + rows строки - + Maximum expanded height for card view window: Макс. высота окна для просмотра карт - + Card counters Жетоны на картах - + Counter %1 Жетон %1 - + Hand layout Раскладка руки - + Display hand horizontally (wastes space) Отображать руку горизонтально (требует места) - + Enable left justification Выравнивание по левому краю - + Table grid layout Сетка - + Invert vertical coordinate Инвертировать вертикальные координаты - + Minimum player count for multi-column layout: Минимальное количество игроков для столбчатого расположения: - + Maximum font size for information displayed on cards: Максимальный размер шрифта для текста карт: + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -347,112 +362,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name Забанить по &Имени игрока - + ban &IP address Забанить по &адресу IP - + ban client I&D Забанить по I&D клиента - + Ban type Тип бана - + &permanent ban &постоянный бан - + &temporary ban &временный бан - + &Days: &Дни - + &Hours: &Часы - + &Minutes: &Минуты - + Duration of the ban Длительность бана - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Пожалуйста, назовите причину бана. Эта информация сохраняется для модераторов и не будет видна забаненному игроку - + Please enter the reason for the ban that will be visible to the banned person. Пожалуйста, назовите причину бана, которая будет видна забаненному игроку. - + Redact all messages from this user in all rooms Отредактировать все сообщения от этого пользователя во всех комнатах - + &OK &ОК - + &Cancel &Отмена - + Ban user from server Забанить игрока на этом сервере - + + - - + Error Ошибка - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Необходимо выбрать тип бана: по имени, по адресу IP, по ID клиента или комбинацию этих трех типов. - + You must have a value in the name ban when selecting the name ban checkbox. Необходимо ввести имя игрока при выборе опции бана по имени. - + You must have a value in the ip ban when selecting the ip ban checkbox. Необходимо ввести адрес IP при выборе опции бана по IP. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Необходимо ввести ID клиента при выборе опции бана по ID. @@ -483,32 +498,32 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name Название - + Sets Издания - + Mana cost Мана-стоимость - + Card type Тип - + P/T Сила/Выносливость: - + Color(s) Цвет(а) @@ -516,96 +531,101 @@ This is only saved for moderators and cannot be seen by the banned person. CardFilter - + AND Logical conjunction operator used in card filter И - + OR Logical disjunction operator used in card filter ИЛИ - + AND NOT Negated logical conjunction operator used in card filter И НЕ - + OR NOT Negated logical disjunction operator used in card filter ИЛИ НЕ - + Name Название - + + Name (Exact) + + + + Type Тип - + Color Цвет - + Text Текст - + Set Издание - + Mana Cost Мана-стоимость - + Mana Value Количество маны - + Rarity Редкость - + Power Сила - + Toughness Выносливость - + Loyalty Преданность - + Format Формат - + Main Type Тип - + Sub Type Подтип @@ -613,22 +633,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoFrameWidget - + Image Изображение - + Description Описание - + Both Оба - + View transformation Обратная сторона @@ -636,22 +656,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards Посмотреть связанные карты - + Add card to deck Добавить карту в колоду - + Mainboard Колода - + Sideboard Сайд @@ -664,12 +684,22 @@ This is only saved for moderators and cannot be seen by the banned person.Название: - + + Set: + + + + + Collector Number: + + + + Related cards: Связанные карты: - + Unknown card: Неизвестная карта: @@ -677,124 +707,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... Показать... - + &All players Всем игрокам - + View related cards Посмотреть связанные карты - + Token: Фишка: - + All tokens Все фишки: - + &Select All Выбрать все - + S&elect Row Выбрать строку - + S&elect Column Выбрать колонку - + &Play Разыграть - + &Hide Спрятать - + Play &Face Down Разыграть лицом вниз - + &Tap / Untap Turn sideways or back again Повернуть / Развернуть - + Toggle &normal untapping Выключить разворот в фазе разворота - + T&urn Over Turn face up/face down Перевернуть - + &Peek at card face Посмотреть лицевую сторону - + &Clone Клонировать - + Attac&h to card... Прикрепить к карте... - + Unattac&h Открепить - + &Draw arrow... Нарисовать стрелку - + &Set annotation... Добавить заметку - + Ca&rd counters Жетоны на карте - + &Add counter (%1) &Добавить жетон (%1) - + &Remove counter (%1) &Убрать жетон (%1) - + &Set counters (%1)... &Установить жетоны (%1)... @@ -802,7 +832,7 @@ This is only saved for moderators and cannot be seen by the banned person. CardSizeWidget - + Card Size Размер карты @@ -810,133 +840,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative своей руки - + %1's hand nominative %1 руки - - - their library - look at zone - своей библиотеки - - - - %1's library - look at zone - библиотеки %1 - - - - of their library - top cards of zone, - своей библиотеки - - - - of %1's library - top cards of zone - библиотеки %1 - their library - reveal zone + look at zone своей библиотеки %1's library + look at zone + библиотеки %1 + + + + of their library + top cards of zone, + своей библиотеки + + + + of %1's library + top cards of zone + библиотеки %1 + + + + their library + reveal zone + своей библиотеки + + + + %1's library reveal zone библиотеки %1 - + their library shuffle своей библиотеки - + %1's library shuffle библиотеки %1 - + their library nominative своей библиотеки - + %1's library nominative библиотеки %1 - + their graveyard nominative своего кладбища - + %1's graveyard nominative кладбища %1 - + their exile nominative своего изгнания - + %1's exile nominative изгнание %1 - + their sideboard look at zone своего сайда - + %1's sideboard look at zone сайда %1 - - - their sideboard - nominative - своего сайда - - - - %1's sideboard - nominative - сайда %1 - + their sideboard + nominative + своего сайда + + + + %1's sideboard + nominative + сайда %1 + + + their custom zone '%1' nominative кастомной зоны '%1' - + %1's custom zone '%2' nominative кастомной зоны '%2' игрока '%1' @@ -945,7 +975,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml3Parser - + Parse error at line %1 col %2: Ошибка распознавания, строка %1 колонка %2: @@ -953,7 +983,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml4Parser - + Parse error at line %1 col %2: Ошибка распознавания, строка %1 колонка %2: @@ -972,6 +1002,37 @@ This is only saved for moderators and cannot be seen by the banned person.Посмотреть кастомную зону '%1' + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -983,47 +1044,47 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) Искать по названию карты (или по поисковому запросу) - + Add to Deck Добавить в колоду - + Add to Sideboard Добавить в сайд - + Select Printing Выбрать издание - + Show on EDHRec (Commander) Посмотреть на EDHRec (Командир) - + Show on EDHRec (Card) Посмотреть на EDHRec (карта) - + Show Related cards Посмотреть связанные карты - + Add card to &maindeck Добавить карту в колоду - + Add card to &sideboard Добавить карту в сайд @@ -1031,87 +1092,97 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card Обложка колоды - + Main Type Тип - + Mana Cost Мана-стоимость - + Colors Цвет - + Select Printing Выбрать издание - + Deck Колода - + Deck &name: Название колоды: - + Banner Card/Tags Visibility Settings Настройки отоборажение обложки и тегов - + Show banner card selection menu Показать меню выбора обложки - + Show tags selection menu Показать меню выбора тегов - + &Comments: Комментарий: - + Group by: Группировать по: - + + Format: + + + + Hash: Хэш-сумма - + &Increment number Увеличить количество - + &Decrement number Уменьшить количество - + &Remove row Удалить строку - + Swap card to/from sideboard Переместить карту в/из сайда @@ -1119,17 +1190,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters Фильтры - + &Clear all filters Убрать все фильтры - + Delete selected Удалить выбранное @@ -1137,114 +1208,114 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorMenu - + &Deck Editor Редактор колоды - + &New deck Новая колода - + &Load deck... Загрузить колоду - + Load recent deck... Загрузить недавнюю колоду - + Clear Очистить - + &Save deck Сохранить колоду - + Save deck &as... Сохранить колоду как... - + Load deck from cl&ipboard... Загрузить колоду из буфера обмена - + Edit deck in clipboard Редактировать колоду из буфера обмена - - + + Annotated Аннотировано - - + + Not Annotated Не аннотировано - + Save deck to clipboard Сохранить колоду в буфер обмена - + Annotated (No set info) Аннотировано (нет информации об издании) - + Not Annotated (No set info) Не аннотировано (нет информации об издании) - + &Print deck... Распечатать колоду... - + Load deck from online service... Загрузить колоду из онлайн-сервиса - + &Send deck to online service Сохранить колоду в онлайн-сервис - + Create decklist (decklist.org) Создать деклист (decklist.org) - + Create decklist (decklist.xyz) Создать деклист (decklist.xyz) - + Analyze deck (deckstats.net) Анализировать колоду (deckstats.net) - + Analyze deck (tappedout.net) Анализировать колоду (tappedout.net) - + &Close Закрыть @@ -1252,7 +1323,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector Выбрать издание @@ -1260,194 +1331,227 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers Обновить спойлеры - - + + Success Успешно - + Download URLs have been reset. Адреса URL загрузки были сброшены. - + Downloaded card pictures have been reset. Загруженные изображения карт были сброшены. - + Error Ошибка - + One or more downloaded card pictures could not be cleared. Одно или более загруженных изображений карт не может быть удалено. - + Add URL Добавить URL - - + + URL: URL: - - + + Edit URL Изменить URL: - + Network Cache Size: Размер сетевого кеша: - + Redirect Cache TTL: TTL кеша перенаправлений: - + How long cached redirects for urls are valid for. Время жизни кеша редиректов - + Picture Cache Size: Размер кеша изображений: - + Add New URL Добавить новый URL - + Remove URL Удалить URL - + Day(s) Дней - + Updating... Обновление... - + Choose path Выбрать путь - + URL Download Priority Приоритет загрузки URL - + Spoilers Спойлеры - + Download Spoilers Automatically Скачивать спойлеры автоматически - + Spoiler Location: Расположение спойлеров: - + Last Change Последние изменения - + Spoilers download automatically on launch Спойлеры скачиваются автоматически при запуске - + Press the button to manually update without relaunching Нажмите, чтобы обновить вручную без перезапуска - + Do not close settings until manual update is complete Не закрывать настройки до завершения ручного обновления - + Download card pictures on the fly Синхронная загрузка изображений карт - + How to add a custom URL Как добавить пользовательский URL - + Delete Downloaded Images Удалить загруженные изображения - + Reset Download URLs Обнулить загруженные URL - + On-disk cache for downloaded pictures Размер кеша для загруженных артов - + In-memory cache for pictures not currently on screen Размер кеша в памяти для загруженных артов + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count Количество - + Set Издание - + Number Номер - + Provider ID Provider ID - + Card Название @@ -1455,12 +1559,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) Формат колоды (%1) - + All files (*.*) Все файлы (*.*) @@ -1468,17 +1572,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match Режим: полное совпадение - + Mode: Includes Режим: частичное совпадение - + Color identity filter mode (AND/OR/NOT conjunctions of filters) Фильтра совпадения цветов (И/ИЛИ/НЕ фильтр) @@ -1486,7 +1590,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... Редактировать теги ... @@ -1494,62 +1598,62 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewTagDialog - + Deck Tags Manager Управление тегами колоды - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: Редактируйте теги в колоде. Выберите нужные теги или добавьте новый: - + Add a new tag (e.g., Aggro️) Новый тег (например, Aggro) - + Add Tag Добавить тег - + Filter tags... Фильтр тегов... - + OK ОК - + Edit default tags Редактировать теги по умолчанию - + Cancel Отменить - + Invalid Input Неправильный ввод - + Tag name cannot be empty! Название тега не может быть пустым! - + Duplicate Tag Копировать тег - + This tag already exists. Этот тег уже существует @@ -1562,93 +1666,151 @@ This is only saved for moderators and cannot be seen by the banned person.Обложка - + Open in deck editor Открыть в редакторе колоды - + Edit Tags Редактировать теги - + Rename Deck Переименовать колоду - + Save Deck to Clipboard Сохранить колоду в буфер обмена - + Annotated Аннотировано - + Annotated (No set info) Аннотировано (нет информации об издании) - + Not Annotated Не аннотировано - + Not Annotated (No set info) Не аннотировано (нет информации об издании) - + Rename File Переименовать файл - + Delete File Удалить файл - + Set Banner Card Установить обложку колоды - - + + New name: Новое название: - - + + Error Ошибка - + Rename failed Не удалось переименовать - + Delete file Удалить файл - + Are you sure you want to delete the selected file? Уверены, что хотите удалить выбранный файл? - + Delete failed Не удалось удалить + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1666,75 +1828,75 @@ This is only saved for moderators and cannot be seen by the banned person. DeckViewContainer - + Load deck... Загрузить колоду... - + Load remote deck... Загрузить колоду удаленно... - + Load from clipboard... Загрузить из буфера обмена... - + Load from website... Загрузить из интернета... - + Unload deck Выгрузить колоду - + Ready to start &Готов - + Force start Принудительный старт - + Sideboard unlocked Сайдборд разблокирован - + Sideboard locked Сайдборд заблокирован - - + + Error Ошибка - + The selected file could not be loaded. Выбранный файл не может быть загружен - + Deck is greater than maximum file size. Колода больше максимально возможного размера файла - + Are you sure you want to force start? This will kick all non-ready players from the game. Уверены что хотите принудительно начать? Это кикнет всех неготовых игроков из игры - + Cockatrice Cockatrice @@ -1769,143 +1931,143 @@ This will kick all non-ready players from the game. Загрузка... - + Known Hosts Известные Хосты - + Delete the currently selected saved server Удалить текущий выбранный сервер - + Refresh the server list with known public servers Обновить список известных публичных серверов - + New Host Новый хост - + Name: Название: - + &Host: &Хост: - + &Port: &Порт: - + Player &name: &Имя пользователя: - + P&assword: П&ароль: - + &Save password &Сохранить пароль - + A&uto connect Присоединяться автоматически - + Automatically connect to the most recent login when Cockatrice opens Автоматически подключится с последним логином при открытии Кокатрис - + If you have any trouble connecting or registering then contact the server staff for help! Если у вас появилась проблема при присоединении или регистрации - свяжитесь с сотрудниками для помощи! - - + + Webpage Веб-страница - + Reset Password Обнулить пароль - + Forgot password? Забыли пароль? - + &Connect &Подключиться - + Server Сервер - + Login Логин - + Server Contact Соединение с сервером - + Connect to Server Подключиться к серверу - + Server URL URL сервера - + Communication Port Порт связи - + Unique Server Name Уникальное имя сервера - + Connection Warning Предупреждение при соединении - + You need to name your new connection profile. Назовите ваш профиль для соединения. - + Connect Warning Ошибка при соединении - + The player name can't be empty. Необходимо указать имя пользователя. @@ -1913,117 +2075,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings &Запомнить настройки - + &Description: &Описание: - + P&layers: &Игроки: - + General Основные - + Game type Формат игры - + &Password: &Пароль: - + Only &buddies can join Только для &друзей - + Only &registered users can join Только для &зарегистрированных пользователей - + Joining restrictions Ограничения - + &Spectators can watch &Зрители могут наблюдать за игрой - + Spectators &need a password to watch Зрителям &необходим пароль для просмотра игры - + Spectators can &chat Позволить зрителям &комментировать - + Spectators can see &hands Зрители могут видеть &руки игроков - + Create game as spectator Создать игру как зритель - + Spectators Зрители - + Starting life total: Начальное количество жизней: - + Open decklists in lobby Открыть деклист в лобби - + + Create game as judge + + + + Game setup options Настройки игры - + &Clear &Очистить - + Create game Создать игру - + Game information Информация об игре - + Error Ошибка - + Server error. Ошибка сервера. @@ -2031,97 +2198,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Название: - + Token Фишка - + C&olor: &Цвет: - + white белый - + blue синий - + black черный - + red красный - + green зеленый - + multicolor многоцветный - + colorless бесцветный - + &P/T: &Сила/Выносливость: - + &Annotation: &Примечание: - + &Destroy token when it leaves the table &Уничтожить фишку, когда она покинет поле битвы - + Create face-down (Only hides name) Создать лицом вниз (скроет только название) - + Token data Описание фишки - + Show &all tokens Показать &все фишки - + Show tokens from this &deck Показать фишки из указанной &колоды - + Choose token from list Выбрать фишку из списка - + Create token Создать фишку @@ -2129,53 +2296,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags Редактировать теги - + Add Добавить - + Confirm Подтвердить - + Cancel Отменить - + Enter a tag and press Enter Введите тег и нажмите Enter - - + + - + Invalid Input Неправильный ввод - + Tag name cannot be empty! Название тега не может быть пустым! - + Duplicate Tag Копировать тег - + This tag already exists. Этот тег уже существует @@ -2183,40 +2350,40 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. Изображение не выбрано. - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. Чтобы сменить аватар, выберите новое изображение. Чтобы удалить текущий аватар, нажмите "Сменить аватар", оставив поле выбора изображения пустым. - + Browse... Поиск... - + Change avatar Сменить аватар - + Open Image Открыть изображение - + Image Files (*.png *.jpg *.bmp) Файлы изображений (*.png *.jpg *.bmp) - + Invalid image chosen. Выбрано не подходящее изображение. @@ -2224,17 +2391,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard Редактировать колоду из буфера обмена - + Error Ошибка - + Invalid deck list. Неправильный деклист @@ -2242,38 +2409,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: Старый пароль: - + New password: Новый пароль: - + Confirm new password: Подтверждение пароля: - + Change password Сменить пароль - - + + Error Ошибка - + Your password is too short. Пароль слишком короткий - + The new passwords don't match. Введенные пароли не совпадают. @@ -2281,93 +2448,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: &Название - + C&olor: &Цвет - + white белый - + blue синий - + black чёрный - + red красный - + green зелёный - + multicolor мультицветный - + colorless бесцветный - + &P/T: &Сила/Выносливость: - + &Annotation: &Примечание: - + Token data Описание фишки - - + + Add token Добавить фишку - + Remove token Удалить фишку - + Edit custom tokens Редактировать пользовательские фишки - + Please enter the name of the token: Введите имя фишки: - + Error Ошибка - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Выбранное имя конфликтует с существующей картой или токеном. @@ -2461,8 +2628,9 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games not created by buddy - Скрыть игры, не созданные друзьями + Hide games not created by buddies + Hide games not created by buddy + @@ -2548,52 +2716,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning Предупреждение при проверке сброса пароля - + A problem has occurred. Please try to request a new password again. Возникла проблема. Попробуй запросить новый пароль еще раз - + Enter the information of the server and the account you'd like to request a new password for. Введите информации о сервере и учетной записи, для которой нужен новый пароль - + &Host: &Хост: - + &Port: &Порт: - + Player &name: &Имя игрока: - + Email: Электронная почта: - + Reset Password Challenge Сбросить пароль - + Reset Password Challenge Error Предупреждение при проверке сброса пароля - + The email address can't be empty. Необходимо указать ваш e-mail @@ -2601,37 +2769,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Введите информации о сервере и учетной записи, для которой нужен новый пароль - + &Host: &Хост: - + &Port: &Порт: - + Player &name: &Имя игрока: - + Reset Password Request Забыл пароль - + Reset Password Error Ошибка сброса пароля - + The player name can't be empty. Необходимо указать имя игрока. @@ -2639,86 +2807,86 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning Сбросить предупреждение о пароле - + A problem has occurred. Please try to request a new password again. Возникла проблема. Попробуй запросить новый пароль еще раз - + Enter the received token and the new password in order to set your new password. Введите полученный токен и новый пароль - + &Host: &Хост: - + &Port: &Порт: - + Player &name: &Имя игрока: - + Token: Жетон: - - + + New Password: Новый пароль: - + Reset Password Сбросить пароль - + The player name can't be empty. Необходимо указать имя игрока. - - - - + + + + Reset Password Error Ошибка сброса пароля - + The token can't be empty. Необходимо указать код активации. - + The new password can't be empty. Необходимо ввести новый пароль. - + Error Ошибка - + Your password is too short. Пароль слишком короткий - + The passwords do not match. Введенные пароли не совпадают. @@ -2734,17 +2902,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard Загрузить колоду из буфера - + Error Ошибка - + Invalid deck list. Неверный деклист. @@ -2752,45 +2920,45 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Вставьте ссылку на сайт с деклистом для импорта (Поддерживаются Archidekt, Deckstats, Moxfield, and TappedOut) - - - - - + + + + + Load Deck from Website Загрузить колоду из интернета - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Недоступен парсер для данного источника (Поддерживаются Archidekt, Deckstats, Moxfield, and TappedOut) - + Network error: %1 Ошибка сети: %1 - + Received empty deck data. Получен пустой ответ - + Failed to parse deck data: %1 Ошибка парсинга колоды: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2810,7 +2978,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck Загрузить колоду @@ -2818,37 +2986,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): Название карты (или поисковый запрос) - + Number of hits: Количество: - + Auto play hits Автоматически разыгрывать найденные карты - + Put top cards on stack until... Поместить вернюю карту в стек, пока... - + No cards matching the search expression exists in the card database. Proceed anyways? В базе данных карт ничего не найдено. Все равно продолжить? - + Cockatrice Cockatrice - + Invalid filter Неправильный фильтр @@ -2856,92 +3024,92 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Введите информацию о себе и о сервере, на котором хотите зарегистрироваться. На электронную почту будет отправлено письмо активации учетной записи. - + &Host: &Хост - + &Port: &Порт: - + Player &name: &Имя пользователя: - + P&assword: Пароль: - + Password (again): Подтверждение пароля: - + Email: Адрес email: - + Email (again): Подтверждение email: - + Country: Страна: - + Undefined Не определено - + Real name: Настоящее имя: - + Register to server Зарегистрироваться на сервере - - - - + + + + Registration Warning Предкпреждение - + Your password is too short. Пароль слишком короткий - + Your passwords do not match, please try again. Введенные пароли не совпадают, повторите снова. - + Your email addresses do not match, please try again. Введенные адреса email не совпадают, повторите снова. - + The player name can't be empty. Необходимо указать имя пользователя. @@ -2967,40 +3135,55 @@ Your email will be used to verify your account. DlgSelectSetForCards - + Unmodified Cards: Карты без изменений: - + Modified Cards: Карты с изменениями: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. Выделите издания для их включения. Перетаскиваете для изменения их порядка и приоритета. Для карт будут использованы арты с наибольшим приоритетом. - + Clear all set information Очистить информацию об издании - + Set all to preferred Поменять все на предпочтительный + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database Неизвестная ошибка при загрузке базы карт. - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3014,7 +3197,7 @@ Cockatrice не может корректно работать с испорче Вы хотите сменить расположение файла базы карт? - + Your card database version is too old. This can cause problems loading card information or images @@ -3028,7 +3211,7 @@ Would you like to change your database location setting? Вы хотите сменить расположение файла базы карт? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3041,7 +3224,7 @@ Would you like to change your database location setting? Вы хотите изменить путь до локальной базы карт? - + File Error loading your card database. Would you like to change your database location setting? @@ -3049,7 +3232,7 @@ Would you like to change your database location setting? Вы хотите сменить расположение файла базы карт? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3057,7 +3240,7 @@ Would you like to change your database location setting? Вы хотите сменить расположение файла базы карт? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3070,59 +3253,59 @@ Would you like to change your database location setting? Вы хотите изменить путь до локальной базы карт? - - - + + + Error Ошибка - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Ваши колоды отсутствуют в указанной папке. Вернуться и задать правильный путь? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Изображения карт не найдены. Вернуться и задать правильный путь? - + Settings Настройки - + General Основные - + Appearance Вид - + User Interface Интерфейс - + Card Sources Источник карт - + Chat Чат - + Sound Звук - + Shortcuts Горячие клавиши @@ -3130,12 +3313,12 @@ Would you like to change your database location setting? DlgStartupCardCheck - + Card Update Check База карт: - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. @@ -3144,27 +3327,27 @@ You can always change this behavior in the 'General' settings tab. - + Run in foreground Запустить на переднем плане - + Run in background Запустить в фоне - + Run in background and always from now on Запустить в фоне сейчас и всегда а будущем - + Don't prompt again and don't run Не спрашивать снова и не запускать - + Don't run this time Не запускать в этот раз @@ -3172,17 +3355,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next Следующий - + Previous Предыдущий - + Tip of the Day Совет дня @@ -3190,40 +3373,40 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel Канал актуального обновления - + Reinstall Переустановить - + Cancel Download Отменить загрузку - + Open Download Page Открыть страницу загрузки - + Check for Client Updates Проверить обновления клиента - - - - + + + + Error Ошибка - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. В Cockatrice нет поддержки SSL и вы не можете обновиться автоматически! @@ -3231,125 +3414,125 @@ Please visit the download page to update manually. Пожалуйста, посетите страницу загрузки для получения обновления. - + Downloading update: %1 Скачивание обновлений: %1 - + Checking for updates... Проверка обновлений... - + Finished checking for updates Закончена проверка обновлений - + No Update Available Нет доступных обновлений - + Cockatrice is up to date! Установлена последняя версия Cockatrice! - + You are already running the latest version available in the chosen release channel. Уже установлена последняя версия, доступная в выбранной ветке обновлений. - + Current version Текущая версия - + Selected release channel Выбранная ветка обновлений - - + + Update Available Обновление доступно - - + + A new version of Cockatrice is available! Доступна новая версия Cockatrice! - - + + New version Новая версия - - + + Released Выпущена - - + + Changelog Список изменений - + Do you want to update now? Вы хотите обновить сейчас? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. Автообновление не удалось - подходящая версия не найдена. Скачайте новую версию вручную. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. Проверьте <a href="%1">страницу релиза</a> в нашем репозитории на Github и скачайте сборку для своей системы. - - - + + + Update Error Ошибка при оновлении - + An error occurred while checking for updates: В процессе проверки обновлений произошла ошибка: - + An error occurred while downloading an update: В процессе загрузки обновления произошла ошибка: - + Installing... Установка... - + Cockatrice is unable to open the installer. Невозможно открыть установщик. - + Try to update manually by closing Cockatrice and running the installer. Попробуйте обновить вручную, закрыв Cockatrice и запустив инсталлятор. - + Download location @@ -3357,21 +3540,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing Очистить лог при закрытии - + Copy to clipboard Скопировать в буфер обмена - + Debug Log Лог + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3441,33 +3756,39 @@ You may have to manually download the new version. %1% Сенергии + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos Комбо - + Average Deck Средняя сборка - - - Game Changers - Карты, переворачивающие стол - - - - Budget - Бюджетные - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: Цена: @@ -3483,22 +3804,22 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete Подтвердить удаление - + Are you sure you want to delete the filter '%1'? Вы уверены что хотите удалить фильтр '%1'? - + Delete Failed Не удалось удалить - + Failed to delete filter '%1'. Не удалось удалить фильтр '%1'. @@ -3506,22 +3827,22 @@ You may have to manually download the new version. GameEventHandler - + kicked by game host or moderator кикнут из игры хостом или модератором - + player left the game игрок покинул игру - + player disconnected from server игрок отсоединился от сервера - + reason unknown причина неизвестна @@ -3529,226 +3850,279 @@ You may have to manually download the new version. GameSelector - - - - - - + + + + + + Error Ошибка - + Please join the appropriate room first. Пожалуйста, сперва войдите в соответствующую комнату. - + Wrong password. Неверный пароль. - + Spectators are not allowed in this game. Зрители не допускаются в эту игру. - + The game is already full. Все места уже заняты! - + The game does not exist any more. Эта игра больше не существует. - + This game is only open to registered users. Доступно только для зарегистрированных. - + This game is only open to its creator's buddies. Доступно только для друзей. - + You are being ignored by the creator of this game. Вы добавлены в игнор-лист данного игрока. - + Join Game Присоединиться - + Spectate Game Наблюдать - + Game Information Информация об игре - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game Присоединиться - + Password: Пароль: - + Please join the respective room first. Пожалуйста, сначала присоединитесь к соответствующей комнате. - + &Filter games &Фильтр игр - + C&lear filter О&чистить фильтр - + C&reate &Создать - + &Join &Присоединиться - + + Join as judge + + + + J&oin as spectator Присоединиться как &зритель - + + Join as judge spectator + + + + Games shown: %1 / %2 Показано игр: %1 / %2 - + Games Игры + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day > 1 дня - + %1%2 hr short age in hours %1%2 час%1%2 часа%1%2 часов%1%2 часов - + new создать - + %1%2 min short age in minutes %1%2 минута%1%2 минут%1%2 минут%1%2 минут - + password пароль - + buddies only только друзья - + reg. users only только для зарегистрированных пользователей - + open decklists открыть деклист - - + + can chat зрители могут комментировать в чате - + see hands видеть руки игроков - + can see hands могут видеть руки - + not allowed не допускаются - + Room Комната - + Age Возраст - + Description Описание - + Creator Создатель - + Type Тип - + Restrictions Ограничения - + Players Количество игроков - + Spectators Зрители @@ -3756,143 +4130,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths Сбросить - + All paths have been reset Все пути были сброшены - - - - - - - + + + + + + + Choose path Путь - + Personal settings Настройки пользователя - + Language: Язык: - + Paths (editing disabled in portable mode) Путь (в портативном режиме редактирование недоступно) - + Paths Пути расположения - + How to help with translations Как помочь с переводом - + Decks directory: Колоды: - + Filters directory: Фильтровать директории: - + Replays directory: Повторы: - + Pictures directory: Изображения карт: - + Card database: База данных карт: - + Custom database directory: Пользовательские сеты: - + Token database: База данных токенов: - + Update channel Обновить канал - + Check for client updates on startup Проверять обновления при запуске - + Check for card database updates on startup Проверять обновления базы карт при запуске - + Don't check Не проверять - + Prompt for update Поиск обновлений - + Always update in the background Всегда обновлять на фоне - + Check for card database updates every Проверять обновления каждые - + days дней - + Notify if a feature supported by the server is missing in my client Предупреждать, если функции, используемые сервером, отсутствуют в этом клиенте. - + Automatically run Oracle when running a new version of Cockatrice Автоматически запускать Oracle в новой версии Cockatrice - + Show tips on startup Показывать советы при запуске клиента - + Last update check on %1 (%2 days ago) Последнее проверка обновлений была %1 (%2 дней назад) @@ -3900,47 +4274,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard Кладбище - + &View graveyard Посмотреть кладбище - + &Move graveyard to... Переместить кладбище в... - + &Top of library Верх библиотеки - + &Bottom of library Низ библиотеки - + &All players Всем игрокам - + &Hand Рука - + &Exile Изгнание - + Reveal random card to... Показать случайно карту... @@ -3948,111 +4322,141 @@ You may have to manually download the new version. HandMenu - + &Hand Рука - + &View hand Посмотреть руку - - &Sort hand - Сортировать руку + + Sort hand by... + - - Take &mulligan - Взять муллиган + + Name + - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... Переместить руку... - + &Top of library Верх библиотеки - + &Bottom of library Низ библиотеки - + &Graveyard Кладбище - + &Exile Изгнание - + &Reveal hand to... Показать руку... - - Reveal r&andom card to... - Показать случайную карту... + + + All players + - - - &All players - Все игроки + + Reveal r&andom card to... + Показать случайную карту... HomeWidget - + Create New Deck Создать новую колоду - + Browse Decks Посмотреть колоды - + Browse Card Database Посмотреть базу карт - + Browse EDHRec Посмотреть EDHRec - + + Browse Archidekt + + + + View Replays Посмотреть повтор - + Quit Выход - + Connecting... Подключение... - + Connect Подключиться - + Play Играть @@ -4254,61 +4658,61 @@ You may have to manually download the new version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Этот сервер достиг максимального количества пользователей, попробуйте позже. - + There are too many concurrent connections from your address. Слишком много одновременных подключений с Вашего адреса. - + Banned by moderator Забанен модератором - + Expected end time: %1 Ожидаемое время: %1 - + This ban lasts indefinitely. Этот бан не ограничен по времени. - + Scheduled server shutdown. Плановый перерыв в работе сервера. - - + + Invalid username. Неверное имя пользователя. - + You have been logged out due to logging in at another location. Вы вышли из аккаунта, так как был выполнен вход из другого расположения. - + Connection closed Соединение прервано - + The server has terminated your connection. Reason: %1 Ваше подключение было прервано сервером. Причина: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -4323,689 +4727,689 @@ Reason for shutdown: %1 Причина перезагрузки: %1 - + Scheduled server shutdown Плановый перерыв в работе сервера - - + + Success Успешно - + Registration accepted. Will now login. Регистрация прошла успешно. Идет соединение. - + Account activation accepted. Will now login. Активация аккаунта прошла успешно. Идет соединение. - + Number of players Количество игроков - + Please enter the number of players. Введите количество игроков. - - + + Player %1 Игрок %1 - + Load replay Загрузить повтор - + About Cockatrice О программе - + Version Версия - + Cockatrice Webpage Страница Cockatrice - + Project Manager: Руководитель проекта: - + Past Project Managers: Предыдущие руководители проекта: - + Developers: Разработчики: - + Our Developers Наши разработчики - + Help Develop! Помочь в разработке! - + Translators: Переводчики: - + Our Translators Наши переводчики - + Help Translate! Помочь с переводом! - + Support: Поддержать проект: - + Report an Issue Сообщить о проблеме - + Troubleshooting Устранение неполадок - + F.A.Q. ЧАВо - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error Ошибка - + Server timeout Нет связи с сервером - + Failed Login Не могу подключиться - + Your client seems to be missing features this server requires for connection. Ваш клиент не поддерживает некоторые функции, необходимые для соединения с сервером. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. Неверное имя пользователя или пароль. Пожалуйста, уточните регистрационные данные и попробуйте снова. - + There is already an active session using this user name. Please close that session first and re-login. Пользователь с таким именем уже подключен. Пожалуйста, закройте это подключение и войдите заново. - - + + You are banned until %1. Вы забанены до %1. - - + + You are banned indefinitely. Вы забанены бессрочно. - + This server requires user registration. Do you want to register now? На этом сервере требуется регистрация. Вы хотите зарегистрироваться сейчас? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation Активация аккаунта - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Ваш аккаунт ещё не активирован. Вам нужно предоставить жетон/код активации, полученный по электронной почте. - + Server Full Сервер заполнен - + Unknown login error: %1 Неизвестная ошибка входа: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Обычно это означает, что Вы используете устаревшую версию клиента, и тот не отвечает запросам сервера. - + Your username must respect these rules: Имя пользователя должно удовлетворять следующим условиям: - + is %1 - %2 characters long длиной в %1 - %2 символов - + can %1 contain lowercase characters %1 может содержать строчные буквы - - - - + + + + NOT НЕ - + can %1 contain uppercase characters %1 может содержать заглавные буквы - + can %1 contain numeric characters %1 может содержать цифры - + can contain the following punctuation: %1 Может содержать следующие символы: %1 - + first character can %1 be a punctuation mark Первый знак %1 может быть знаком пунктуации - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 не может содержать следующие слова: %1 - + can not match any of the following expressions: %1 не может соответствовать следующим выражениям: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Имя пользователя может включать только следующие символы: A-Z, a-z, 0-9, "_", "." и "-" - - - - - - + + + + + + Registration denied Запрос на регистрацию отклонен - + Registration is currently disabled on this server Регистрация на данном сервере в настоящий момент недоступна - + There is already an existing account with the same user name. Аккаунт с таким именем уже существует. - + It's mandatory to specify a valid email address when registering. При регистрации необходимо указать действующий e-mail. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. Слишком короткий пароль. - + Registration failed for a technical problem on the server. Регистрация не удалась из-за технических неполадок на сервере. - + The connection to the server has been lost. - + Unknown registration error: %1 Неизвестная ошибка при регистрации: %1 - + Account activation failed Не удалось активировать аккаунт - + Socket error: %1 Ошибка сокета: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Вы пытаетесь подключиться к несуществующему серверу. Пожалуйста, обновите Cockatrice или выберите другой сервер. Локальная версия %1, удаленная версия %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Ваш клиент Cockatrice устарел. Пожалуйста, обновите Cockatrice. Локальная версия %1, удаленная версия %2. - + Connecting to %1... Подключение к %1... - + Registering to %1 as %2... Регистрация на %1 как %2... - + Disconnected Подключение прервано - + Connected, logging in at %1 Соединение установлено, выполняется вход на %1 + - Requesting forgotten password to %1 as %2... - + &Connect... Подключение... &С - + &Disconnect П&рервать подключение - + Start &local game... &Начать локальную игру... - + &Watch replay... &Смотреть повтор... - + &Full screen Полный экран &F - + &Register to server... &Регистрация на сервере... - + &Restore password... - + &Settings... &Настройки - + &Exit &Выход - + A&ctions &Действия - + &Cockatrice &Cockatrice - + C&ard Database &База карт - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder Открыть папку с изображениями - + Open custom sets folder Открыть папку с изданиями - + Add custom sets/cards Добавить издания/карты - + Reload card database - + Tabs - + &Help &Справка - + &About Cockatrice О про&грамме - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database База карт - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" Cockatrice не может загрузить базу карт. Обновить вашу базу карт сейчас? Если Вы не уверены, или впервые запустили программу, нажмите "Да" - - + + Yes Да - - + + No Нет - + Open settings Открыть настройки - + New sets found Найдены новые издания - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets Просмотреть издания - + Welcome Добро пожаловать - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information Информация - + A card database update is already running. Обновление базы карт уже идет. - + Unable to run the card database updater: Не удалось запустить обновление базы карт: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5016,673 +5420,843 @@ To update your client, go to Help -> Check for Updates. Чтобы обновить клиент, перейдите в Справка-> Проверка обновлений. - - - - - + + + + + Load sets/cards Загрузить издания/карты - + Selected file cannot be found. Выбранный файл не найден. - + You can only import XML databases at this time. На данный момент вы только можете предостовлять базы данных в формате XML. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Новые издания/карты успешно добавлены. Сейчас Cockatrice перезагрузит базу карт. - + Sets/cards failed to import. Не удалось импортировать издания/карты. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. Невозможно сбросить пароль. Пожалуйста, свяжитесь с оператором сервера. - + Activation request received, please check your email for an activation token. Запрос активации получен. Вам отправлен код активации. Пожалуйста, проверьте свой e-mail. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play из игры - + from their graveyard из их кладбищ - + from exile из изгнания - + from their hand из их рук - + the top card of %1's library верхняя карта библиотеки %1 - + the top card of their library верхнюю карту своей библиотеки - + from the top of %1's library с верха библиотеки %1 - + from the top of their library с верха своей библиотеки - + the bottom card of %1's library нижняя карта библиотеки %1 - + the bottom card of their library нижнюю карту своей библиотеки - + from the bottom of %1's library Со дна библиотеки %1 - + from the bottom of their library с низа своей библиотеки - + from %1's library из библиотеки %1 - + from their library из своей библиотеки - + from sideboard из сайдборда - + from the stack из стека - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 оставляет верхнюю карту %2 открытой. - + %1 is not revealing the top card %2 any longer. Верхняя карта %2 %1 больше не остается открытой. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 прикрепляет %2 к %4 под контролем %3. - + %1 has conceded the game. %1 сдался. - + %1 has unconceded the game. - + %1 has restored connection to the game. %1 восстановил связь с игрой. - + %1 has lost connection to the game. %1 потерял связь с игрой. - + %1 points from their %2 to themselves. %1 указывает с их %2 на себя. - + %1 points from their %2 to %3. %1 указывает с их %2 на %3. - + %1 points from %2's %3 to themselves. %1 указывает с %3 под контролем %2 на себя. - + %1 points from %2's %3 to %4. %1 указывает с %3, контролируемого %2, на %4. - + %1 points from their %2 to their %3. %1 указывает с их %2 на их %3. - + %1 points from their %2 to %3's %4. %1 указывает с их %2 на %4 под контролем %3. - + %1 points from %2's %3 to their own %4. %1 указывает с %3 под контролем %2 на %4, владельцем которого они являются. - + %1 points from %2's %3 to %4's %5. %1 указывает с %3 контролируемого %2 на %5 под контролем %4. - + %1 creates a face down token. - + %1 creates token: %2%3. %1 создает фишку: %2%3. - + %1 has loaded a deck (%2). %1 загрузил колоду (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 загрузил колоду с %2 картами из сайдборда (%3). - + %1 destroys %2. %1 уничтожает %2. - + a card карту - + %1 gives %2 control over %3. %1 передает %2 контроль над %3. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 поместил %2 на поле битвы %3. - + %1 puts %2%3 into their graveyard. %1 кладет %2%3 на своё кладбище. - + %1 exiles %2%3. %1 изгоняет %2%3. - + %1 moves %2%3 to their hand. %1 кладет %2%3 в свою руку. - + %1 puts %2%3 into their library. %1 кладет %2%3 в свою библиотеку. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. %1 кладет %2%3 на верх своей библиотеки. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. %1 поместил %2%3 в сайд. - + %1 plays %2%3. %1 разыгрывает %2%3. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). %1 берет %2 карту.%1 берет %2 карты.%1 берет %2 карт.%1 берет %2 карты. - + %1 is looking at %2. %1 просматривает %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. Игра закрыта. - + The game has started. Игра началась. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 присоединился к игре. - + %1 is now watching the game. %1 вошел как зритель. - + You have been kicked out of the game. Вы были отключены от игры - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. %1 пока не готов начать игру. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 тасует колоду и берет новую руку из %2 карты%1 тасует колоду и берет новую руку из %2 карт%1 тасует колоду и берет новую руку из %2 карт%1 тасует колоду и берет новую руку из %2 карт - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. Вы просматриваете запись игры #%1. - + %1 is ready to start the game. %1 готов начать игру. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 открывает %2 для %3. - + %1 reveals %2. %1 открывает %2. - + %1 randomly reveals %2%3 to %4. %1 случайным образом открывает %2%3 для %4. - + %1 randomly reveals %2%3. %1 случайным образом открывает %2%3. - + %1 peeks at face down card #%2. %1 указывает на перевернутую рубашкой вверх карту #%2. - + %1 peeks at face down card #%2: %3. %1 указывает на перевернутую рубашкой вверх карту #%2: %3. - + %1 reveals %2%3 to %4. %1 открывает %2%3 для %4. - + %1 reveals %2%3. %1 открывает %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads Орел - + Tails Решка - + %1 flipped a coin. It landed as %2. %1 подбросил монету. Выпал(а) %2. - + %1 rolls a %2 with a %3-sided die. %1 выбросил %2 на %3-гранном кубике. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. - + %1 sets annotation of %2 to %3. %1 помечает %2 "%3". - + %1 places %2 "%3" counter(s) on %4 (now %5). %1 разместил %2 "%3" жетон на %4 (теперь %5).%1 разместил %2 "%3" жетонов на %4 (теперь %5).%1 разместил %2 "%3" жетонов на %4 (теперь %5).%1 разместил %2 "%3" жетонов на %4 (теперь %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). %1 удалил %2 "%3" жетон на %4 (теперь %5).%1 удалил %2 "%3" жетона на %4 (теперь %5).%1 удалил %2 "%3" жетонов на %4 (теперь %5).%1 удалил %2 "%3" жетона на %4 (теперь %5). - + %1 sets counter %2 to %3 (%4%5). %1 устанавливает жетон %2 на %3 (%4%5). - + %1 sets %2 to not untap normally. %2 теперь не разворачивается как обычно (%1). - + %1 sets %2 to untap normally. %2 теперь разворачивается как обычно (%1). - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. %1 заблокировал(а) свой сайдборд. - + %1 has unlocked their sideboard. %1 разблокировал(а) свой сайдборд. - + %1 taps their permanents. %1 поворачивает свои перманенты. - + %1 untaps their permanents. %1 разворачивает свои перманенты. - + %1 taps %2. %1 поворачивает %2. - + %1 untaps %2. %1 разворачивает %2. - + %1 shuffles %2. %1 размешал %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. %1 открепляет %2. - + %1 undoes their last draw. %1 отменяет последние взятия. - + %1 undoes their last draw (%2). %1 отменяет последние взятия (%2). @@ -5690,110 +6264,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Добавить сообщение - - + + Message: Сообщение: - + Edit message - + Chat settings Настройки чата - + Custom alert words Пользовательские предупреждения - + Enable chat mentions Включить упоминания в чате - + Enable mention completer Включить автозавершение упоминаний - + In-game message macros Макросы сообщений игрового чата - + How to use in-game message macros Как использовать внутриигровые макросы для сообщений - + Ignore chat room messages sent by unregistered users Не уведомлять о сообщениях в чате от незарегистрированных пользователей - + Ignore private messages sent by unregistered users Не уведомлять о личных сообщениях от незарегистрированных пользователей - - + + Invert text color Инвертировать цвет текста - + Enable desktop notifications for private messages Включить оповещения о личных сообщениях - + Enable desktop notification for mentions Включить оповещения об упоминаниях - + Enable room message history on join Отображать историю сообщений при присоединении к комнате. - - + + (Color is hexadecimal) (Шестнадцатеричный цвет) - + Separate words with a space, alphanumeric characters only Разделять слова пробелом, только для букв и цифр @@ -5910,62 +6484,62 @@ Cockatrice will now reload the card database. Phase - + Unknown Phase - + Untap - + Upkeep - + Draw - + First Main - + Beginning of Combat - + Declare Attackers - + Declare Blockers - + Combat Damage - + End of Combat - + Second Main - + End/Cleanup @@ -6031,7 +6605,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages ru @@ -6040,134 +6614,134 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards Взять карты - + Move top cards to grave - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards Взять карты с низа библиотеки - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters Установить жетоны @@ -6175,17 +6749,17 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters &Жетоны - + S&ay @@ -6193,7 +6767,7 @@ Cockatrice will now reload the card database. PrintingSelector - + Display Navigation Buttons @@ -6201,22 +6775,22 @@ Cockatrice will now reload the card database. PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6232,17 +6806,17 @@ Cockatrice will now reload the card database. PrintingSelectorCardSelectionWidget - + Previous Card in Deck - + Bulk Selection - + Next Card in Deck @@ -6250,28 +6824,28 @@ Cockatrice will now reload the card database. PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -6337,37 +6911,37 @@ Cockatrice will now reload the card database. QMenuBar - + Services Сервисы - + Hide %1 Скрыть %1 - + Hide Others Скрыть остальные - + Show All Показать все - + Preferences... Параметры... - + Quit %1 Выйти %1 - + About %1 О %1 @@ -6375,42 +6949,42 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) База карт Cockatrice (*.xml) - + All files (*.*) Все файлы (*.*) - + Cockatrice replays (*.cor) Записи игр Cockatrice (*.cor) - + Maindeck Основная колода - + Sideboard Сайдборд - + Tokens Фишки - + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? @@ -6418,92 +6992,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -6600,37 +7174,37 @@ Cockatrice will now reload the card database. RoomSelector - + Rooms Комнаты - + Joi&n &Присоединиться - + Room Комната - + Description Пометка - + Permissions Разрешения - + Players Игроки - + Games Игры @@ -6671,27 +7245,27 @@ Cockatrice will now reload the card database. SetsModel - + Enabled Включено - + Set type Установить тип - + Set code Установить код - + Long name Полное название - + Release date Дата релиза @@ -6699,53 +7273,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -6753,12 +7327,12 @@ Cockatrice will now reload the card database. ShortcutTreeView - + Action - + Shortcut @@ -6766,13 +7340,13 @@ Cockatrice will now reload the card database. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -6799,12 +7373,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -6812,27 +7386,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Включить &звуки - + Current sounds theme: Звуковая тема: - + Test system sound engine Протестировать системный звук - + Sound settings Настройки звука - + Master volume Громкости @@ -6840,48 +7414,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -7045,56 +7619,123 @@ Please check your shortcut settings! + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info Информация о карте - + Deck Колода - + Filters Фильтры - + &View Вид - + + Card Database + + + + Printing - - - - + + + + + Visible Видимый - - - - + + + + + Floating Плавающий - + Reset layout Сбросить слой - + Deck: %1 Колода: %1 @@ -7102,61 +7743,61 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + + + + Visible - - - - + + + + Floating - + Reset layout @@ -7164,22 +7805,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7187,134 +7828,134 @@ Please check your shortcut settings! TabDeckStorage - + Local file system Локальная файловая система - + Server deck storage Хранилище колод на сервере - - + + Open in deck editor Открыть колоду в редакторе - + Rename deck or folder - + Upload deck Загрузить колоду в хранилище - + Download deck Загрузить колоду из хранилища + - - - + + New folder Новая папка + - Delete Удалить - + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + Error - + Rename failed - - + + Invalid deck file - + Enter deck name Введите название колоды - + This decklist does not have a name. Please enter a name: У этой колоды нет названия. Пожалуйста, введите имя: - + Unnamed deck Безымянная колода - + Failed to upload deck to server - + Delete local file Удалить файл - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + Name of new folder: Название новой папки: @@ -7327,17 +7968,17 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 @@ -7378,7 +8019,7 @@ Please enter a name: - + EDHRec: @@ -7386,197 +8027,197 @@ Please enter a name: TabGame - - - + + + Replay Запись игры - - + + Game Игра - - + + Player List Список игроков - - + + Card Info Информация о карте - - + + Messages Сообщения - - + + Replay Timeline Полоса прокрутки записи - + &Phases &Фазы - + &Game &Игра - + Next &phase Следующая &фаза - + Next phase with &action - + Next &turn Следующий &ход - + Reverse turn order - + &Remove all local arrows &Удалить все указатели - + Rotate View Cl&ockwise Повернуть вид по часовой стрелке - + Rotate View Co&unterclockwise Повернуть вид против часовой стрелки - + Game &information Информация &об игре - + Un&concede - - - + + + &Concede &Сдаться... - + &Leave game Покинуть и&гру - + C&lose replay З&акрыть повтор - + &Focus Chat &Установить фокус на чат - + &Say: Ска&зать: - + Selected cards - + &View Вид - - + + + - Visible Видимый - - + + + - Floating Плавающий - + Reset layout Сбросить слой - + Concede Сдаться - + Are you sure you want to concede this game? Вы точно хотите сдаться? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game Покинуть игру - + Are you sure you want to leave this game? Вы уверены, что хотите уйти? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. Вы были отключены от игры @@ -7584,7 +8225,7 @@ Please enter a name: TabHome - + Home @@ -7592,158 +8233,158 @@ Please enter a name: TabLog - + Logs Логи - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Время;ИмяОтправителя;IPОтправителя;Сообщение;IDПолучателя;ИмяПолучателя - + Room Logs Логи Комнаты - + Game Logs Логи Игры - + Chat Logs Логи Чата - - + + Error Ошибка - + You must select at least one filter. Вы должны выбрать хотя бы один фильтр - + You have to select a valid number of days to locate. Вы должны указать верное количество дней для размещения. - + Username: Имя пользователя: - + IP Address: IP адрес - + Game Name: Имя игры: - + GameID: ID Игры: - + Message: Сообщение: - + Main Room Основная комната - + Game Room Игровая комната - + Private Chat Приватный чат - + Past X Days: Последние X Дней: - + Today Сегодня - + Last Hour За последний час - + Maximum Results: Максимальный результат: - + At least one filter is required. The more information you put in, the more specific your results will be. Должен быть как минимум один фильтр. Чем больше информации Вы укажете, тем точнее будет результат. - + Get User Logs Получить логи пользователя - + Clear Filters Очистить фильтры - + Filters Фильтры - + Log Locations Размещение логов - + Date Range Интервал дат - + Maximum Results Максимальный результат - - + + Message History История сообщений - + Failed to collect message history information. Не удалось собрать историю сообщений. - + There are no messages for the selected filters. Нет сообщений для указанных фильтров. @@ -7789,180 +8430,180 @@ The more information you put in, the more specific your results will be. TabReplays - + Local file system Локальная файловая система - + Server replay storage Хранилище записей игр на сервере - - + + Watch replay Посмотреть повтор - + Rename - - + + New folder - - + + Delete Удалить - + Open replays folder - + Download replay Скачать запись игры - + Toggle expiration lock Переключить блокировку срока - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file Удалить локальный файл - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay Удалить запись игры @@ -8028,30 +8669,30 @@ The more information you put in, the more specific your results will be.Сервер + - Error Ошибка - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -8059,92 +8700,97 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - - - Home - - - - - &Visual Deck Storage - - - - - Visual Database Display - - - Server + Archidekt - Account + Home - Deck Storage + &Visual Deck Storage - Game Replays + Visual Database Display - Administration + Server + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + Logs - + Are you sure? Вы уверены? - + There are still open games. Are you sure you want to quit? У вас все еще есть активные игры. Уверены, что хотите выйти? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event Неопознанное событие - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8155,38 +8801,38 @@ To update your client, go to Help -> Check for Updates. Чтобы обновить клиент, перейдите в Справка-> Проверка обновлений. - + Idle Timeout Исчерпано время бездействия - + You are about to be logged out due to inactivity. Вы были отключены из-за отсутствия активности. - + Promotion Повышение - + You have been promoted. Please log out and back in for changes to take effect. - + Warned Предупрежден - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Вы получили предупреждение по причине %1. Пожалуйста, воздержитесь от подобных действий, иначе в ваш адрес будут применены дальнейшие меры. При возникновении каких-либо вопросов напишите личное сообщение любому модератору. - + You have received the following message from the server. (custom messages like these could be untranslated) Вы получили сообщение от сервера. @@ -8196,7 +8842,12 @@ Please refrain from engaging in this activity or further actions may be taken ag TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display @@ -8276,7 +8927,7 @@ Please refrain from engaging in this activity or further actions may be taken ag UpdateDownloader - + Could not open the file for reading. Невозможно открыть файл для чтения. @@ -8284,206 +8935,206 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details &Детали пользователя - + Private &chat &Приватный чат - + Show this user's &games Показать &игры этого пользователя - + Add to &buddy list Добавить в &список друзей - + Remove from &buddy list Удалить из &списка друзей - + Add to &ignore list Добавить в &игнор-лист - + Remove from &ignore list Удалить из &игнор-листа - + Kick from &game Выкинуть из &игры - + Warn user Дать предупреждение пользователю - + View user's war&n history Просмотреть &историю предупреждений пользователя - + Ban from &server Забанить на &сервере - + View user's &ban history Просмотреть &историю банов пользователя - + &Promote user to moderator &Повысить пользователя до модератора - + Dem&ote user from moderator &Разжаловать пользователя из модератора - + Promote user to &judge - + Demote user from judge - + View admin notes - - - + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games Игры %1 - - - + + + Ban History История банов - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason ВремяБана;Модератор;ПродолжительностьБана;ПричинаБана;ВидимаяПричина - + User has never been banned. Пользователя никогда не банили. - + Failed to collect ban information. - - - + + + Warning History История предупреждений - + Warning Time;Moderator;User Name;Reason ВремяПредупреждения;Модератор;ИмяПольозователя;Причина - + User has never been warned. Пользователя никогда не предупреждали. - + Failed to collect warning information. - + Failed to get admin notes. - - + + Success Успешно - + Successfully promoted user. Пользователь успешно повышен. - + Successfully demoted user. Пользователь успешно понижен. - + + - Failed Не удалось - + Failed to promote user. Не удалось повысить пользователя. - + Failed to demote user. Не удалось понизить пользователя. - + Copy hash to clipboard - + Remove this user's messages @@ -8665,137 +9316,142 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings Основные настройки интерфейса - + &Double-click cards to play them (instead of single-click) &Двойной клик, чтобы разыграть карту (вместо одинарного) - + &Clicking plays all selected cards (instead of just the clicked card) &Клик для разыгрывания всех выбранных карт (вместо только выбранной) - + &Play all nonlands onto the stack (not the battlefield) by default &Помещать все заклинания в стек при разыгрывании (вместо поля боя) - + + Do not delete &arrows inside of subphases + + + + Close card view window when last card is removed Закрыть окно просмотра карт когда последняя карта удалена из него - + Auto focus search bar when card view window is opened - + Annotate card text on tokens Изменить текст карты на токенах - + Use tear-off menus, allowing right click menus to persist on screen Разрешить закрепление контекстных меню на экране - + Notifications settings - + Enable notifications in taskbar Включить оповещения в панели задач. - + Notify in the taskbar for game events while you are spectating Уведомлять об игровых событиях в панели задач, когда вы наблюдаете за игрой - + Notify in the taskbar when users in your buddy list connect Уведомлять при заходе в игру друга - + Animation settings Настройки анимации - + &Tap/untap animation &Анимировать поворот/разворот карты - + Deck editor/storage settings Редактор колод/Настройки хранилища - + Open deck in new tab by default Открывать колоду в новой вкладке - + Use visual deck storage in game lobby Использовать графическое представление карт в лобби игры - + Use selection animation for Visual Deck Storage Использовать анимацию при выборе карты в графическом хранилище - + When adding a tag in the visual deck storage to a .txt deck: - + do nothing - + ask to convert to .cod - + always convert to .cod всегда конвертировать в .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + Replay settings - + Buffer time for backwards skip via shortcut: Время отката назад при нажатии шортката @@ -8803,22 +9459,22 @@ Please refrain from engaging in this activity or further actions may be taken ag UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 @@ -8826,32 +9482,32 @@ Please refrain from engaging in this activity or further actions may be taken ag UtilityMenu - + Increment all card counters Добавить по одному каждого жетона - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -8859,22 +9515,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match - + Mode: Includes - + Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -8882,21 +9538,49 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8922,28 +9606,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - Load from Deck + Filter by name... (Exact match) - Apply all card names in currently loaded deck as exact match name filters + Load from Deck - Load from Clipboard + Apply all card names in currently loaded deck as exact match name filters + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -9007,50 +9691,115 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - + + Sort by: + + + + + Filter by: + + + + Save and load filters - + Filter by exact card name - + Filter by card sub-type - + Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9058,46 +9807,25 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -9153,7 +9881,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9161,22 +9889,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -9184,17 +9912,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + Visual Deck Storage Settings @@ -9202,43 +9930,43 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? Какое предупреждение Вы бы хотели отправить? - + Redact all messages from this user in all rooms - + &OK &ОК - + &Cancel &Отменить - + Warn user for misconduct Дать предупреждение пользователю за проступок - - + + Error Ошибка - + User name to send a warning to can not be blank, please specify a user to warn. Поле не может быть пустым. Пожалуйста, уточните имя пользователя для выдачи предупреждения. - + Warning to use can not be blank, please select a valid warning to send. Поле предупреждения не может быть пустым. Пожалуйста, выберите корректную причину. @@ -9246,133 +9974,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top Переместить выбранное издание наверх - + Move selected set up Переместить выбранное издание вверх - + Move selected set down Переместить выбранное издание вниз - + Move selected set to the bottom Переместить выбранное издание в конец. - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets Включить все издания - + Disable all sets Отключить все издания - + Enable selected set(s) Включить выбранные издания - + Disable selected set(s) Отключить выбранные издания - + Deck Editor Редактор колод - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + How to use custom card art Как использовать кастомный арт для карты - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -9380,72 +10108,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing Перемешать после просмотра - + pile view просмотр вразнобой @@ -9453,7 +10181,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English Русский (Russian) @@ -9461,12 +10189,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -9474,1005 +10202,1041 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window Главное окно - - + + Deck Editor Редактор колоды - + Game Lobby Вкладка игры - + Card Counters Жетоны на картах - + Player Counters Жетоны игрока - + Power and Toughness Сила и Выносливость - + Game Phases Игровые фазы - + Playing Area Игровое поле - + Move Selected Card - + View Вид - + Move Top Card - + Move Bottom Card - + Gameplay Процесс игры - + Drawing - + Chat Room Чат - + Game Window - + Load Deck from Clipboard - - + + Replays - + Tabs - + Check for Card Updates... - + Connect... - + Disconnect Прервать подключение - + Exit Выход - + Full screen На весь экран - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters - + Clear Selected Filter - + Close Закрыть - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card - + Load Deck... - - + + Load Deck from Clipboard... - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck - + Open Custom Pictures Folder - + Print Deck... - + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... - + Load Remote Deck... - + Set Ready to Start - + Toggle Sideboard Lock - + Add Green Counter Добавить зелёный жетон - + Remove Green Counter Удалить зелёный жетон - + Set Green Counters... Установить зелёные жетоны - + Add Red Counter Добавить красный жетон - + Remove Red Counter Убрать красный жетон - + Set Red Counters... Установить кол-во красных жетонов... - + Add Life Counter Добавить 1 ед. жизни - + Show Status Bar - + Unload Deck - + Force Start - + Add Card Counter (F) Добавить жетон (F) - + Remove Card Counter (F) Удалить жетон (F) - + Set Card Counters (F)... Установить жетоны (F) - + Add Card Counter (E) Добавить жетон (E) - + Remove Card Counter (E) Удалить жетон (E) - + Set Card Counters (E)... Установить жетоны (E)... - + Add Card Counter(D) Добавить жетон(D) - + Remove Card Counter (D) Удалить жетон (D) - + Set Card Counters (D)... Установить жетоны (D)... - + Add Card Counter (C) Добавить жетон (C) - + Remove Card Counter (C) Удалить жетон (C) - + Set Card Counters (C)... Установить жетоны (C) - + Add Card Counter (B) Добавить жетон (B) - + Remove Card Counter (B) Удалить жетон (B) - + Set Card Counters (B)... Установить жетоны (B)... - + Add Card Counter (A) Добавить жетон (A) - + Remove Card Counter (A) Удалить жетон (A) - + Set Card Counters (A)... Установить жетоны (A)... - + Remove Life Counter Убрать 1 ед. жизни - + Set Life Counters... Установить кол-во жизней - + Add White Counter Добавить белый жетон - + Remove White Counter Убрать белый жетон - + Set White Counters... Установить белые жетоны... - + Add Blue Counter Добавить синий жетон - + Remove Blue Counter Убрать синие жетоны - + Set Blue Counters... Установить синие жетоны... - + Add Black Counter Добавить черные жетон - + Remove Black Counter Удалить черные жетон - + Set Black Counters... Установить черные жетоны... - + Add Colorless Counter Добавить бесцветные жетон - + Remove Colorless Counter Удалить бесцветные жетон - + Set Colorless Counters... Установить бесцветные жетоны - + Add Other Counter Добавить другие жетоны - + Remove Other Counter Удалить другие жетоны - + Set Other Counters... Установить другие жетоны... - + Increment all card counters Добавить по одному каждого жетона - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap Шаг разворота - + Upkeep Шаг поддержки - + Draw Шаг взятия карты - + First Main Phase - + Start Combat - + Attack Атака - + Block Блок - + Damage Повреждения - + End Combat - + Second Main Phase - + End Конец хода - + Next Phase - + Next Phase Action - + Next Turn - + Hide Card in Reveal Window - + Tap / Untap Card Повернуть / развернуть карту - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library - - - - + + + + Exile Изгнание - - - - + + + + Graveyard Кладбище - - + + + Hand Рука - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - - Sort Hand - - - - + Library Библиотека - + Sideboard Сайдборд - + Top Cards of Library - + Bottom Cards of Library - + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card Взять карту с низа библиотеки - + Draw Multiple Cards from Bottom... Взять несколько карт с низа библиотеки... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede Сдаться - + Roll Dice... - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan Муллиган - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card Взять карту - + Draw Multiple Cards... Взять несколько карт - + Undo Draw - + Always Reveal Top Card Всегда показывать верхнюю карту - + Always Look At Top Card Всегда смотреть верхнюю карту - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box Убрать фокус с окна ввода - + Focus Chat Установить фокус на чат - + Clear Chat Очистить чат - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs Логи diff --git a/cockatrice/translations/cockatrice_sr.ts b/cockatrice/translations/cockatrice_sr.ts index 1aa21336a..1116a20ae 100644 --- a/cockatrice/translations/cockatrice_sr.ts +++ b/cockatrice/translations/cockatrice_sr.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... Podesi brojač... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Podesi brojač - + New value for counter '%1': Nova vrednost za brojač '%1': @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,79 +36,81 @@ AbstractTabDeckEditor - + Open in new tab Otvori u novoj kartici - + Are you sure? Da li ste sigurni? - + The decklist has been modified. Do you want to save the changes? - - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. + + Add Analytics Panel AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -116,12 +118,12 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard - + Sideboard @@ -129,22 +131,22 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error - + Could not create themes directory at '%1'. - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -155,7 +157,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -166,152 +168,165 @@ Are you sure you would like to disable this feature? - + Confirm Change - + Theme settings Podešavanja teme - + Current theme: Sadašnja tema: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + + Show game filter toolbar above list in room tab + + + + Card rendering - + Display card names on cards having a picture Pokazuj imena karti na kartama koje imaju sliku - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Raspored ruke - + Display hand horizontally (wastes space) Prikazuj ruku horizontalno (troši prostor) - + Enable left justification - + Table grid layout - + Invert vertical coordinate - + Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -333,112 +348,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name zabrani ime igrača - + ban &IP address zabrani IP adresa - + ban client I&D zabranite ID klijenta - + Ban type Tip zabrane - + &permanent ban stalna zabrana - + &temporary ban privremena zabrana - + &Days: Dani: - + &Hours: Sati: - + &Minutes: Minuti: - + Duration of the ban Dužina trajanja zabrane - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Molimo Vas ukucajte razlog zabrane. Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene osobe. - + Please enter the reason for the ban that will be visible to the banned person. Molimo Vas ukucajte razlog zabrane koji će biti vidljiv zabranjenoj osobi. - + Redact all messages from this user in all rooms - + &OK U redu - + &Cancel Poništi - + Ban user from server Zabranite ulaz korisniku u server - + + - - + Error Greška - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. - + You must have a value in the name ban when selecting the name ban checkbox. - + You must have a value in the ip ban when selecting the ip ban checkbox. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. @@ -469,32 +484,32 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CardDatabaseModel - + Name Ime - + Sets Setovi - + Mana cost Cena mane - + Card type Tip karte - + P/T - + Color(s) Boja (boje) @@ -502,96 +517,101 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CardFilter - + AND Logical conjunction operator used in card filter - + OR Logical disjunction operator used in card filter - + AND NOT Negated logical conjunction operator used in card filter - + OR NOT Negated logical disjunction operator used in card filter - + Name - + + Name (Exact) + + + + Type - + Color - + Text - + Set - + Mana Cost - + Mana Value - + Rarity - + Power - + Toughness - + Loyalty - + Format - + Main Type - + Sub Type @@ -599,22 +619,22 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CardInfoFrameWidget - + Image - + Description - + Both - + View transformation @@ -622,22 +642,22 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -650,12 +670,22 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene - + + Set: + + + + + Collector Number: + + + + Related cards: - + Unknown card: @@ -663,124 +693,124 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -788,7 +818,7 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CardSizeWidget - + Card Size @@ -796,133 +826,133 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CardZoneLogic - + their hand nominative - + %1's hand nominative - - - their library - look at zone - - - - - %1's library - look at zone - - - - - of their library - top cards of zone, - - - - - of %1's library - top cards of zone - - their library - reveal zone + look at zone %1's library + look at zone + + + + + of their library + top cards of zone, + + + + + of %1's library + top cards of zone + + + + + their library reveal zone - + + %1's library + reveal zone + + + + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - - - their graveyard - nominative - - - - - %1's graveyard - nominative - - - - - their exile - nominative - - - - - %1's exile - nominative - - + their graveyard + nominative + + + + + %1's graveyard + nominative + + + + + their exile + nominative + + + + + %1's exile + nominative + + + + their sideboard look at zone - + %1's sideboard look at zone - - - their sideboard - nominative - - - - - %1's sideboard - nominative - - - their custom zone '%1' + their sideboard nominative + %1's sideboard + nominative + + + + + their custom zone '%1' + nominative + + + + %1's custom zone '%2' nominative @@ -931,7 +961,7 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CockatriceXml3Parser - + Parse error at line %1 col %2: @@ -939,7 +969,7 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CockatriceXml4Parser - + Parse error at line %1 col %2: @@ -958,6 +988,37 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -969,47 +1030,47 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add to Deck - + Add to Sideboard - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1017,87 +1078,97 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing - + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + + Format: + + + + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1105,17 +1176,17 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1123,114 +1194,114 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1238,7 +1309,7 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1246,194 +1317,227 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorSettingsPage - - + + Update Spoilers - - + + Success - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + Add URL - - + + URL: - - + + Edit URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL - + Remove URL - + Day(s) - + Updating... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count - + Set - + Number Broj - + Provider ID - + Card Karta @@ -1441,12 +1545,12 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1454,17 +1558,17 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1472,7 +1576,7 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -1480,62 +1584,62 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -1548,93 +1652,151 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1652,74 +1814,74 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckViewContainer - + Load deck... Učitaj špil... - + Load remote deck... Učitaj daljinski špil... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start - + Force start - + Sideboard unlocked - + Sideboard locked - - + + Error Greška - + The selected file could not be loaded. Izabrani fajl nije mogao biti učitan. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -1752,143 +1914,143 @@ This will kick all non-ready players from the game. - + Known Hosts - + Delete the currently selected saved server - + Refresh the server list with known public servers - + New Host Novi Domaćin - + Name: - + &Host: Domaćin: - + &Port: - + Player &name: Ime igrača: - + P&assword: Lozinka: - + &Save password Sačuvajte lozinku: - + A&uto connect Automatsko povezivanje - + Automatically connect to the most recent login when Cockatrice opens Automatski se poveži na najskoriju prijavu kada se Cockatrice otvori - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + Forgot password? - + &Connect - + Server Server - + Login Prijava - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning - + You need to name your new connection profile. - + Connect Warning Upozorenje o povezivanju - + The player name can't be empty. Ime igrača ne može biti prazno. @@ -1896,117 +2058,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings Za&pamti podešavanja - + &Description: Opis: - + P&layers: Igrači: - + General - + Game type Tip igre - + &Password: Lozinka: - + Only &buddies can join Samo prijatelji mogu da se pridruže - + Only &registered users can join Samo &registrovani korisnici mogu da se pridruže - + Joining restrictions Ograničenja pridruživanja - + &Spectators can watch Posmatrači mogu da gledaju - + Spectators &need a password to watch Posmatračima &je potrebna lozinka da gledaju - + Spectators can &chat Posmatrači mogu &da ćaskaju - + Spectators can see &hands Posmatrači mogu da &vide ruke - + Create game as spectator - + Spectators Posmatrači - + Starting life total: - + Open decklists in lobby - + + Create game as judge + + + + Game setup options - + &Clear Očisti - + Create game Započni igru - + Game information Informacije o igri - + Error Greška - + Server error. Greška servera. @@ -2014,97 +2181,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: Ime: - + Token Žeton - + C&olor: Boja: - + white belo - + blue plavo - + black crno - + red crveno - + green zeleno - + multicolor višebojno - + colorless bezbojno - + &P/T: M/I: - + &Annotation: Anotacija: - + &Destroy token when it leaves the table Uništi token kada napusti sto - + Create face-down (Only hides name) - + Token data Podaci o tokenima - + Show &all tokens Prikaži sve tokene - + Show tokens from this &deck Prikaži tokene iz ovog špila - + Choose token from list Izaberi token iz liste - + Create token Napravi token @@ -2112,53 +2279,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2166,40 +2333,40 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. Slika nije izabrana. - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. Da promenite svoj avatar, izaberite novu sliku Da izbrišete vaš sadašnji avatar, potvrdite bez biranja nove slike. - + Browse... Pretraži... - + Change avatar Promeni avatar - + Open Image Otvori sliku - + Image Files (*.png *.jpg *.bmp) Fajlovi Slika (*.png *.jpg *.bmp) - + Invalid image chosen. Neispravna slika izabrana. @@ -2207,17 +2374,17 @@ Da izbrišete vaš sadašnji avatar, potvrdite bez biranja nove slike. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2225,38 +2392,38 @@ Da izbrišete vaš sadašnji avatar, potvrdite bez biranja nove slike. DlgEditPassword - + Old password: Stara lozinka: - + New password: Nova lozinka: - + Confirm new password: Potvrdi novu lozinku: - + Change password Promeni lozinku - - + + Error Greška - + Your password is too short. - + The new passwords don't match. Nove lozinke se ne podudaraju @@ -2264,93 +2431,93 @@ Da izbrišete vaš sadašnji avatar, potvrdite bez biranja nove slike. DlgEditTokens - + &Name: Ime: - + C&olor: Boja: - + white belo - + blue plavo - + black crno - + red crveno - + green zeleno - + multicolor višebojno - + colorless bezbojno - + &P/T: M/I: - + &Annotation: Anotacija: - + Token data Podaci o tokenima - - + + Add token Dodaj token - + Remove token Uklonite token - + Edit custom tokens - + Please enter the name of the token: Molim Vas, ukucajte ime tokena: - + Error Greška - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. @@ -2443,7 +2610,8 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games not created by buddy + Hide games not created by buddies + Hide games not created by buddy @@ -2530,52 +2698,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: - + &Port: - + Player &name: - + Email: - + Reset Password Challenge - + Reset Password Challenge Error - + The email address can't be empty. @@ -2583,37 +2751,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: - + &Port: - + Player &name: - + Reset Password Request - + Reset Password Error - + The player name can't be empty. @@ -2621,86 +2789,86 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: - + &Port: - + Player &name: - + Token: - - + + New Password: - + Reset Password - + The player name can't be empty. - - - - + + + + Reset Password Error - + The token can't be empty. - + The new password can't be empty. - + Error - + Your password is too short. - + The passwords do not match. @@ -2716,17 +2884,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard - + Error Greška - + Invalid deck list. @@ -2734,43 +2902,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2784,7 +2952,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck Učitaj špil @@ -2792,37 +2960,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -2830,91 +2998,91 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: Domaćin: - + &Port: - + Player &name: Ime igrača: - + P&assword: Lozinka: - + Password (again): Lozinka (ponovo): - + Email: I-mejl: - + Email (again): I-mejl (ponovo): - + Country: Država: - + Undefined Neodređeno - + Real name: Pravo ime: - + Register to server Registruj se na server - - - - + + + + Registration Warning Upozorenje o registraciji - + Your password is too short. - + Your passwords do not match, please try again. Vaše lozinke se ne podudaraju, molim Vas, pokušajte ponovo - + Your email addresses do not match, please try again. Vaše i-mejl adrese se ne podudaraju, molimo Vas, pokušajte ponovo. - + The player name can't be empty. Ime igrača ne može biti prazno. @@ -2940,40 +3108,55 @@ Your email will be used to verify your account. DlgSelectSetForCards - + Unmodified Cards: - + Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database Nepoznata Greška učitavanja baze podataka karte - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -2984,7 +3167,7 @@ Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -2995,7 +3178,7 @@ Would you like to change your database location setting? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3004,14 +3187,14 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3020,7 +3203,7 @@ Would you like to change your database location setting? Da li bi ste želeli da promenite vaše podešavanje lokacije baze podataka? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3029,59 +3212,59 @@ Would you like to change your database location setting? - - - + + + Error Greška - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings Podešavanja - + General Opšto - + Appearance Izgled - + User Interface Korisnički interfejs - + Card Sources - + Chat Ćaskanje - + Sound Zvuk - + Shortcuts Prečice @@ -3089,39 +3272,39 @@ Would you like to change your database location setting? DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3129,17 +3312,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next - + Previous - + Tip of the Day @@ -3147,164 +3330,164 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel - + Reinstall - + Cancel Download - + Open Download Page Otvori Stranicu Za Preuzimanje - + Check for Client Updates - - - - + + + + Error Greška - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - + Downloading update: %1 - + Checking for updates... Provera za ažuriranja... - + Finished checking for updates - + No Update Available - + Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. - + Current version - + Selected release channel + + + + Update Available + + + + + + A new version of Cockatrice is available! + + + + + + New version + + - Update Available - - - - - - A new version of Cockatrice is available! - - - - - - New version - - - - - Released - - + + Changelog - + Do you want to update now? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + + Update Error Greška Ažuriranja - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... Instalacija... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -3312,21 +3495,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing - + Copy to clipboard - + Debug Log + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3396,33 +3711,39 @@ You may have to manually download the new version. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3438,22 +3759,22 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. @@ -3461,22 +3782,22 @@ You may have to manually download the new version. GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -3484,226 +3805,279 @@ You may have to manually download the new version. GameSelector - - - - - - + + + + + + Error Greška - + Please join the appropriate room first. - + Wrong password. Pogrešna lozinka. - + Spectators are not allowed in this game. Posmatrači nisu dozvoljeni u ovoj igri. - + The game is already full. Igra je već puna. - + The game does not exist any more. Ova igra više ne postoji. - + This game is only open to registered users. Ova igra je samo otvorena za registrovane korisnike. - + This game is only open to its creator's buddies. Ova igra je samo otvorena za prijatelje njenog kreatora. - + You are being ignored by the creator of this game. Vi ste ignorisani od strane kreatore ove igre. - + Join Game - + Spectate Game - + Game Information - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game Pridruži se igri - + Password: Lozinka: - + Please join the respective room first. - + &Filter games Filtriraj igre - + C&lear filter Očisti filter - + C&reate Kreiraj - + &Join Pridruži se - + + Join as judge + + + + J&oin as spectator Pridruži se kao posmatrač - + + Join as judge spectator + + + + Games shown: %1 / %2 - + Games Igre + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password lozinka - + buddies only samo prijatelji - + reg. users only samo reg. korisnici - + open decklists - - + + can chat može da ćaska - + see hands vidi ruke - + can see hands može da vidi ruke - + not allowed nije dozvoljeno - + Room Soba - + Age Starost - + Description Opis - + Creator Kreator - + Type Tip - + Restrictions Ograničenja - + Players Igrači - + Spectators Posmatrači @@ -3711,143 +4085,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path Izaberi put - + Personal settings Lična podešavanja - + Language: Jezik: - + Paths (editing disabled in portable mode) - + Paths Putevi - + How to help with translations - + Decks directory: - + Filters directory: - + Replays directory: - + Pictures directory: - + Card database: Baza podataka karata: - + Custom database directory: - + Token database: Baza podataka tokena: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup - + Last update check on %1 (%2 days ago) @@ -3855,47 +4229,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -3903,111 +4277,141 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - - &Sort hand + + Sort hand by... - - Take &mulligan + + Name - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - Reveal r&andom card to... + + + All players - - - &All players + + Reveal r&andom card to... HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + + Browse Archidekt + + + + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4209,743 +4613,743 @@ You may have to manually download the new version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator Zabranjen od strane moderatora - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. Nevažeće korisničko ime. - + You have been logged out due to logging in at another location. - + Connection closed Povezanost prekinuta - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - - + + Success Uspeh - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Number of players - + Please enter the number of players. - - + + Player %1 - + Load replay - + About Cockatrice - + Version - + Cockatrice Webpage - + Project Manager: - + Past Project Managers: - + Developers: - + Our Developers - + Help Develop! - + Translators: - + Our Translators - + Help Translate! - + Support: - + Report an Issue - + Troubleshooting - + F.A.Q. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error - + Server timeout - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - + + + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 + - Requesting forgotten password to %1 as %2... - + &Connect... - + &Disconnect - + Start &local game... - + &Watch replay... - + &Full screen - + &Register to server... - + &Restore password... - + &Settings... - + &Exit - + A&ctions - + &Cockatrice - + C&ard Database - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + Reload card database - + Tabs - + &Help - + &About Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information - + A card database update is already running. - + Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -4953,672 +5357,842 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play - + from their graveyard - + from exile - + from their hand - + the top card of %1's library - + the top card of their library - + from the top of %1's library - + from the top of their library - + the bottom card of %1's library - + the bottom card of their library - + from the bottom of %1's library - + from the bottom of their library - + from %1's library - + from their library - + from sideboard - + from the stack - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. - + a card - + %1 gives %2 control over %3. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. - + %1 puts %2%3 into their graveyard. - + %1 exiles %2%3. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. - + %1 plays %2%3. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. - + The game has started. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. - + %1 is now watching the game. - + You have been kicked out of the game. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. - + %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -5626,110 +6200,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message - - + + Message: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -5846,62 +6420,62 @@ Cockatrice will now reload the card database. Phase - + Unknown Phase - + Untap - + Upkeep - + Draw - + First Main - + Beginning of Combat - + Declare Attackers - + Declare Blockers - + Combat Damage - + End of Combat - + Second Main - + End/Cleanup @@ -5967,7 +6541,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -5976,134 +6550,134 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - + Move top cards to grave - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6111,17 +6685,17 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters - + S&ay @@ -6129,7 +6703,7 @@ Cockatrice will now reload the card database. PrintingSelector - + Display Navigation Buttons @@ -6137,22 +6711,22 @@ Cockatrice will now reload the card database. PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6168,17 +6742,17 @@ Cockatrice will now reload the card database. PrintingSelectorCardSelectionWidget - + Previous Card in Deck - + Bulk Selection - + Next Card in Deck @@ -6186,28 +6760,28 @@ Cockatrice will now reload the card database. PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -6273,37 +6847,37 @@ Cockatrice will now reload the card database. QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -6311,42 +6885,42 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - + All files (*.*) - + Cockatrice replays (*.cor) - + Maindeck - + Sideboard - + Tokens - + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? @@ -6354,92 +6928,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -6536,37 +7110,37 @@ Cockatrice will now reload the card database. RoomSelector - + Rooms - + Joi&n - + Room - + Description - + Permissions - + Players - + Games @@ -6607,27 +7181,27 @@ Cockatrice will now reload the card database. SetsModel - + Enabled - + Set type - + Set code - + Long name - + Release date @@ -6635,53 +7209,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -6689,12 +7263,12 @@ Cockatrice will now reload the card database. ShortcutTreeView - + Action - + Shortcut @@ -6702,13 +7276,13 @@ Cockatrice will now reload the card database. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -6735,12 +7309,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -6748,27 +7322,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -6776,48 +7350,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -6981,56 +7555,123 @@ Please check your shortcut settings! + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info - + Deck - + Filters - + &View - + + Card Database + + + + Printing - - - - + + + + + Visible - - - - + + + + + Floating - + Reset layout - + Deck: %1 @@ -7038,61 +7679,61 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + + + + Visible - - - - + + + + Floating - + Reset layout @@ -7100,22 +7741,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7123,133 +7764,133 @@ Please check your shortcut settings! TabDeckStorage - + Local file system - + Server deck storage - - + + Open in deck editor - + Rename deck or folder - + Upload deck - + Download deck + - - - + + New folder + - Delete - + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + Error - + Rename failed - - + + Invalid deck file - + Enter deck name - + This decklist does not have a name. Please enter a name: - + Unnamed deck - + Failed to upload deck to server - + Delete local file - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + Name of new folder: @@ -7262,17 +7903,17 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 @@ -7313,7 +7954,7 @@ Please enter a name: - + EDHRec: @@ -7321,197 +7962,197 @@ Please enter a name: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + Un&concede - - - + + + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + Selected cards - + &View - - + + + - Visible - - + + + - Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game - + Are you sure you want to leave this game? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. @@ -7519,7 +8160,7 @@ Please enter a name: TabHome - + Home @@ -7527,157 +8168,157 @@ Please enter a name: TabLog - + Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -7723,180 +8364,180 @@ The more information you put in, the more specific your results will be. TabReplays - + Local file system - + Server replay storage - - + + Watch replay - + Rename - - + + New folder - - + + Delete - + Open replays folder - + Download replay - + Toggle expiration lock - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay @@ -7962,30 +8603,30 @@ The more information you put in, the more specific your results will be. + - Error - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -7993,92 +8634,97 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - - - Home - - - - - &Visual Deck Storage - - - - - Visual Database Display - - - Server + Archidekt - Account + Home - Deck Storage + &Visual Deck Storage - Game Replays + Visual Database Display - Administration + Server + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + Logs - + Are you sure? - + There are still open games. Are you sure you want to quit? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8086,38 +8732,38 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -8126,7 +8772,12 @@ Please refrain from engaging in this activity or further actions may be taken ag TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display @@ -8206,7 +8857,7 @@ Please refrain from engaging in this activity or further actions may be taken ag UpdateDownloader - + Could not open the file for reading. @@ -8214,206 +8865,206 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details - + Private &chat - + Show this user's &games - + Add to &buddy list - + Remove from &buddy list - + Add to &ignore list - + Remove from &ignore list - + Kick from &game - + Warn user - + View user's war&n history - + Ban from &server - + View user's &ban history - + &Promote user to moderator - + Dem&ote user from moderator - + Promote user to &judge - + Demote user from judge - + View admin notes - - - + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - + Failed to get admin notes. - - + + Success - + Successfully promoted user. - + Successfully demoted user. - + + - Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -8595,137 +9246,142 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default - - - Close card view window when last card is removed - - - - - Auto focus search bar when card view window is opened - - - - - Annotate card text on tokens - - - - - Use tear-off menus, allowing right click menus to persist on screen - - - - - Notifications settings - - - - - Enable notifications in taskbar - - - - - Notify in the taskbar for game events while you are spectating - - - - - Notify in the taskbar when users in your buddy list connect - - - - - Animation settings - - - - - &Tap/untap animation - - - Deck editor/storage settings + Do not delete &arrows inside of subphases - Open deck in new tab by default + Close card view window when last card is removed - Use visual deck storage in game lobby + Auto focus search bar when card view window is opened - Use selection animation for Visual Deck Storage + Annotate card text on tokens + + + + + Use tear-off menus, allowing right click menus to persist on screen - When adding a tag in the visual deck storage to a .txt deck: + Notifications settings + + + + + Enable notifications in taskbar - do nothing + Notify in the taskbar for game events while you are spectating + + + + + Notify in the taskbar when users in your buddy list connect - ask to convert to .cod + Animation settings + + + + + &Tap/untap animation - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -8733,22 +9389,22 @@ Please refrain from engaging in this activity or further actions may be taken ag UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 @@ -8756,32 +9412,32 @@ Please refrain from engaging in this activity or further actions may be taken ag UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -8789,22 +9445,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match - + Mode: Includes - + Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -8812,21 +9468,49 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8852,28 +9536,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - Load from Deck + Filter by name... (Exact match) - Apply all card names in currently loaded deck as exact match name filters + Load from Deck - Load from Clipboard + Apply all card names in currently loaded deck as exact match name filters + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -8937,50 +9621,115 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - + + Sort by: + + + + + Filter by: + + + + Save and load filters - + Filter by exact card name - + Filter by card sub-type - + Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -8988,46 +9737,25 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -9083,7 +9811,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9091,22 +9819,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -9114,17 +9842,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + Visual Deck Storage Settings @@ -9132,43 +9860,43 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK U redu - + &Cancel - + Warn user for misconduct - - + + Error - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. @@ -9176,133 +9904,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -9310,72 +10038,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing - + pile view @@ -9383,7 +10111,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English Srpski (Serbian) @@ -9391,12 +10119,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -9404,1005 +10132,1041 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window - - + + Deck Editor - + Game Lobby - + Card Counters - + Player Counters - + Power and Toughness Moć i Izdržljivost - + Game Phases Faze Igre - + Playing Area - + Move Selected Card - + View Pogled - + Move Top Card - + Move Bottom Card - + Gameplay - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - - + + Replays - + Tabs - + Check for Card Updates... - + Connect... - + Disconnect - + Exit Izađite - + Full screen Ceo ekran - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters - + Clear Selected Filter - + Close Zatvorite - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card - + Load Deck... - - + + Load Deck from Clipboard... - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck - + Open Custom Pictures Folder - + Print Deck... - + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... - + Load Remote Deck... - + Set Ready to Start - + Toggle Sideboard Lock - + Add Green Counter - + Remove Green Counter - + Set Green Counters... - + Add Red Counter - + Remove Red Counter - + Set Red Counters... - + Add Life Counter - + Show Status Bar - + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Increment all card counters - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap - + Upkeep - + Draw Izvlačenje - + First Main Phase - + Start Combat - + Attack Napad - + Block - + Damage - + End Combat - + Second Main Phase - + End Kraj - + Next Phase - + Next Phase Action - + Next Turn - + Hide Card in Reveal Window - + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library - - - - + + + + Exile Izgnanstvo - - - - + + + + Graveyard Groblje - - + + + Hand Ruka - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - - Sort Hand - - - - + Library Špil - + Sideboard - + Top Cards of Library - + Bottom Cards of Library - + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede - + Roll Dice... - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_sv.ts b/cockatrice/translations/cockatrice_sv.ts index 6464f1edb..227983d31 100644 --- a/cockatrice/translations/cockatrice_sv.ts +++ b/cockatrice/translations/cockatrice_sv.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Placera polett... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter - + New value for counter '%1': @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,79 +36,81 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. + + Add Analytics Panel AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -116,12 +118,12 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard - + Sideboard @@ -129,22 +131,22 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error - + Could not create themes directory at '%1'. - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -155,7 +157,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -166,152 +168,165 @@ Are you sure you would like to disable this feature? - + Confirm Change - + Theme settings Temainställningar - + Current theme: Aktuellt tema: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + + Show game filter toolbar above list in room tab + + + + Card rendering Kortrendering - + Display card names on cards having a picture Visa kortnamn på kort som har bilder - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Skala kort på musen över - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Handlayout - + Display hand horizontally (wastes space) Visa hand horisontellt (slösar plats) - + Enable left justification Aktivera vänsterjustering - + Table grid layout Bordets rutnätlayout - + Invert vertical coordinate Invertera vertikal koordinat - + Minimum player count for multi-column layout: Minst antal spelare för multi-kolumnlayout: - + Maximum font size for information displayed on cards: Maximal teckenstorlek för information som visas på kort: + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -333,112 +348,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name bannlys användar&namn - + ban &IP address bannlys &IP address - + ban client I&D förbjuda klienten I & D - + Ban type Typ av bannlysning - + &permanent ban &permanent bannlysning - + &temporary ban &temporär bannlysning - + &Days: &Dagar: - + &Hours: &Timmar: - + &Minutes: &Minuter: - + Duration of the ban Bannlysningens längd - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Vänligen ange anledningen till bannlysningen. Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. - + Please enter the reason for the ban that will be visible to the banned person. Vänligen ange en anledning för bannlysningen som kommer att visas för den bannlysta personen. - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Avbryt - + Ban user from server Bannlys användaren från servern - + + - - + Error Fel - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Du måste välja en namnbaserad, IP-baserad, clientId-baserad eller en kombination av de tre för att lägga ett förbud. - + You must have a value in the name ban when selecting the name ban checkbox. Du måste ha ett värde i namnförbudet när du markerar kryssrutan för namnförbud. - + You must have a value in the ip ban when selecting the ip ban checkbox. Du måste ha ett värde i ip-förbudet när du markerar kryssrutan ip-banan. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Du måste ha ett värde i clientidförbudet när du markerar kryssrutan för klientidförbud. @@ -469,32 +484,32 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardDatabaseModel - + Name Namn - + Sets Utgåvor - + Mana cost Manakostnad - + Card type Korttyp - + P/T P/T - + Color(s) Färg(er) @@ -502,96 +517,101 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardFilter - + AND Logical conjunction operator used in card filter OCH - + OR Logical disjunction operator used in card filter ELLER - + AND NOT Negated logical conjunction operator used in card filter OCH INTE - + OR NOT Negated logical disjunction operator used in card filter ELLER INTE - + Name Namn - + + Name (Exact) + + + + Type Typ - + Color Färg - + Text Text - + Set - + Mana Cost Manakostnad - + Mana Value - + Rarity Raritet - + Power Styrka - + Toughness - + Loyalty Lojalitet - + Format Format - + Main Type - + Sub Type @@ -599,22 +619,22 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardInfoFrameWidget - + Image - + Description - + Both - + View transformation @@ -622,22 +642,22 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -650,12 +670,22 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. - + + Set: + + + + + Collector Number: + + + + Related cards: - + Unknown card: @@ -663,124 +693,124 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -788,7 +818,7 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardSizeWidget - + Card Size @@ -796,133 +826,133 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardZoneLogic - + their hand nominative - + %1's hand nominative - - - their library - look at zone - - - - - %1's library - look at zone - - - - - of their library - top cards of zone, - - - - - of %1's library - top cards of zone - - their library - reveal zone + look at zone %1's library + look at zone + + + + + of their library + top cards of zone, + + + + + of %1's library + top cards of zone + + + + + their library reveal zone - + + %1's library + reveal zone + + + + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - - - their graveyard - nominative - - - - - %1's graveyard - nominative - - - - - their exile - nominative - - - - - %1's exile - nominative - - + their graveyard + nominative + + + + + %1's graveyard + nominative + + + + + their exile + nominative + + + + + %1's exile + nominative + + + + their sideboard look at zone - + %1's sideboard look at zone - - - their sideboard - nominative - - - - - %1's sideboard - nominative - - - their custom zone '%1' + their sideboard nominative + %1's sideboard + nominative + + + + + their custom zone '%1' + nominative + + + + %1's custom zone '%2' nominative @@ -931,7 +961,7 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CockatriceXml3Parser - + Parse error at line %1 col %2: @@ -939,7 +969,7 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CockatriceXml4Parser - + Parse error at line %1 col %2: @@ -958,6 +988,37 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -969,47 +1030,47 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add to Deck - + Add to Sideboard - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1017,87 +1078,97 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing - + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + + Format: + + + + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1105,17 +1176,17 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1123,114 +1194,114 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1238,7 +1309,7 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1246,194 +1317,227 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckEditorSettingsPage - - + + Update Spoilers Uppdatera Spoilers - - + + Success - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error Fel - + One or more downloaded card pictures could not be cleared. - + Add URL Lägg till webbadress - - + + URL: - - + + Edit URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL - + Remove URL - + Day(s) - + Updating... Uppdaterar ... - + Choose path Välj sökväg - + URL Download Priority - + Spoilers Spoilers - + Download Spoilers Automatically Ladda ner Spoilers automatiskt - + Spoiler Location: Spoiler Plats: - + Last Change Senaste ändring - + Spoilers download automatically on launch Spoilers laddas ner automatiskt vid lanseringen - + Press the button to manually update without relaunching Tryck på knappen för att manuellt uppdatera utan omstart - + Do not close settings until manual update is complete - + Download card pictures on the fly Ladda enkelt ned bilder på kort - + How to add a custom URL Hur man lägger till en valfri webbadress - + Delete Downloaded Images Ta bort nedladdade bilder - + Reset Download URLs Återställ nedladdnings-webbadresser - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count - + Set - + Number Nummer - + Provider ID - + Card Kort @@ -1441,12 +1545,12 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1454,17 +1558,17 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1472,7 +1576,7 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -1480,62 +1584,62 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -1548,93 +1652,151 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1652,74 +1814,74 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckViewContainer - + Load deck... Ladda lek... - + Load remote deck... Ladda remote lek... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start - + Force start - + Sideboard unlocked - + Sideboard locked - - + + Error Fel - + The selected file could not be loaded. Den valda filen kunde ej laddas. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -1752,143 +1914,143 @@ This will kick all non-ready players from the game. Hämtar ... - + Known Hosts Kända värdar - + Delete the currently selected saved server - + Refresh the server list with known public servers Uppdatera servern listan med kända offentliga servrar - + New Host Ny värd - + Name: Namn: - + &Host: &Värd: - + &Port: &Port: - + Player &name: Spelar&namn: - + P&assword: &Lösenord: - + &Save password &Spara lösenord - + A&uto connect Anslut automatiskt - + Automatically connect to the most recent login when Cockatrice opens Koppla automatiskt till den senaste inloggningen när Cockatrice öppnas - + If you have any trouble connecting or registering then contact the server staff for help! Om du har problem med att ansluta eller registrera kontaktar du serverns personal för hjälp! - - + + Webpage Webbsida - + Reset Password - + Forgot password? - + &Connect &Ansluta - + Server Server - + Login Logga in - + Server Contact Serverkontakt - + Connect to Server Anslut till servern - + Server URL Server webbadress - + Communication Port - + Unique Server Name Unikt servernamn - + Connection Warning Anslutningsvarning - + You need to name your new connection profile. Du måste ange din nya anslutningsprofil. - + Connect Warning Anslutningsvarning - + The player name can't be empty. Fältet för spelarnamn kan inte lämnas tomt. @@ -1896,117 +2058,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings Kom ihåg inställningar - + &Description: &Beskrivning: - + P&layers: &Spelare: - + General Allmän - + Game type Speltyp - + &Password: &Lösenord: - + Only &buddies can join Endast &vänner kan ansluta - + Only &registered users can join Endast &registerade användare kan ansluta - + Joining restrictions Anslutingsbegränsningar - + &Spectators can watch Åskådare tillåtna - + Spectators &need a password to watch Åskådare behöver lösenord - + Spectators can &chat Åskådare kan &chatta - + Spectators can see &hands Åskådare kan se spelares händer - + Create game as spectator - + Spectators Åskådare - + Starting life total: - + Open decklists in lobby - + + Create game as judge + + + + Game setup options - + &Clear Tydlig - + Create game Skapa spel - + Game information Spelinformation - + Error Fel - + Server error. Serverfel. @@ -2014,97 +2181,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Namn: - + Token Jetong - + C&olor: &Färg: - + white vit - + blue blå - + black svart - + red röd - + green grön - + multicolor multifärgad - + colorless färglös - + &P/T: &P/T: - + &Annotation: &Annotering: - + &Destroy token when it leaves the table &Förstör jetong när den lämnar bordet - + Create face-down (Only hides name) - + Token data Jetongdata - + Show &all tokens Visa alla &jetonger - + Show tokens from this &deck Visa jetonger från denna &lek - + Choose token from list Välj jetong från lista - + Create token Skapa jetong @@ -2112,53 +2279,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2166,40 +2333,40 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. Ingen bild vald. - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. För att ändra din avatar, välj en ny bild. För att radera din nuvarande avatar, klicka "bekräfta" utan att välja en ny bild. - + Browse... Leta... - + Change avatar Ändra avatar - + Open Image Öppna bild - + Image Files (*.png *.jpg *.bmp) Bildfiler (*.png *.jpg *.bmp) - + Invalid image chosen. Ogiltigt val av bild. @@ -2207,17 +2374,17 @@ För att radera din nuvarande avatar, klicka "bekräfta" utan att väl DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2225,38 +2392,38 @@ För att radera din nuvarande avatar, klicka "bekräfta" utan att väl DlgEditPassword - + Old password: Ditt gamla lösenord: - + New password: Nytt lösenord: - + Confirm new password: Bekräfta ditt nya lösenord: - + Change password Ändra lösenord - - + + Error Fel - + Your password is too short. - + The new passwords don't match. Dina nya lösenord matchar inte. @@ -2264,93 +2431,93 @@ För att radera din nuvarande avatar, klicka "bekräfta" utan att väl DlgEditTokens - + &Name: &Namn: - + C&olor: &Färg: - + white vit - + blue blå - + black svart - + red röd - + green grön - + multicolor multifärgad - + colorless färglös - + &P/T: &P/T: - + &Annotation: &Annotering: - + Token data Jetongdata - - + + Add token Lägg till jetong - + Remove token Ta bort jetong - + Edit custom tokens - + Please enter the name of the token: Vänligen ange namnet på jetongen: - + Error Fel - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Det valda namnet står i konflikt med ett befintligt kort eller token. @@ -2444,7 +2611,8 @@ Se till att du aktiverar "Token" i dialogrutan "Hantera uppsättn - Hide games not created by buddy + Hide games not created by buddies + Hide games not created by buddy @@ -2531,52 +2699,52 @@ Se till att du aktiverar "Token" i dialogrutan "Hantera uppsättn DlgForgotPasswordChallenge - + Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: &Värd: - + &Port: &Hamn: - + Player &name: - + Email: E-post: - + Reset Password Challenge - + Reset Password Challenge Error - + The email address can't be empty. E-postadressen kan inte vara tom. @@ -2584,37 +2752,37 @@ Se till att du aktiverar "Token" i dialogrutan "Hantera uppsättn DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: &Värd: - + &Port: &Hamn: - + Player &name: - + Reset Password Request - + Reset Password Error - + The player name can't be empty. Spelarens namn kan inte vara tomt. @@ -2622,86 +2790,86 @@ Se till att du aktiverar "Token" i dialogrutan "Hantera uppsättn DlgForgotPasswordReset - + Reset Password Warning - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: &Värd: - + &Port: &Hamn: - + Player &name: - + Token: Tecken: - - + + New Password: Nytt lösenord: - + Reset Password - + The player name can't be empty. Spelarens namn kan inte vara tomt. - - - - + + + + Reset Password Error - + The token can't be empty. Token kan inte vara tomt. - + The new password can't be empty. Det nya lösenordet kan inte vara tomt. - + Error - + Your password is too short. - + The passwords do not match. Lösenorden stämmer inte överens. @@ -2717,17 +2885,17 @@ Se till att du aktiverar "Token" i dialogrutan "Hantera uppsättn DlgLoadDeckFromClipboard - + Load deck from clipboard Ladda lek från urklipp - + Error Fel - + Invalid deck list. Ogiltig leklista. @@ -2735,43 +2903,43 @@ Se till att du aktiverar "Token" i dialogrutan "Hantera uppsättn DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2785,7 +2953,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck Ladda lek @@ -2793,37 +2961,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -2831,91 +2999,91 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: Värd: - + &Port: Port: - + Player &name: Spelarnamn: - + P&assword: Lösenord: - + Password (again): Lösenord (igen): - + Email: E-post: - + Email (again): E-post (igen): - + Country: Land: - + Undefined Odefinierad - + Real name: Riktiga namn: - + Register to server Registrera på server - - - - + + + + Registration Warning Registreringsvarning - + Your password is too short. - + Your passwords do not match, please try again. Dina lösenord matchar inte, var god försök igen. - + Your email addresses do not match, please try again. Dina e-postadresser matchar inte, var god försök igen. - + The player name can't be empty. Fältet för spelarnamn kan inte lämnas tomt. @@ -2941,40 +3109,55 @@ Your email will be used to verify your account. DlgSelectSetForCards - + Unmodified Cards: - + Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database Ett okänt fel har inträffat vid laddande av kortdatabasen - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -2991,7 +3174,7 @@ Möjligen kan du behöva starta om oracle för att uppdatera din kortdatabas. Vill du ändra dina databaslokaliseringsinställningar? - + Your card database version is too old. This can cause problems loading card information or images @@ -3008,7 +3191,7 @@ Detta kan oftast fixas genom att starta om oracle för att uppdatera din kortdat Vill du ändra dina kortdatabaslokaliseringsinställningar? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3017,7 +3200,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3026,7 +3209,7 @@ Would you like to change your database location setting? Vill du ändra dina kortdatabaslokaliseringsinställningar? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3035,7 +3218,7 @@ Would you like to change your database location setting? Vill du ändra dina kortdatabaslokaliseringsinställningar? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3044,59 +3227,59 @@ Would you like to change your database location setting? - - - + + + Error Fel - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Sökvägen till din lekkatalog är ogiltig. Vill du gå tillbaka och ange den korrekta sökvägen? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Sökvägen till din kortbildsdatabas är ogiltig. Vill du gå tillbaka och ange den korrekta sökvägen? - + Settings Inställningar - + General Allmänt - + Appearance Utseende - + User Interface Gränssnitt - + Card Sources - + Chat Chatt - + Sound Ljud - + Shortcuts Genvägar @@ -3104,39 +3287,39 @@ Would you like to change your database location setting? DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3144,17 +3327,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next Nästa - + Previous Tidigare - + Tip of the Day Dagens Tips @@ -3162,165 +3345,165 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel Nuvarande släppkanal - + Reinstall Installera - + Cancel Download Avbryt Ladda ner - + Open Download Page Öppna nedladdningssidan - + Check for Client Updates - - - - + + + + Error Fel - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice byggdes inte med SSL-stöd, därför kan du inte ladda ner uppdateringar automatiskt! Besök sidan för nedladdning för att uppdatera manuellt. - + Downloading update: %1 - + Checking for updates... Letar efter nya uppdateringar... - + Finished checking for updates Slutfört efter uppdateringar - + No Update Available Ingen uppdatering tillgänglig - + Cockatrice is up to date! Cockatris är aktuell! - + You are already running the latest version available in the chosen release channel. Du kör redan den senaste versionen i den valda utgåvan. - + Current version Aktuell version - + Selected release channel Vald utgivningskanal - - + + Update Available Uppdatering tillgänglig - - + + A new version of Cockatrice is available! En ny version av Cockatrice är tillgänglig! - - + + New version Ny version - - + + Released Släppte - - + + Changelog Ändringslogg - + Do you want to update now? Vill du uppdatera nu? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + + Update Error Uppdateringsfel - + An error occurred while checking for updates: Ett fel uppstod när du letade efter uppdateringar: - + An error occurred while downloading an update: Ett fel uppstod när du hämtade en uppdatering: - + Installing... Installerar... - + Cockatrice is unable to open the installer. Cockatrice kan inte öppna installatören. - + Try to update manually by closing Cockatrice and running the installer. Försök att uppdatera manuellt genom att stänga Cockatrice och köra installationsprogrammet. - + Download location Hämta plats @@ -3328,21 +3511,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing Tydlig logg vid stängning - + Copy to clipboard - + Debug Log Felsökningslogg + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3412,33 +3727,39 @@ You may have to manually download the new version. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3454,22 +3775,22 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. @@ -3477,22 +3798,22 @@ You may have to manually download the new version. GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -3500,226 +3821,279 @@ You may have to manually download the new version. GameSelector - - - - - - + + + + + + Error Fel - + Please join the appropriate room first. Vänligen anslut till det lämpliga rummet först. - + Wrong password. Fel lösenord. - + Spectators are not allowed in this game. Åskådare är ej tillåtna i detta spelet. - + The game is already full. Spelet är redan fullt. - + The game does not exist any more. Det här spelet finns inte längre. - + This game is only open to registered users. Det här spelet är bara öppet för registrerade användare. - + This game is only open to its creator's buddies. Det här spelet är bara öppet för skaparens vänner. - + You are being ignored by the creator of this game. Spelets skapare ignorerar dig. - + Join Game - + Spectate Game - + Game Information - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game Anslut till spel - + Password: Lösenord: - + Please join the respective room first. Vänligen anslut till respektive rum först. - + &Filter games Specificera speltyp - + C&lear filter Rensa filter - + C&reate Skapa - + &Join Anslut - + + Join as judge + + + + J&oin as spectator Anslut som &åskådare - + + Join as judge spectator + + + + Games shown: %1 / %2 - + Games Spel + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password lösenord - + buddies only endast vänner - + reg. users only endast reg. användare - + open decklists - - + + can chat kan chatta - + see hands visa händer - + can see hands Tillåts se händer - + not allowed ej tillåtna - + Room Rum - + Age Ålder - + Description Beskrivning - + Creator Skapare - + Type Typ - + Restrictions Begränsningar - + Players Spelare - + Spectators Åskådare @@ -3727,143 +4101,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path Välj sökväg - + Personal settings Personliga inställningar - + Language: Språk: - + Paths (editing disabled in portable mode) Sökvägar (redigering inaktiverad i bärbart läge) - + Paths Sökvägar - + How to help with translations - + Decks directory: Lekkatalog: - + Filters directory: - + Replays directory: Repriskatalog: - + Pictures directory: Bildkatalog: - + Card database: Kortdatabas: - + Custom database directory: - + Token database: Token-databas: - + Update channel Uppdatera kanal - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client Meddela om en funktion som stöds av servern saknas i min klient - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup Visa tips vid uppstart - + Last update check on %1 (%2 days ago) @@ -3871,47 +4245,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -3919,111 +4293,141 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - - &Sort hand + + Sort hand by... - - Take &mulligan + + Name - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - Reveal r&andom card to... + + + All players - - - &All players + + Reveal r&andom card to... HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + + Browse Archidekt + + + + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4225,61 +4629,61 @@ You may have to manually download the new version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Servern har nått sin maximala kapacitet för användare, var god försök igen vid ett senare tillfälle. - + There are too many concurrent connections from your address. Din adress har för många uppkopplingar samtidigt. - + Banned by moderator Bannlyst av moderator - + Expected end time: %1 Förväntad sluttid: %1 - + This ban lasts indefinitely. Denna bannlysning varar för evigt. - + Scheduled server shutdown. Schemalagd serverstängning. - - + + Invalid username. Ogiltigt användarnamn. - + You have been logged out due to logging in at another location. Du loggades ut på grund av att du loggade in via en annan plats. - + Connection closed Uppkoppling avslutad - + The server has terminated your connection. Reason: %1 Servern har avslutat din uppkoppling. Anledning: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -4290,688 +4694,688 @@ Alla pågående spel kommer att gå förlorade. Anledning till nedstängning: %1 - + Scheduled server shutdown Schemalagd serverstängning - - + + Success Framgång - + Registration accepted. Will now login. Registrering lyckad. Inloggning påbörjad. - + Account activation accepted. Will now login. Aktivering tillåten. Inloggning påbörjad. - + Number of players Antal spelare - + Please enter the number of players. Vänligen ange antal spelare. - - + + Player %1 Spelare %1 - + Load replay Ladda repris - + About Cockatrice Om Cockatrice - + Version - + Cockatrice Webpage - + Project Manager: Projektledare: - + Past Project Managers: Tidigare projektledare: - + Developers: utvecklare: - + Our Developers Våra utvecklare - + Help Develop! - + Translators: Översättare: - + Our Translators - + Help Translate! - + Support: - + Report an Issue - + Troubleshooting - + F.A.Q. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error Fel - + Server timeout Server timeout - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. Det finns redan en aktiv session med det användarnamnet. Vänligen stäng den sessionen först och försök igen. - - + + You are banned until %1. Du är bannlyst till %1. - - + + You are banned indefinitely. Du är bannlyst för evigt. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full - + Unknown login error: %1 Okänt inloggningsfel: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - + + + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 Socketfel: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Du försöker koppla upp dig till en föråldrad server. Vänligen nergradera din version av Cockatrice eller koppla upp dig till en lämplig server. Lokal version är %1, avlägsen version är %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Din version av Cockatrice är föråldrad. Vänligen uppdatera din version av Cockatrice. Lokal version är %1, avlägsen version är %2. - + Connecting to %1... Ansluter till %1... - + Registering to %1 as %2... - + Disconnected Frånkopplad - + Connected, logging in at %1 Uppkopplad, loggar in hos %1 + - Requesting forgotten password to %1 as %2... Begär att få glömt lösenord skickat till %1 som %2 - + &Connect... &Anslut... - + &Disconnect &Frånkoppla - + Start &local game... Starta &lokalt spel... - + &Watch replay... &Titta på repris... - + &Full screen &Fullskärmsläge - + &Register to server... - + &Restore password... - + &Settings... &Inställningar... - + &Exit A&vsluta - + A&ctions - + &Cockatrice &Cockatrice - + C&ard Database - + &Manage sets... &Hantera utgåvor... - + Edit custom &tokens... Redigera egna &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + Reload card database - + Tabs - + &Help &Hjälp - + &About Cockatrice &Om Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information - + A card database update is already running. - + Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -4982,672 +5386,842 @@ Det behöver inte innebära problem men det kan vara ett tecken på att det fin För att uppdatera din klient, gå till Hjälp -> Leta efter uppdateringar. - - - - - + + + + + Load sets/cards Ladda utgåvor/kort - + Selected file cannot be found. Den valda filen kunde inte laddas. - + You can only import XML databases at this time. För tillfället går det bara att importera XML-databaser. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. Det gick inte att importera utgåvor/kort. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. Det gick inte att återställa kontots lösenord. Kontakta serveroperatören för att få hjälp att återställa lösenordet. - + Activation request received, please check your email for an activation token. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play - + from their graveyard - + from exile från exil - + from their hand - + the top card of %1's library det översta kortet av %1's lek - + the top card of their library - + from the top of %1's library från toppen av %1's lek - + from the top of their library - + the bottom card of %1's library det sista kortet i %1's lek - + the bottom card of their library - + from the bottom of %1's library från botten av %1's lek - + from the bottom of their library - + from %1's library från %1'a lek - + from their library - + from sideboard från sidbrädan - + from the stack från stapeln - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 håller nu det översta kortet %2 avslöjat. - + %1 is not revealing the top card %2 any longer. %1 håller inte längre det översta kortet %2 avslöjat. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 fäster %2 på %3s %4. - + %1 has conceded the game. - + %1 has unconceded the game. %1 har ångrat att ge upp spelet. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. - + a card ett kort - + %1 gives %2 control over %3. %1 ger kontroll över %3 till %2. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 sätter %2 i spel%3. - + %1 puts %2%3 into their graveyard. - + %1 exiles %2%3. %1 sätter %2%3 i exil. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. %1 lägger %2%3 i sitt bibliotek %4 kort från toppen. - + %1 moves %2%3 to sideboard. %1 flyttar %2%3 till sidbrädan. - + %1 plays %2%3. %1 spelar %2%3. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. %1 vänder %2 nedåt. - + %1 turns %2 face-up. %1 vänder %2 uppåt. - + The game has been closed. Spelet har stängts. - + The game has started. Spelet har börjat. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. - + %1 is now watching the game. %1 kollar nu på spelet. - + You have been kicked out of the game. Du har blivit utsparkad från spelet. - + %1 has left the game (%2). %1 har lämnat spelet (%2) - + %1 is not watching the game any more (%2). %1 tittar inte längre på spelet (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. %1 blandar sin lek och drar en ny hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards kort - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. %1's tur - + %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -5655,110 +6229,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Lägg till meddelande - - + + Message: Meddelande: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -5875,62 +6449,62 @@ Cockatrice will now reload the card database. Phase - + Unknown Phase - + Untap - + Upkeep - + Draw - + First Main - + Beginning of Combat - + Declare Attackers - + Declare Blockers - + Combat Damage - + End of Combat - + Second Main - + End/Cleanup @@ -5996,7 +6570,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -6005,134 +6579,134 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - + Move top cards to grave - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6140,17 +6714,17 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters - + S&ay @@ -6158,7 +6732,7 @@ Cockatrice will now reload the card database. PrintingSelector - + Display Navigation Buttons @@ -6166,22 +6740,22 @@ Cockatrice will now reload the card database. PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6197,17 +6771,17 @@ Cockatrice will now reload the card database. PrintingSelectorCardSelectionWidget - + Previous Card in Deck - + Bulk Selection - + Next Card in Deck @@ -6215,28 +6789,28 @@ Cockatrice will now reload the card database. PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -6302,37 +6876,37 @@ Cockatrice will now reload the card database. QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -6340,42 +6914,42 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - + All files (*.*) Alla filer (*.*) - + Cockatrice replays (*.cor) Cockatricerepriser (*.cor) - + Maindeck - + Sideboard - + Tokens - + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? @@ -6383,92 +6957,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -6565,37 +7139,37 @@ Cockatrice will now reload the card database. RoomSelector - + Rooms Rum - + Joi&n &Anslut - + Room Rum - + Description Beskrivning - + Permissions - + Players Spelare - + Games Spel @@ -6636,27 +7210,27 @@ Cockatrice will now reload the card database. SetsModel - + Enabled - + Set type - + Set code - + Long name Långt namna - + Release date @@ -6664,53 +7238,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -6718,12 +7292,12 @@ Cockatrice will now reload the card database. ShortcutTreeView - + Action - + Shortcut @@ -6731,13 +7305,13 @@ Cockatrice will now reload the card database. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -6764,12 +7338,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -6777,27 +7351,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -6805,48 +7379,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -7010,56 +7584,123 @@ Please check your shortcut settings! + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info - + Deck - + Filters - + &View - + + Card Database + + + + Printing - - - - + + + + + Visible - - - - + + + + + Floating - + Reset layout - + Deck: %1 Lek: %1 @@ -7067,61 +7708,61 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + + + + Visible - - - - + + + + Floating - + Reset layout @@ -7129,22 +7770,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7152,133 +7793,133 @@ Please check your shortcut settings! TabDeckStorage - + Local file system Lokalt filsystem - + Server deck storage Serverns leklagring - - + + Open in deck editor Öppna i lekredigeraren - + Rename deck or folder - + Upload deck Ladda upp lek - + Download deck Ladda ner lek + - - - + + New folder Ny mapp + - Delete Radera - + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + Error - + Rename failed - - + + Invalid deck file - + Enter deck name Ange leknamn - + This decklist does not have a name. Please enter a name: Denna leklista har inget namn. Vänligen ange ett namn: - + Unnamed deck Namnlös lek - + Failed to upload deck to server - + Delete local file Radera lokal fil - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + Name of new folder: Namn på den nya mappen: @@ -7291,17 +7932,17 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 @@ -7342,7 +7983,7 @@ Please enter a name: - + EDHRec: @@ -7350,197 +7991,197 @@ Please enter a name: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases &Faser - + &Game &Spel - + Next &phase Nästa &fas - + Next phase with &action - + Next &turn Nästa &tur - + Reverse turn order - + &Remove all local arrows Ta &bort alla lokala pilar - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information Spel&information - + Un&concede - - - + + + &Concede &Ge upp - + &Leave game &Lämna spel - + C&lose replay S&täng repris - + &Focus Chat - + &Say: S&äg: - + Selected cards - + &View - - + + + - Visible - - + + + - Floating - + Reset layout - + Concede Ge upp - + Are you sure you want to concede this game? Är du säker på att du vill ge upp detta spel? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game Lämna spel - + Are you sure you want to leave this game? Är du säker på att du vill lämna detta spel? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. @@ -7548,7 +8189,7 @@ Please enter a name: TabHome - + Home @@ -7556,157 +8197,157 @@ Please enter a name: TabLog - + Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -7752,180 +8393,180 @@ The more information you put in, the more specific your results will be. TabReplays - + Local file system Lokalt filsystem - + Server replay storage Serverns reprislagring - - + + Watch replay Titta på repris - + Rename - - + + New folder - - + + Delete Radera - + Open replays folder - + Download replay Ladda ner repris - + Toggle expiration lock Växla utgångslås - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file Radera lokal fil - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay Radera avlägsen repris @@ -7991,30 +8632,30 @@ The more information you put in, the more specific your results will be. + - Error - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -8022,92 +8663,97 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - - - Home - - - - - &Visual Deck Storage - - - - - Visual Database Display - - - Server + Archidekt - Account + Home - Deck Storage + &Visual Deck Storage - Game Replays + Visual Database Display - Administration + Server + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + Logs - + Are you sure? - + There are still open games. Are you sure you want to quit? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8115,38 +8761,38 @@ To update your client, go to Help -> Check for Updates. Servern har skickat dig ett meddelande som din klient inte förstår. Det här meddelandet kan betyda att det finns en ny version av Cockatrice tillgänglig eller så kör den här servern en användarinställd eller förhandsutgiven version. För att uppdatera din klient, gå till Hjälp -> Leta efter uppdateringar. - + Idle Timeout - + You are about to be logged out due to inactivity. Du kommer snart loggas ut på grund av inaktivitet. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned Varnad - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -8155,7 +8801,12 @@ Please refrain from engaging in this activity or further actions may be taken ag TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display @@ -8235,7 +8886,7 @@ Please refrain from engaging in this activity or further actions may be taken ag UpdateDownloader - + Could not open the file for reading. @@ -8243,206 +8894,206 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details Användar&detaljer - + Private &chat - + Show this user's &games Visa denna användarens &spel - + Add to &buddy list Lägg till som &vän - + Remove from &buddy list Ta bort som &vän - + Add to &ignore list &Ignorera - + Remove from &ignore list Sluta &ignorera - + Kick from &game &Sparka från spelet - + Warn user - + View user's war&n history - + Ban from &server &Bannlys från servern - + View user's &ban history - + &Promote user to moderator - + Dem&ote user from moderator - + Promote user to &judge - + Demote user from judge - + View admin notes - - - + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games %1's spel - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - + Failed to get admin notes. - - + + Success - + Successfully promoted user. - + Successfully demoted user. - + + - Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -8624,137 +9275,142 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings Allmänna gränssnittsinställningar - + &Double-click cards to play them (instead of single-click) &Dubbelklicka på kort för att spela dem (istället för enkelklick) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default - - - Close card view window when last card is removed - - - - - Auto focus search bar when card view window is opened - - - - - Annotate card text on tokens - - - - - Use tear-off menus, allowing right click menus to persist on screen - - - - - Notifications settings - - - - - Enable notifications in taskbar - - - - - Notify in the taskbar for game events while you are spectating - - - - - Notify in the taskbar when users in your buddy list connect - - - - - Animation settings - Animationsinställningar - - - - &Tap/untap animation - &Tappnings/Upptappningsanimation - - Deck editor/storage settings + Do not delete &arrows inside of subphases - Open deck in new tab by default + Close card view window when last card is removed - Use visual deck storage in game lobby + Auto focus search bar when card view window is opened - Use selection animation for Visual Deck Storage + Annotate card text on tokens + + + + + Use tear-off menus, allowing right click menus to persist on screen - When adding a tag in the visual deck storage to a .txt deck: + Notifications settings + + + + + Enable notifications in taskbar - do nothing + Notify in the taskbar for game events while you are spectating + + + + + Notify in the taskbar when users in your buddy list connect - ask to convert to .cod - + Animation settings + Animationsinställningar + + + + &Tap/untap animation + &Tappnings/Upptappningsanimation - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -8762,22 +9418,22 @@ Please refrain from engaging in this activity or further actions may be taken ag UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 @@ -8785,32 +9441,32 @@ Please refrain from engaging in this activity or further actions may be taken ag UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -8818,22 +9474,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match - + Mode: Includes - + Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -8841,21 +9497,49 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8881,28 +9565,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - Load from Deck + Filter by name... (Exact match) - Apply all card names in currently loaded deck as exact match name filters + Load from Deck - Load from Clipboard + Apply all card names in currently loaded deck as exact match name filters + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -8966,50 +9650,115 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - + + Sort by: + + + + + Filter by: + + + + Save and load filters - + Filter by exact card name - + Filter by card sub-type - + Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9017,46 +9766,25 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -9112,7 +9840,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9120,22 +9848,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -9143,17 +9871,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + Visual Deck Storage Settings @@ -9161,43 +9889,43 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Warn user for misconduct - - + + Error - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. @@ -9205,133 +9933,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -9339,72 +10067,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing blanda när du stänger - + pile view @@ -9412,7 +10140,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English Svenska (Swedish) @@ -9420,12 +10148,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -9433,1005 +10161,1041 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window - - + + Deck Editor - + Game Lobby - + Card Counters - + Player Counters - + Power and Toughness - + Game Phases - + Playing Area - + Move Selected Card - + View Se - + Move Top Card - + Move Bottom Card - + Gameplay Gameplay - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - - + + Replays - + Tabs - + Check for Card Updates... - + Connect... - + Disconnect - + Exit - + Full screen - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters - + Clear Selected Filter - + Close - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card - + Load Deck... - - + + Load Deck from Clipboard... - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck - + Open Custom Pictures Folder - + Print Deck... - + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... - + Load Remote Deck... - + Set Ready to Start - + Toggle Sideboard Lock - + Add Green Counter - + Remove Green Counter - + Set Green Counters... - + Add Red Counter - + Remove Red Counter - + Set Red Counters... - + Add Life Counter - + Show Status Bar - + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Increment all card counters - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap - + Upkeep - + Draw - + First Main Phase - + Start Combat - + Attack - + Block - + Damage - + End Combat - + Second Main Phase - + End - + Next Phase - + Next Phase Action - + Next Turn - + Hide Card in Reveal Window - + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library - - - - + + + + Exile Exil - - - - + + + + Graveyard Kyrkogård - - + + + Hand - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - - Sort Hand - - - - + Library Bibliotek - + Sideboard Skänk - + Top Cards of Library - + Bottom Cards of Library - + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game Lämna spelet - + Concede Medge - + Roll Dice... Kasta tärning - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card Dra ett kort - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage Visuell Leklagring - + Deck Storage Leklagring - + Server Server - + Account Konto - + Administration Administratör - + Logs Loggar diff --git a/cockatrice/translations/cockatrice_yue.ts b/cockatrice/translations/cockatrice_yue.ts index 69541f3b7..6b0d0b9a8 100644 --- a/cockatrice/translations/cockatrice_yue.ts +++ b/cockatrice/translations/cockatrice_yue.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &設置指示物 @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter 設置指示物 - + New value for counter '%1': 數值物新價值為 '%1' @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,79 +36,81 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. + + Add Analytics Panel AdminNotesDialog - + Update Notes 更新後筆記 - + Admin Notes for %1 %1 的管理筆記 @@ -116,12 +118,12 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard 主牌庫 - + Sideboard 副牌庫 @@ -129,22 +131,22 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error 出現錯誤 - + Could not create themes directory at '%1'. 不能在'%1'創造主題目錄 - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -155,7 +157,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -166,152 +168,165 @@ Are you sure you would like to disable this feature? - + Confirm Change - + Theme settings 主題設定 - + Current theme: 現在主題 - + Open themes folder 打開主題文件夾 - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings 選單設定 - + Show keyboard shortcuts in right-click menus 在右擊選單中顯示鍵盤快速鍵 - + + Show game filter toolbar above list in room tab + + + + Card rendering 卡面算繪 - + Display card names on cards having a picture 最有圖片的牌上顯示牌名 - + Auto-Rotate cards with sideways layout 將打橫佈局卡片自動向上旋轉 - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector 將牌庫中包含的牌組推到藝術選擇視窗之頂 - + Scale cards on mouse over 卡牌隨遊標縮放 - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand 在疊區中和直行手牌中,盡量減少交重疊百分點 - + Maximum initial height for card view window: 卡望框最高開頭高度 - - + + rows 橫列 - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout 手牌佈局 - + Display hand horizontally (wastes space) 打棟顯示手牌(浪費空間) - + Enable left justification 啟用向左對齊 - + Table grid layout 表格布局 - + Invert vertical coordinate 反轉垂直坐標 - + Minimum player count for multi-column layout: 界面佈局之內能夠容納的最少玩家數量: - + Maximum font size for information displayed on cards: 卡牌上顯示的最大字體大小 + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -333,112 +348,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name 禁用 &用家名字 - + ban &IP address 禁用&IP地址 - + ban client I&D 禁用客户ID - + Ban type 禁用類型 - + &permanent ban &永久禁用 - + &temporary ban &暫時禁用 - + &Days: &日 - + &Hours: &小時 - + &Minutes: &分鐘 - + Duration of the ban 禁用時間 - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. 請記載禁用原因 這項只保留為版主, 被禁用戶不能看到. - + Please enter the reason for the ban that will be visible to the banned person. 請記載被禁用戶能看到的被禁原因. - + Redact all messages from this user in all rooms 將這用戶所有室中打出的訊息完全移除 - + &OK &OK - + &Cancel &取消 - + Ban user from server 把用戶從本伺服器禁用 - + + - - + Error 出現錯誤 - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. 若要對用戶實施禁用,請你選擇以下三種的拼合:名字,IP,或用戶id. - + You must have a value in the name ban when selecting the name ban checkbox. 若用用戶名字施行禁用,必須要填寫姓名. - + You must have a value in the ip ban when selecting the ip ban checkbox. 若用ip地址施行禁用,必須要寫下IP地址. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. 若用用戶ID施行禁用,必須要寫下用戶ID. @@ -469,32 +484,32 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name 名字 - + Sets 牌組 - + Mana cost 法力費用 - + Card type 卡牌類別 - + P/T 力量/防御力 - + Color(s) 顏色 @@ -502,96 +517,101 @@ This is only saved for moderators and cannot be seen by the banned person. CardFilter - + AND Logical conjunction operator used in card filter - + OR Logical disjunction operator used in card filter - + AND NOT Negated logical conjunction operator used in card filter 和不是 - + OR NOT Negated logical disjunction operator used in card filter 或不是 - + Name 名稱 - + + Name (Exact) + + + + Type 種類 - + Color 顏色 - + Text 詞語 - + Set 卡組 - + Mana Cost 法力費用 - + Mana Value 法力數值 - + Rarity 稀有度 - + Power 力量 - + Toughness 防御力 - + Loyalty 忠诚值 - + Format 賽制 - + Main Type - + Sub Type @@ -599,22 +619,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoFrameWidget - + Image 卡圖 - + Description 描述 - + Both 兩個都顯示 - + View transformation @@ -622,22 +642,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards 查看關聯卡牌 - + Add card to deck - + Mainboard - + Sideboard @@ -650,12 +670,22 @@ This is only saved for moderators and cannot be seen by the banned person.名稱: - + + Set: + + + + + Collector Number: + + + + Related cards: 有關牌名: - + Unknown card: 不知名的牌 @@ -663,124 +693,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -788,7 +818,7 @@ This is only saved for moderators and cannot be seen by the banned person. CardSizeWidget - + Card Size 卡牌大小 @@ -796,133 +826,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative - + %1's hand nominative - - - their library - look at zone - - - - - %1's library - look at zone - - - - - of their library - top cards of zone, - - - - - of %1's library - top cards of zone - - their library - reveal zone + look at zone %1's library + look at zone + + + + + of their library + top cards of zone, + + + + + of %1's library + top cards of zone + + + + + their library reveal zone - + + %1's library + reveal zone + + + + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - - - their graveyard - nominative - - - - - %1's graveyard - nominative - - - - - their exile - nominative - - - - - %1's exile - nominative - - + their graveyard + nominative + + + + + %1's graveyard + nominative + + + + + their exile + nominative + + + + + %1's exile + nominative + + + + their sideboard look at zone - + %1's sideboard look at zone - - - their sideboard - nominative - - - - - %1's sideboard - nominative - - - their custom zone '%1' + their sideboard nominative + %1's sideboard + nominative + + + + + their custom zone '%1' + nominative + + + + %1's custom zone '%2' nominative @@ -931,7 +961,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml3Parser - + Parse error at line %1 col %2: @@ -939,7 +969,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml4Parser - + Parse error at line %1 col %2: @@ -958,6 +988,37 @@ This is only saved for moderators and cannot be seen by the banned person. + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -969,47 +1030,47 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add to Deck - + Add to Sideboard - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1017,87 +1078,97 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing - + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + + Format: + + + + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1105,17 +1176,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1123,114 +1194,114 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1238,7 +1309,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1246,194 +1317,227 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers 更新預覽卡牌 - - + + Success 成功 - + Download URLs have been reset. 下載URL已重置。 - + Downloaded card pictures have been reset. 下載的卡牌圖片已被重置。 - + Error 出現錯誤 - + One or more downloaded card pictures could not be cleared. 一幅或以上的卡牌圖片未能被清除。 - + Add URL 添加URL - - + + URL: URL: - - + + Edit URL 更改URL - + Network Cache Size: 圖片網路緩存大小: - + Redirect Cache TTL: 網路緩處再定向生存時間 - + How long cached redirects for urls are valid for. 再定向緩處有效時間 - + Picture Cache Size: 圖片緩存大小: - + Add New URL 添置新 URL - + Remove URL 移除 URL. - + Day(s) - + Updating... 更新中... - + Choose path 選擇路徑 - + URL Download Priority URL下載優先次序 - + Spoilers 預覽 - + Download Spoilers Automatically 自動下載預覽 - + Spoiler Location: 預覽位置: - + Last Change 最後變更 - + Spoilers download automatically on launch 開啟時自動下載預覽 - + Press the button to manually update without relaunching 免重新啟動人手按制更新 - + Do not close settings until manual update is complete 人手更新完成前,不能關掉设置 - + Download card pictures on the fly 即時下載卡牌圖片 - + How to add a custom URL 如何添加自定URL - + Delete Downloaded Images 刪除已下載的圖片 - + Reset Download URLs 重置下載URL - + On-disk cache for downloaded pictures 下載圖片專用硬盤緩儲 - + In-memory cache for pictures not currently on screen 現不在螢光幕上記憶中圖片緩儲 + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count 數目 - + Set 牌組 - + Number 號碼 - + Provider ID 提供者 id - + Card 卡牌 @@ -1441,12 +1545,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1454,17 +1558,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1472,7 +1576,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -1480,62 +1584,62 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -1548,93 +1652,151 @@ This is only saved for moderators and cannot be seen by the banned person. - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1652,74 +1814,74 @@ This is only saved for moderators and cannot be seen by the banned person. DeckViewContainer - + Load deck... 載入牌庫... - + Load remote deck... 載入伺服器上的牌庫... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start 準備好 - + Force start 強迫開始 - + Sideboard unlocked 備牌解鎖 - + Sideboard locked 解鎖鎖上 - - + + Error 出現錯誤 - + The selected file could not be loaded. 選擇的檔案不能再入 - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -1752,143 +1914,143 @@ This will kick all non-ready players from the game. 下載中... - + Known Hosts 已知主辦機 - + Delete the currently selected saved server 刪除現選儲藏伺服器. - + Refresh the server list with known public servers 從已知的伺服器更新伺服器列表 - + New Host 新主辦 - + Name: 名稱: - + &Host: &主辦機: - + &Port: &端口: - + Player &name: 玩家 &名字: - + P&assword: &密碼: - + &Save password &儲存密碼 - + A&uto connect &自動連接 - + Automatically connect to the most recent login when Cockatrice opens 當Cockatrice啓動時自動連接到最近的登錄 - + If you have any trouble connecting or registering then contact the server staff for help! 如在連接或註冊誰中遇到問題,請聯絡伺服器職員尋求援助! - - + + Webpage 網頁 - + Reset Password 重設密碼 - + Forgot password? 密碼忘記了? - + &Connect 連接 - + Server 伺服器 - + Login 登錄 - + Server Contact 伺服器聯絡人 - + Connect to Server 連接伺服器 - + Server URL 伺服器URL - + Communication Port 通信端口 - + Unique Server Name 獨特伺服器名字 - + Connection Warning 連接警告 - + You need to name your new connection profile. 你必須將你的連接簡介命名 - + Connect Warning 連接警告 - + The player name can't be empty. 玩家名稱不能空置 @@ -1896,117 +2058,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings &記住設定 - + &Description: &描述: - + P&layers: 玩家: - + General 一般 - + Game type 遊戲類型 - + &Password: &密碼: - + Only &buddies can join 只容許&好友加入 - + Only &registered users can join 只容許&註冊用戶加入 - + Joining restrictions 加入條件 - + &Spectators can watch 允許觀看者 - + Spectators &need a password to watch 觀看者&需要密碼 - + Spectators can &chat 觀看者可以&聊天 - + Spectators can see &hands 觀看者可見&手牌 - + Create game as spectator 以旁觀者的身份創建遊戲 - + Spectators 觀看者 - + Starting life total: 開始生命總數 - + Open decklists in lobby - + + Create game as judge + + + + Game setup options 遊戲設定選擇 - + &Clear &清除 - + Create game 創建遊戲 - + Game information 遊戲資訊 - + Error 出現錯誤 - + Server error. 伺服器出現錯誤. @@ -2014,97 +2181,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &名稱: - + Token 令牌 - + C&olor: &顏色: - + white 白色 - + blue 藍色 - + black 黑色 - + red 紅色 - + green 綠色 - + multicolor 多色 - + colorless 無色 - + &P/T: &力量/防御力: - + &Annotation: &註釋: - + &Destroy token when it leaves the table &此令牌離場時會被摧毀 - + Create face-down (Only hides name) - + Token data 令牌資料 - + Show &all tokens 顯示&所有令牌 - + Show tokens from this &deck 顯示本牌庫所有令牌 - + Choose token from list 從清單中選擇令牌 - + Create token 派出令牌 @@ -2112,53 +2279,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2166,40 +2333,40 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. 未選擇圖像 - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. 若要改變頭著,請選擇新的圖片. 若要移除現有頭像, 請不要選擇新的圖片,直接確認. - + Browse... 瀏覽... - + Change avatar 更改頭像 - + Open Image 打開圖片 - + Image Files (*.png *.jpg *.bmp) 圖片文件(*.png *.jpg *.bmp格式) - + Invalid image chosen. 所選圖片不能使用。 @@ -2207,17 +2374,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2225,38 +2392,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: 舊密碼: - + New password: 新密碼: - + Confirm new password: 確認新密碼: - + Change password 更改密碼 - - + + Error 出現錯誤 - + Your password is too short. 你的密碼太短。 - + The new passwords don't match. 新舊密碼不一致 @@ -2264,93 +2431,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: &名稱: - + C&olor: &顏色: - + white 白色 - + blue 藍色 - + black 黑色 - + red 红色 - + green 綠色 - + multicolor 多色 - + colorless 無色 - + &P/T: &力量/防御力: - + &Annotation: &註釋: - + Token data 令牌資料 - - + + Add token 添加令牌 - + Remove token 移除令牌 - + Edit custom tokens 編輯自定令牌 - + Please enter the name of the token: 請輸入令牌名稱: - + Error 出現錯誤 - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. 現時選擇的名字跟現有的卡牌或令牌出現衝突.請從"牌組管理"中啟動'令牌牌組',保持正確顯示. @@ -2443,7 +2610,8 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games not created by buddy + Hide games not created by buddies + Hide games not created by buddy @@ -2530,52 +2698,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning 重設密碼驗證警告 - + A problem has occurred. Please try to request a new password again. 程序出現故障,請再要求新的密碼. - + Enter the information of the server and the account you'd like to request a new password for. 請輸入需要新密碼的伺服器和相關戶口資料. - + &Host: &主辦機: - + &Port: &端口: - + Player &name: 玩家 &名字: - + Email: 電郵: - + Reset Password Challenge 重設密碼驗證 - + Reset Password Challenge Error 重設密碼驗證錯誤 - + The email address can't be empty. 電郵不能空置. @@ -2583,37 +2751,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. 請輸入需要新密碼的伺服器資料. - + &Host: &主辦機: - + &Port: &端口: - + Player &name: 玩家 &名字: - + Reset Password Request 重設密碼要求 - + Reset Password Error 重設密碼錯誤 - + The player name can't be empty. 玩家名稱不能空置 @@ -2621,86 +2789,86 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning 重設密碼警告 - + A problem has occurred. Please try to request a new password again. 程序出現故障,請再要求新的密碼. - + Enter the received token and the new password in order to set your new password. 請輸入收到驗證碼和新的密碼,以便定立新的密碼. - + &Host: &主辦機: - + &Port: &端口: - + Player &name: 玩家 &名字: - + Token: 驗證碼 - - + + New Password: 新密碼: - + Reset Password 重設密碼 - + The player name can't be empty. 玩家名稱不能空置 - - - - + + + + Reset Password Error 重設密碼錯誤 - + The token can't be empty. 驗證碼不能為空白 - + The new password can't be empty. 新密碼不能為空白 - + Error 出現錯誤 - + Your password is too short. 你的新密碼太短。 - + The passwords do not match. 新舊密碼不一致. @@ -2716,17 +2884,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard 從剪貼板載入牌庫 - + Error 出現錯誤 - + Invalid deck list. 牌表不能載入 @@ -2734,43 +2902,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2784,7 +2952,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck 載入牌庫 @@ -2792,37 +2960,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): 卡牌名稱(或搜尋表達式) - + Number of hits: 因為cockatrice原生沒有ssl支援, 所以你不能自動下載更新. 請瀏覽到我們的下載網頁,進行人手更新. - + Auto play hits 開關副牌上鎖 - + Put top cards on stack until... 把頂牌放在重疊區, 直到... - + No cards matching the search expression exists in the card database. Proceed anyways? 在卡牌資料庫中找不到符合搜尋表達式的卡牌.你想繼續嗎? - + Cockatrice Cockatrice - + Invalid filter 篩選無效 @@ -2830,7 +2998,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. 輸入你, 和你想登記的伺服器的資料. @@ -2838,85 +3006,85 @@ Your email will be used to verify your account. 我們會使用你的電郵來核實你的戶口. - + &Host: &主辦機: - + &Port: &端口: - + Player &name: 玩家 &名字: - + P&assword: &密碼: - + Password (again): (再次)輸入密碼: - + Email: 電郵: - + Email (again): (再次)輸入電郵: - + Country: 國家: - + Undefined 未定義 - + Real name: 實名: - + Register to server 註冊加入伺服器 - - - - + + + + Registration Warning 注册警告 - + Your password is too short. 你的密碼太短。 - + Your passwords do not match, please try again. 兩次輸入的密碼不一致,請重新輸入, - + Your email addresses do not match, please try again. 兩次輸入的電子郵箱不一致,請重新輸入。 - + The player name can't be empty. 玩家名稱不能空置 @@ -2942,40 +3110,55 @@ Your email will be used to verify your account. DlgSelectSetForCards - + Unmodified Cards: - + Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database 讀取卡牌資料庫時出現不知名錯誤 - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -2992,7 +3175,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + Your card database version is too old. This can cause problems loading card information or images @@ -3009,7 +3192,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3022,7 +3205,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + File Error loading your card database. Would you like to change your database location setting? @@ -3030,7 +3213,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3039,7 +3222,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3055,59 +3238,59 @@ https://github.com/Cockatrice/Cockatrice/issues 你想不想更改你的資料庫位置設定? - - - + + + Error 出現錯誤 - + The path to your deck directory is invalid. Would you like to go back and set the correct path? 你的牌庫目錄路徑現在無效. 你想要重新設置套牌路徑嗎? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? 你的卡牌圖片目錄路徑現在無效. 你想要重新設置卡牌圖片資料庫路徑嗎? - + Settings 設定 - + General 一般 - + Appearance 外觀 - + User Interface 用戶界面 - + Card Sources 卡牌來源 - + Chat 聊天 - + Sound 聲音 - + Shortcuts 快捷键 @@ -3115,39 +3298,39 @@ https://github.com/Cockatrice/Cockatrice/issues DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3155,17 +3338,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next 下一個 - + Previous 上一個 - + Tip of the Day 每日提示 @@ -3173,165 +3356,165 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel 現在發行渠道 - + Reinstall 重新安裝 - + Cancel Download 取消下載 - + Open Download Page 打開下載頁面 - + Check for Client Updates 檢查客戶端更新 - - - - + + + + Error 出現錯誤 - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. 因為Cockatrice不設立ssl支援,所以你不能自動下載更新! 請你瀏覽到我們的下載網頁進行人手更新. - + Downloading update: %1 - + Checking for updates... 正在檢查更新... - + Finished checking for updates 更新檢查已完成 - + No Update Available 沒有更新可用 - + Cockatrice is up to date! Cockatrice 已經達到最新版本! - + You are already running the latest version available in the chosen release channel. 你運行的已經是最新的發行版本了。 - + Current version 現行版本 - + Selected release channel 現選發行渠道 - - + + Update Available 現有更新版本 - - + + A new version of Cockatrice is available! Cockatrice有新版本可用! - - + + New version 新版本 - - + + Released 已發行 - - + + Changelog 變更記載 - + Do you want to update now? 你打算現在更新嗎? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + + Update Error 更新出現錯誤 - + An error occurred while checking for updates: 檢測更新時出現錯誤: - + An error occurred while downloading an update: 下載更新時出現錯誤 - + Installing... 安裝中 - + Cockatrice is unable to open the installer. Cockatrice 不能啟動安裝程式 - + Try to update manually by closing Cockatrice and running the installer. 請你嘗試人手更新, 關掉cockatrice, 然後重開安裝程式. - + Download location 下載位置 @@ -3339,21 +3522,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing 關閉時清除紀錄 - + Copy to clipboard - + Debug Log 偵錯紀錄 + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3423,33 +3738,39 @@ You may have to manually download the new version. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3465,22 +3786,22 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. @@ -3488,22 +3809,22 @@ You may have to manually download the new version. GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -3511,226 +3832,279 @@ You may have to manually download the new version. GameSelector - - - - - - + + + + + + Error 出現錯誤 - + Please join the appropriate room first. 請先參加合適房間 - + Wrong password. 密碼錯誤. - + Spectators are not allowed in this game. 這個遊戲不允許觀眾加入. - + The game is already full. 這個遊戲已經滿了. - + The game does not exist any more. 這個遊戲已經不存在了。 - + This game is only open to registered users. 這個遊戲只開放給註冊用戶. - + This game is only open to its creator's buddies. 這個遊戲只開放給房間主辦的好友。 - + You are being ignored by the creator of this game. 你被這個房間的主辦屏蔽了. - + Join Game - + Spectate Game - + Game Information - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game 加入遊戲 - + Password: 密碼: - + Please join the respective room first. 請先參加相關的房間. - + &Filter games &篩選遊戲 - + C&lear filter &清除篩選項目 - + C&reate &創建 - + &Join &加入 - + + Join as judge + + + + J&oin as spectator &作為觀看者加入 - + + Join as judge spectator + + + + Games shown: %1 / %2 顯示遊戲:%1/%2 - + Games 遊戲 + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day >1日 - + %1%2 hr short age in hours %1%2 小時 - + new 新建 - + %1%2 min short age in minutes %1%2 分鐘 - + password 密碼 - + buddies only 僅限好友 - + reg. users only 僅限註冊用户 - + open decklists - - + + can chat 允許聊天 - + see hands 查看手牌 - + can see hands 允許查看手牌 - + not allowed 不允許 - + Room 房間 - + Age 時長 - + Description 描述: - + Creator 創建者 - + Type 類型 - + Restrictions 限制 - + Players 玩家 - + Spectators 觀看者 @@ -3738,143 +4112,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths 重置所有路徑 - + All paths have been reset 重置所有路徑 - - - - - - - + + + + + + + Choose path 選擇路徑 - + Personal settings 個人設定 - + Language: 語言: - + Paths (editing disabled in portable mode) 路徑(在便攜模式下不能編輯) - + Paths 路徑 - + How to help with translations - + Decks directory: 牌庫路徑: - + Filters directory: - + Replays directory: 遊戲錄像目錄: - + Pictures directory: 圖片目錄: - + Card database: 卡牌資料庫: - + Custom database directory: 自主資料庫目錄 - + Token database: 令牌資料庫: - + Update channel 更新渠道 - + Check for client updates on startup 啟動時檢查客戶更新 - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client 如果客戶端缺少伺服器支持的更功能時,請提示我 - + Automatically run Oracle when running a new version of Cockatrice 啟動新版本Cockatrice時,自動運行Oracle. - + Show tips on startup 顯示啓動時提示 - + Last update check on %1 (%2 days ago) @@ -3882,47 +4256,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -3930,111 +4304,141 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - - &Sort hand + + Sort hand by... - - Take &mulligan + + Name - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - Reveal r&andom card to... + + + All players - - - &All players + + Reveal r&andom card to... HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + + Browse Archidekt + + + + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4236,643 +4640,643 @@ You may have to manually download the new version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. 伺服器已達到最最高用戶限額,請另外選擇吉時再試. - + There are too many concurrent connections from your address. 你的網路地址擁有太多同時連繫. - + Banned by moderator 你已被版主禁用 - + Expected end time: %1 預計結束時間: %1 - + This ban lasts indefinitely. 這是無限期禁用 - + Scheduled server shutdown. 排期伺服器關機 - - + + Invalid username. 不可用的用戶名。 - + You have been logged out due to logging in at another location. 因為你已在另外的位置登入, 所以你現有的聯繫已被登出. - + Connection closed 連繫已被關閉 - + The server has terminated your connection. Reason: %1 伺服器已經中斷你的聯繫/ 原因: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 伺服器將會在 %n 分鐘內重新啟動. 所有進行中的遊戲都會報銷.伺服器關閉原因: %1 - + Scheduled server shutdown 排期伺服器關機 - - + + Success 成功 - + Registration accepted. Will now login. 註冊已成功。 現在登入。 - + Account activation accepted. Will now login. 戶口啟動已經成功 現在登入. - + Number of players 玩家人數 - + Please enter the number of players. 請輸入玩家人數 - - + + Player %1 玩家 %1 - + Load replay 載入遊戲錄像 - + About Cockatrice 關於Cockatrice - + Version 版本 - + Cockatrice Webpage Cockatrice 網頁 - + Project Manager: 項目經理: - + Past Project Managers: 前項目經理: - + Developers: 開發者: - + Our Developers 我們的開發者: - + Help Develop! 協助開發! - + Translators: 翻譯者: - + Our Translators 翻譯者名單 - + Help Translate! 協助翻譯! - + Support: 支援: - + Report an Issue 報告問題 - + Troubleshooting 排除故障 - + F.A.Q. 問答 - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error 出現錯誤 - + Server timeout 伺服器超時 - + Failed Login 登入失敗 - + Your client seems to be missing features this server requires for connection. 你的客戶端似乎脫小本伺服器需要用來達成連繫的功能. - + To update your client, go to 'Help -> Check for Client Updates'. 請去"求助->檢查客戶端更新" 內更新你的客戶. - + Incorrect username or password. Please check your authentication information and try again. 用户名或密碼錯誤。請檢查賬號信息,然後重試。 - + There is already an active session using this user name. Please close that session first and re-login. 已經有在線用户正在使用這用户名.請先關閉連線,然後重新登入. - - + + You are banned until %1. 你被禁止直到: %1. - - + + You are banned indefinitely. 你已被永久封禁. - + This server requires user registration. Do you want to register now? 本伺服器要求用戶註冊. 你想不想現在登記? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. 本伺服器要求用戶擁有客戶id. 你的客戶端現在不能造成客戶id,或是你正在使用修改過的客戶端. 請你關掉現有的客戶端,然後重新啟動, 擇吉日再試。 - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. 因為內部出現故障, 請關閉,重開Cockatrice 然後再次嘗試. 如果故障繼續出現,請將客戶端軟件升級為最新版本, 有需要時請聯絡軟件開發人士. - + Account activation 戶口啟動 - + Your account has not been activated yet. You need to provide the activation token received in the activation email. 你的戶口還未嘗啟動. 你需要提供從啟動電郵得來的驗證碼. - + Server Full 伺服器載滿 - + Unknown login error: %1 不知名登入錯誤: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. 這通常意味住你的客戶端版本已經過期,引起你的客戶端不能明曉伺服器的回覆. - + Your username must respect these rules: 您的用戶名字必須遵守以下規則: - + is %1 - %2 characters long 在%1 - %2个字符之间。 - + can %1 contain lowercase characters 可以 $1 包含細楷/小寫字母 - - - - + + + + NOT 並否 - + can %1 contain uppercase characters 可以 $1 包含大楷/大寫字母 - + can %1 contain numeric characters 可以 $1 包含數字 - + can contain the following punctuation: %1 可以包含以下符號:%1 - + first character can %1 be a punctuation mark 第一個字符可以 %1 是標點符號 - + no unacceptable language as specified by these server rules: note that the following lines will not be translated 在以下伺服器規矩中,道明不能接受的語言: - + can not contain any of the following words: %1 不能含有以下字眼: %1 - + can not match any of the following expressions: %1 不能含有以下語句: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. 你的用戶名字只可以用以下的字符: A-Z, a-z, 0-9, _, ., 和 - . - - - - - - + + + + + + Registration denied 註冊被拒絕 - + Registration is currently disabled on this server 這伺服器暫時謝絕登記 - + There is already an existing account with the same user name. 這個用戶名字已經被使用. - + It's mandatory to specify a valid email address when registering. 登記時必要指明合能用的電郵地址. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. 你看來好像在本伺服器嘗試申請新戶口, 但是你已經有跟你電郵掛鉤的戶口名字.我們的伺服器限制每用戶在每個地址可以有多少個戶口. 如果需要更多援助或是得取你的登入資料,請聯絡伺服器辦事人. - + Password too short. 密碼太短。 - + Registration failed for a technical problem on the server. 由於伺服器出現技術問題, 你的登記暫時失敗. - + The connection to the server has been lost. - + Unknown registration error: %1 不知名登記錯誤: %1 - + Account activation failed 戶口啟動失敗 - + Socket error: %1 接口錯誤: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. 你正在嘗試跟過時的伺服器連繫. 請把你的 Cockatrice 版本降級,或是連繫於合適的伺服器. 當地/離線版本維 %1, 遙距/上線版本維 %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. 你的 Cockatrice 版本現已過時.請把你的版本更新. 當地/離線版本維 %1, 遙距/上線版本維 %2. - + Connecting to %1... 向 %1 連接中... - + Registering to %1 as %2... 正在用%2的身份登記到%1伺服器... - + Disconnected 已經斷線了 - + Connected, logging in at %1 連線成功, 在%1登入 + - Requesting forgotten password to %1 as %2... 正在用%2的身份對%1要求遺忘密碼. - + &Connect... &連線... - + &Disconnect &斷線 - + Start &local game... 開始&離線遊戲 - + &Watch replay... &觀看遊戲錄像 - + &Full screen &全屏 - + &Register to server... &註冊加入伺服器 - + &Restore password... &弄回密碼 - + &Settings... 設置⋯ - + &Exit &退出 - + A&ctions &動作 - + &Cockatrice &Cockatrice - + C&ard Database &卡牌資料庫 - + &Manage sets... &管理牌組 - + Edit custom &tokens... 編輯自定 &令牌... - + Open custom image folder 打開自定圖片文件夾 - + Open custom sets folder 打開自定牌组文件夾 - + Add custom sets/cards 添加自定卡组/卡牌 - + Reload card database 刷新卡牌資料庫 - + Tabs - + &Help &求助 - + &About Cockatrice &關於Cockatrice - + &Tip of the Day &每日提示 - + Check for Client Updates 檢查客戶端更新 - + Check for Card Updates... 檢查卡牌更新 - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log 查看&偵錯紀錄 - + Open Settings Folder 打開設置文件夾 - + Show/Hide 顯示/隱藏 - + New Version 新版本 - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. 恭喜你將Cockatrice升級為%1. 現在會幫你啟動oracle, 更新你的卡牌資料庫. - + Cockatrice installed Cockatrice 已被安裝 - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. 恭喜你成功安裝Cockatrice%1. 現在會幫你啟動oracle, 安裝你的卡牌資料庫. - + Card database 卡牌資料庫 - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -4881,29 +5285,29 @@ If unsure or first time user, choose "Yes" 如果不確定, 或者是第一次使用,請選擇“是/Yes” - - + + Yes 是/Yes - - + + No 否/No - + Open settings 打開設置 - + New sets found 發現新卡组 - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -4912,17 +5316,17 @@ Do you want to enable it/them? 要啟用它們嗎? - + View sets 查看卡组 - + Welcome 歡迎 - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -4931,64 +5335,64 @@ Read more about changing the set order or disabling specific sets and consequent 請閱讀,了解更多在“卡组设置”對話框裡.更改卡组顺序或者禁用某些卡组的功能. - - + + Information 資訊 - + A card database update is already running. 資料庫更新現已進行. - + Unable to run the card database updater: 不能運行卡牌資料附更新程式: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5000,672 +5404,842 @@ To update your client, go to Help -> Check for Updates. 請到"援助->檢查更新"去更新你的客戶端. - - - - - + + + + + Load sets/cards 载入卡组/卡牌 - + Selected file cannot be found. 找不到選擇的文件。 - + You can only import XML databases at this time. 你暫時只能導入xml資料庫. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. 已經成功添加新的卡組/卡牌. Cockatrice會重新載入卡牌資料庫. - + Sets/cards failed to import. 卡組/卡牌導入失敗. - - - + + + Reset Password 重設密碼 - + Your password has been reset successfully, you can now log in using the new credentials. 你的密碼已經成功重置, 你可以用新密碼重新登入。 - + Failed to reset user account password, please contact the server operator to reset your password. 重置用戶密碼失敗, 請聯絡伺服器管責人重置密碼. - + Activation request received, please check your email for an activation token. 已經收到啟動要求, 請從你的電郵獲取啟動驗證碼. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play 從戰場上 - + from their graveyard 從它們的墳場 - + from exile 從放逐區 - + from their hand 從它們的手牌 - + the top card of %1's library %1牌庫的頂牌 - + the top card of their library 它們的頂牌 - + from the top of %1's library 從%1牌庫的頂 - + from the top of their library 從它們1牌庫頂 - + the bottom card of %1's library %1牌庫的底牌 - + the bottom card of their library 它們牌庫的底牌 - + from the bottom of %1's library 從%1的牌庫底 - + from the bottom of their library 從它們的牌庫底 - + from %1's library 從%1的牌庫 - + from their library 從它們的牌庫 - + from sideboard 從備牌中 - + from the stack 從疊區中 - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1現在保持%2頂牌顯現. - + %1 is not revealing the top card %2 any longer. %1不再展示牌庫頂牌%2。 - + %1 can now look at top card %2 at any time. %1可以隨時查看牌庫頂牌 %2. - + %1 no longer can look at top card %2 at any time. %1再不可以隨時查看牌庫頂牌 %2. - + %1 attaches %2 to %3's %4. %1 結附 %2 指向 %3的 %4. - + %1 has conceded the game. %1 在此遊戲宣佈降服. - + %1 has unconceded the game. %1 收回在此遊戲降服. - + %1 has restored connection to the game. %1已恢復連接。 - + %1 has lost connection to the game. %1 已失去連接。 - + %1 points from their %2 to themselves. %1 從它們的%2 指向自己。 - + %1 points from their %2 to %3. %1 從它們的%2指向%3。 - + %1 points from %2's %3 to themselves. %1 從%2的%3 指向自己。 - + %1 points from %2's %3 to %4. %1 從%2的%3 指向%4。 - + %1 points from their %2 to their %3. %1 從它們的%2指向它們的%3。 - + %1 points from their %2 to %3's %4. %1 從它們的%2指向%3的%4。 - + %1 points from %2's %3 to their own %4. %1 從%2的%3指向自己的%4。 - + %1 points from %2's %3 to %4's %5. %1 從%2的%3指向%4的%5。 - + %1 creates a face down token. - + %1 creates token: %2%3. %1 派出令牌: %2%3 - + %1 has loaded a deck (%2). %1 已载入牌庫(%2) - + %1 has loaded a deck with %2 sideboard cards (%3). %1已載入含有 %2張備牌的牌庫(%3). - + %1 destroys %2. %1销毁了%2。 - + a card 一張牌 - + %1 gives %2 control over %3. %1將%3的控制轉移給%2. - + %1 puts %2 into play%3 face down. %1將%2%3牌面朝下地放進戰場。 - + %1 puts %2 into play%3. %1將%2放進戰場%3。 - + %1 puts %2%3 into their graveyard. %1將%2%3置入它們的的墳場。 - + %1 exiles %2%3. %1 放逐%2%3. - + %1 moves %2%3 to their hand. %1將%2%3移到它們的的手牌中。 - + %1 puts %2%3 into their library. %1將%2%3放入它們的牌庫。 - + %1 puts %2%3 onto the bottom of their library. %1將%2%3放在它們的牌庫底部。 - + %1 puts %2%3 on top of their library. %1將%2%3放在它們的牌庫頂部。 - + %1 puts %2%3 into their library %4 cards from the top. %1將%2%3放在它們的牌庫頂部%4張牌以下。 - + %1 moves %2%3 to sideboard. %1 將%2%3移到副牌庫. - + %1 plays %2%3. %1使出 %2%3。 - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1嘗試從空牌庫抽牌 - + %1 draws %2 card(s). %1抽%2張牌。 - + %1 is looking at %2. %1 正在查看%2。 - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 正在查看%2的 %4 %3張卡牌. - + bottom 底部 - + top 頂部 - + %1 turns %2 face-down. %1把%2翻為面朝下。 - + %1 turns %2 face-up. %1把%2翻為面朝上。 - + The game has been closed. 遊戲已經關閉。 - + The game has started. 遊戲已開始。 - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 已經加入遊戲。 - + %1 is now watching the game. %1 正在旁觀遊戲。 - + You have been kicked out of the game. 你已被踢出遊戲。 - + %1 has left the game (%2). %1 已經離開遊戲(%2)。 - + %1 is not watching the game any more (%2). %1 已經不再旁觀遊戲(%2)。 - + %1 is not ready to start the game any more. %1 還未準備好開始遊戲。 - + %1 shuffles their deck and draws a new hand of %2 card(s). %1將其牌庫洗牌,然後抽%2張牌作為新的起手。 - + %1 shuffles their deck and draws a new hand. %1將其牌庫洗牌,然後抽起新的手牌。 - + You are watching a replay of game #%1. 你正在觀看遊戲#%1的錄像。 - + %1 is ready to start the game. %1已準備好開始遊戲。 - + cards an unknown amount of cards 卡牌(眾)  - + %1 card(s) a card for singular, %1 cards for plural %1 卡牌(眾)  - + %1 lends %2 to %3. %1把&2借給%3. - + %1 reveals %2 to %3. %1把%2展示給%3. - + %1 reveals %2. %1 展示 %2。 - + %1 randomly reveals %2%3 to %4. %1隨機展示%2%3給%4。 - + %1 randomly reveals %2%3. %1隨機展示%2%3。 - + %1 peeks at face down card #%2. %1查看面朝下的卡牌#%2。 - + %1 peeks at face down card #%2: %3. %1查看面朝下的卡牌#%2:%3。 - + %1 reveals %2%3 to %4. %1展示%2%3給%4。 - + %1 reveals %2%3. %1展示%2%3。 - + %1 reversed turn order, now it's %2. %1 逆轉回合次序,現在輪到%2。 - + reversed 逆轉 - + normal 正常 - + Heads 正面 - + Tails 翻面 - + %1 flipped a coin. It landed as %2. %1擲了硬幣。結果為%2。 - + %1 rolls a %2 with a %3-sided die. %1擲%3面骰子,結果為%2。 - + %1 flips %2 coins. There are %3 heads and %4 tails. %1擲了%2個硬幣。結果為%3個正面和%4個反面。 - + %1 rolls a %2-sided dice %3 times: %4. %1把%2面骰子擲了%3次:%4 - + %1's turn. 輪到 %1. - + %1 sets annotation of %2 to %3. %1給%2添加了註釋成%3。 - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 將%2指示物設置為 %3 (%4%5)。 - + %1 sets %2 to not untap normally. %1 將 %2 設置為不被重置。 - + %1 sets %2 to untap normally. %1 將 %2 設置為照常重置。 - + %1 removes the PT of %2. %1 移除%2的力量/防禦. - + %1 changes the PT of %2 from nothing to %4. %1把%2的力量/防禦從無數值轉為%4. - + %1 changes the PT of %2 from %3 to %4. %1把%2的力量/防禦從%3轉為%4. - + %1 has locked their sideboard. %1已經鎖定它們的的副牌庫。 - + %1 has unlocked their sideboard. %1已經把它們的的副牌庫解鎖。 - + %1 taps their permanents. %1横置它們的永久卡牌 - + %1 untaps their permanents. %1重置它們的永久卡牌. - + %1 taps %2. %1 横置 %2. - + %1 untaps %2. %1 重置 %2。 - + %1 shuffles %2. % 1 對 %2 洗牌 - + %1 shuffles the bottom %3 cards of %2. %1 洗 %2 底的 %3 張卡牌. - + %1 shuffles the top %3 cards of %2. %1 洗 %2 頂的 %3 張卡牌. - + %1 shuffles cards %3 - %4 of %2. %1到%2.洗%3 - %4 - + %1 unattaches %2. %1 取消了%2的結附。 - + %1 undoes their last draw. %1 放回它們最後一張抽到的卡牌. - + %1 undoes their last draw (%2). %1 放回它們最後一張抽到的卡牌(%2). @@ -5673,110 +6247,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 詞1 詞2 詞3 - + Add New Message 添加新信息 - + Edit Message 更改信息 - + Remove Message 移除信息 - + Add message 添加信息 - - + + Message: 信息: - + Edit message 更改信息 - + Chat settings 聊天設定 - + Custom alert words 自定通知觸發詞語 - + Enable chat mentions 允許聊天中提到某人 - + Enable mention completer 允許自動完成提名 - + In-game message macros 遊戲內短信速鍵 - + How to use in-game message macros 如何使用遊戲內短信速鍵 - + Ignore chat room messages sent by unregistered users 不要顯示未註冊用户的聊天消息。 - + Ignore private messages sent by unregistered users 不要顯示未註冊用户的私人消息. - - + + Invert text color 反轉文本顏色 - + Enable desktop notifications for private messages 容許桌面提醒提及私人消息 - + Enable desktop notification for mentions 容許桌面提醒提及聊天提名 - + Enable room message history on join 加入聊天室時開啓消息歷史 - - + + (Color is hexadecimal) (顏色為16進制) - + Separate words with a space, alphanumeric characters only 將單詞以空格區分,僅支持字母. @@ -5893,62 +6467,62 @@ Cockatrice will now reload the card database. Phase - + Unknown Phase 不知名階段 - + Untap 重置步 - + Upkeep 維持步 - + Draw 抓牌步 - + First Main 第一主階步 - + Beginning of Combat 開戰步 - + Declare Attackers 宣告進攻步 - + Declare Blockers 宣告阻擋步 - + Combat Damage 戰鬥損害步 - + End of Combat 戰鬥結束步 - + Second Main 第二主階步 - + End/Cleanup 結束/清理步 @@ -6014,7 +6588,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -6023,134 +6597,134 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - + Move top cards to grave - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6158,17 +6732,17 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters - + S&ay @@ -6176,7 +6750,7 @@ Cockatrice will now reload the card database. PrintingSelector - + Display Navigation Buttons @@ -6184,22 +6758,22 @@ Cockatrice will now reload the card database. PrintingSelectorCardOverlayWidget - + Preference 喜好 - + Pin Printing 版本上別針 - + Unpin Printing 版本脫別針 - + Show Related cards 顯示關聯卡牌 @@ -6215,17 +6789,17 @@ Cockatrice will now reload the card database. PrintingSelectorCardSelectionWidget - + Previous Card in Deck 牌庫次序中上一張卡牌 - + Bulk Selection - + Next Card in Deck 牌庫次序中下一張卡牌 @@ -6233,28 +6807,28 @@ Cockatrice will now reload the card database. PrintingSelectorCardSortingWidget - + Alphabetical 英文字母次序 - + Preference 喜好 - + Release Date 發行日期 - - + + Descending 由後到先 - + Ascending 由先倒後 @@ -6320,37 +6894,37 @@ Cockatrice will now reload the card database. QMenuBar - + Services 服務 - + Hide %1 隐藏 %1 - + Hide Others 隱藏其他 - + Show All 全部展示 - + Preferences... 喜好... - + Quit %1 退出%1 - + About %1 關於 %1 @@ -6358,42 +6932,42 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) Cockatrice卡牌資料庫 (*.xml) - + All files (*.*) 所有檔案格式 (*.*) - + Cockatrice replays (*.cor) Cockatrice錄像文件 (*.cor) - + Maindeck 主牌庫 - + Sideboard 副牌庫 - + Tokens 眾令牌 - + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? @@ -6401,92 +6975,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK OK/確認 - + Save 保存 - + Save All 全部保存 - + Open 打開 - + &Yes &是/Yes - + Yes to &All &全選是/Yes - + &No &否/No - + N&o to All &全選否/No - + Abort 退出 - + Retry 重試 - + Ignore 不理 - + Close 關閉 - + Cancel 取消 - + Discard 棄牌 - + Help 協助 - + Apply 應用 - + Reset 重設 - + Restore Defaults 恢復常用認值 @@ -6583,37 +7157,37 @@ Cockatrice will now reload the card database. RoomSelector - + Rooms 房間(眾)  - + Joi&n &加入 - + Room 房間 - + Description 描述 - + Permissions 許可 - + Players 玩家(眾) - + Games 遊戲(眾) @@ -6654,27 +7228,27 @@ Cockatrice will now reload the card database. SetsModel - + Enabled 已啓用 - + Set type 設置類型 - + Set code 設置代碼 - + Long name 長名 - + Release date 發行日期 @@ -6682,53 +7256,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts 恢復所有常用快鍵 - + Do you really want to restore all default shortcuts? 確定要恢復所有常用快鍵嗎? - + Clear all default shortcuts 清除所有常用快鍵 - + Do you really want to clear all shortcuts? 確認清除所有快鍵嗎? - + Section: 部分: - + Action: 行動: - + Shortcut: 快鍵: - + How to set custom shortcuts 如何設置自定快鍵 - + Clear all shortcuts 清除所有快键 - + Search by shortcut name 按快鍵名稱搜尋 @@ -6736,12 +7310,12 @@ Cockatrice will now reload the card database. ShortcutTreeView - + Action 行動 - + Shortcut 快鍵 @@ -6749,14 +7323,14 @@ Cockatrice will now reload the card database. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! 您的配置檔案含有無效的快捷鍵. 請檢查您的快捷鍵設定. - + The following shortcuts have been set to default: 以下快捷鍵設置已經返回常用狀態: @@ -6784,12 +7358,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -6797,27 +7371,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds 啓用&聲效 - + Current sounds theme: 現前聲效主題: - + Test system sound engine 測試系統聲效引擎 - + Sound settings 聲效設置 - + Master volume 主音量 @@ -6825,48 +7399,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended 預覽季節已結束 - + Deleting spoiler.xml. Please run Oracle 現在正刪除spoiler.xml。請啟動Oracle - - + + Spoilers download failed 預覽下載出錯 - + No internet connection 沒有網絡連接 - + Error 出現錯誤 - + Spoilers already up to date 預覽已是最新 - + No new spoilers added 沒有添加新預覽 - + Spoilers have been updated! 預覽已被更新 - + Last change: 最後變更: @@ -7030,56 +7604,123 @@ Please check your shortcut settings! 不能啟動用戶戶口.內部故障 + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info 卡牌資訊 - + Deck 牌庫 - + Filters 篩選項目 - + &View &視圖 - + + Card Database + + + + Printing 印刷版本 - - - - + + + + + Visible 可見 - - - - + + + + + Floating 浮動 - + Reset layout 重置布局 - + Deck: %1 牌庫:%1 @@ -7087,61 +7728,61 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + + + + Visible - - - - + + + + Floating - + Reset layout @@ -7149,22 +7790,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7172,133 +7813,133 @@ Please check your shortcut settings! TabDeckStorage - + Local file system 本地檔案系統 - + Server deck storage 伺服器牌庫儲藏 - - + + Open in deck editor 在套牌編輯打開 - + Rename deck or folder - + Upload deck 上載牌庫 - + Download deck 下载套牌 + - - - + + New folder 新文件夾 + - Delete 删除 - + Open decks folder 打開牌庫文件夾 - + Rename local folder - + Rename local file - + New name: - - - - + + + + Error 出現錯誤 - + Rename failed - - + + Invalid deck file 無效牌庫檔案 - + Enter deck name 輸入牌庫名稱 - + This decklist does not have a name. Please enter a name: 本牌庫列表未有名稱.請輸入名稱: - + Unnamed deck 未命名牌庫 - + Failed to upload deck to server 牌庫上載於伺服器時失敗 - + Delete local file 删除本地檔案 - + Are you sure you want to delete the selected files? 你確定要刪除被選檔案(眾)嗎 ? - + Delete remote decks 刪除伺服器上的牌庫 - + Are you sure you want to delete the selected decks? 你確定要剷除所有被選的牌庫嗎? - - + + Name of new folder: 新建文件夾名稱: @@ -7311,17 +7952,17 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 @@ -7362,7 +8003,7 @@ Please enter a name: - + EDHRec: @@ -7370,197 +8011,197 @@ Please enter a name: TabGame - - - + + + Replay 遊戲錄像重溫 - - + + Game 遊戲 - - + + Player List 玩家列表 - - + + Card Info 卡牌資訊 - - + + Messages 信息 - - + + Replay Timeline 錄像時間線 - + &Phases 階段 - + &Game 遊戲 - + Next &phase 下個&階段 - + Next phase with &action 下個階段&行動 - + Next &turn 下個&回合 - + Reverse turn order 逆轉回合次序 - + &Remove all local arrows &移除所有自設箭頭 - + Rotate View Cl&ockwise &順時針旋轉視角 - + Rotate View Co&unterclockwise &逆時針旋轉視角 - + Game &information 遊戲&資訊 - + Un&concede 撤銷&投降 - - - + + + &Concede &投降 - + &Leave game &離開遊戲 - + C&lose replay &關閉遊戲錄像 - + &Focus Chat &聚焦聊天 - + &Say: &説: - + Selected cards - + &View &視圖 - - + + + - Visible 可見 - - + + + - Floating 浮動 - + Reset layout 重置布局 - + Concede 投降 - + Are you sure you want to concede this game? 你確定要宣佈投降嗎? - + Unconcede 撤銷投降 - + You have already conceded. Do you want to return to this game? 你已經宣佈投降,你想回到這場遊戲嗎? - + Leave game 離開遊戲 - + Are you sure you want to leave this game? 你確定要離開這個遊戲嗎? - + A player has joined game #%1 有玩家加入遊戲 #%1 - + %1 has joined the game %1 已經加入遊戲。 - + You have been kicked out of the game. 你已被踢出遊戲。 @@ -7568,7 +8209,7 @@ Please enter a name: TabHome - + Home @@ -7576,158 +8217,158 @@ Please enter a name: TabLog - + Logs 記錄 - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName 時間;發送者姓名;發送者IP;信息;目標ID;目標姓名 - + Room Logs 房間記錄 - + Game Logs 遊戲記錄 - + Chat Logs 聊天記錄 - - + + Error 出現錯誤 - + You must select at least one filter. 您必須選擇至少一個過濾項. - + You have to select a valid number of days to locate. 您必須選擇一個可用的日數來作為定位. - + Username: 用戶名稱: - + IP Address: IP地址 - + Game Name: 遊戲名稱: - + GameID: 遊戲ID: - + Message: 信息: - + Main Room 主房 - + Game Room 遊戲房間 - + Private Chat 私人聊天 - + Past X Days: 過去 X 天: - + Today 今天 - + Last Hour 1小時前 - + Maximum Results: 最多結果: - + At least one filter is required. The more information you put in, the more specific your results will be. 至少需要1個篩選項。 您輸入的信息越多,找到結果越準確。 - + Get User Logs 獲取用戶記錄 - + Clear Filters 清除篩選項目 - + Filters 篩選項目 - + Log Locations 記錄位置 - + Date Range 日期範圍 - + Maximum Results 最多結果 - - + + Message History 信息歷史 - + Failed to collect message history information. 不能獲取信息歷史消息 - + There are no messages for the selected filters. 沒有符合篩選條件的信息。 @@ -7773,180 +8414,180 @@ The more information you put in, the more specific your results will be. TabReplays - + Local file system 本地檔案系統 - + Server replay storage 伺服器錄像倉庫 - - + + Watch replay 觀看遊戲錄像 - + Rename 改名 - - + + New folder 新文件夾 - - + + Delete 刪除 - + Open replays folder 打開錄像文件夾 - + Download replay 下載遊戲錄像 - + Toggle expiration lock 過期鎖定 - + Get replay share code - - + + Look up replay by share code - + Rename local folder 本地檔案夾改名 - + Rename local file 本地檔案改名 - + New name: 新名稱 - + Error 出錯 - + Rename failed 改名不成功 - + Name of new folder: 新建文件夾名稱: - + Delete local file 刪除本地檔案 - + Are you sure you want to delete the selected files? 你確定要剷除所有被選的檔案嗎? - + Are you sure you want to delete the selected replays? 你確定要刪除被選錄像檔案嗎 ? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay 刪除伺服器錄像 @@ -8012,30 +8653,30 @@ The more information you put in, the more specific your results will be.伺服器 + - Error 出現錯誤 - + Failed to join the server room: it doesn't exist on the server. 加入伺服器房間失敗:在本伺服器上這房間並不存在。 - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. 伺服器認為你在房間內, 但Cockatrice無法顯示。請重新啓動Cockatrice。 - + You do not have the required permission to join this server room. 你未被允許進入該房間。 - + Failed to join the server room due to an unknown error: %1. 因為以下不知名的錯誤,所以不能參入伺服器房間: %1. @@ -8043,92 +8684,97 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - - - Home - - - - - &Visual Deck Storage - - - - - Visual Database Display - - - Server + Archidekt - Account + Home - Deck Storage + &Visual Deck Storage - Game Replays + Visual Database Display - Administration + Server + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + Logs - + Are you sure? 你確定嗎? - + There are still open games. Are you sure you want to quit? 遊戲還在繼續, 你確定要退出嗎? - + Click to view 點擊查看 - + Your buddy %1 has signed on! 你的好友%1已登錄! - + Unknown Event 不知名事件 - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8139,38 +8785,38 @@ To update your client, go to Help -> Check for Updates. 要升級你的客户端,請點擊“幫助”->“檢查更新” - + Idle Timeout 閒置超時 - + You are about to be logged out due to inactivity. 你即將因為長時間沒有活動而被退出。 - + Promotion 升級 - + You have been promoted. Please log out and back in for changes to take effect. 你已被升級.請登出然後再登入讓本更改生效. - + Warned 被警告 - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. 你由於$1 原因,現在接受警告. 請避免繼續本行為, 否則你將有可能會接受進一步的警誡或懲罰. 若有任何疑問, 請私信我們門下的版主. - + You have received the following message from the server. (custom messages like these could be untranslated) 你收到松本伺服器發出的以下訊息(類似本信息的自定訊息可能未經翻譯) @@ -8179,7 +8825,12 @@ Please refrain from engaging in this activity or further actions may be taken ag TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display @@ -8261,7 +8912,7 @@ Please refrain from engaging in this activity or further actions may be taken ag UpdateDownloader - + Could not open the file for reading. 無法打開文件。 @@ -8269,206 +8920,206 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details 用户&詳細信息 - + Private &chat 私人&聊天 - + Show this user's &games 顯示這個用户的&遊戲 - + Add to &buddy list 加入到&好友列表 - + Remove from &buddy list 從&好友列表移除 - + Add to &ignore list 加入到&屏蔽列表 - + Remove from &ignore list 從&屏蔽列表移除 - + Kick from &game 從&遊戲中踢出 - + Warn user 警告用戶 - + View user's war&n history 查看用户警告记录 - + Ban from &server 把用戶從&伺服器禁用 - + View user's &ban history 查看用户&禁止記錄 - + &Promote user to moderator 升$用戶作為版主 - + Dem&ote user from moderator 把&用戶從版主降級 - + Promote user to &judge 把&用戶升級成裁判 - + Demote user from judge 把&用戶從裁判降級 - + View admin notes 閱讀管理筆記 - - - + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games %1 的遊戲(眾) - - - + + + Ban History 禁用歷史 - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason 禁止時間;版主;禁止時長;禁止理由;可見理由 - + User has never been banned. 用戶從未被禁 - + Failed to collect ban information. 不能獲取禁用訊息 - - - + + + Warning History 警告歷史 - + Warning Time;Moderator;User Name;Reason 警告時間;版主;用戶名稱;理由 - + User has never been warned. 用戶從未接收警告 - + Failed to collect warning information. 不能獲取警告信息 - + Failed to get admin notes. 不能獲取管理筆記 - - + + Success 成功 - + Successfully promoted user. 用戶升級成功 - + Successfully demoted user. 用戶降級成功 - + + - Failed 失敗 - + Failed to promote user. 用戶升級失敗 - + Failed to demote user. 用戶降級失敗 - + Copy hash to clipboard 把哈希覆印到用戶剪貼 - + Remove this user's messages 移除這用戶訊息 @@ -8650,137 +9301,142 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings 一般通用接口設置 - + &Double-click cards to play them (instead of single-click) 雙點擊使出卡牌 (而不是單點擊使出) - + &Clicking plays all selected cards (instead of just the clicked card) &單一點擊把所有選擇的卡牌使出(不只是被點擊的單張卡牌) - + &Play all nonlands onto the stack (not the battlefield) by default &把所有使出的非地牌放入堆疊區(不是戰場) 作為常用設置 - - - Close card view window when last card is removed - - - - - Auto focus search bar when card view window is opened - - - - - Annotate card text on tokens - 在令牌上標註牌中文字 - - - - Use tear-off menus, allowing right click menus to persist on screen - 使用浮動菜單,允許右鍵單擊, 把菜單保留在屏幕上 - - - - Notifications settings - 通告設置 - - - - Enable notifications in taskbar - 開啓任務欄通告 - - - - Notify in the taskbar for game events while you are spectating - 觀看時在任務欄提示遊戲信息 - - - - Notify in the taskbar when users in your buddy list connect - 在任務欄中提示好友連到伺服器 - - - - Animation settings - 動畫設定 - - - - &Tap/untap animation - &橫置/重置 動畫 - - Deck editor/storage settings + Do not delete &arrows inside of subphases - Open deck in new tab by default - 把用新頁開啟牌庫作為常用設置 + Close card view window when last card is removed + - Use visual deck storage in game lobby + Auto focus search bar when card view window is opened - Use selection animation for Visual Deck Storage - + Annotate card text on tokens + 在令牌上標註牌中文字 + + + + Use tear-off menus, allowing right click menus to persist on screen + 使用浮動菜單,允許右鍵單擊, 把菜單保留在屏幕上 - When adding a tag in the visual deck storage to a .txt deck: - + Notifications settings + 通告設置 + + + + Enable notifications in taskbar + 開啓任務欄通告 - do nothing - + Notify in the taskbar for game events while you are spectating + 觀看時在任務欄提示遊戲信息 + + + + Notify in the taskbar when users in your buddy list connect + 在任務欄中提示好友連到伺服器 - ask to convert to .cod - + Animation settings + 動畫設定 + + + + &Tap/untap animation + &橫置/重置 動畫 - always convert to .cod + Deck editor/storage settings - Default deck editor type - + Open deck in new tab by default + 把用新頁開啟牌庫作為常用設置 - Classic Deck Editor + Use visual deck storage in game lobby + Use selection animation for Visual Deck Storage + + + + + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + Visual Deck Editor - + Replay settings 重播設置 - + Buffer time for backwards skip via shortcut: 用速鍵啟動向後跳的緩衝時間: @@ -8788,22 +9444,22 @@ Please refrain from engaging in this activity or further actions may be taken ag UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 @@ -8811,32 +9467,32 @@ Please refrain from engaging in this activity or further actions may be taken ag UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -8844,22 +9500,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match - + Mode: Includes - + Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -8867,21 +9523,49 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8907,28 +9591,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - Load from Deck + Filter by name... (Exact match) - Apply all card names in currently loaded deck as exact match name filters + Load from Deck - Load from Clipboard + Apply all card names in currently loaded deck as exact match name filters + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -8992,50 +9676,115 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - + + Sort by: + + + + + Filter by: + + + + Save and load filters - + Filter by exact card name - + Filter by card sub-type - + Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9043,46 +9792,25 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -9138,7 +9866,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9146,22 +9874,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -9169,17 +9897,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + Visual Deck Storage Settings @@ -9187,43 +9915,43 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? 你想發出那種警告? - + Redact all messages from this user in all rooms 把這用戶在所有房間留下的訊息刪除 - + &OK &確認 - + &Cancel &取消 - + Warn user for misconduct 對行為不當的用戶發出警告 - - + + Error 出現錯誤 - + User name to send a warning to can not be blank, please specify a user to warn. 接收警告的用戶名稱不能空白.請定好要接收警告的用戶名字. - + Warning to use can not be blank, please select a valid warning to send. 警告名稱不能留空. 請選擇有效的警告. @@ -9231,133 +9959,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top 將選擇的牌組送到最頂 - + Move selected set up 將選擇的牌組往上一格 - + Move selected set down 將選擇的牌組往下一格 - + Move selected set to the bottom 將選擇的牌組送到最底 - + Search by set name, code, or type 用排組名稱,代碼,或類型進行搜尋 - + Default order 常用次序 - + Restore original art priority order 恢復常用藝術先後次序 - + Enable all sets 啟用所有牌組 - + Disable all sets 禁用所有卡组 - + Enable selected set(s) 啟用選擇的牌組(眾)  - + Disable selected set(s) 停用選擇的牌組(眾) - + Deck Editor 牌庫編輯 - + Use CTRL+A to select all sets in the view. 請按 CTRL + A, 以此選擇所有看到的牌組 - + Only cards in enabled sets will appear in the card list of the deck editor. 只有已經被啟用的卡牌組中的卡牌,才會在牌庫編輯中的卡牌清單出現 - + Image priority is decided in the following order: 卡牌圖片先後次序由以下次序決定: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki 自訂檔案夾(%`1)優先, 此對話窗中已被啟用的牌組為後(由頂到底) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art 卡牌藝術 - + How to use custom card art 如何使用自定卡牌圖片 - + Hints 提示 - + Note 筆記 - + Sorting by column allows you to find a set while not changing set priority. 按列排序允許您在不更改系列優先級別的情況下查找牌組。 - + To enable ordering again, click the column header until this message disappears. 要再次啓用排序,請單擊列標題,直到此消息消失。 - + Use the current sorting as the set priority instead 用現在排序代替牌組先後 - + Sorts the set priority using the same column 用同一列分牌組先後 - + Manage sets 管理牌組 @@ -9365,72 +10093,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped 未分組 - + Group by Type 按類型分組 - + Group by Mana Value 按法力數值分組 - + Group by Color 按顏色排序 - + Unsorted 未排序 - + Sort by Name 按名稱排次序 - + Sort by Type 按類型排序 - + Sort by Mana Cost 按法力費用排序 - + Sort by Colors 按顏色(眾) 排序 - + Sort by P/T 按力量/防禦力排序 - + Sort by Set 按排組排序 - + shuffle when closing 關閉界面時洗牌 - + pile view 棟疊觀看 @@ -9438,7 +10166,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English @@ -9446,12 +10174,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup 啟動時自動連接 - + Debug to file 偵錯紀錄寫進檔案 @@ -9459,1005 +10187,1041 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window 主視窗 - - + + Deck Editor 牌庫編輯 - + Game Lobby 遊戲廳堂 - + Card Counters 牌指示物 - + Player Counters 玩家指示物 - + Power and Toughness 力量和防御力 - + Game Phases 遊戲階段 - + Playing Area 遊戲區域 - + Move Selected Card 移動所選牌張 - + View 查看 - + Move Top Card 移動頂牌 - + Move Bottom Card 移動底牌 - + Gameplay 遊戲 - + Drawing 抽牌 - + Chat Room 聊天室 - + Game Window 遊戲視窗 - + Load Deck from Clipboard 從剪貼板載入牌庫 - - + + Replays 遊戲錄像 - + Tabs - + Check for Card Updates... 檢查卡牌更新 - + Connect... 連線 - + Disconnect 斷開連線 - + Exit 退出 - + Full screen 全屏 - + Register... 註冊... - + Settings... 設定... - + Start a Local Game... 開始離線遊戲 - + Watch Replay... 觀看錄像 - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters 清除所有篩選項目 - + Clear Selected Filter 清除所選的篩選項目 - + Close 關閉 - + Remove Card 移除卡牌 - + Manage Sets... 管理牌組 - + Edit Custom Tokens... 編輯自定眾令牌... - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card 添加卡牌 - + Load Deck... 載入牌庫... - - + + Load Deck from Clipboard... 從剪貼板載入牌庫 - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck 創建新牌庫 - + Open Custom Pictures Folder 打開自定圖片文件夾 - + Print Deck... 印刷牌庫 - + Delete Card 刪除卡牌 - - + + Reset Layout 重置佈局 - + Save Deck 保存牌庫 - + Save Deck as... 牌庫另存為 - + Save Deck to Clipboard, Annotated 從剪貼板儲藏牌庫, 帶注釋 - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard 從剪貼板儲藏牌庫 - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... 從自身檔案載入牌庫 - + Load Remote Deck... 載入伺服器上的牌庫... - + Set Ready to Start 設定準備開始 - + Toggle Sideboard Lock 切換副牌庫鎖 - + Add Green Counter 增加綠色指示物 - + Remove Green Counter 移除綠色指示物 - + Set Green Counters... 設定綠色指示物 - + Add Red Counter 增加红色指示物 - + Remove Red Counter 移除紅色指示物 - + Set Red Counters... 設定紅色指示物 - + Add Life Counter 增加生命指示物 - + Show Status Bar - + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter 移除生命指示物 - + Set Life Counters... 設定生命指示物 - + Add White Counter 增加白色指示物 - + Remove White Counter 移除白色指示物 - + Set White Counters... 設定白色指示物 - + Add Blue Counter 增加藍色指示物 - + Remove Blue Counter 移除藍色指示物 - + Set Blue Counters... 設定藍色指示物 - + Add Black Counter 增加黑色指示物 - + Remove Black Counter 移除黑色指示物 - + Set Black Counters... 設定黑色指示物 - + Add Colorless Counter 增加無色指示物 - + Remove Colorless Counter 移除無色指示物 - + Set Colorless Counters... 設定無色指示物 - + Add Other Counter 增加其他指示物 - + Remove Other Counter 移除其他指示物 - + Set Other Counters... 設定其他指示物 - + Increment all card counters - + Add Power (+1/+0) 增加力量(+1/+0) - + Remove Power (-1/-0) 削減力量(-1/-0) - + Move Toughness to Power (+1/-1) 防禦移到力量(+1/-1) - + Add Toughness (+0/+1) 增加防御(+0/+1) - + Remove Toughness (-0/-1) 削減防御(-0/-1) - + Move Power to Toughness (-1/+1) 力量移到防禦(-1/+1) - + Add Power and Toughness (+1/+1) 增加力量和防御(+1/+1) - + Remove Power and Toughness (-1/-1) 削減力量和防御(-1/-1) - + Set Power and Toughness... 設置力量和防禦 - + Reset Power and Toughness 重置力量和防御 - + Untap 重置 - + Upkeep 維持 - + Draw 抽牌 - + First Main Phase 第一主階段 - + Start Combat 開始戰鬥 - + Attack 攻擊 - + Block 擋格 - + Damage 計算損害 - + End Combat 戰鬥结束 - + Second Main Phase 第二主階段 - + End 完結 - + Next Phase 下個階段 - + Next Phase Action 下個行動階段 - + Next Turn 下一回合 - + Hide Card in Reveal Window 從揭露視窗中隱藏這牌 - + Tap / Untap Card 横置/重置這牌 - + Untap All 重置全牌 - + Toggle Untap 鎖定重置狀態 - + Turn Card Over 反轉本牌 - + Peek Card 查看卡牌 - + Play Card 使出本牌 - + Attach Card... 结附牌 - + Unattach Card 解除牌结附 - + Clone Card 複製卡牌 - + Create Token... 派出令牌 - + Create All Related Tokens 派出所有相關令牌 - + Create Another Token 再做另外令牌 - + Set Annotation... 記下註解 - + Select All Cards in Zone 選擇所有在區域的牌 - + Select All Cards in Row 選擇所有橫行牌 - + Select All Cards in Column 選擇所有直行牌 - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library 牌庫底 - - - - + + + + Exile 放逐 - - - - + + + + Graveyard 墳場 - - + + + Hand 手牌 - - + + Top of Library 牌庫頂 - - - + + + Battlefield, Face Down 戰場,面朝下 - + Battlefield 戰場 - - Sort Hand - - - - + Library 牌庫 - + Sideboard 副牌庫 - + Top Cards of Library 排副多張頂牌 - + Bottom Cards of Library 排副多張底牌 - + Close Recent View 關閉最近查看 - - + + Stack 疊區 - - + + Graveyard (Multiple) 多張送墳場 - - + + Exile (Multiple) 多張放逐 - + Stack Until Found 找到以下牌前全送疊區 - + Draw Bottom Card 從牌庫底抽一張牌 - + Draw Multiple Cards from Bottom... 從牌庫底抽超過一張牌 - + Draw Arrow... 畫箭咀 - + Remove Local Arrows 清除自設箭咀 - + Leave Game 離開遊戲 - + Concede 投降 - + Roll Dice... 擲骰 - + Shuffle Library 洗牌 - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan 再調度 - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card 抽一張牌 - + Draw Multiple Cards... 抽超過一張牌 - + Undo Draw 撤銷抽牌 - + Always Reveal Top Card 永遠顯示頂牌 - + Always Look At Top Card 永遠看到頂牌 - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise 視角順時針轉 - + Rotate View Counterclockwise 視角逆時針轉 - + Unfocus Text Box 取消聚焦文本框 - + Focus Chat 聚焦聊天 - + Clear Chat 掃清聊天 - + Refresh 刷新 - + Skip Forward 跳前 - + Skip Backward 跳後 - + Skip Forward by a lot 向前大跳 - + Skip Backward by a lot 向後大跳 - + Play/Pause 播放/暫停 - + Toggle Fast Forward 切換快進 - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_zh-Hans.ts b/cockatrice/translations/cockatrice_zh-Hans.ts index 7793b3f35..f26c5939b 100644 --- a/cockatrice/translations/cockatrice_zh-Hans.ts +++ b/cockatrice/translations/cockatrice_zh-Hans.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &设置指示物 @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter 设置指示物 - + New value for counter '%1': 数值物新价值为 '%1' @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,79 +36,81 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. + + Add Analytics Panel AdminNotesDialog - + Update Notes 更新后笔记 - + Admin Notes for %1 %1 的管理笔记 @@ -116,12 +118,12 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard 主牌库 - + Sideboard 副牌库 @@ -129,22 +131,22 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error 出现错误 - + Could not create themes directory at '%1'. 不能在'%1'创造主题目录 - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -155,7 +157,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -166,152 +168,165 @@ Are you sure you would like to disable this feature? - + Confirm Change - + Theme settings 主题设定 - + Current theme: 现在主题 - + Open themes folder 打开主题文件夹 - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings 选单设定 - + Show keyboard shortcuts in right-click menus 在右击选单中显示键盘快速键 - + + Show game filter toolbar above list in room tab + + + + Card rendering 卡面算绘 - + Display card names on cards having a picture 最有图片的牌上显示牌名 - + Auto-Rotate cards with sideways layout 将打横佈局卡片自动向上旋转 - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector 将牌库中包含的牌组推到艺术选择视窗之顶 - + Scale cards on mouse over 卡牌随游标缩放 - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand 在迭区中和直行手牌中,尽量减少交重迭百分点 - + Maximum initial height for card view window: 卡望框最高开头高度 - - + + rows 横列 - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout 手牌佈局 - + Display hand horizontally (wastes space) 打栋显示手牌(浪费空间) - + Enable left justification 啟用向左对齐 - + Table grid layout 表格布局 - + Invert vertical coordinate 反转垂直坐标 - + Minimum player count for multi-column layout: 界面佈局之内能够容纳的最少玩家数量: - + Maximum font size for information displayed on cards: 卡牌上显示的最大字体大小 + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -333,112 +348,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name 禁用 &用家名字 - + ban &IP address 禁用&IP地址 - + ban client I&D 禁用客户ID - + Ban type 禁用类型 - + &permanent ban &永久禁用 - + &temporary ban &暂时禁用 - + &Days: &日 - + &Hours: &小时 - + &Minutes: &分鐘 - + Duration of the ban 禁用时间 - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. 请记载禁用原因 这项只保留为版主, 被禁用户不能看到. - + Please enter the reason for the ban that will be visible to the banned person. 请记载被禁用户能看到的被禁原因. - + Redact all messages from this user in all rooms 将这用户所有室中打出的讯息完全移除 - + &OK &OK - + &Cancel &取消 - + Ban user from server 把用户从本伺服器禁用 - + + - - + Error 出现错误 - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. 若要对用户实施禁用,请你选择以下叁种的拼合:名字,IP,或用户id. - + You must have a value in the name ban when selecting the name ban checkbox. 若用用户名字施行禁用,必须要填写姓名. - + You must have a value in the ip ban when selecting the ip ban checkbox. 若用ip地址施行禁用,必须要写下IP地址. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. 若用用户ID施行禁用,必须要写下用户ID. @@ -469,32 +484,32 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name 名字 - + Sets 牌组 - + Mana cost 法力费用 - + Card type 卡牌类别 - + P/T 力量/防御力 - + Color(s) 顏色 @@ -502,96 +517,101 @@ This is only saved for moderators and cannot be seen by the banned person. CardFilter - + AND Logical conjunction operator used in card filter - + OR Logical disjunction operator used in card filter - + AND NOT Negated logical conjunction operator used in card filter 和不是 - + OR NOT Negated logical disjunction operator used in card filter 或不是 - + Name 名称 - + + Name (Exact) + + + + Type 种类 - + Color 顏色 - + Text 词语 - + Set 卡组 - + Mana Cost 法力费用 - + Mana Value 法力数值 - + Rarity 稀有度 - + Power 力量 - + Toughness 防御力 - + Loyalty 忠诚值 - + Format 赛制 - + Main Type - + Sub Type @@ -599,22 +619,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoFrameWidget - + Image 卡图 - + Description 描述 - + Both 两个都显示 - + View transformation @@ -622,22 +642,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards 查看关联卡牌 - + Add card to deck - + Mainboard - + Sideboard @@ -650,12 +670,22 @@ This is only saved for moderators and cannot be seen by the banned person.名称: - + + Set: + + + + + Collector Number: + + + + Related cards: 有关牌名: - + Unknown card: 不知名的牌 @@ -663,124 +693,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -788,7 +818,7 @@ This is only saved for moderators and cannot be seen by the banned person. CardSizeWidget - + Card Size 卡牌大小 @@ -796,133 +826,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative - + %1's hand nominative - - - their library - look at zone - - - - - %1's library - look at zone - - - - - of their library - top cards of zone, - - - - - of %1's library - top cards of zone - - their library - reveal zone + look at zone %1's library + look at zone + + + + + of their library + top cards of zone, + + + + + of %1's library + top cards of zone + + + + + their library reveal zone - + + %1's library + reveal zone + + + + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - - - their graveyard - nominative - - - - - %1's graveyard - nominative - - - - - their exile - nominative - - - - - %1's exile - nominative - - + their graveyard + nominative + + + + + %1's graveyard + nominative + + + + + their exile + nominative + + + + + %1's exile + nominative + + + + their sideboard look at zone - + %1's sideboard look at zone - - - their sideboard - nominative - - - - - %1's sideboard - nominative - - - their custom zone '%1' + their sideboard nominative + %1's sideboard + nominative + + + + + their custom zone '%1' + nominative + + + + %1's custom zone '%2' nominative @@ -931,7 +961,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml3Parser - + Parse error at line %1 col %2: @@ -939,7 +969,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml4Parser - + Parse error at line %1 col %2: @@ -958,6 +988,37 @@ This is only saved for moderators and cannot be seen by the banned person. + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -969,47 +1030,47 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add to Deck - + Add to Sideboard - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1017,87 +1078,97 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing - + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + + Format: + + + + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1105,17 +1176,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1123,114 +1194,114 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1238,7 +1309,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1246,194 +1317,227 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers 更新预览卡牌 - - + + Success 成功 - + Download URLs have been reset. 下载URL已重置。 - + Downloaded card pictures have been reset. 下载的卡牌图片已被重置。 - + Error 出现错误 - + One or more downloaded card pictures could not be cleared. 一幅或以上的卡牌图片未能被清除。 - + Add URL 添加URL - - + + URL: URL: - - + + Edit URL 更改URL - + Network Cache Size: 图片网路缓存大小: - + Redirect Cache TTL: 网路缓处再定向生存时间 - + How long cached redirects for urls are valid for. 再定向缓处有效时间 - + Picture Cache Size: 图片缓存大小: - + Add New URL 添置新 URL - + Remove URL 移除 URL. - + Day(s) - + Updating... 更新中... - + Choose path 选择路径 - + URL Download Priority URL下载优先次序 - + Spoilers 预览 - + Download Spoilers Automatically 自动下载预览 - + Spoiler Location: 预览位置: - + Last Change 最后变更 - + Spoilers download automatically on launch 开啟时自动下载预览 - + Press the button to manually update without relaunching 免重新啟动人手按制更新 - + Do not close settings until manual update is complete 人手更新完成前,不能关掉设置 - + Download card pictures on the fly 即时下载卡牌图片 - + How to add a custom URL 如何添加自定URL - + Delete Downloaded Images 删除已下载的图片 - + Reset Download URLs 重置下载URL - + On-disk cache for downloaded pictures 下载图片专用硬盘缓储 - + In-memory cache for pictures not currently on screen 现不在萤光幕上记忆中图片缓储 + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count 数目 - + Set 牌组 - + Number 号码 - + Provider ID 提供者 id - + Card 卡牌 @@ -1441,12 +1545,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1454,17 +1558,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1472,7 +1576,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -1480,62 +1584,62 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -1548,93 +1652,151 @@ This is only saved for moderators and cannot be seen by the banned person. - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1652,74 +1814,74 @@ This is only saved for moderators and cannot be seen by the banned person. DeckViewContainer - + Load deck... 载入牌库... - + Load remote deck... 载入伺服器上的牌库... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start 準备好 - + Force start 强迫开始 - + Sideboard unlocked 备牌解锁 - + Sideboard locked 解锁锁上 - - + + Error 出现错误 - + The selected file could not be loaded. 选择的档案不能再入 - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -1752,143 +1914,143 @@ This will kick all non-ready players from the game. 下载中... - + Known Hosts 已知主办机 - + Delete the currently selected saved server 删除现选储藏伺服器. - + Refresh the server list with known public servers 从已知的伺服器更新伺服器列表 - + New Host 新主办 - + Name: 名称: - + &Host: &主办机: - + &Port: &端口: - + Player &name: 玩家 &名字: - + P&assword: &密码: - + &Save password &储存密码 - + A&uto connect &自动连接 - + Automatically connect to the most recent login when Cockatrice opens 当Cockatrice启动时自动连接到最近的登录 - + If you have any trouble connecting or registering then contact the server staff for help! 如在连接或註册谁中遇到问题,请联络伺服器职员寻求援助! - - + + Webpage 网页 - + Reset Password 重设密码 - + Forgot password? 密码忘记了? - + &Connect 连接 - + Server 伺服器 - + Login 登录 - + Server Contact 伺服器联络人 - + Connect to Server 连接伺服器 - + Server URL 伺服器URL - + Communication Port 通信端口 - + Unique Server Name 独特伺服器名字 - + Connection Warning 连接警告 - + You need to name your new connection profile. 你必须将你的连接简介命名 - + Connect Warning 连接警告 - + The player name can't be empty. 玩家名称不能空置 @@ -1896,117 +2058,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings &记住设定 - + &Description: &描述: - + P&layers: 玩家: - + General 一般 - + Game type 游戏类型 - + &Password: &密码: - + Only &buddies can join 只容许&好友加入 - + Only &registered users can join 只容许&註册用户加入 - + Joining restrictions 加入条件 - + &Spectators can watch 允许观看者 - + Spectators &need a password to watch 观看者&需要密码 - + Spectators can &chat 观看者可以&聊天 - + Spectators can see &hands 观看者可见&手牌 - + Create game as spectator 以旁观者的身份创建游戏 - + Spectators 观看者 - + Starting life total: 开始生命总数 - + Open decklists in lobby - + + Create game as judge + + + + Game setup options 游戏设定选择 - + &Clear &清除 - + Create game 创建游戏 - + Game information 游戏资讯 - + Error 出现错误 - + Server error. 伺服器出现错误. @@ -2014,97 +2181,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &名称: - + Token 令牌 - + C&olor: &顏色: - + white 白色 - + blue 蓝色 - + black 黑色 - + red 红色 - + green 绿色 - + multicolor 多色 - + colorless 无色 - + &P/T: &力量/防御力: - + &Annotation: &註释: - + &Destroy token when it leaves the table &此令牌离场时会被摧毁 - + Create face-down (Only hides name) - + Token data 令牌资料 - + Show &all tokens 显示&所有令牌 - + Show tokens from this &deck 显示本牌库所有令牌 - + Choose token from list 从清单中选择令牌 - + Create token 派出令牌 @@ -2112,53 +2279,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2166,40 +2333,40 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. 未选择图像 - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. 若要改变头着,请选择新的图片. 若要移除现有头像, 请不要选择新的图片,直接确认. - + Browse... 瀏览... - + Change avatar 更改头像 - + Open Image 打开图片 - + Image Files (*.png *.jpg *.bmp) 图片文件(*.png *.jpg *.bmp格式) - + Invalid image chosen. 所选图片不能使用。 @@ -2207,17 +2374,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2225,38 +2392,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: 旧密码: - + New password: 新密码: - + Confirm new password: 确认新密码: - + Change password 更改密码 - - + + Error 出现错误 - + Your password is too short. 你的密码太短。 - + The new passwords don't match. 新旧密码不一致 @@ -2264,93 +2431,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: &名称: - + C&olor: &顏色: - + white 白色 - + blue 蓝色 - + black 黑色 - + red 红色 - + green 绿色 - + multicolor 多色 - + colorless 无色 - + &P/T: &力量/防御力: - + &Annotation: &註释: - + Token data 令牌资料 - - + + Add token 添加令牌 - + Remove token 移除令牌 - + Edit custom tokens 编辑自定令牌 - + Please enter the name of the token: 请输入令牌名称: - + Error 出现错误 - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. 现时选择的名字跟现有的卡牌或令牌出现衝突.请从"牌组管理"中啟动'令牌牌组',保持正确显示. @@ -2443,7 +2610,8 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games not created by buddy + Hide games not created by buddies + Hide games not created by buddy @@ -2530,52 +2698,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning 重设密码验证警告 - + A problem has occurred. Please try to request a new password again. 程序出现故障,请再要求新的密码. - + Enter the information of the server and the account you'd like to request a new password for. 请输入需要新密码的伺服器和相关户口资料. - + &Host: &主办机: - + &Port: &端口: - + Player &name: 玩家 &名字: - + Email: 电邮: - + Reset Password Challenge 重设密码验证 - + Reset Password Challenge Error 重设密码验证错误 - + The email address can't be empty. 电邮不能空置. @@ -2583,37 +2751,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. 请输入需要新密码的伺服器资料. - + &Host: &主办机: - + &Port: &端口: - + Player &name: 玩家 &名字: - + Reset Password Request 重设密码要求 - + Reset Password Error 重设密码错误 - + The player name can't be empty. 玩家名称不能空置 @@ -2621,86 +2789,86 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning 重设密码警告 - + A problem has occurred. Please try to request a new password again. 程序出现故障,请再要求新的密码. - + Enter the received token and the new password in order to set your new password. 请输入收到验证码和新的密码,以便定立新的密码. - + &Host: &主办机: - + &Port: &端口: - + Player &name: 玩家 &名字: - + Token: 验证码 - - + + New Password: 新密码: - + Reset Password 重设密码 - + The player name can't be empty. 玩家名称不能空置 - - - - + + + + Reset Password Error 重设密码错误 - + The token can't be empty. 验证码不能为空白 - + The new password can't be empty. 新密码不能为空白 - + Error 出现错误 - + Your password is too short. 你的新密码太短。 - + The passwords do not match. 新旧密码不一致. @@ -2716,17 +2884,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard 从剪贴板载入牌库 - + Error 出现错误 - + Invalid deck list. 牌表不能载入 @@ -2734,43 +2902,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2784,7 +2952,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck 载入牌库 @@ -2792,37 +2960,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): 卡牌名称(或搜寻表达式) - + Number of hits: 因为cockatrice原生没有ssl支援, 所以你不能自动下载更新. 请瀏览到我们的下载网页,进行人手更新. - + Auto play hits 开关副牌上锁 - + Put top cards on stack until... 把顶牌放在重迭区, 直到... - + No cards matching the search expression exists in the card database. Proceed anyways? 在卡牌资料库中找不到符合搜寻表达式的卡牌.你想继续吗? - + Cockatrice Cockatrice - + Invalid filter 筛选无效 @@ -2830,7 +2998,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. 输入你, 和你想登记的伺服器的资料. @@ -2838,85 +3006,85 @@ Your email will be used to verify your account. 我们会使用你的电邮来核实你的户口. - + &Host: &主办机: - + &Port: &端口: - + Player &name: 玩家 &名字: - + P&assword: &密码: - + Password (again): (再次)输入密码: - + Email: 电邮: - + Email (again): (再次)输入电邮: - + Country: 国家: - + Undefined 未定义 - + Real name: 实名: - + Register to server 註册加入伺服器 - - - - + + + + Registration Warning 注册警告 - + Your password is too short. 你的密码太短。 - + Your passwords do not match, please try again. 两次输入的密码不一致,请重新输入, - + Your email addresses do not match, please try again. 两次输入的电子邮箱不一致,请重新输入。 - + The player name can't be empty. 玩家名称不能空置 @@ -2942,40 +3110,55 @@ Your email will be used to verify your account. DlgSelectSetForCards - + Unmodified Cards: - + Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database 读取卡牌资料库时出现不知名错误 - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -2992,7 +3175,7 @@ Would you like to change your database location setting? 你想要重新设置卡牌资料库路径吗? - + Your card database version is too old. This can cause problems loading card information or images @@ -3009,7 +3192,7 @@ Would you like to change your database location setting? 你想要重新设置卡牌资料库路径吗? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3022,7 +3205,7 @@ Would you like to change your database location setting? 你想要重新设置卡牌资料库路径吗? - + File Error loading your card database. Would you like to change your database location setting? @@ -3030,7 +3213,7 @@ Would you like to change your database location setting? 你想要重新设置卡牌资料库路径吗? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3039,7 +3222,7 @@ Would you like to change your database location setting? 你想要重新设置卡牌资料库路径吗? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3055,59 +3238,59 @@ https://github.com/Cockatrice/Cockatrice/issues 你想不想更改你的资料库位置设定? - - - + + + Error 出现错误 - + The path to your deck directory is invalid. Would you like to go back and set the correct path? 你的牌库目录路径现在无效. 你想要重新设置套牌路径吗? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? 你的卡牌图片目录路径现在无效. 你想要重新设置卡牌图片资料库路径吗? - + Settings 设定 - + General 一般 - + Appearance 外观 - + User Interface 用户界面 - + Card Sources 卡牌来源 - + Chat 聊天 - + Sound 声音 - + Shortcuts 快捷键 @@ -3115,39 +3298,39 @@ https://github.com/Cockatrice/Cockatrice/issues DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3155,17 +3338,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next 下一个 - + Previous 上一个 - + Tip of the Day 每日提示 @@ -3173,165 +3356,165 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel 现在发行渠道 - + Reinstall 重新安装 - + Cancel Download 取消下载 - + Open Download Page 打开下载页面 - + Check for Client Updates 检查客户端更新 - - - - + + + + Error 出现错误 - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. 鸡蛇不支持建立SSL连接,因此你不能自动下载更新! 请访问下载页面以手动更新。 - + Downloading update: %1 - + Checking for updates... 正在检查更新... - + Finished checking for updates 更新检查已完成 - + No Update Available 没有更新可用 - + Cockatrice is up to date! Cockatrice 已经达到最新版本! - + You are already running the latest version available in the chosen release channel. 你运行的已经是最新的发行版本了。 - + Current version 现行版本 - + Selected release channel 现选发行渠道 - - + + Update Available 现有更新版本 - - + + A new version of Cockatrice is available! Cockatrice有新版本可用! - - + + New version 新版本 - - + + Released 已发行 - - + + Changelog 变更记载 - + Do you want to update now? 你打算现在更新吗? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + + Update Error 更新出现错误 - + An error occurred while checking for updates: 检测更新时出现错误: - + An error occurred while downloading an update: 下载更新时出现错误 - + Installing... 安装中 - + Cockatrice is unable to open the installer. Cockatrice 不能啟动安装程式 - + Try to update manually by closing Cockatrice and running the installer. 请你尝试人手更新, 关掉cockatrice, 然后重开安装程式. - + Download location 下载位置 @@ -3339,21 +3522,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing 关闭时清除纪录 - + Copy to clipboard - + Debug Log 侦错纪录 + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3423,33 +3738,39 @@ You may have to manually download the new version. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3465,22 +3786,22 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. @@ -3488,22 +3809,22 @@ You may have to manually download the new version. GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -3511,226 +3832,279 @@ You may have to manually download the new version. GameSelector - - - - - - + + + + + + Error 出现错误 - + Please join the appropriate room first. 请先参加合适房间 - + Wrong password. 密码错误. - + Spectators are not allowed in this game. 这个游戏不允许观眾加入. - + The game is already full. 这个游戏已经满了. - + The game does not exist any more. 这个游戏已经不存在了。 - + This game is only open to registered users. 这个游戏只开放给註册用户. - + This game is only open to its creator's buddies. 这个游戏只开放给房间主办的好友。 - + You are being ignored by the creator of this game. 你被这个房间的主办屏蔽了. - + Join Game - + Spectate Game - + Game Information - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game 加入游戏 - + Password: 密码: - + Please join the respective room first. 请先参加相关的房间. - + &Filter games &筛选游戏 - + C&lear filter &清除筛选项目 - + C&reate &创建 - + &Join &加入 - + + Join as judge + + + + J&oin as spectator &作为观看者加入 - + + Join as judge spectator + + + + Games shown: %1 / %2 显示游戏:%1/%2 - + Games 游戏 + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day >1日 - + %1%2 hr short age in hours %1%2 小时 - + new 新建 - + %1%2 min short age in minutes %1%2 分鐘 - + password 密码 - + buddies only 仅限好友 - + reg. users only 仅限註册用户 - + open decklists - - + + can chat 允许聊天 - + see hands 查看手牌 - + can see hands 允许查看手牌 - + not allowed 不允许 - + Room 房间 - + Age 时长 - + Description 描述: - + Creator 创建者 - + Type 类型 - + Restrictions 限制 - + Players 玩家 - + Spectators 观看者 @@ -3738,143 +4112,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths 重置所有路径 - + All paths have been reset 重置所有路径 - - - - - - - + + + + + + + Choose path 选择路径 - + Personal settings 个人设定 - + Language: 语言: - + Paths (editing disabled in portable mode) 路径(在便携模式下不能编辑) - + Paths 路径 - + How to help with translations - + Decks directory: 牌库路径: - + Filters directory: - + Replays directory: 游戏录像目录: - + Pictures directory: 图片目录: - + Card database: 卡牌资料库: - + Custom database directory: 自主资料库目录 - + Token database: 令牌资料库: - + Update channel 更新渠道 - + Check for client updates on startup 啟动时检查客户更新 - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client 如果客户端缺少伺服器支持的更功能时,请提示我 - + Automatically run Oracle when running a new version of Cockatrice 啟动新版本Cockatrice时,自动运行Oracle. - + Show tips on startup 显示启动时提示 - + Last update check on %1 (%2 days ago) @@ -3882,47 +4256,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -3930,111 +4304,141 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - - &Sort hand + + Sort hand by... - - Take &mulligan + + Name - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - Reveal r&andom card to... + + + All players - - - &All players + + Reveal r&andom card to... HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + + Browse Archidekt + + + + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4236,643 +4640,643 @@ You may have to manually download the new version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. 伺服器已达到最最高用户限额,请另外选择吉时再试. - + There are too many concurrent connections from your address. 你的网路地址拥有太多同时连繫. - + Banned by moderator 你已被版主禁用 - + Expected end time: %1 预计结束时间: %1 - + This ban lasts indefinitely. 这是无限期禁用 - + Scheduled server shutdown. 排期伺服器关机 - - + + Invalid username. 不可用的用户名。 - + You have been logged out due to logging in at another location. 因为你已在另外的位置登入, 所以你现有的联繫已被登出. - + Connection closed 连繫已被关闭 - + The server has terminated your connection. Reason: %1 伺服器已经中断你的联繫/ 原因: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 伺服器将会在 %n 分鐘内重新啟动. 所有进行中的游戏都会报销.伺服器关闭原因: %1 - + Scheduled server shutdown 排期伺服器关机 - - + + Success 成功 - + Registration accepted. Will now login. 註册已成功。 现在登入。 - + Account activation accepted. Will now login. 户口啟动已经成功 现在登入. - + Number of players 玩家人数 - + Please enter the number of players. 请输入玩家人数 - - + + Player %1 玩家 %1 - + Load replay 载入游戏录像 - + About Cockatrice 关於Cockatrice - + Version 版本 - + Cockatrice Webpage Cockatrice 网页 - + Project Manager: 项目经理: - + Past Project Managers: 前项目经理: - + Developers: 开发者: - + Our Developers 我们的开发者: - + Help Develop! 协助开发! - + Translators: 翻译者: - + Our Translators 翻译者名单 - + Help Translate! 协助翻译! - + Support: 支援: - + Report an Issue 报告问题 - + Troubleshooting 排除故障 - + F.A.Q. 问答 - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error 出现错误 - + Server timeout 伺服器超时 - + Failed Login 登入失败 - + Your client seems to be missing features this server requires for connection. 你的客户端似乎脱小本伺服器需要用来达成连繫的功能. - + To update your client, go to 'Help -> Check for Client Updates'. 请去"求助->检查客户端更新" 内更新你的客户. - + Incorrect username or password. Please check your authentication information and try again. 用户名或密码错误。请检查账号信息,然后重试。 - + There is already an active session using this user name. Please close that session first and re-login. 已经有在线用户正在使用这用户名.请先关闭连线,然后重新登入. - - + + You are banned until %1. 你被禁止直到: %1. - - + + You are banned indefinitely. 你已被永久封禁. - + This server requires user registration. Do you want to register now? 本伺服器要求用户註册. 你想不想现在登记? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. 本伺服器要求用户拥有客户id. 你的客户端现在不能造成客户id,或是你正在使用修改过的客户端. 请你关掉现有的客户端,然后重新啟动, 择吉日再试。 - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. 因为内部出现故障, 请关闭,重开Cockatrice 然后再次尝试. 如果故障继续出现,请将客户端软件升级为最新版本, 有需要时请联络软件开发人士. - + Account activation 户口啟动 - + Your account has not been activated yet. You need to provide the activation token received in the activation email. 你的户口还未尝啟动. 你需要提供从啟动电邮得来的验证码. - + Server Full 伺服器载满 - + Unknown login error: %1 不知名登入错误: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. 这通常意味住你的客户端版本已经过期,引起你的客户端不能明晓伺服器的回覆. - + Your username must respect these rules: 您的用户名字必须遵守以下规则: - + is %1 - %2 characters long 在%1 - %2个字符之间。 - + can %1 contain lowercase characters 可以 $1 包含细楷/小写字母 - - - - + + + + NOT 并否 - + can %1 contain uppercase characters 可以 $1 包含大楷/大写字母 - + can %1 contain numeric characters 可以 $1 包含数字 - + can contain the following punctuation: %1 可以包含以下符号:%1 - + first character can %1 be a punctuation mark 第一个字符可以 %1 是标点符号 - + no unacceptable language as specified by these server rules: note that the following lines will not be translated 在以下伺服器规矩中,道明不能接受的语言: - + can not contain any of the following words: %1 不能含有以下字眼: %1 - + can not match any of the following expressions: %1 不能含有以下语句: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. 你的用户名字只可以用以下的字符: A-Z, a-z, 0-9, _, ., 和 - . - - - - - - + + + + + + Registration denied 註册被拒绝 - + Registration is currently disabled on this server 这伺服器暂时谢绝登记 - + There is already an existing account with the same user name. 这个用户名字已经被使用. - + It's mandatory to specify a valid email address when registering. 登记时必要指明合能用的电邮地址. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. 你看来好像在本伺服器尝试申请新户口, 但是你已经有跟你电邮掛鉤的户口名字.我们的伺服器限制每用户在每个地址可以有多少个户口. 如果需要更多援助或是得取你的登入资料,请联络伺服器办事人. - + Password too short. 密码太短。 - + Registration failed for a technical problem on the server. 由於伺服器出现技术问题, 你的登记暂时失败. - + The connection to the server has been lost. - + Unknown registration error: %1 不知名登记错误: %1 - + Account activation failed 户口啟动失败 - + Socket error: %1 接口错误: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. 你正在尝试跟过时的伺服器连繫. 请把你的 Cockatrice 版本降级,或是连繫於合适的伺服器. 当地/离线版本维 %1, 遥距/上线版本维 %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. 你的 Cockatrice 版本现已过时.请把你的版本更新. 当地/离线版本维 %1, 遥距/上线版本维 %2. - + Connecting to %1... 向 %1 连接中... - + Registering to %1 as %2... 正在用%2的身份登记到%1伺服器... - + Disconnected 已经断线了 - + Connected, logging in at %1 连线成功, 在%1登入 + - Requesting forgotten password to %1 as %2... 正在用%2的身份对%1要求遗忘密码. - + &Connect... &连线... - + &Disconnect &断线 - + Start &local game... 开始&离线游戏 - + &Watch replay... &观看游戏录像 - + &Full screen &全屏 - + &Register to server... &註册加入伺服器 - + &Restore password... &弄回密码 - + &Settings... 设置⋯ - + &Exit &退出 - + A&ctions &动作 - + &Cockatrice &Cockatrice - + C&ard Database &卡牌资料库 - + &Manage sets... &管理牌组 - + Edit custom &tokens... 编辑自定 &令牌... - + Open custom image folder 打开自定图片文件夹 - + Open custom sets folder 打开自定牌组文件夹 - + Add custom sets/cards 添加自定卡组/卡牌 - + Reload card database 刷新卡牌资料库 - + Tabs - + &Help &求助 - + &About Cockatrice &关於Cockatrice - + &Tip of the Day &每日提示 - + Check for Client Updates 检查客户端更新 - + Check for Card Updates... 检查卡牌更新 - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log 查看&侦错纪录 - + Open Settings Folder 打开设置文件夹 - + Show/Hide 显示/隐藏 - + New Version 新版本 - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. 恭喜你将Cockatrice升级为%1. 现在会帮你啟动oracle, 更新你的卡牌资料库. - + Cockatrice installed Cockatrice 已被安装 - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. 恭喜你成功安装Cockatrice%1. 现在会帮你啟动oracle, 安装你的卡牌资料库. - + Card database 卡牌资料库 - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -4881,29 +5285,29 @@ If unsure or first time user, choose "Yes" 如果不确定, 或者是第一次使用,请选择“是/Yes” - - + + Yes 是/Yes - - + + No 否/No - + Open settings 打开设置 - + New sets found 发现新卡组 - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -4912,17 +5316,17 @@ Do you want to enable it/them? 要啟用它们吗? - + View sets 查看卡组 - + Welcome 欢迎 - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -4931,64 +5335,64 @@ Read more about changing the set order or disabling specific sets and consequent 请阅读,了解更多在“卡组设置”对话框裡.更改卡组顺序或者禁用某些卡组的功能. - - + + Information 资讯 - + A card database update is already running. 资料库更新现已进行. - + Unable to run the card database updater: 不能运行卡牌资料附更新程式: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5000,672 +5404,842 @@ To update your client, go to Help -> Check for Updates. 请到"援助->检查更新"去更新你的客户端. - - - - - + + + + + Load sets/cards 载入卡组/卡牌 - + Selected file cannot be found. 找不到选择的文件。 - + You can only import XML databases at this time. 你暂时只能导入xml资料库. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. 已经成功添加新的卡组/卡牌. Cockatrice会重新载入卡牌资料库. - + Sets/cards failed to import. 卡组/卡牌导入失败. - - - + + + Reset Password 重设密码 - + Your password has been reset successfully, you can now log in using the new credentials. 你的密码已经成功重置, 你可以用新密码重新登入。 - + Failed to reset user account password, please contact the server operator to reset your password. 重置用户密码失败, 请联络伺服器管责人重置密码. - + Activation request received, please check your email for an activation token. 已经收到啟动要求, 请从你的电邮获取啟动验证码. + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play 从战场上 - + from their graveyard 从它们的坟场 - + from exile 从放逐区 - + from their hand 从它们的手牌 - + the top card of %1's library %1牌库的顶牌 - + the top card of their library 它们的顶牌 - + from the top of %1's library 从%1牌库的顶 - + from the top of their library 从它们1牌库顶 - + the bottom card of %1's library %1牌库的底牌 - + the bottom card of their library 它们牌库的底牌 - + from the bottom of %1's library 从%1的牌库底 - + from the bottom of their library 从它们的牌库底 - + from %1's library 从%1的牌库 - + from their library 从它们的牌库 - + from sideboard 从备牌中 - + from the stack 从迭区中 - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1现在保持%2顶牌显现. - + %1 is not revealing the top card %2 any longer. %1不再展示牌库顶牌%2。 - + %1 can now look at top card %2 at any time. %1可以随时查看牌库顶牌 %2. - + %1 no longer can look at top card %2 at any time. %1再不可以随时查看牌库顶牌 %2. - + %1 attaches %2 to %3's %4. %1 结附 %2 指向 %3的 %4. - + %1 has conceded the game. %1 在此游戏宣佈降服. - + %1 has unconceded the game. %1 收回在此游戏降服. - + %1 has restored connection to the game. %1已恢復连接。 - + %1 has lost connection to the game. %1 已失去连接。 - + %1 points from their %2 to themselves. %1 从它们的%2 指向自己。 - + %1 points from their %2 to %3. %1 从它们的%2指向%3。 - + %1 points from %2's %3 to themselves. %1 从%2的%3 指向自己。 - + %1 points from %2's %3 to %4. %1 从%2的%3 指向%4。 - + %1 points from their %2 to their %3. %1 从它们的%2指向它们的%3。 - + %1 points from their %2 to %3's %4. %1 从它们的%2指向%3的%4。 - + %1 points from %2's %3 to their own %4. %1 从%2的%3指向自己的%4。 - + %1 points from %2's %3 to %4's %5. %1 从%2的%3指向%4的%5。 - + %1 creates a face down token. - + %1 creates token: %2%3. %1 派出令牌: %2%3 - + %1 has loaded a deck (%2). %1 已载入牌库(%2) - + %1 has loaded a deck with %2 sideboard cards (%3). %1已载入含有 %2张备牌的牌库(%3). - + %1 destroys %2. %1销毁了%2。 - + a card 一张牌 - + %1 gives %2 control over %3. %1将%3的控制转移给%2. - + %1 puts %2 into play%3 face down. %1将%2%3牌面朝下地放进战场。 - + %1 puts %2 into play%3. %1将%2放进战场%3。 - + %1 puts %2%3 into their graveyard. %1将%2%3置入它们的的坟场。 - + %1 exiles %2%3. %1 放逐%2%3. - + %1 moves %2%3 to their hand. %1将%2%3移到它们的的手牌中。 - + %1 puts %2%3 into their library. %1将%2%3放入它们的牌库。 - + %1 puts %2%3 onto the bottom of their library. %1将%2%3放在它们的牌库底部。 - + %1 puts %2%3 on top of their library. %1将%2%3放在它们的牌库顶部。 - + %1 puts %2%3 into their library %4 cards from the top. %1将%2%3放在它们的牌库顶部%4张牌以下。 - + %1 moves %2%3 to sideboard. %1 将%2%3移到副牌库. - + %1 plays %2%3. %1使出 %2%3。 - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1尝试从空牌库抽牌 - + %1 draws %2 card(s). %1抽%2张牌。 - + %1 is looking at %2. %1 正在查看%2。 - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 正在查看%2的 %4 %3张卡牌. - + bottom 底部 - + top 顶部 - + %1 turns %2 face-down. %1把%2翻为面朝下。 - + %1 turns %2 face-up. %1把%2翻为面朝上。 - + The game has been closed. 游戏已经关闭。 - + The game has started. 游戏已开始。 - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 已经加入游戏。 - + %1 is now watching the game. %1 正在旁观游戏。 - + You have been kicked out of the game. 你已被踢出游戏。 - + %1 has left the game (%2). %1 已经离开游戏(%2)。 - + %1 is not watching the game any more (%2). %1 已经不再旁观游戏(%2)。 - + %1 is not ready to start the game any more. %1 还未準备好开始游戏。 - + %1 shuffles their deck and draws a new hand of %2 card(s). %1将其牌库洗牌,然后抽%2张牌作为新的起手。 - + %1 shuffles their deck and draws a new hand. %1将其牌库洗牌,然后抽起新的手牌。 - + You are watching a replay of game #%1. 你正在观看游戏#%1的录像。 - + %1 is ready to start the game. %1已準备好开始游戏。 - + cards an unknown amount of cards 卡牌(眾)  - + %1 card(s) a card for singular, %1 cards for plural %1 卡牌(眾)  - + %1 lends %2 to %3. %1把&2借给%3. - + %1 reveals %2 to %3. %1把%2展示给%3. - + %1 reveals %2. %1 展示 %2。 - + %1 randomly reveals %2%3 to %4. %1随机展示%2%3给%4。 - + %1 randomly reveals %2%3. %1随机展示%2%3。 - + %1 peeks at face down card #%2. %1查看面朝下的卡牌#%2。 - + %1 peeks at face down card #%2: %3. %1查看面朝下的卡牌#%2:%3。 - + %1 reveals %2%3 to %4. %1展示%2%3给%4。 - + %1 reveals %2%3. %1展示%2%3。 - + %1 reversed turn order, now it's %2. %1 逆转回合次序,现在轮到%2。 - + reversed 逆转 - + normal 正常 - + Heads 正面 - + Tails 翻面 - + %1 flipped a coin. It landed as %2. %1掷了硬币。结果为%2。 - + %1 rolls a %2 with a %3-sided die. %1掷%3面骰子,结果为%2。 - + %1 flips %2 coins. There are %3 heads and %4 tails. %1掷了%2个硬币。结果为%3个正面和%4个反面。 - + %1 rolls a %2-sided dice %3 times: %4. %1把%2面骰子掷了%3次:%4 - + %1's turn. 轮到 %1. - + %1 sets annotation of %2 to %3. %1给%2添加了註释成%3。 - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 将%2指示物设置为 %3 (%4%5)。 - + %1 sets %2 to not untap normally. %1 将 %2 设置为不被重置。 - + %1 sets %2 to untap normally. %1 将 %2 设置为照常重置。 - + %1 removes the PT of %2. %1 移除%2的力量/防御. - + %1 changes the PT of %2 from nothing to %4. %1把%2的力量/防御从无数值转为%4. - + %1 changes the PT of %2 from %3 to %4. %1把%2的力量/防御从%3转为%4. - + %1 has locked their sideboard. %1已经锁定它们的的副牌库。 - + %1 has unlocked their sideboard. %1已经把它们的的副牌库解锁。 - + %1 taps their permanents. %1横置它们的永久卡牌 - + %1 untaps their permanents. %1重置它们的永久卡牌. - + %1 taps %2. %1 横置 %2. - + %1 untaps %2. %1 重置 %2。 - + %1 shuffles %2. % 1 对 %2 洗牌 - + %1 shuffles the bottom %3 cards of %2. %1 洗 %2 底的 %3 张卡牌. - + %1 shuffles the top %3 cards of %2. %1 洗 %2 顶的 %3 张卡牌. - + %1 shuffles cards %3 - %4 of %2. %1到%2.洗%3 - %4 - + %1 unattaches %2. %1 取消了%2的结附。 - + %1 undoes their last draw. %1 放回它们最后一张抽到的卡牌. - + %1 undoes their last draw (%2). %1 放回它们最后一张抽到的卡牌(%2). @@ -5673,110 +6247,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 词1 词2 词3 - + Add New Message 添加新信息 - + Edit Message 更改信息 - + Remove Message 移除信息 - + Add message 添加信息 - - + + Message: 信息: - + Edit message 更改信息 - + Chat settings 聊天设定 - + Custom alert words 自定通知触发词语 - + Enable chat mentions 允许聊天中提到某人 - + Enable mention completer 允许自动完成提名 - + In-game message macros 游戏内短信速键 - + How to use in-game message macros 如何使用游戏内短信速键 - + Ignore chat room messages sent by unregistered users 不要显示未註册用户的聊天消息。 - + Ignore private messages sent by unregistered users 不要显示未註册用户的私人消息. - - + + Invert text color 反转文本顏色 - + Enable desktop notifications for private messages 容许桌面提醒提及私人消息 - + Enable desktop notification for mentions 容许桌面提醒提及聊天提名 - + Enable room message history on join 加入聊天室时开启消息历史 - - + + (Color is hexadecimal) (顏色为16进制) - + Separate words with a space, alphanumeric characters only 将单词以空格区分,仅支持字母. @@ -5893,62 +6467,62 @@ Cockatrice will now reload the card database. Phase - + Unknown Phase 不知名阶段 - + Untap 重置步 - + Upkeep 维持步 - + Draw 抓牌步 - + First Main 第一主阶步 - + Beginning of Combat 开战步 - + Declare Attackers 宣告进攻步 - + Declare Blockers 宣告阻挡步 - + Combat Damage 战斗损害步 - + End of Combat 战斗结束步 - + Second Main 第二主阶步 - + End/Cleanup 结束/清理步 @@ -6014,7 +6588,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages zhs @@ -6023,134 +6597,134 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - + Move top cards to grave - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6158,17 +6732,17 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters - + S&ay @@ -6176,7 +6750,7 @@ Cockatrice will now reload the card database. PrintingSelector - + Display Navigation Buttons @@ -6184,22 +6758,22 @@ Cockatrice will now reload the card database. PrintingSelectorCardOverlayWidget - + Preference 喜好 - + Pin Printing 版本上别针 - + Unpin Printing 版本脱别针 - + Show Related cards 显示关联卡牌 @@ -6215,17 +6789,17 @@ Cockatrice will now reload the card database. PrintingSelectorCardSelectionWidget - + Previous Card in Deck 牌库次序中上一张卡牌 - + Bulk Selection - + Next Card in Deck 牌库次序中下一张卡牌 @@ -6233,28 +6807,28 @@ Cockatrice will now reload the card database. PrintingSelectorCardSortingWidget - + Alphabetical 英文字母次序 - + Preference 喜好 - + Release Date 发行日期 - - + + Descending 由后到先 - + Ascending 由先倒后 @@ -6320,37 +6894,37 @@ Cockatrice will now reload the card database. QMenuBar - + Services 服务 - + Hide %1 隐藏 %1 - + Hide Others 隐藏其他 - + Show All 全部展示 - + Preferences... 喜好... - + Quit %1 退出%1 - + About %1 关於 %1 @@ -6358,42 +6932,42 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) Cockatrice卡牌资料库 (*.xml) - + All files (*.*) 所有档案格式 (*.*) - + Cockatrice replays (*.cor) Cockatrice录像文件 (*.cor) - + Maindeck 主牌库 - + Sideboard 副牌库 - + Tokens 眾令牌 - + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? @@ -6401,92 +6975,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK OK/确认 - + Save 保存 - + Save All 全部保存 - + Open 打开 - + &Yes &是/Yes - + Yes to &All &全选是/Yes - + &No &否/No - + N&o to All &全选否/No - + Abort 退出 - + Retry 重试 - + Ignore 不理 - + Close 关闭 - + Cancel 取消 - + Discard 弃牌 - + Help 协助 - + Apply 应用 - + Reset 重设 - + Restore Defaults 恢復常用认值 @@ -6583,37 +7157,37 @@ Cockatrice will now reload the card database. RoomSelector - + Rooms 房间(眾)  - + Joi&n &加入 - + Room 房间 - + Description 描述 - + Permissions 许可 - + Players 玩家(眾) - + Games 游戏(眾) @@ -6654,27 +7228,27 @@ Cockatrice will now reload the card database. SetsModel - + Enabled 已启用 - + Set type 设置类型 - + Set code 设置代码 - + Long name 长名 - + Release date 发行日期 @@ -6682,53 +7256,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts 恢復所有常用快键 - + Do you really want to restore all default shortcuts? 确定要恢復所有常用快键吗? - + Clear all default shortcuts 清除所有常用快键 - + Do you really want to clear all shortcuts? 确认清除所有快键吗? - + Section: 部分: - + Action: 行动: - + Shortcut: 快键: - + How to set custom shortcuts 如何设置自定快键 - + Clear all shortcuts 清除所有快键 - + Search by shortcut name 按快键名称搜寻 @@ -6736,12 +7310,12 @@ Cockatrice will now reload the card database. ShortcutTreeView - + Action 行动 - + Shortcut 快键 @@ -6749,14 +7323,14 @@ Cockatrice will now reload the card database. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! 您的配置档案含有无效的快捷键. 请检查您的快捷键设定. - + The following shortcuts have been set to default: 以下快捷键设置已经返回常用状态: @@ -6784,12 +7358,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -6797,27 +7371,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds 启用&声效 - + Current sounds theme: 现前声效主题: - + Test system sound engine 测试系统声效引擎 - + Sound settings 声效设置 - + Master volume 主音量 @@ -6825,48 +7399,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended 预览季节已结束 - + Deleting spoiler.xml. Please run Oracle 现在正删除spoiler.xml。请啟动Oracle - - + + Spoilers download failed 预览下载出错 - + No internet connection 没有网络连接 - + Error 出现错误 - + Spoilers already up to date 预览已是最新 - + No new spoilers added 没有添加新预览 - + Spoilers have been updated! 预览已被更新 - + Last change: 最后变更: @@ -7030,56 +7604,123 @@ Please check your shortcut settings! 不能啟动用户户口.内部故障 + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info 卡牌资讯 - + Deck 牌库 - + Filters 筛选项目 - + &View &视图 - + + Card Database + + + + Printing 印刷版本 - - - - + + + + + Visible 可见 - - - - + + + + + Floating 浮动 - + Reset layout 重置布局 - + Deck: %1 牌库:%1 @@ -7087,61 +7728,61 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + + + + Visible - - - - + + + + Floating - + Reset layout @@ -7149,22 +7790,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7172,133 +7813,133 @@ Please check your shortcut settings! TabDeckStorage - + Local file system 本地档案系统 - + Server deck storage 伺服器牌库储藏 - - + + Open in deck editor 在套牌编辑打开 - + Rename deck or folder - + Upload deck 上载牌库 - + Download deck 下载套牌 + - - - + + New folder 新文件夹 + - Delete 删除 - + Open decks folder 打开牌库文件夹 - + Rename local folder - + Rename local file - + New name: - - - - + + + + Error 出现错误 - + Rename failed - - + + Invalid deck file 无效牌库档案 - + Enter deck name 输入牌库名称 - + This decklist does not have a name. Please enter a name: 本牌库列表未有名称.请输入名称: - + Unnamed deck 未命名牌库 - + Failed to upload deck to server 牌库上载於伺服器时失败 - + Delete local file 删除本地档案 - + Are you sure you want to delete the selected files? 你确定要删除被选档案(眾)吗 ? - + Delete remote decks 删除伺服器上的牌库 - + Are you sure you want to delete the selected decks? 你确定要剷除所有被选的牌库吗? - - + + Name of new folder: 新建文件夹名称: @@ -7311,17 +7952,17 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 @@ -7362,7 +8003,7 @@ Please enter a name: - + EDHRec: @@ -7370,197 +8011,197 @@ Please enter a name: TabGame - - - + + + Replay 游戏录像重温 - - + + Game 游戏 - - + + Player List 玩家列表 - - + + Card Info 卡牌资讯 - - + + Messages 信息 - - + + Replay Timeline 录像时间线 - + &Phases 阶段 - + &Game 游戏 - + Next &phase 下个&阶段 - + Next phase with &action 下个阶段&行动 - + Next &turn 下个&回合 - + Reverse turn order 逆转回合次序 - + &Remove all local arrows &移除所有自设箭头 - + Rotate View Cl&ockwise &顺时针旋转视角 - + Rotate View Co&unterclockwise &逆时针旋转视角 - + Game &information 游戏&资讯 - + Un&concede 撤销&投降 - - - + + + &Concede &投降 - + &Leave game &离开游戏 - + C&lose replay &关闭游戏录像 - + &Focus Chat &聚焦聊天 - + &Say: &説: - + Selected cards - + &View &视图 - - + + + - Visible 可见 - - + + + - Floating 浮动 - + Reset layout 重置布局 - + Concede 投降 - + Are you sure you want to concede this game? 你确定要宣佈投降吗? - + Unconcede 撤销投降 - + You have already conceded. Do you want to return to this game? 你已经宣佈投降,你想回到这场游戏吗? - + Leave game 离开游戏 - + Are you sure you want to leave this game? 你确定要离开这个游戏吗? - + A player has joined game #%1 有玩家加入游戏 #%1 - + %1 has joined the game %1 已经加入游戏。 - + You have been kicked out of the game. 你已被踢出游戏。 @@ -7568,7 +8209,7 @@ Please enter a name: TabHome - + Home @@ -7576,158 +8217,158 @@ Please enter a name: TabLog - + Logs 记录 - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName 时间;发送者姓名;发送者IP;信息;目标ID;目标姓名 - + Room Logs 房间记录 - + Game Logs 游戏记录 - + Chat Logs 聊天记录 - - + + Error 出现错误 - + You must select at least one filter. 您必须选择至少一个过滤项. - + You have to select a valid number of days to locate. 您必须选择一个可用的日数来作为定位. - + Username: 用户名称: - + IP Address: IP地址 - + Game Name: 游戏名称: - + GameID: 游戏ID: - + Message: 信息: - + Main Room 主房 - + Game Room 游戏房间 - + Private Chat 私人聊天 - + Past X Days: 过去 X 天: - + Today 今天 - + Last Hour 1小时前 - + Maximum Results: 最多结果: - + At least one filter is required. The more information you put in, the more specific your results will be. 至少需要1个筛选项。 您输入的信息越多,找到结果越準确。 - + Get User Logs 获取用户记录 - + Clear Filters 清除筛选项目 - + Filters 筛选项目 - + Log Locations 记录位置 - + Date Range 日期范围 - + Maximum Results 最多结果 - - + + Message History 信息历史 - + Failed to collect message history information. 不能获取信息历史消息 - + There are no messages for the selected filters. 没有符合筛选条件的信息。 @@ -7773,180 +8414,180 @@ The more information you put in, the more specific your results will be. TabReplays - + Local file system 本地档案系统 - + Server replay storage 伺服器录像仓库 - - + + Watch replay 观看游戏录像 - + Rename 改名 - - + + New folder 新文件夹 - - + + Delete 删除 - + Open replays folder 打开录像文件夹 - + Download replay 下载游戏录像 - + Toggle expiration lock 过期锁定 - + Get replay share code - - + + Look up replay by share code - + Rename local folder 本地档案夹改名 - + Rename local file 本地档案改名 - + New name: 新名称 - + Error 出错 - + Rename failed 改名不成功 - + Name of new folder: 新建文件夹名称: - + Delete local file 删除本地档案 - + Are you sure you want to delete the selected files? 你确定要剷除所有被选的档案吗? - + Are you sure you want to delete the selected replays? 你确定要删除被选录像档案吗 ? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay 删除伺服器录像 @@ -8012,30 +8653,30 @@ The more information you put in, the more specific your results will be.伺服器 + - Error 出现错误 - + Failed to join the server room: it doesn't exist on the server. 加入伺服器房间失败:在本伺服器上这房间并不存在。 - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. 伺服器认为你在房间内, 但Cockatrice无法显示。请重新启动Cockatrice。 - + You do not have the required permission to join this server room. 你未被允许进入该房间。 - + Failed to join the server room due to an unknown error: %1. 因为以下不知名的错误,所以不能参入伺服器房间: %1. @@ -8043,92 +8684,97 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - - - Home - - - - - &Visual Deck Storage - - - - - Visual Database Display - - - Server + Archidekt - Account + Home - Deck Storage + &Visual Deck Storage - Game Replays + Visual Database Display - Administration + Server + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + Logs - + Are you sure? 你确定吗? - + There are still open games. Are you sure you want to quit? 游戏还在继续, 你确定要退出吗? - + Click to view 点击查看 - + Your buddy %1 has signed on! 你的好友%1已登录! - + Unknown Event 不知名事件 - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8139,38 +8785,38 @@ To update your client, go to Help -> Check for Updates. 要升级你的客户端,请点击“帮助”->“检查更新” - + Idle Timeout 閒置超时 - + You are about to be logged out due to inactivity. 你即将因为长时间没有活动而被退出。 - + Promotion 升级 - + You have been promoted. Please log out and back in for changes to take effect. 你已被升级.请登出然后再登入让本更改生效. - + Warned 被警告 - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. 你由於$1 原因,现在接受警告. 请避免继续本行为, 否则你将有可能会接受进一步的警诫或惩罚. 若有任何疑问, 请私信我们门下的版主. - + You have received the following message from the server. (custom messages like these could be untranslated) 你收到松本伺服器发出的以下讯息(类似本信息的自定讯息可能未经翻译) @@ -8179,7 +8825,12 @@ Please refrain from engaging in this activity or further actions may be taken ag TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display @@ -8261,7 +8912,7 @@ Please refrain from engaging in this activity or further actions may be taken ag UpdateDownloader - + Could not open the file for reading. 无法打开文件。 @@ -8269,206 +8920,206 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details 用户&详细信息 - + Private &chat 私人&聊天 - + Show this user's &games 显示这个用户的&游戏 - + Add to &buddy list 加入到&好友列表 - + Remove from &buddy list 从&好友列表移除 - + Add to &ignore list 加入到&屏蔽列表 - + Remove from &ignore list 从&屏蔽列表移除 - + Kick from &game 从&游戏中踢出 - + Warn user 警告用户 - + View user's war&n history 查看用户警告记录 - + Ban from &server 把用户从&伺服器禁用 - + View user's &ban history 查看用户&禁止记录 - + &Promote user to moderator 升$用户作为版主 - + Dem&ote user from moderator 把&用户从版主降级 - + Promote user to &judge 把&用户升级成裁判 - + Demote user from judge 把&用户从裁判降级 - + View admin notes 阅读管理笔记 - - - + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games %1 的游戏(眾) - - - + + + Ban History 禁用历史 - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason 禁止时间;版主;禁止时长;禁止理由;可见理由 - + User has never been banned. 用户从未被禁 - + Failed to collect ban information. 不能获取禁用讯息 - - - + + + Warning History 警告历史 - + Warning Time;Moderator;User Name;Reason 警告时间;版主;用户名称;理由 - + User has never been warned. 用户从未接收警告 - + Failed to collect warning information. 不能获取警告信息 - + Failed to get admin notes. 不能获取管理笔记 - - + + Success 成功 - + Successfully promoted user. 用户升级成功 - + Successfully demoted user. 用户降级成功 - + + - Failed 失败 - + Failed to promote user. 用户升级失败 - + Failed to demote user. 用户降级失败 - + Copy hash to clipboard 把哈希覆印到用户剪贴 - + Remove this user's messages 移除这用户讯息 @@ -8650,137 +9301,142 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings 一般通用接口设置 - + &Double-click cards to play them (instead of single-click) 双点击使出卡牌 (而不是单点击使出) - + &Clicking plays all selected cards (instead of just the clicked card) &单一点击把所有选择的卡牌使出(不只是被点击的单张卡牌) - + &Play all nonlands onto the stack (not the battlefield) by default &把所有使出的非地牌放入堆迭区(不是战场) 作为常用设置 - - - Close card view window when last card is removed - - - - - Auto focus search bar when card view window is opened - - - - - Annotate card text on tokens - 在令牌上标註牌中文字 - - - - Use tear-off menus, allowing right click menus to persist on screen - 使用浮动菜单,允许右键单击, 把菜单保留在屏幕上 - - - - Notifications settings - 通告设置 - - - - Enable notifications in taskbar - 开启任务栏通告 - - - - Notify in the taskbar for game events while you are spectating - 观看时在任务栏提示游戏信息 - - - - Notify in the taskbar when users in your buddy list connect - 在任务栏中提示好友连到伺服器 - - - - Animation settings - 动画设定 - - - - &Tap/untap animation - &横置/重置 动画 - - Deck editor/storage settings + Do not delete &arrows inside of subphases - Open deck in new tab by default - 把用新页开啟牌库作为常用设置 + Close card view window when last card is removed + - Use visual deck storage in game lobby + Auto focus search bar when card view window is opened - Use selection animation for Visual Deck Storage - + Annotate card text on tokens + 在令牌上标註牌中文字 + + + + Use tear-off menus, allowing right click menus to persist on screen + 使用浮动菜单,允许右键单击, 把菜单保留在屏幕上 - When adding a tag in the visual deck storage to a .txt deck: - + Notifications settings + 通告设置 + + + + Enable notifications in taskbar + 开启任务栏通告 - do nothing - + Notify in the taskbar for game events while you are spectating + 观看时在任务栏提示游戏信息 + + + + Notify in the taskbar when users in your buddy list connect + 在任务栏中提示好友连到伺服器 - ask to convert to .cod - + Animation settings + 动画设定 + + + + &Tap/untap animation + &横置/重置 动画 - always convert to .cod + Deck editor/storage settings - Default deck editor type - + Open deck in new tab by default + 把用新页开啟牌库作为常用设置 - Classic Deck Editor + Use visual deck storage in game lobby + Use selection animation for Visual Deck Storage + + + + + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + Visual Deck Editor - + Replay settings 重播设置 - + Buffer time for backwards skip via shortcut: 用速键啟动向后跳的缓衝时间: @@ -8788,22 +9444,22 @@ Please refrain from engaging in this activity or further actions may be taken ag UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 @@ -8811,32 +9467,32 @@ Please refrain from engaging in this activity or further actions may be taken ag UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -8844,22 +9500,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match - + Mode: Includes - + Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -8867,21 +9523,49 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8907,28 +9591,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - Load from Deck + Filter by name... (Exact match) - Apply all card names in currently loaded deck as exact match name filters + Load from Deck - Load from Clipboard + Apply all card names in currently loaded deck as exact match name filters + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -8992,50 +9676,115 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - + + Sort by: + + + + + Filter by: + + + + Save and load filters - + Filter by exact card name - + Filter by card sub-type - + Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9043,46 +9792,25 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -9138,7 +9866,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9146,22 +9874,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -9169,17 +9897,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + Visual Deck Storage Settings @@ -9187,43 +9915,43 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? 你想发出那种警告? - + Redact all messages from this user in all rooms 把这用户在所有房间留下的讯息删除 - + &OK &确认 - + &Cancel &取消 - + Warn user for misconduct 对行为不当的用户发出警告 - - + + Error 出现错误 - + User name to send a warning to can not be blank, please specify a user to warn. 接收警告的用户名称不能空白.请定好要接收警告的用户名字. - + Warning to use can not be blank, please select a valid warning to send. 警告名称不能留空. 请选择有效的警告. @@ -9231,133 +9959,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top 将选择的牌组送到最顶 - + Move selected set up 将选择的牌组往上一格 - + Move selected set down 将选择的牌组往下一格 - + Move selected set to the bottom 将选择的牌组送到最底 - + Search by set name, code, or type 用排组名称,代码,或类型进行搜寻 - + Default order 常用次序 - + Restore original art priority order 恢復常用艺术先后次序 - + Enable all sets 啟用所有牌组 - + Disable all sets 禁用所有卡组 - + Enable selected set(s) 啟用选择的牌组(眾)  - + Disable selected set(s) 停用选择的牌组(眾) - + Deck Editor 牌库编辑 - + Use CTRL+A to select all sets in the view. 请按 CTRL + A, 以此选择所有看到的牌组 - + Only cards in enabled sets will appear in the card list of the deck editor. 只有已经被啟用的卡牌组中的卡牌,才会在牌库编辑中的卡牌清单出现 - + Image priority is decided in the following order: 卡牌图片先后次序由以下次序决定: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki 自订档案夹(%`1)优先, 此对话窗中已被啟用的牌组为后(由顶到底) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art 卡牌艺术 - + How to use custom card art 如何使用自定卡牌图片 - + Hints 提示 - + Note 笔记 - + Sorting by column allows you to find a set while not changing set priority. 按列排序允许您在不更改系列优先级别的情况下查找牌组。 - + To enable ordering again, click the column header until this message disappears. 要再次启用排序,请单击列标题,直到此消息消失。 - + Use the current sorting as the set priority instead 用现在排序代替牌组先后 - + Sorts the set priority using the same column 用同一列分牌组先后 - + Manage sets 管理牌组 @@ -9365,72 +10093,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped 未分组 - + Group by Type 按类型分组 - + Group by Mana Value 按法力数值分组 - + Group by Color 按顏色排序 - + Unsorted 未排序 - + Sort by Name 按名称排次序 - + Sort by Type 按类型排序 - + Sort by Mana Cost 按法力费用排序 - + Sort by Colors 按顏色(眾) 排序 - + Sort by P/T 按力量/防御力排序 - + Sort by Set 按排组排序 - + shuffle when closing 关闭界面时洗牌 - + pile view 栋迭观看 @@ -9438,7 +10166,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English 简体中文 (Chinese Simplified) @@ -9446,12 +10174,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup 啟动时自动连接 - + Debug to file 侦错纪录写进档案 @@ -9459,1005 +10187,1041 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window 主视窗 - - + + Deck Editor 牌库编辑 - + Game Lobby 游戏厅堂 - + Card Counters 牌指示物 - + Player Counters 玩家指示物 - + Power and Toughness 力量和防御力 - + Game Phases 游戏阶段 - + Playing Area 游戏区域 - + Move Selected Card 移动所选牌张 - + View 查看 - + Move Top Card 移动顶牌 - + Move Bottom Card 移动底牌 - + Gameplay 游戏 - + Drawing 抽牌 - + Chat Room 聊天室 - + Game Window 游戏视窗 - + Load Deck from Clipboard 从剪贴板载入牌库 - - + + Replays 游戏录像 - + Tabs - + Check for Card Updates... 检查卡牌更新 - + Connect... 连线 - + Disconnect 断开连线 - + Exit 退出 - + Full screen 全屏 - + Register... 註册... - + Settings... 设定... - + Start a Local Game... 开始离线游戏 - + Watch Replay... 观看录像 - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters 清除所有筛选项目 - + Clear Selected Filter 清除所选的筛选项目 - + Close 关闭 - + Remove Card 移除卡牌 - + Manage Sets... 管理牌组 - + Edit Custom Tokens... 编辑自定眾令牌... - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card 添加卡牌 - + Load Deck... 载入牌库... - - + + Load Deck from Clipboard... 从剪贴板载入牌库 - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck 创建新牌库 - + Open Custom Pictures Folder 打开自定图片文件夹 - + Print Deck... 印刷牌库 - + Delete Card 删除卡牌 - - + + Reset Layout 重置佈局 - + Save Deck 保存牌库 - + Save Deck as... 牌库另存为 - + Save Deck to Clipboard, Annotated 从剪贴板储藏牌库, 带注释 - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard 从剪贴板储藏牌库 - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... 从自身档案载入牌库 - + Load Remote Deck... 载入伺服器上的牌库... - + Set Ready to Start 设定準备开始 - + Toggle Sideboard Lock 切换副牌库锁 - + Add Green Counter 增加绿色指示物 - + Remove Green Counter 移除绿色指示物 - + Set Green Counters... 设定绿色指示物 - + Add Red Counter 增加红色指示物 - + Remove Red Counter 移除红色指示物 - + Set Red Counters... 设定红色指示物 - + Add Life Counter 增加生命指示物 - + Show Status Bar - + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter 移除生命指示物 - + Set Life Counters... 设定生命指示物 - + Add White Counter 增加白色指示物 - + Remove White Counter 移除白色指示物 - + Set White Counters... 设定白色指示物 - + Add Blue Counter 增加蓝色指示物 - + Remove Blue Counter 移除蓝色指示物 - + Set Blue Counters... 设定蓝色指示物 - + Add Black Counter 增加黑色指示物 - + Remove Black Counter 移除黑色指示物 - + Set Black Counters... 设定黑色指示物 - + Add Colorless Counter 增加无色指示物 - + Remove Colorless Counter 移除无色指示物 - + Set Colorless Counters... 设定无色指示物 - + Add Other Counter 增加其他指示物 - + Remove Other Counter 移除其他指示物 - + Set Other Counters... 设定其他指示物 - + Increment all card counters - + Add Power (+1/+0) 增加力量(+1/+0) - + Remove Power (-1/-0) 削减力量(-1/-0) - + Move Toughness to Power (+1/-1) 防御移到力量(+1/-1) - + Add Toughness (+0/+1) 增加防御(+0/+1) - + Remove Toughness (-0/-1) 削减防御(-0/-1) - + Move Power to Toughness (-1/+1) 力量移到防御(-1/+1) - + Add Power and Toughness (+1/+1) 增加力量和防御(+1/+1) - + Remove Power and Toughness (-1/-1) 削减力量和防御(-1/-1) - + Set Power and Toughness... 设置力量和防御 - + Reset Power and Toughness 重置力量和防御 - + Untap 重置 - + Upkeep 维持 - + Draw 抽牌 - + First Main Phase 第一主阶段 - + Start Combat 开始战斗 - + Attack 攻击 - + Block 挡格 - + Damage 计算损害 - + End Combat 战斗结束 - + Second Main Phase 第二主阶段 - + End 完结 - + Next Phase 下个阶段 - + Next Phase Action 下个行动阶段 - + Next Turn 下一回合 - + Hide Card in Reveal Window 从揭露视窗中隐藏这牌 - + Tap / Untap Card 横置/重置这牌 - + Untap All 重置全牌 - + Toggle Untap 锁定重置状态 - + Turn Card Over 反转本牌 - + Peek Card 查看卡牌 - + Play Card 使出本牌 - + Attach Card... 结附牌 - + Unattach Card 解除牌结附 - + Clone Card 复製卡牌 - + Create Token... 派出令牌 - + Create All Related Tokens 派出所有相关令牌 - + Create Another Token 再做另外令牌 - + Set Annotation... 记下註解 - + Select All Cards in Zone 选择所有在区域的牌 - + Select All Cards in Row 选择所有横行牌 - + Select All Cards in Column 选择所有直行牌 - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library 牌库底 - - - - + + + + Exile 放逐 - - - - + + + + Graveyard 坟场 - - + + + Hand 手牌 - - + + Top of Library 牌库顶 - - - + + + Battlefield, Face Down 战场,面朝下 - + Battlefield 战场 - - Sort Hand - - - - + Library 牌库 - + Sideboard 副牌库 - + Top Cards of Library 排副多张顶牌 - + Bottom Cards of Library 排副多张底牌 - + Close Recent View 关闭最近查看 - - + + Stack 迭区 - - + + Graveyard (Multiple) 多张送坟场 - - + + Exile (Multiple) 多张放逐 - + Stack Until Found 找到以下牌前全送迭区 - + Draw Bottom Card 从牌库底抽一张牌 - + Draw Multiple Cards from Bottom... 从牌库底抽超过一张牌 - + Draw Arrow... 画箭咀 - + Remove Local Arrows 清除自设箭咀 - + Leave Game 离开游戏 - + Concede 投降 - + Roll Dice... 掷骰 - + Shuffle Library 洗牌 - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan 再调度 - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card 抽一张牌 - + Draw Multiple Cards... 抽超过一张牌 - + Undo Draw 撤销抽牌 - + Always Reveal Top Card 永远显示顶牌 - + Always Look At Top Card 永远看到顶牌 - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise 视角顺时针转 - + Rotate View Counterclockwise 视角逆时针转 - + Unfocus Text Box 取消聚焦文本框 - + Focus Chat 聚焦聊天 - + Clear Chat 扫清聊天 - + Refresh 刷新 - + Skip Forward 跳前 - + Skip Backward 跳后 - + Skip Forward by a lot 向前大跳 - + Skip Backward by a lot 向后大跳 - + Play/Pause 播放/暂停 - + Toggle Fast Forward 切换快进 - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_zh-Hant.ts b/cockatrice/translations/cockatrice_zh-Hant.ts index a9c2a37bf..ce3fa8f00 100644 --- a/cockatrice/translations/cockatrice_zh-Hant.ts +++ b/cockatrice/translations/cockatrice_zh-Hant.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... 設置指示物為... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter 設置指示物 - + New value for counter '%1': 新的指示物數值'%1': @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,79 +36,81 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported + + + AddAnalyticsPanelDialog - - No deck was selected to be exported. + + Add Analytics Panel AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -116,12 +118,12 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard - + Sideboard @@ -129,22 +131,22 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error 錯誤 - + Could not create themes directory at '%1'. - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -155,7 +157,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -166,152 +168,165 @@ Are you sure you would like to disable this feature? - + Confirm Change - + Theme settings 主題設定 - + Current theme: 當前主題 - + Open themes folder 打開主題文件夾 - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + + Show game filter toolbar above list in room tab + + + + Card rendering 牌面 - + Display card names on cards having a picture 顯示有圖卡牌的名稱 - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over 卡牌隨指針縮放 - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout 手牌區域佈局 - + Display hand horizontally (wastes space) 水平顯示手牌區域 (浪費空間) - + Enable left justification 開啓左對齊 - + Table grid layout 表格佈局 - + Invert vertical coordinate 反轉垂直坐標 - + Minimum player count for multi-column layout: 界面佈局之內能夠容納的玩家欄數量: - + Maximum font size for information displayed on cards: 卡牌上顯示的最大字號 + + ArchidektApiResponseDeckDisplayWidget + + + Open Deck in Deck Editor + + + BackgroundSources @@ -333,112 +348,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name 禁止該用戶 - + ban &IP address 禁止該IP地址 - + ban client I&D 禁止該客户端 - + Ban type 禁止類型 - + &permanent ban 永久禁止 - + &temporary ban 暫時禁止 - + &Days: 天: - + &Hours: 小時: - + &Minutes: 分鐘: - + Duration of the ban 持續時長 - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. 請輸入禁止原因. 此項為管理緣由而保存並且對於這位被禁止的用戶不可見. - + Please enter the reason for the ban that will be visible to the banned person. 請輸入對於被禁止用戶可見的禁止原因 - + Redact all messages from this user in all rooms 修改該用戶在所有房間中的所有消息 - + &OK 確認 - + &Cancel 取消 - + Ban user from server 禁止此用戶 - + + - - + Error 錯誤 - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. 你必須選擇姓名、IP、客户端或任意多選組合來執行禁止操作。 - + You must have a value in the name ban when selecting the name ban checkbox. 在禁用姓名時必須填寫一個姓名。 - + You must have a value in the ip ban when selecting the ip ban checkbox. 在禁用IP時必須填寫一個IP。 - + You must have a value in the clientid ban when selecting the clientid ban checkbox. 在禁用客戶端ID時必須填寫一個客戶端ID。 @@ -469,32 +484,32 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name 名稱 - + Sets 系列 - + Mana cost 魔法力費用 - + Card type 卡牌類別 - + P/T 力量/防禦力 - + Color(s) 顏色 @@ -502,96 +517,101 @@ This is only saved for moderators and cannot be seen by the banned person. CardFilter - + AND Logical conjunction operator used in card filter - + OR Logical disjunction operator used in card filter - + AND NOT Negated logical conjunction operator used in card filter 和不是 - + OR NOT Negated logical disjunction operator used in card filter 或不是 - + Name 名稱 - + + Name (Exact) + + + + Type 類型 - + Color 顏色 - + Text 文本 - + Set 系列 - + Mana Cost 魔法力費用 - + Mana Value 魔法力值 - + Rarity 稀有度 - + Power 力量 - + Toughness 防禦力 - + Loyalty 忠誠 - + Format 賽制 - + Main Type - + Sub Type @@ -599,22 +619,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoFrameWidget - + Image - + Description - + Both - + View transformation @@ -622,22 +642,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -650,12 +670,22 @@ This is only saved for moderators and cannot be seen by the banned person. - + + Set: + + + + + Collector Number: + + + + Related cards: - + Unknown card: @@ -663,124 +693,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -788,7 +818,7 @@ This is only saved for moderators and cannot be seen by the banned person. CardSizeWidget - + Card Size @@ -796,133 +826,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative - + %1's hand nominative - - - their library - look at zone - - - - - %1's library - look at zone - - - - - of their library - top cards of zone, - - - - - of %1's library - top cards of zone - - their library - reveal zone + look at zone %1's library + look at zone + + + + + of their library + top cards of zone, + + + + + of %1's library + top cards of zone + + + + + their library reveal zone - + + %1's library + reveal zone + + + + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - - - their graveyard - nominative - - - - - %1's graveyard - nominative - - - - - their exile - nominative - - - - - %1's exile - nominative - - + their graveyard + nominative + + + + + %1's graveyard + nominative + + + + + their exile + nominative + + + + + %1's exile + nominative + + + + their sideboard look at zone - + %1's sideboard look at zone - - - their sideboard - nominative - - - - - %1's sideboard - nominative - - - their custom zone '%1' + their sideboard nominative + %1's sideboard + nominative + + + + + their custom zone '%1' + nominative + + + + %1's custom zone '%2' nominative @@ -931,7 +961,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml3Parser - + Parse error at line %1 col %2: @@ -939,7 +969,7 @@ This is only saved for moderators and cannot be seen by the banned person. CockatriceXml4Parser - + Parse error at line %1 col %2: @@ -958,6 +988,37 @@ This is only saved for moderators and cannot be seen by the banned person. + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget @@ -969,47 +1030,47 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add to Deck - + Add to Sideboard - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1017,87 +1078,97 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing - + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + + Format: + + + + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1105,17 +1176,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1123,114 +1194,114 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1238,7 +1309,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1246,194 +1317,227 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers 更新預覽 - - + + Success 成功 - + Download URLs have been reset. 下載URL已重置。 - + Downloaded card pictures have been reset. 下載的卡牌圖片已被重置。 - + Error 錯誤 - + One or more downloaded card pictures could not be cleared. 1個或多個卡牌圖片未能被清除。 - + Add URL 添加URL - - + + URL: URL: - - + + Edit URL 編輯URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL 添加新的URL - + Remove URL 移除URL - + Day(s) - + Updating... 更新中... - + Choose path 選擇路徑 - + URL Download Priority URL下載優先級 - + Spoilers 預覽 - + Download Spoilers Automatically 自動下載預覽 - + Spoiler Location: 預覽位置: - + Last Change 最近的變更 - + Spoilers download automatically on launch 自動下載預覽運行 - + Press the button to manually update without relaunching 按下按鈕手動更新而不重新啓動 - + Do not close settings until manual update is complete 手動更新完成之前,請勿關閉設置 - + Download card pictures on the fly 即時下載卡牌圖片 - + How to add a custom URL 如何添加自定義URL - + Delete Downloaded Images 刪除已下載的圖片 - + Reset Download URLs 重置用於下載的URL - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen + + DeckListHistoryManagerWidget + + + Undo + + + + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] + + + DeckListModel - + Count - + Set - + Number 數量 - + Provider ID - + Card 卡牌 @@ -1441,12 +1545,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1454,17 +1558,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1472,7 +1576,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -1480,62 +1584,62 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -1548,93 +1652,151 @@ This is only saved for moderators and cannot be seen by the banned person. - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1652,74 +1814,74 @@ This is only saved for moderators and cannot be seen by the banned person. DeckViewContainer - + Load deck... 讀取套牌... - + Load remote deck... 載入伺服器上的套牌... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start 準備好開始 - + Force start - + Sideboard unlocked 解鎖備牌 - + Sideboard locked 鎖定備牌 - - + + Error 錯誤 - + The selected file could not be loaded. 此文件無法被載入 - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -1752,143 +1914,143 @@ This will kick all non-ready players from the game. 下載中... - + Known Hosts 已知主機: - + Delete the currently selected saved server - + Refresh the server list with known public servers 從已知的伺服器更新伺服器列表 - + New Host 新主機 - + Name: 名稱: - + &Host: 主機: - + &Port: 端口: - + Player &name: 玩家名字: - + P&assword: 密碼: - + &Save password 記住密碼 - + A&uto connect 自動連接 - + Automatically connect to the most recent login when Cockatrice opens 當Cockatrice啓動時自動連接到最近的登錄 - + If you have any trouble connecting or registering then contact the server staff for help! 如果你在連接或者註冊上遇到了任何麻煩,可以聯繫伺服器的工作人員獲得幫助。 - - + + Webpage 網頁 - + Reset Password 重設密碼 - + Forgot password? - + &Connect 連接 - + Server 伺服器 - + Login 登錄 - + Server Contact 聯繫伺服器 - + Connect to Server 連接伺服器 - + Server URL 伺服器URL - + Communication Port 通信端口 - + Unique Server Name 唯一的伺服器名稱 - + Connection Warning 連接警告 - + You need to name your new connection profile. 你需要給連接配置文件命名。 - + Connect Warning 連接警告 - + The player name can't be empty. 玩家名稱不能為空白 @@ -1896,117 +2058,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings 記住設定 - + &Description: 描述: - + P&layers: 玩家: - + General 綜合 - + Game type 房間類型 - + &Password: 密碼: - + Only &buddies can join 只容許好友加入 - + Only &registered users can join 只容許註冊用戶加入 - + Joining restrictions 加入條件 - + &Spectators can watch 允許觀看者 - + Spectators &need a password to watch 觀看者需要密碼 - + Spectators can &chat 觀看者可以聊天 - + Spectators can see &hands 觀看者可見手牌 - + Create game as spectator 以旁觀者的身份創建遊戲 - + Spectators 觀看者 - + Starting life total: - + Open decklists in lobby - + + Create game as judge + + + + Game setup options - + &Clear 清除 - + Create game 創建一個房間 - + Game information 房間信息 - + Error 錯誤 - + Server error. 伺服器錯誤. @@ -2014,97 +2181,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: 名稱: - + Token 衍生物 - + C&olor: 顏色: - + white - + blue - + black - + red - + green - + multicolor 多色 - + colorless 無色 - + &P/T: 力量/防禦力: - + &Annotation: 註釋: - + &Destroy token when it leaves the table 此衍生物離場時會被摧毀 - + Create face-down (Only hides name) - + Token data 衍生物數據資料 - + Show &all tokens 顯示所有衍生物 - + Show tokens from this &deck 顯示此套牌所使用的衍生物 - + Choose token from list 從列表中選擇衍生物 - + Create token 派出一個衍生物 @@ -2112,53 +2279,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2166,40 +2333,40 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. 未選擇圖像 - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. 要更改你的頭像,請選擇一個新圖片。 要清除你當前的頭像,可以不用選擇新頭像直接確認。 - + Browse... 瀏覽... - + Change avatar 改變頭像 - + Open Image 打開圖片 - + Image Files (*.png *.jpg *.bmp) 圖片文件(*.png *.jpg *.bmp格式) - + Invalid image chosen. 所選圖片不可用。 @@ -2207,17 +2374,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2225,38 +2392,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: 舊密碼: - + New password: 新密碼: - + Confirm new password: 確認新密碼: - + Change password 修改密碼: - - + + Error 錯誤 - + Your password is too short. 你的密碼太短。 - + The new passwords don't match. 兩次輸入的新密碼不一致。 @@ -2264,93 +2431,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: 名稱: - + C&olor: 顏色: - + white - + blue - + black - + red - + green - + multicolor 多色 - + colorless 無色 - + &P/T: 力量/防禦力: - + &Annotation: 註釋: - + Token data 衍生物數據資料 - - + + Add token 添加衍生物 - + Remove token 刪除衍生物 - + Edit custom tokens 編輯自定義衍生物 - + Please enter the name of the token: 請輸入衍生物名稱: - + Error 錯誤 - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. 所選擇的名字與現有的卡牌或衍生物有衝突。 @@ -2444,7 +2611,8 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games not created by buddy + Hide games not created by buddies + Hide games not created by buddy @@ -2531,52 +2699,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning 重設密碼驗證警告 - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: 主機: - + &Port: 端口: - + Player &name: 玩家名字: - + Email: 電子郵箱: - + Reset Password Challenge 重設密碼驗證 - + Reset Password Challenge Error 重設密碼驗證錯誤 - + The email address can't be empty. 電郵不能為空白 @@ -2584,37 +2752,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: 主機: - + &Port: 端口: - + Player &name: 玩家名字: - + Reset Password Request 重設密碼請求 - + Reset Password Error 重設密碼錯誤 - + The player name can't be empty. 玩家名稱不能為空白 @@ -2622,86 +2790,86 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning 重設密碼警告 - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: 主機: - + &Port: 端口: - + Player &name: 玩家名字: - + Token: 驗證碼 - - + + New Password: 新密碼: - + Reset Password - + The player name can't be empty. 玩家名稱不能為空白 - - - - + + + + Reset Password Error - + The token can't be empty. 驗證碼不能為空白 - + The new password can't be empty. 新密碼不能為空白 - + Error 錯誤 - + Your password is too short. 你的密碼太短。 - + The passwords do not match. 密碼不一致 @@ -2717,17 +2885,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard 從剪貼板讀取套牌 - + Error 錯誤 - + Invalid deck list. 無效的牌表. @@ -2735,43 +2903,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2785,7 +2953,7 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck 讀取套牌 @@ -2793,37 +2961,37 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -2831,91 +2999,91 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: 主機: - + &Port: 端口: - + Player &name: 玩家名字: - + P&assword: 密碼: - + Password (again): (再次)輸入密碼: - + Email: 電子郵箱: - + Email (again): (再次)輸入電郵: - + Country: 國家: - + Undefined 未定義 - + Real name: 真實姓名: - + Register to server 註冊到伺服器 - - - - + + + + Registration Warning 註冊警告 - + Your password is too short. 你的密碼太短。 - + Your passwords do not match, please try again. 兩次輸入的密碼不一致,請重新輸入, - + Your email addresses do not match, please try again. 兩次輸入的電子郵箱不一致,請重新輸入。 - + The player name can't be empty. 玩家名稱不能為空白 @@ -2941,40 +3109,55 @@ Your email will be used to verify your account. DlgSelectSetForCards - + Unmodified Cards: - + Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database 讀取卡牌數據庫時出現未知錯誤 - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -2991,7 +3174,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + Your card database version is too old. This can cause problems loading card information or images @@ -3008,7 +3191,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3017,7 +3200,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3026,7 +3209,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌數據庫路徑嗎? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3035,7 +3218,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3044,59 +3227,59 @@ Would you like to change your database location setting? - - - + + + Error 錯誤 - + The path to your deck directory is invalid. Would you like to go back and set the correct path? 你的套牌路徑無效。你想要重新設置正確的路徑嗎? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? 你想要重新設置卡牌數據庫路徑嗎? - + Settings 設定 - + General 常規 - + Appearance 外觀 - + User Interface 用戶界面 - + Card Sources 卡牌的來源 - + Chat 聊天 - + Sound 聲音 - + Shortcuts 快捷鍵 @@ -3104,39 +3287,39 @@ Would you like to change your database location setting? DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3144,17 +3327,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next 下一個 - + Previous 已知 - + Tip of the Day 每日提示 @@ -3162,165 +3345,165 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel 當前的發行通道 - + Reinstall 重新安裝 - + Cancel Download 取消下載 - + Open Download Page 打開下載頁面 - + Check for Client Updates - - - - + + + + Error 錯誤 - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. 雞蛇不支持建立SSL連接,因此你不能自動下載更新! 請訪問下載頁面以手動更新。 - + Downloading update: %1 - + Checking for updates... 正在檢查更新... - + Finished checking for updates 更新檢查完成 - + No Update Available 沒有更新可用 - + Cockatrice is up to date! 雞蛇已經是最新的了! - + You are already running the latest version available in the chosen release channel. 你運行的已經是最新的發行版本了。 - + Current version 當前版本 - + Selected release channel 選擇發行渠道 - - + + Update Available 更新可用 - - + + A new version of Cockatrice is available! 雞蛇有新版本可用! - - + + New version 新版本 - - + + Released 發佈 - - + + Changelog 更新日誌 - + Do you want to update now? 你打算現在就更新嗎? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + + Update Error 更新錯誤 - + An error occurred while checking for updates: 檢查更新時發生錯誤: - + An error occurred while downloading an update: 下載更新時發生錯誤: - + Installing... 正在安裝... - + Cockatrice is unable to open the installer. 雞蛇無法打開安裝程序。 - + Try to update manually by closing Cockatrice and running the installer. 嘗試關閉雞蛇運行安裝程序手動更新。 - + Download location 下載區域信息 @@ -3328,21 +3511,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing 關閉時清除日誌 - + Copy to clipboard - + Debug Log 問題日誌 + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3412,33 +3727,39 @@ You may have to manually download the new version. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3454,22 +3775,22 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. @@ -3477,22 +3798,22 @@ You may have to manually download the new version. GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -3500,226 +3821,279 @@ You may have to manually download the new version. GameSelector - - - - - - + + + + + + Error 錯誤 - + Please join the appropriate room first. 首先請加入一個房間. - + Wrong password. 密碼錯誤. - + Spectators are not allowed in this game. 這個遊戲不允許觀眾加入. - + The game is already full. 這個遊戲已經滿了. - + The game does not exist any more. 這個遊戲不能裝下啊更多了. - + This game is only open to registered users. 這個遊戲只開放給註冊用戶. - + This game is only open to its creator's buddies. 這個遊戲只開放給好友。 - + You are being ignored by the creator of this game. 你屏蔽了這個與遊戲的創造者. - + Join Game - + Spectate Game - + Game Information - + + Join Game as Judge + + + + + Spectate Game as Judge + + + + Join game 加入遊戲 - + Password: 密碼: - + Please join the respective room first. 請各自加入一個房間. - + &Filter games 過濾房間 - + C&lear filter 清除篩選項目 - + C&reate 創建 - + &Join 加入 - + + Join as judge + + + + J&oin as spectator 觀看 - + + Join as judge spectator + + + + Games shown: %1 / %2 顯示遊戲:%1/%2 - + Games 遊戲 + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day - + %1%2 hr short age in hours - + new 新建 - + %1%2 min short age in minutes - + password 密碼 - + buddies only 僅限好友 - + reg. users only 僅限註冊用户 - + open decklists - - + + can chat 允許聊天 - + see hands 查看手牌 - + can see hands 允許查看手牌 - + not allowed 不允許 - + Room 房間 - + Age 時長 - + Description 描述 - + Creator 創建者 - + Type 類型 - + Restrictions 限制 - + Players 玩家 - + Spectators 觀看者 @@ -3727,143 +4101,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path 選擇路徑 - + Personal settings 個人設定 - + Language: 語言: - + Paths (editing disabled in portable mode) 路徑(在便攜模式下禁止編輯) - + Paths 路徑 - + How to help with translations - + Decks directory: 套牌路徑: - + Filters directory: - + Replays directory: 遊戲錄像目錄: - + Pictures directory: 圖片目錄: - + Card database: 卡牌數據庫: - + Custom database directory: - + Token database: 衍生物數據庫: - + Update channel 更新通道 - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client 當客戶端缺少伺服器支持的特性時提示我 - + Automatically run Oracle when running a new version of Cockatrice 運行新版本雞蛇時自動運行Oracle - + Show tips on startup 顯示啓動時提示 - + Last update check on %1 (%2 days ago) @@ -3871,47 +4245,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -3919,111 +4293,141 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - - &Sort hand + + Sort hand by... - - Take &mulligan + + Name - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - Reveal r&andom card to... + + + All players - - - &All players + + Reveal r&andom card to... HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + + Browse Archidekt + + + + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4225,644 +4629,644 @@ You may have to manually download the new version. MainWindow - - + + The server has reached its maximum user capacity, please check back later. 伺服器已達到最大用戶數,請稍微再試。 - + There are too many concurrent connections from your address. 你的地址有太多連接. - + Banned by moderator 已被版主禁止 - + Expected end time: %1 預計結束時間: %1 - + This ban lasts indefinitely. 這個封禁是永久的. - + Scheduled server shutdown. 預定伺服器關閉. - - + + Invalid username. 不可用的用戶名。 - + You have been logged out due to logging in at another location. 由於在其他地點登陸,您已被登出。 - + Connection closed 連接關閉 - + The server has terminated your connection. Reason: %1 伺服器中斷連接. 原因: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown 預定伺服器關閉 - - + + Success 成功 - + Registration accepted. Will now login. 註冊已成功。 現在登陸。 - + Account activation accepted. Will now login. 賬號已激活。 現在登陸 - + Number of players 玩家人數 - + Please enter the number of players. 請輸入玩家的數量. - - + + Player %1 玩家 %1 - + Load replay 載入遊戲錄像 - + About Cockatrice 關於Cockatrice雞蛇 - + Version 版本 - + Cockatrice Webpage Cockatrice雞蛇網站 - + Project Manager: 項目經理: - + Past Project Managers: 前項目經理: - + Developers: 開發者: - + Our Developers 我們的開發者: - + Help Develop! 協助開發! - + Translators: 翻譯者: - + Our Translators 翻譯者名單 - + Help Translate! 協助翻譯! - + Support: 支持: - + Report an Issue 報告錯誤 - + Troubleshooting 排除故障 - + F.A.Q. 問答 - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Error 錯誤 - + Server timeout 伺服器超時 - + Failed Login 登錄失敗 - + Your client seems to be missing features this server requires for connection. 你的客戶端缺少連接這個伺服器所需要的要素 - + To update your client, go to 'Help -> Check for Client Updates'. 轉到幫助->檢查客户端更新,更新你的客户端。 - + Incorrect username or password. Please check your authentication information and try again. 用户名或密碼錯誤。請檢查賬號信息並重試。 - + There is already an active session using this user name. Please close that session first and re-login. 已經有一個在線用户正在使用這個用户名. 首先請關閉這個對話框然後重新登陸. - - + + You are banned until %1. 你被禁止直到: %1. - - + + You are banned indefinitely. 你被永久封禁. - + This server requires user registration. Do you want to register now? 伺服器需要註冊。現在註冊嗎? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. 伺服器需要客户端ID。你的客户端未能生成ID或你正在運行修改過的客户端。 請關閉並重新打開客户端。 - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. 出現內部錯誤,請關閉並重啓客戶端再嘗試。如果錯誤仍然出現請將您的客戶端升級到最新版本,如有需要請聯繫您的軟件提供商。 - + Account activation 賬號激活 - + Your account has not been activated yet. You need to provide the activation token received in the activation email. 你的賬號還未激活。 請提供激活郵件中的驗證碼。 - + Server Full 伺服器已滿 - + Unknown login error: %1 未知的登陸錯誤: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. 這通常表示你的客户端已經過於陳舊,未能響應伺服器提出的請求。 - + Your username must respect these rules: 您的用戶名須遵守以下規則: - + is %1 - %2 characters long 在%1 - %2個字符之間。 - + can %1 contain lowercase characters 能包含 %1 個小寫字母 - - - - + + + + NOT - + can %1 contain uppercase characters 能包含%1個小寫字母 - + can %1 contain numeric characters 能包含%1個數字 - + can contain the following punctuation: %1 能包含以下符號:%1 - + first character can %1 be a punctuation mark 第一個字符不能為%1 標點符號 - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 不能包含以下詞:%1 - + can not match any of the following expressions: %1 不能包含以下語句:%1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. 你可以使用A-Z,a-z,0-9,_,.以及-作為用户名。 - - - - - - + + + + + + Registration denied 註冊失敗 - + Registration is currently disabled on this server 當前伺服器無法註冊 - + There is already an existing account with the same user name. 當前用戶名已經被使用。 - + It's mandatory to specify a valid email address when registering. 註冊時必須提供一個有效的電郵。 - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. 看起來你正試圖在這個伺服器上註冊一個新賬户,但是你已經有了一個用提供的郵箱註冊的賬户。 此伺服器限制每個郵箱地址可以註冊用户帳户的數量。請聯繫伺服器運營商以獲得進一步幫助或獲取你的憑據信息。 - + Password too short. 密碼太短。 - + Registration failed for a technical problem on the server. 由於伺服器出現技術問題,註冊失敗。 - + The connection to the server has been lost. - + Unknown registration error: %1 未知的註冊錯誤:%1 - + Account activation failed 賬號激活失敗 - + Socket error: %1 接口錯誤: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. 你正在連接到一個過時的伺服器,請下調你的版本或者連接到一個匹配的伺服器. 你當前的版本 %1, 伺服器版本 %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. 你使用的客户端意見過時,請使用更新你版本. 你當前的版本 %1, 伺服器版本 %2. - + Connecting to %1... 連接到 %1⋯ - + Registering to %1 as %2... 正在將 %2 註冊到 %1 - + Disconnected 斷開連接 - + Connected, logging in at %1 連接登陸到 %1 + - Requesting forgotten password to %1 as %2... 正在將%2向%1申請忘記密碼流程。 - + &Connect... 連接⋯ - + &Disconnect 斷開連接 - + Start &local game... 開始本地遊戲... - + &Watch replay... 觀看錄像... - + &Full screen 全屏 - + &Register to server... 註冊到伺服器 - + &Restore password... - + &Settings... 設置⋯ - + &Exit 退出 - + A&ctions 動作 - + &Cockatrice CockatriceCockatrice - + C&ard Database 卡牌資料庫 - + &Manage sets... 系列管理 - + Edit custom &tokens... 編輯自定義&tokens - + Open custom image folder 打開自定義圖片文件夾 - + Open custom sets folder 打開自定義系列文件夾 - + Add custom sets/cards 添加自定義系列/卡牌 - + Reload card database - + Tabs - + &Help 幫助 - + &About Cockatrice 關於Cockatrice雞蛇 - + &Tip of the Day & 每日提示 - + Check for Client Updates 檢查客戶端更新 - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + Show/Hide - + New Version 新版本 - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. 恭喜Cockatrice更新到%1! 現在啓動Oracle更新你的卡牌資料庫。 - + Cockatrice installed 雞蛇已安裝 - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. 恭喜Cockatrice更新到%1! 現在啓動Oracle更新你的卡牌資料庫。 - + Card database 卡牌數據庫 - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -4871,46 +5275,46 @@ If unsure or first time user, choose "Yes" 如果不確定或者你是第一次使用,請選擇“是” - - + + Yes - - + + No - + Open settings 打開設定 - + New sets found 發現新系列 - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets 查看系列 - + Welcome 歡迎 - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -4919,64 +5323,64 @@ Read more about changing the set order or disabling specific sets and consequent 瞭解更多用“系列管理”窗口更改系列順序或者禁用某系列的信息。 - - + + Information 信息 - + A card database update is already running. 數據庫更新已經在運行中。 - + Unable to run the card database updater: 無法運行卡組數據庫更新器: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -4986,673 +5390,843 @@ To update your client, go to Help -> Check for Updates. 要升級客户端,請點擊 幫助->檢查更新 - - - - - + + + + + Load sets/cards 載入系列/卡牌 - + Selected file cannot be found. 找不到選擇的文件。 - + You can only import XML databases at this time. 當前只能導入XML格式數據庫。 - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. 新的系列/卡牌已添加成功。 CockatriceCockatrice現在會重新載入卡組數據庫。 - + Sets/cards failed to import. 系列/卡牌導入失敗。 - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. 你的密碼已重置,現在你可以使用新密碼登錄了。 - + Failed to reset user account password, please contact the server operator to reset your password. 重置密碼失敗,請聯繫伺服器管理員重置密碼。 - + Activation request received, please check your email for an activation token. 激活請求已收到,請查看你的電子郵箱獲取激活驗證碼。 + + ManaBaseConfigDialog + + + Mana Base Configuration + + + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel + + + ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play 從戰場上 - + from their graveyard 從他的墳墓場 - + from exile 從放逐區 - + from their hand 從他的手牌 - + the top card of %1's library %1牌庫頂的牌 - + the top card of their library 他牌庫頂的卡牌 - + from the top of %1's library 從%1的牌庫頂 - + from the top of their library 從他的牌庫頂 - + the bottom card of %1's library %1的牌庫底牌 - + the bottom card of their library 他牌庫底部的牌 - + from the bottom of %1's library 從%1的牌庫底 - + from the bottom of their library 從他的牌庫底 - + from %1's library 從%1的牌庫中 - + from their library 從他的牌庫 - + from sideboard 從備牌中 - + from the stack 從堆疊中 - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1已持續展示牌庫頂牌%2 - + %1 is not revealing the top card %2 any longer. %1不再%2展示牌庫頂牌。 - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 結附 %2 指向 %3的 %4. - + %1 has conceded the game. %1 已放棄遊戲。 - + %1 has unconceded the game. %1 已放棄遊戲。 - + %1 has restored connection to the game. %1已恢復連接。 - + %1 has lost connection to the game. %1 已失去連接。 - + %1 points from their %2 to themselves. %1 將%2 指向自己。 - + %1 points from their %2 to %3. %1 將%2指向%3。 - + %1 points from %2's %3 to themselves. %1將%2的%3 - + %1 points from %2's %3 to %4. %1 將%2的%3指向%4。 - + %1 points from their %2 to their %3. %1將他的%2指向他的%3。 - + %1 points from their %2 to %3's %4. %1將他的%2指向%3的%4。 - + %1 points from %2's %3 to their own %4. %1將%2的%3指向他自己的%4。 - + %1 points from %2's %3 to %4's %5. %1 將%2的%3指向%4的%5。 - + %1 creates a face down token. - + %1 creates token: %2%3. %1創建衍生物:%2%3。 - + %1 has loaded a deck (%2). %1 已載入套牌(%2) - + %1 has loaded a deck with %2 sideboard cards (%3). %1已載入含有 %2張備牌的套牌(%3) - + %1 destroys %2. %1銷燬了%2。 - + a card 一張牌 - + %1 gives %2 control over %3. %1讓%2控制%3。 - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1將%2%3放進戰場。 - + %1 puts %2%3 into their graveyard. %1將%2%3置入他的墳墓場。 - + %1 exiles %2%3. %1 %3放逐了 %2 - + %1 moves %2%3 to their hand. %1將%2%3放回他的手牌中。 - + %1 puts %2%3 into their library. %1將%2%3放入他的牌庫。 - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. %1將%2%3置於其牌庫頂。 - + %1 puts %2%3 into their library %4 cards from the top. %1將%2%3放入牌庫%4張牌放在牌庫頂 - + %1 moves %2%3 to sideboard. %1 將%2%3移動到備牌 - + %1 plays %2%3. %1使用 %2%3。 - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. %1 正在查看%2。 - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. %1回合%2翻為面朝下。 - + %1 turns %2 face-up. %1回合%2翻為面朝上。 - + The game has been closed. 遊戲已經關閉。 - + The game has started. 遊戲已開始。 - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 已經加入遊戲。 - + %1 is now watching the game. %1 正在旁觀遊戲。 - + You have been kicked out of the game. 你已被踢出遊戲。 - + %1 has left the game (%2). %1 已經離開了遊戲(%2)。 - + %1 is not watching the game any more (%2). %1 不再旁觀遊戲(%2)。 - + %1 is not ready to start the game any more. %1 還未準備好開始遊戲。 - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. %1洗牌並抓了新的起手 - + You are watching a replay of game #%1. 你正在觀看遊戲#%1的錄像。 - + %1 is ready to start the game. %1已準備好開始遊戲。 - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. %1將%2展示給%3。 - + %1 reveals %2. %1 展示 %2。 - + %1 randomly reveals %2%3 to %4. %1隨機展示%2%3給%4。 - + %1 randomly reveals %2%3. %1隨機展示%2%3。 - + %1 peeks at face down card #%2. %1查看面朝下的卡牌#%2。 - + %1 peeks at face down card #%2: %3. %1查看面朝下的卡牌#%2:%3。 - + %1 reveals %2%3 to %4. %1展示%2%3給%4。 - + %1 reveals %2%3. %1展示%2%3。 - + %1 reversed turn order, now it's %2. %1 反轉回合順序,現在是%2的回合。 - + reversed 反轉 - + normal 正常 - + Heads 正面 - + Tails 反面 - + %1 flipped a coin. It landed as %2. %1擲硬幣。結果為%2。 - + %1 rolls a %2 with a %3-sided die. %1擲%3面骰子,結果為%2。 - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. 現在是%1的回合。 - + %1 sets annotation of %2 to %3. %1給%2添加了註釋%3。 - + %1 places %2 "%3" counter(s) on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 將%2指示物設置為 %3 (%4%5)。 - + %1 sets %2 to not untap normally. %1 將 %2 設置為不會被通常重置。 - + %1 sets %2 to untap normally. %1 將 %2 設置為可通常重置。 - + %1 removes the PT of %2. %1 移除%2力量/防禦 - + %1 changes the PT of %2 from nothing to %4. %1改變%2的力量/防禦至%4 - + %1 changes the PT of %2 from %3 to %4. %1將%2的力量/防禦從%3改變至%4 - + %1 has locked their sideboard. %1已鎖定他的備牌。 - + %1 has unlocked their sideboard. %1 解除鎖定他的備牌。 - + %1 taps their permanents. %1 橫置了永久物。 - + %1 untaps their permanents. %1 重置了永久物。 - + %1 taps %2. %1 橫置了 %2。 - + %1 untaps %2. %1 重置了 %2。 - + %1 shuffles %2. %1 切洗了%2。 - + %1 shuffles the bottom %3 cards of %2. %1 洗 %2 底的 %3 張卡 - + %1 shuffles the top %3 cards of %2. %1 洗 %2 頂的 %3 張卡 - + %1 shuffles cards %3 - %4 of %2. %1洗%3 - %4 到%2. - + %1 unattaches %2. %1 取消了%2的結附。 - + %1 undoes their last draw. %1撤銷了最後的抓牌。 - + %1 undoes their last draw (%2). %1撤銷了最後的抓牌(%2)。 @@ -5660,110 +6234,110 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 MessagesSettingsPage - + Word1 Word2 Word3 詞1 詞2 詞3 - + Add New Message 添加新信息 - + Edit Message 編輯信息 - + Remove Message - + Add message 添加信息 - - + + Message: 信息: - + Edit message 編輯消息 - + Chat settings 聊天設定 - + Custom alert words 自定義警告語 - + Enable chat mentions 允許聊天中提到某人 - + Enable mention completer 允許自動完成提名 - + In-game message macros 遊戲內消息宏 - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users 忽略未註冊用戶的聊天室消息。 - + Ignore private messages sent by unregistered users 忽略未註冊用戶發出的私人消息 - - + + Invert text color 反轉文本顏色 - + Enable desktop notifications for private messages 開啓私人消息桌面提醒 - + Enable desktop notification for mentions 開啓聊天提名桌面提醒 - + Enable room message history on join 開啓加入聊天室時的歷史消息 - - + + (Color is hexadecimal) (顏色為16進制) - + Separate words with a space, alphanumeric characters only 將單詞以空格區分,僅支持字母 @@ -5880,62 +6454,62 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 Phase - + Unknown Phase 未知步驟 - + Untap 重置步驟 - + Upkeep 維持步驟 - + Draw 抓牌步驟 - + First Main 主要步驟1 - + Beginning of Combat 戰鬥開始步驟 - + Declare Attackers 宣告進攻步驟 - + Declare Blockers 宣告阻擋步驟 - + Combat Damage 戰鬥傷害步驟 - + End of Combat 戰鬥結束步驟 - + Second Main 主要步驟2 - + End/Cleanup 結束/清理階段 @@ -6001,7 +6575,7 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 PictureLoader - + en code for scryfall's language property, not available for all languages @@ -6010,134 +6584,134 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - + Move top cards to grave - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6145,17 +6719,17 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 PlayerMenu - + Player "%1" - + &Counters - + S&ay @@ -6163,7 +6737,7 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 PrintingSelector - + Display Navigation Buttons @@ -6171,22 +6745,22 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6202,17 +6776,17 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 PrintingSelectorCardSelectionWidget - + Previous Card in Deck - + Bulk Selection - + Next Card in Deck @@ -6220,28 +6794,28 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -6307,37 +6881,37 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 QMenuBar - + Services 服務 - + Hide %1 隱藏 %1 - + Hide Others 隱藏其他 - + Show All 展示所有 - + Preferences... 參數 - + Quit %1 退出%1 - + About %1 關於 %1 @@ -6345,42 +6919,42 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 QObject - + Cockatrice card database (*.xml) CockatriceCockatrice卡牌數據庫 (*.xml) - + All files (*.*) 全部文件 (*.*) - + Cockatrice replays (*.cor) Cockatrice錄像文件 (*.cor) - + Maindeck 主套牌 - + Sideboard 備牌 - + Tokens 衍生物 - + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? @@ -6388,92 +6962,92 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 QPlatformTheme - + OK 確定 - + Save 保存 - + Save All 保存全部 - + Open 打開 - + &Yes - + Yes to &All 全選是 - + &No - + N&o to All 全選否 - + Abort 退出 - + Retry 重試 - + Ignore 忽略 - + Close 關閉 - + Cancel 取消 - + Discard 棄牌 - + Help 幫助 - + Apply 應用 - + Reset 重置 - + Restore Defaults 恢復為默認值 @@ -6570,37 +7144,37 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 RoomSelector - + Rooms 房間 - + Joi&n 加入 - + Room 房間 - + Description 信息 - + Permissions 許可證 - + Players 玩家 - + Games 遊戲 @@ -6641,27 +7215,27 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 SetsModel - + Enabled 已啓用 - + Set type 設置類型 - + Set code 設置代碼 - + Long name 長的名稱 - + Release date 發佈日期 @@ -6669,53 +7243,53 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 ShortcutSettingsPage - - + + Restore all default shortcuts 恢復所有默認快捷鍵 - + Do you really want to restore all default shortcuts? 確定要恢復所有默認快捷鍵嗎? - + Clear all default shortcuts 清楚所有默認快捷鍵 - + Do you really want to clear all shortcuts? 確定要清楚所有快捷鍵? - + Section: 部分: - + Action: 行動: - + Shortcut: 快捷鍵: - + How to set custom shortcuts 如何設置自定義快捷鍵 - + Clear all shortcuts - + Search by shortcut name @@ -6723,12 +7297,12 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 ShortcutTreeView - + Action - + Shortcut @@ -6736,14 +7310,14 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! 您的配置文件包涵了無效的快捷鍵 請檢查您的快捷鍵設定 - + The following shortcuts have been set to default: 以下快捷方式被設置為默認狀態: @@ -6771,12 +7345,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -6784,27 +7358,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds 啓用聲效 - + Current sounds theme: 當前聲效主題: - + Test system sound engine 測試系統聲效 - + Sound settings 聲效設定 - + Master volume 主音量 @@ -6812,48 +7386,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended 預覽季節已結束 - + Deleting spoiler.xml. Please run Oracle 刪除spoiler.xml。請運行Oracle - - + + Spoilers download failed 預覽下載錯誤 - + No internet connection 沒有網絡連接 - + Error 錯誤 - + Spoilers already up to date Spoilers更新已準備好 - + No new spoilers added 沒有新的預覽被添加 - + Spoilers have been updated! 預覽已更新! - + Last change: 最後變更: @@ -7017,56 +7591,123 @@ Please check your shortcut settings! + + TabArchidekt + + + + Desc. + + + + + Asc. + + + + + + Any Bracket + + + + + Deck name contains... + + + + + Owner name contains... + + + + + Disabled + + + + + Search + + + + + Formats + + + + + Min. # of Cards: + + + + + Page: + + + + + Archidekt: + + + TabDeckEditor - + Card Info 卡牌信息 - + Deck 套牌 - + Filters 過濾器 - + &View 視圖 - + + Card Database + + + + Printing - - - - + + + + + Visible 可見 - - - - + + + + + Floating 移動窗口 - + Reset layout 重置界面 - + Deck: %1 套牌: %1 @@ -7074,61 +7715,61 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + + + + Visible - - - - + + + + Floating - + Reset layout @@ -7136,22 +7777,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7159,134 +7800,134 @@ Please check your shortcut settings! TabDeckStorage - + Local file system 本地文件系統 - + Server deck storage 伺服器上的套牌倉庫 - - + + Open in deck editor 打開套牌編輯 - + Rename deck or folder - + Upload deck 上傳套牌 - + Download deck 下載套牌 + - - - + + New folder 新建文件夾 + - Delete 刪除 - + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + Error 錯誤 - + Rename failed - - + + Invalid deck file - + Enter deck name 輸入套牌名稱 - + This decklist does not have a name. Please enter a name: 這個套牌沒有名稱. 請輸入一個名稱: - + Unnamed deck 未命名套牌 - + Failed to upload deck to server - + Delete local file 刪除本地文件 - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + Name of new folder: 新建文件夾的名稱: @@ -7299,17 +7940,17 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 @@ -7350,7 +7991,7 @@ Please enter a name: - + EDHRec: @@ -7358,197 +7999,197 @@ Please enter a name: TabGame - - - + + + Replay 錄像 - - + + Game 遊戲 - - + + Player List 玩家列表 - - + + Card Info 卡牌信息 - - + + Messages 消息 - - + + Replay Timeline 錄像時間線 - + &Phases 階段 - + &Game 遊戲 - + Next &phase 下個階段 - + Next phase with &action 下個階段&行動 - + Next &turn 下個回合 - + Reverse turn order 反轉回合順序 - + &Remove all local arrows 移除所有箭頭 - + Rotate View Cl&ockwise 順時針旋轉視角 - + Rotate View Co&unterclockwise 逆時針旋轉視角 - + Game &information 遊戲信息 - + Un&concede - - - + + + &Concede 放棄遊戲 - + &Leave game 離開遊戲 - + C&lose replay 關閉遊戲錄像 - + &Focus Chat 聚焦聊天室 - + &Say: 説: - + Selected cards - + &View 視圖 - - + + + - Visible 可見 - - + + + - Floating 移動窗口 - + Reset layout 重置界面 - + Concede 投降 - + Are you sure you want to concede this game? 你確定放棄這個遊戲? - + Unconcede 取消投降 - + You have already conceded. Do you want to return to this game? 你已經投降,你想回到這場遊戲嗎? - + Leave game 離開遊戲 - + Are you sure you want to leave this game? 你確定離開這這個遊戲? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. 你已被踢出遊戲。 @@ -7556,7 +8197,7 @@ Please enter a name: TabHome - + Home @@ -7564,158 +8205,158 @@ Please enter a name: TabLog - + Logs 記錄 - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName 時間;發送者姓名;發送者IP;消息;目標ID;目標姓名 - + Room Logs 房間記錄 - + Game Logs 遊戲記錄 - + Chat Logs 聊天記錄 - - + + Error 錯誤 - + You must select at least one filter. 您必須至少選擇一個過濾項 - + You have to select a valid number of days to locate. 您必須選擇一個可用的天數用來定位 - + Username: 用户名: - + IP Address: IP地址: - + Game Name: 遊戲名: - + GameID: 遊戲ID: - + Message: 消息: - + Main Room 主房間 - + Game Room 遊戲房間 - + Private Chat 私人聊天 - + Past X Days: 過去 X 天: - + Today 今天 - + Last Hour 1小時前 - + Maximum Results: 最大結果: - + At least one filter is required. The more information you put in, the more specific your results will be. 至少需要1個篩選項。 您輸入的信息越多,找到結果越準確。 - + Get User Logs 獲取用戶記錄 - + Clear Filters 清楚篩選項目 - + Filters 過濾器 - + Log Locations 記錄位置 - + Date Range 日期範圍 - + Maximum Results 最大結果 - - + + Message History 消息歷史 - + Failed to collect message history information. 獲取消息歷史信息失敗。 - + There are no messages for the selected filters. 沒有符合篩選條件的消息。 @@ -7761,180 +8402,180 @@ The more information you put in, the more specific your results will be. TabReplays - + Local file system 本地文件系統 - + Server replay storage 伺服器錄像倉庫 - - + + Watch replay 觀看錄像 - + Rename - - + + New folder - - + + Delete 刪除 - + Open replays folder - + Download replay 下載遊戲錄像 - + Toggle expiration lock 過期鎖定 - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file 刪除本地文件 - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay 刪除伺服器上的錄像 @@ -8000,30 +8641,30 @@ The more information you put in, the more specific your results will be.伺服器 + - Error 錯誤 - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -8031,92 +8672,97 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - - - Home - - - - - &Visual Deck Storage - - - - - Visual Database Display - - - Server + Archidekt - Account + Home - Deck Storage + &Visual Deck Storage - Game Replays + Visual Database Display - Administration + Server + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + Logs - + Are you sure? 你確定嗎? - + There are still open games. Are you sure you want to quit? 遊戲還在繼續, 你確定要退出嗎? - + Click to view 點擊查看 - + Your buddy %1 has signed on! 你的好友%1已登錄! - + Unknown Event 未知事件 - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8127,39 +8773,39 @@ To update your client, go to Help -> Check for Updates. 要升級你的客户端,請點擊“幫助”->“檢查更新” - + Idle Timeout 閒置超時 - + You are about to be logged out due to inactivity. 你即將因為長時間未操作而被退出。 - + Promotion 晉升 - + You have been promoted. Please log out and back in for changes to take effect. 你已被提升為版主。請退出再登陸以使修改生效。 - + Warned 被警告 - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. 你因為%1被警告。 請杜絕此類行為否則會被採取進一步行動。如果有任何疑問,請私信版主。 - + You have received the following message from the server. (custom messages like these could be untranslated) 你收到來自伺服器的如下消息。 @@ -8169,7 +8815,12 @@ Please refrain from engaging in this activity or further actions may be taken ag TabVisualDatabaseDisplay - + + Database Display + + + + Visual Database Display @@ -8251,7 +8902,7 @@ Please refrain from engaging in this activity or further actions may be taken ag UpdateDownloader - + Could not open the file for reading. 無法打開文件。 @@ -8259,206 +8910,206 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details 用户詳細信息 - + Private &chat 私人聊天 - + Show this user's &games 顯示這個用户的遊戲 - + Add to &buddy list 加入到好友列表 - + Remove from &buddy list 從好友列表移除 - + Add to &ignore list 加入到屏蔽列表 - + Remove from &ignore list 從屏蔽列表移除 - + Kick from &game 從遊戲中踢出 - + Warn user 警告用戶 - + View user's war&n history 查看用户警告記錄 - + Ban from &server 在伺服器中禁止 - + View user's &ban history 查看用户禁止記錄 - + &Promote user to moderator 將用戶提升為版主 - + Dem&ote user from moderator 撤銷用户版主職位 - + Promote user to &judge - + Demote user from judge 撤銷用戶的裁判身份 - + View admin notes - - - + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games %1的遊戲 - - - + + + Ban History 禁止歷史 - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason 禁止時間;版主;禁止時長;禁止理由;可見理由 - + User has never been banned. 用戶從未被禁止 - + Failed to collect ban information. 獲取禁止信息失敗。 - - - + + + Warning History 警告歷史 - + Warning Time;Moderator;User Name;Reason 警告時間;版主;用户名;理由 - + User has never been warned. 用戶從未被警告 - + Failed to collect warning information. 獲取警告信息失敗。 - + Failed to get admin notes. - - + + Success 成功 - + Successfully promoted user. 提升用戶成功。 - + Successfully demoted user. 撤銷用戶成功。 - + + - Failed 失敗 - + Failed to promote user. 提升用戶失敗。 - + Failed to demote user. 撤銷用戶失敗。 - + Copy hash to clipboard 複製哈希到剪貼板 - + Remove this user's messages @@ -8640,137 +9291,142 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings 通用接口設定 - + &Double-click cards to play them (instead of single-click) 雙擊卡牌開始 (而不是單擊開始) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default 默認將所有非地牌加入堆疊(不是戰場) - - - Close card view window when last card is removed - - - - - Auto focus search bar when card view window is opened - - - - - Annotate card text on tokens - 用卡牌信息給衍生物標注 - - - - Use tear-off menus, allowing right click menus to persist on screen - 使用浮動菜單,允許右鍵單擊菜單保留在屏幕上 - - - - Notifications settings - 通知設定 - - - - Enable notifications in taskbar - 開啓任務欄提醒 - - - - Notify in the taskbar for game events while you are spectating - 觀看時在任務欄提示遊戲信息 - - - - Notify in the taskbar when users in your buddy list connect - 在任務欄中提示好友連接到伺服器 - - - - Animation settings - 動畫設定 - - - - &Tap/untap animation - 橫置/重置 動畫 - - Deck editor/storage settings + Do not delete &arrows inside of subphases - Open deck in new tab by default + Close card view window when last card is removed - Use visual deck storage in game lobby + Auto focus search bar when card view window is opened - Use selection animation for Visual Deck Storage - + Annotate card text on tokens + 用卡牌信息給衍生物標注 + + + + Use tear-off menus, allowing right click menus to persist on screen + 使用浮動菜單,允許右鍵單擊菜單保留在屏幕上 - When adding a tag in the visual deck storage to a .txt deck: - + Notifications settings + 通知設定 + + + + Enable notifications in taskbar + 開啓任務欄提醒 - do nothing - + Notify in the taskbar for game events while you are spectating + 觀看時在任務欄提示遊戲信息 + + + + Notify in the taskbar when users in your buddy list connect + 在任務欄中提示好友連接到伺服器 - ask to convert to .cod - + Animation settings + 動畫設定 + + + + &Tap/untap animation + 橫置/重置 動畫 - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -8778,22 +9434,22 @@ Please refrain from engaging in this activity or further actions may be taken ag UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 @@ -8801,32 +9457,32 @@ Please refrain from engaging in this activity or further actions may be taken ag UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -8834,22 +9490,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - + Mode: Exact Match - + Mode: Includes - + Mode: Include/Exclude - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -8857,21 +9513,49 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget @@ -8897,28 +9581,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - Load from Deck + Filter by name... (Exact match) - Apply all card names in currently loaded deck as exact match name filters + Load from Deck - Load from Clipboard + Apply all card names in currently loaded deck as exact match name filters + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -8982,50 +9666,115 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - + + Sort by: + + + + + Filter by: + + + + Save and load filters - + Filter by exact card name - + Filter by card sub-type - + Filter by set + + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: + + + + + Change how cards are divided into categories/groups. + + + + + Sort by: + + + + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9033,46 +9782,25 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -9128,7 +9856,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9136,22 +9864,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -9159,17 +9887,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + Visual Deck Storage Settings @@ -9177,43 +9905,43 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? 你想發出那種警告? - + Redact all messages from this user in all rooms - + &OK 確認 - + &Cancel 取消 - + Warn user for misconduct 警告用戶的不當行為 - - + + Error 錯誤 - + User name to send a warning to can not be blank, please specify a user to warn. 發送警告的用戶名不能為空,請提供用戶名以供警告。 - + Warning to use can not be blank, please select a valid warning to send. 警告不能為空,請選擇警告以供發送。 @@ -9221,133 +9949,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top 將選擇的系列移到頂端 - + Move selected set up 將選擇的系列上移 - + Move selected set down 將選擇的系列下移 - + Move selected set to the bottom 將選擇的系列移到底部 - + Search by set name, code, or type 通過系列名、代碼或類型搜索 - + Default order 默認順序 - + Restore original art priority order 恢復初始優先順序 - + Enable all sets 啓用所有系列 - + Disable all sets 禁用所有系列 - + Enable selected set(s) 啓用所選系列 - + Disable selected set(s) 禁用所選系列 - + Deck Editor 套牌編輯器 - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art 牌張風格 - + How to use custom card art 如何使用自定義卡牌圖片 - + Hints 提示 - + Note - + Sorting by column allows you to find a set while not changing set priority. 按列排序允許您在不更改系列優先級的情況下查找系列。 - + To enable ordering again, click the column header until this message disappears. 要再次啓用排序,請單擊列標題,直到此消息消失。 - + Use the current sorting as the set priority instead 使用當前系列的優先級排序 - + Sorts the set priority using the same column 使用同一列系列的優先級排序 - + Manage sets 系列管理 @@ -9355,72 +10083,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing 當關閉界面時洗牌 - + pile view 柱形圖 @@ -9428,7 +10156,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English 繁體中文 (Chinese Traditional) @@ -9436,12 +10164,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup 啓動連接 - + Debug to file 調試至文件 @@ -9449,1005 +10177,1041 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window 主窗口 - - + + Deck Editor 套牌編輯 - + Game Lobby 遊戲大廳 - + Card Counters 牌指示物 - + Player Counters 玩家指示物 - + Power and Toughness 力量和防禦力 - + Game Phases 遊戲步驟 - + Playing Area 遊戲區域 - + Move Selected Card 移動所選的牌張 - + View 查看 - + Move Top Card 移動上方的牌張 - + Move Bottom Card 移動底部的牌張 - + Gameplay 遊戲 - + Drawing 繪制 - + Chat Room 聊天室 - + Game Window 遊戲窗口 - + Load Deck from Clipboard 從剪貼板載入套牌 - - + + Replays - + Tabs - + Check for Card Updates... 檢查卡牌更新 - + Connect... 連接 - + Disconnect 斷開連接 - + Exit 退出 - + Full screen 全屏 - + Register... 註冊 - + Settings... 設定 - + Start a Local Game... 開始本地遊戲 - + Watch Replay... 觀看錄像 - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters 清除所有篩選項目 - + Clear Selected Filter 清除所選的篩選項目 - + Close 關閉 - + Remove Card 移除卡牌 - + Manage Sets... 系列管理 - + Edit Custom Tokens... 編輯自定義衍生物 - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card 添加卡牌 - + Load Deck... 讀取套牌 - - + + Load Deck from Clipboard... 從剪貼板載入套牌 - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck 創建新套牌 - + Open Custom Pictures Folder 打開自定義圖片文件夾 - + Print Deck... 打印套牌 - + Delete Card 刪除卡牌 - - + + Reset Layout 重置佈局 - + Save Deck 保存套牌 - + Save Deck as... 套牌另存為 - + Save Deck to Clipboard, Annotated 保存套牌到剪貼板,帶注釋 - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard 保存套牌到剪貼板 - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... 從本地載入套牌 - + Load Remote Deck... 載入遠程套牌 - + Set Ready to Start 系列準備好開始 - + Toggle Sideboard Lock 切換備牌鎖 - + Add Green Counter 增加綠色指示物 - + Remove Green Counter 移除綠色指示物 - + Set Green Counters... 設置綠色指示物 - + Add Red Counter 增加紅色指示物 - + Remove Red Counter 移除紅色指示物 - + Set Red Counters... 設置紅色指示物 - + Add Life Counter 增加生命指示物 - + Show Status Bar - + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter 移除生命指示物 - + Set Life Counters... 設置生命指示物 - + Add White Counter 增加白色指示物 - + Remove White Counter 移除白色指示物 - + Set White Counters... 設置白色指示物 - + Add Blue Counter 增加藍色指示物 - + Remove Blue Counter 移除藍色指示物 - + Set Blue Counters... 設置藍色指示物 - + Add Black Counter 增加黑色指示物 - + Remove Black Counter 移除黑色指示物 - + Set Black Counters... 設置黑色指示物 - + Add Colorless Counter 增加無色指示物 - + Remove Colorless Counter 移除無色指示物 - + Set Colorless Counters... 設置無色指示物 - + Add Other Counter 增加其他指示物 - + Remove Other Counter 移除其他指示物 - + Set Other Counters... 設置其他指示物 - + Increment all card counters - + Add Power (+1/+0) 增加力量(+1/+0) - + Remove Power (-1/-0) 減少力量(-1/-0) - + Move Toughness to Power (+1/-1) 移動防禦到力量(+1/-1) - + Add Toughness (+0/+1) 增加防禦(+0/+1) - + Remove Toughness (-0/-1) 減少防禦(-0/-1) - + Move Power to Toughness (-1/+1) 移動力量到防禦(-1/+1) - + Add Power and Toughness (+1/+1) 增加力量和防禦(+1/+1) - + Remove Power and Toughness (-1/-1) 減少力量和防禦(-1/-1) - + Set Power and Toughness... 設置力量和防禦 - + Reset Power and Toughness 重置力量和防禦 - + Untap 重置 - + Upkeep 維持 - + Draw - + First Main Phase 主要步驟1 - + Start Combat 開始戰鬥 - + Attack 攻擊 - + Block 阻擋 - + Damage 傷害 - + End Combat 結束戰鬥 - + Second Main Phase 主要步驟2 - + End 回合結束 - + Next Phase 下個步驟 - + Next Phase Action 下個行動步驟 - + Next Turn 下個回合 - + Hide Card in Reveal Window - + Tap / Untap Card 橫置 / 重置卡牌 - + Untap All 重置所有 - + Toggle Untap 鎖定重置狀態 - + Turn Card Over 將卡翻面 - + Peek Card 查看卡牌 - + Play Card 使用卡牌 - + Attach Card... 結附卡牌 - + Unattach Card 取消結附 - + Clone Card 複製卡牌 - + Create Token... 派出衍生物 - + Create All Related Tokens 派出所有相關的衍生物 - + Create Another Token 派出另一個衍生物 - + Set Annotation... 設置注釋... - + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library 牌庫底 - - - - + + + + Exile 放逐區 - - - - + + + + Graveyard 墳墓場 - - + + + Hand 手牌 - - + + Top of Library 牌庫頂 - - - + + + Battlefield, Face Down 戰場,面朝下 - + Battlefield 戰場 - - Sort Hand - - - - + Library 牌庫 - + Sideboard 備牌 - + Top Cards of Library 牌庫頂牌 - + Bottom Cards of Library - + Close Recent View 關閉最近的查看 - - + + Stack 堆疊 - - + + Graveyard (Multiple) 墳場(多個) - - + + Exile (Multiple) 放逐區(多個) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... 畫箭頭... - + Remove Local Arrows 移除本地箭頭 - + Leave Game 離開遊戲 - + Concede 投降 - + Roll Dice... 擲骰子... - + Shuffle Library 洗牌 - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan 再調度起手牌 - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card 抓一張牌 - + Draw Multiple Cards... 抓多張牌 - + Undo Draw 撤銷抓牌 - + Always Reveal Top Card 一直展示牌庫頂牌 - + Always Look At Top Card 一直檢視牌庫頂牌 - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise 順時針旋轉視角 - + Rotate View Counterclockwise 逆時針旋轉視角 - + Unfocus Text Box 取消聚焦文本框 - + Focus Chat 聚焦聊天室 - + Clear Chat 清除聊天記錄 - + Refresh 刷新 - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/oracle/translations/oracle_el.ts b/oracle/translations/oracle_el.ts index 568bc8172..73260a16d 100644 --- a/oracle/translations/oracle_el.ts +++ b/oracle/translations/oracle_el.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Εισαγωγή - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Αυτός ο αυτόματος οδηγός θα εισάγει τη λίστα των σετ, καρτών και δειγμάτων (tokens) που θα χρησιμοποιηθούν από το Cockatrice. - + Interface language: - + Version: Έκδοση: @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection Επιλογή πηγής - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. - + Download URL: URL λήψης: - + Local file: Τοπικό αρχείο: - + Restore default URL Επαναφορά προκαθορισμένης διεύθυνσης URL - + Choose file... Επιλέξτε αρχείο... - + Load sets file Φόρτωση αρχείων - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + + + Error Σφάλμα - + The provided URL is not valid. Η παρεχόμενη διεύθυνση URL δεν είναι έγκυρη. - + Downloading (0MB) Λήψη (0MB) - + Please choose a file. Παρακαλώ διαλέξτε έναν αρχείο. - + Cannot open file '%1'. Δεν είναι δυνατό να ανοίξει το αρχείο '% 1'. - + Downloading (%1MB) Λήψη (% 1MB) - + Network error: %1. Σφάλμα δικτύου: % 1. - + Parsing file Ανάλυση αρχείου - + Xz extraction failed. - + Sorry, this version of Oracle does not support xz compressed files. - + Failed to open Zip archive: %1. Αποτυχία ανοίγματος αρχείου Zip:% 1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Η εξαγωγή zip απέτυχε: το αρχείο Zip δεν περιέχει ακριβώς ένα αρχείο. - + Zip extraction failed: %1. Η εξαγωγή zip απέτυχε: % 1. - + Sorry, this version of Oracle does not support zipped files. Λυπούμαστε, αυτή η έκδοση του Oracle δεν υποστηρίζει αρχεία τύπου zipped. - + Failed to interpret downloaded data. - + Do you want to download the uncompressed file instead? - + The file was retrieved successfully, but it does not contain any sets data. Το αρχείο ανακτήθηκε με επιτυχία, αλλά δεν περιέχει σύνολα δεδομένων. @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database - + XML; spoiler database (*.xml) - + + spoiler + + + + Spoilers import - + Please specify a compatible source for spoiler data. - + Download URL: - + + Local file: + + + + Restore default URL - + + Choose file... + + + + The spoiler database will be saved at the following location: - + Save to a custom path (not recommended) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database - + XML; token database (*.xml) - + + tokens + + + + Tokens import - + Please specify a compatible source for token data. - + Download URL: URL λήψης: - + + Local file: + + + + Restore default URL Επαναφορά προκαθορισμένης διεύθυνσης URL - + + Choose file... + + + + The token database will be saved at the following location: - + Save to a custom path (not recommended) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Εικονικό σετ που περιέχει tokens @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer Εισαγωγέας Oracle @@ -262,22 +292,22 @@ OutroPage - + Finished - + The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error Σφάλμα - + No set has been imported. Δεν έχει εισαχθεί κανένα σετ. - + Sets imported Εισαγόμενα σετς - + A cockatrice database file of %1 MB has been downloaded. - + The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: - + Save to a custom path (not recommended) - + &Save - + Import finished: %1 cards. Η εισαγωγή ολοκληρώθηκε: % 1 κάρτες. - + %1: %2 cards imported %1: %2 κάρτες εισήχθησαν - + Save card database Αποθήκευση κάρτας βάσης δεδομένων - + XML; card database (*.xml) XML; κάρτα βάσης δεδομένων (* .xml) - + The file could not be saved to %1 Δεν ήταν δυνατή η αποθήκευση του αρχείου στο %1 @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error - + The provided URL is not valid: - + Downloading (0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) - + Network error: %1. - + The file could not be saved to %1 @@ -535,7 +587,7 @@ i18n - + English Ελληνικά (Greek) diff --git a/oracle/translations/oracle_en_US.ts b/oracle/translations/oracle_en_US.ts index f25fb6209..a08b85a1c 100644 --- a/oracle/translations/oracle_en_US.ts +++ b/oracle/translations/oracle_en_US.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Introduction - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. - + Interface language: Interface language: - + Version: Version: @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection Source selection - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. - + Download URL: Download URL: - + Local file: Local file: - + Restore default URL Restore default URL - + Choose file... Choose file... - + Load sets file Load sets file - + Sets file (%1) Sets JSON file (%1) Sets file (%1) - - - - - - - + + + + + + + Error Error - + The provided URL is not valid. The provided URL is not valid. - + Downloading (0MB) Downloading (0MB) - + Please choose a file. Please choose a file. - + Cannot open file '%1'. Cannot open file '%1'. - + Downloading (%1MB) Downloading (%1MB) - + Network error: %1. Network error: %1. - + Parsing file Parsing file - + Xz extraction failed. Xz extraction failed. - + Sorry, this version of Oracle does not support xz compressed files. Sorry, this version of Oracle does not support xz compressed files. - + Failed to open Zip archive: %1. Failed to open Zip archive: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Zip extraction failed: the Zip archive doesn't contain exactly one file. - + Zip extraction failed: %1. Zip extraction failed: %1. - + Sorry, this version of Oracle does not support zipped files. Sorry, this version of Oracle does not support zipped files. - + Failed to interpret downloaded data. Failed to interpret downloaded data. - + Do you want to download the uncompressed file instead? Do you want to download the uncompressed file instead? - + The file was retrieved successfully, but it does not contain any sets data. The file was retrieved successfully, but it does not contain any sets data. @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database Save spoiler database - + XML; spoiler database (*.xml) XML; spoiler database (*.xml) - + + spoiler + + + + Spoilers import Spoilers import - + Please specify a compatible source for spoiler data. Please specify a compatible source for spoiler data. - + Download URL: Download URL: - + + Local file: + + + + Restore default URL Restore default URL - + + Choose file... + + + + The spoiler database will be saved at the following location: The spoiler database will be saved at the following location: - + Save to a custom path (not recommended) Save to a custom path (not recommended) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database Save token database - + XML; token database (*.xml) XML; token database (*.xml) - + + tokens + + + + Tokens import Tokens import - + Please specify a compatible source for token data. Please specify a compatible source for token data. - + Download URL: Download URL: - + + Local file: + + + + Restore default URL Restore default URL - + + Choose file... + + + + The token database will be saved at the following location: The token database will be saved at the following location: - + Save to a custom path (not recommended) Save to a custom path (not recommended) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Dummy set containing tokens @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle Importer @@ -262,22 +292,22 @@ OutroPage - + Finished Finished - + The wizard has finished. The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. If the card databases don't reload automatically, restart the Cockatrice client. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error Error - + No set has been imported. No set has been imported. - + Sets imported Sets imported - + A cockatrice database file of %1 MB has been downloaded. A cockatrice database file of %1 MB has been downloaded. - + The following sets have been found: The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: The card database will be saved at the following location: - + Save to a custom path (not recommended) Save to a custom path (not recommended) - + &Save &Save - + Import finished: %1 cards. Import finished: %1 cards. - + %1: %2 cards imported %1: %2 cards imported - + Save card database Save card database - + XML; card database (*.xml) XML; card database (*.xml) - + The file could not be saved to %1 The file could not be saved to %1 @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error Error - + The provided URL is not valid: The provided URL is not valid: - + Downloading (0MB) Downloading (0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) Downloading (%1MB) - + Network error: %1. Network error: %1. - + The file could not be saved to %1 The file could not be saved to %1 @@ -535,7 +587,7 @@ i18n - + English English diff --git a/oracle/translations/oracle_es.ts b/oracle/translations/oracle_es.ts index 390e23ba7..b50501505 100644 --- a/oracle/translations/oracle_es.ts +++ b/oracle/translations/oracle_es.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Introducción - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Este instalador importará la lista de ediciones, cartas y fichas que va a usar Cockatrice. - + Interface language: Idioma de la interfaz: - + Version: Versión: @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection Seleccionar origen - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Por favor especifica un origen compatible para la lista de sets y cartas. Puedes especificar la URL de donde descargarla o usar un archivo existente de tu ordenador. - + Download URL: URL de descarga: - + Local file: Archivo local: - + Restore default URL Restablecer URL predeterminada - + Choose file... Elegir archivo... - + Load sets file Cargar archivo de ediciones - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + + + Error Error - + The provided URL is not valid. La URL suministrada no es válida. - + Downloading (0MB) Descargando (0MB) - + Please choose a file. Por favor elija un archivo. - + Cannot open file '%1'. No se puede abrir el archivo '%1' - + Downloading (%1MB) Descargando (%1MB) - + Network error: %1. Error de red: %1 - + Parsing file Procesando archivo - + Xz extraction failed. Extracción de Xz fallida - + Sorry, this version of Oracle does not support xz compressed files. Lo sentimos, esta versión de Oracle no soporta archivos comprimidos xz - + Failed to open Zip archive: %1. Error al abrir el archivo Zip: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Fallo al extraer el contenido: el Zip contiene más de un archivo. - + Zip extraction failed: %1. Error al extraer el contenido del Zip: %1. - + Sorry, this version of Oracle does not support zipped files. Lo sentimos, esta versión de Oracle no soporta archivos comprimidos. - + Failed to interpret downloaded data. No se pudieron interpretar los datos descargados. - + Do you want to download the uncompressed file instead? ¿Prefieres descargar el archivo sin comprimir? - + The file was retrieved successfully, but it does not contain any sets data. El archivo fue cargado correctamente pero no contiene datos sobre ningún set. @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database Guardar la base de datos de spoilers - + XML; spoiler database (*.xml) XML; base de datos de spoilers (*.xml) - + + spoiler + + + + Spoilers import Importar spoilers - + Please specify a compatible source for spoiler data. Por favor elija un origen compatible para los datos de los spoilers. - + Download URL: URL de descarga: - + + Local file: + + + + Restore default URL Restaurar URL por defecto - + + Choose file... + + + + The spoiler database will be saved at the following location: La base de datos de los spoilers será guardada en la siguiente ubicación: - + Save to a custom path (not recommended) Guardar en una ruta personalizada (no recomendado) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database Guardar base de datos de fichas - + XML; token database (*.xml) XML; base de datos de fichas (*.xml) - + + tokens + + + + Tokens import Importar tokens - + Please specify a compatible source for token data. Por favor elija un origen compatible para los datos de las fichas. - + Download URL: URL de descarga: - + + Local file: + + + + Restore default URL Restablecer URL predeterminada - + + Choose file... + + + + The token database will be saved at the following location: La base de datos de las fichas será guardada en la siguiente ubicación: - + Save to a custom path (not recommended) Guardar en una ruta personalizada (no recomendado) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Set dedicado para tokens @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer Importador de Oracle @@ -262,22 +292,22 @@ OutroPage - + Finished Completado - + The wizard has finished. El asistente ha terminado. - + You can now start using Cockatrice with the newly updated cards. Ahora puedes usar Cockatrice con las cartas actualizadas. - + If the card databases don't reload automatically, restart the Cockatrice client. Si las bases de datos de cartas no se recargan automáticamente, reinicia el cliente de Cockatrice. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error Error - + No set has been imported. Ningún set ha sido importado. - + Sets imported Sets importados - + A cockatrice database file of %1 MB has been downloaded. Se ha descargado un archivo de base de datos cockatrice de %1 MB. - + The following sets have been found: Las siguientes ediciones han sido encontradas: - + Press "Save" to store the imported cards in the Cockatrice database. Pulsa "Guardar" para guardar las cartas importadas en la base de datos de Cockatrice. - + The card database will be saved at the following location: La base de datos de cartas ha sido guardada en la siguiente ubicación: - + Save to a custom path (not recommended) Guardar en una ruta personalizada (no recomendado) - + &Save &Guardar - + Import finished: %1 cards. Importación terminada: %1 cartas. - + %1: %2 cards imported %1: %2 cartas importadas - + Save card database Guardar base de datos de cartas - + XML; card database (*.xml) XML; base de datos de cartas (*.xml) - + The file could not be saved to %1 El archivo no ha podido ser guardado en %1 @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error Error - + The provided URL is not valid: La URL proporcionada no es válida: - + Downloading (0MB) Descargando (0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) Descargando (%1MB) - + Network error: %1. Error de red: %1. - + The file could not be saved to %1 El archivo no ha podido ser guardado en %1 @@ -535,7 +587,7 @@ i18n - + English Español (Spanish) diff --git a/oracle/translations/oracle_et.ts b/oracle/translations/oracle_et.ts index dc6b079be..a68d71de5 100644 --- a/oracle/translations/oracle_et.ts +++ b/oracle/translations/oracle_et.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Sissejuhatus - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. See võlur impordib Cockatrice’is kasutatava nimekirja komplektidest, kaartidest ja märgistustest. - + Interface language: - + Version: Versioon: @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection Allika valik - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Palun täpsusta ühilduv allikas komplektide ja kaartide nimekirja jaoks. Võid täpsustada URL-i aadressi, mis laaditakse alla või kasutada olemasolevat faili oma arvutist. - + Download URL: Allalaadimise URL: - + Local file: Fail arvutis: - + Restore default URL Taasta tavaURL - + Choose file... Valige fail.... - + Load sets file Lae komplektide kaust - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + + + Error Viga - + The provided URL is not valid. Antud URl pole kehtiv. - + Downloading (0MB) Allalaadimine (0MB) - + Please choose a file. Palun valige fail. - + Cannot open file '%1'. Ei suudeta avada '%1'. - + Downloading (%1MB) Allalaadimine (%1MB) - + Network error: %1. Võrgu viga: %1. - + Parsing file Faili hankimine - + Xz extraction failed. Xzi lahtipakkimine nurjus. - + Sorry, this version of Oracle does not support xz compressed files. Vabandame, aga antud Oracle’i versioon ei toeta xz kokkupakitud faile. - + Failed to open Zip archive: %1. Zip-arhiivi avamine ebaõnnestus: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Zip-i lahtipakkimine ebaõnnestus: Zip-arhiiv sisaldab rohkem faile kui üks. - + Zip extraction failed: %1. Zipi lahtipakkimine ebaõnnestus: %1. - + Sorry, this version of Oracle does not support zipped files. Vabandame, aga antud Oracle versioon ei toeta kokkupakitud faile. - + Failed to interpret downloaded data. - + Do you want to download the uncompressed file instead? Soovid alla laadida hoopis pakkimata faili? - + The file was retrieved successfully, but it does not contain any sets data. Fail on edukalt alla laetud, ent ei sisalda andmeid. @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database Salvesta spoileri andmebaas - + XML; spoiler database (*.xml) XML; spoileri andmebaas (*.xml) - + + spoiler + + + + Spoilers import Spoilerite import - + Please specify a compatible source for spoiler data. Täpsusta spoileri andmetega ühilduv allikas. - + Download URL: Allalaadimise URL: - + + Local file: + + + + Restore default URL Taasta tavaURL - + + Choose file... + + + + The spoiler database will be saved at the following location: Spoileri andmebaas salvestatakse järgmisesse asukohta: - + Save to a custom path (not recommended) Salvesta enda määratud asukohta (pole soovitatav) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database Salvesta märgistuste andmebaas - + XML; token database (*.xml) XML; märgistuste andmebaas (*.xml) - + + tokens + + + + Tokens import Märgistuste importimine - + Please specify a compatible source for token data. Täpsusta märgistuste andmetega ühilduv allikas. - + Download URL: Allalaadimise URL: - + + Local file: + + + + Restore default URL Taasta tavaURL - + + Choose file... + + + + The token database will be saved at the following location: Märgistuste andmebaas salvestatakse järgmisesse asukohta: - + Save to a custom path (not recommended) Salvesta enda määratud asukohta (pole soovitatav) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Nukk-komplekt mis sisaldab märgistusi @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle sissetooja @@ -262,22 +292,22 @@ OutroPage - + Finished Valmis - + The wizard has finished. Võlur on lõpetanud. - + You can now start using Cockatrice with the newly updated cards. Nüüd saad Cockatrice’i kasutada uhiuute kaartidega. - + If the card databases don't reload automatically, restart the Cockatrice client. Kui kaardi andmebaasid ei lae ise, taaskäivita Cockatrice’i klient. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error Viga - + No set has been imported. Komplekti pole sisse toodud. - + Sets imported Allalaetud komplektid - + A cockatrice database file of %1 MB has been downloaded. - + The following sets have been found: Leiti järgmised komplektid: - + Press "Save" to store the imported cards in the Cockatrice database. Vajuta „Salvesta“, et salvestada imporditud kaardid Cockatrice’i andmebaasi. - + The card database will be saved at the following location: Kaardi andmebaas salvestatakse järgmisesse asukohta: - + Save to a custom path (not recommended) Salvesta enda määratud asukohta (pole soovitatav) - + &Save &Salvesta - + Import finished: %1 cards. %1 kaarti imporditi edukalt. - + %1: %2 cards imported %1: imporditi %2 kaarti - + Save card database Salvesta kaartide andmebaas - + XML; card database (*.xml) XML; kaartide andmebaas (*.xml) - + The file could not be saved to %1 Faili salvestamine asukohta %1 ebaõnnestus @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error Viga - + The provided URL is not valid: - + Downloading (0MB) Allalaadimine (0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) Allalaadimine (%1MB) - + Network error: %1. Võrgu viga: %1. - + The file could not be saved to %1 Faili salvestamine asukohta %1 nurjus @@ -535,7 +587,7 @@ i18n - + English Eesti Keel (Estonian) diff --git a/oracle/translations/oracle_fi.ts b/oracle/translations/oracle_fi.ts index defa76c63..d32263f7e 100644 --- a/oracle/translations/oracle_fi.ts +++ b/oracle/translations/oracle_fi.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Johdanto - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Tämä työkalu asentaa Cockatricessa käytettävät setit, kortit ja tokenit. - + Interface language: Käyttöliittymän kieli: - + Version: Versio: @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection Lähteen määritys - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Ole hyvä ja määritä lähde setti- ja korttilistalle. Voit määrittää ladattavan URL-osoitteen tai käyttää tietokoneellasi olevaa tiedostoa. - + Download URL: Ladattavan tiedoston URL-osoite: - + Local file: Paikallinen tiedosto: - + Restore default URL Palauta oletus-URL - + Choose file... Valitse tiedosto... - + Load sets file Ladatta ekspansiotiedosto - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + + + Error Virhe - + The provided URL is not valid. Määritetty URL on virheellinen. - + Downloading (0MB) Ladataan (0MB) - + Please choose a file. Valitse tiedosto. - + Cannot open file '%1'. Ei voida avata tiedostoa '%1'. - + Downloading (%1MB) Ladataan (%1MB) - + Network error: %1. Yhteysvirhe: %1. - + Parsing file Jäsennellään tiedostoa - + Xz extraction failed. Xz purku epäonnistui. - + Sorry, this version of Oracle does not support xz compressed files. Pahoittelut. Tämä versio Oraclesta ei tue xz-pakattuja tiedostoja. - + Failed to open Zip archive: %1. Zip-tiedoston avaaminen epäonnistui: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Zip-tiedoston purkaminen epäonnistui: Zip-tiedosto ei sisällä tarkalleen yhtä tiedostoa. - + Zip extraction failed: %1. Zip-tiedoston purkaminen epäonnistuie: %1. - + Sorry, this version of Oracle does not support zipped files. Pahoittelut. Tämä versio Oraclesta ei tue Zip-pakattuja tiedostoja. - + Failed to interpret downloaded data. Ladattujen tietojen tulkitseminen epäonnistui. - + Do you want to download the uncompressed file instead? Haluatko sensijaan ladata pakkaamattoman tiedoston? - + The file was retrieved successfully, but it does not contain any sets data. Tiedosto palautettiin onnistuneesti, mutta se ei sisällä yhdenkään ekspansion dataa. @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database Tallenna spoileritietokanta - + XML; spoiler database (*.xml) XML; spoileritietokanta (*.xml) - + + spoiler + + + + Spoilers import Spoilerien lataus - + Please specify a compatible source for spoiler data. Ole hyvä ja osoita yhteensopiva spoileridatan lähde. - + Download URL: Ladattavan tiedoston URL-osoite: - + + Local file: + + + + Restore default URL Palauta oletus-URL - + + Choose file... + + + + The spoiler database will be saved at the following location: Spoileritietokanta tallennetaan seuraavaan sijaintiin: - + Save to a custom path (not recommended) Tallenna mukautettuun sijaintiin (ei-suositeltu) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database Tallenna tokenitietokanta - + XML; token database (*.xml) XML; tokenitietokanta (*.xml) - + + tokens + + + + Tokens import Tokenien lataus - + Please specify a compatible source for token data. Ole hyvä ja osoita yhteensopiva tokenidatan lähde. - + Download URL: Ladattavan tiedoston URL-osoite: - + + Local file: + + + + Restore default URL Palauta oletus-URL - + + Choose file... + + + + The token database will be saved at the following location: Tokenitietokanta tallennetaan seuraavaan sijaintiin: - + Save to a custom path (not recommended) Tallenna mukautettuun sijaintiin (ei-suositeltu) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Tokeneja sisältävä mallisetti @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle-lataaja @@ -262,22 +292,22 @@ OutroPage - + Finished Valmis - + The wizard has finished. Asennusohjelma on valmis. - + You can now start using Cockatrice with the newly updated cards. Voit nyt käyttää Cockatricea juuri päivitetyillä korteilla. - + If the card databases don't reload automatically, restart the Cockatrice client. Jos korttitietokannat eivät päivity automaattisesti, käynnistä Cockatrice uudelleen. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error Virhe - + No set has been imported. Yhtään settiä ei ladattu. - + Sets imported Ladatut ekspansiot - + A cockatrice database file of %1 MB has been downloaded. Cockatrice tietokantatiedosto kooltaan %1 MB on ladattu. - + The following sets have been found: Seuraavat setit löydettiin: - + Press "Save" to store the imported cards in the Cockatrice database. Paina "Tallenna" varastoidaksesi ladatut kortit Cockatricen tietokantaan. - + The card database will be saved at the following location: Korttitietokanta tallennetaan seuraavaan sijaintiin: - + Save to a custom path (not recommended) Tallenna mukautettuun sijaintiin (ei-suositeltu) - + &Save &Tallenna - + Import finished: %1 cards. Lataaminen valmis: %1 kortit. - + %1: %2 cards imported %1: %2 kortit ladattu - + Save card database Tallenna korttitietokanta - + XML; card database (*.xml) XML; korttitietokanta (*.xml) - + The file could not be saved to %1 Tiedostoa ei voitu tallentaa osoitteeseen %1 @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error Virhe - + The provided URL is not valid: - + Downloading (0MB) Ladataan (0 MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) Ladataan (%1 MB) - + Network error: %1. Yhteysvirhe: %1. - + The file could not be saved to %1 Tiedostoa ei voitu tallentaa osoitteeseen %1 @@ -535,7 +587,7 @@ i18n - + English Suomi (Finnish) diff --git a/oracle/translations/oracle_fr.ts b/oracle/translations/oracle_fr.ts index 131dc0eb4..7f43ad140 100644 --- a/oracle/translations/oracle_fr.ts +++ b/oracle/translations/oracle_fr.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Introduction - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Cet assistant va importer la liste des éditions, cartes et jetons qui seront utilisés par Cockatrice. - + Interface language: Langage de l'interface : - + Version: Version : @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection Choix du fichier source - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Veuillez spécifier une source compatible pour la liste d'éditions et de cartes. Vous pouvez spécifier une URL qui sera utilisée pour télécharger un fichier ou utiliser un fichier existant sur votre ordinateur. - + Download URL: URL de téléchargement : - + Local file: Fichier local : - + Restore default URL Restaurer l'URL par défaut - + Choose file... Choisissez un fichier... - + Load sets file Charger une liste d'éditions - + Sets file (%1) Sets JSON file (%1) Choisir le fichier (%1) - - - - - - - + + + + + + + Error Erreur - + The provided URL is not valid. L'URL fournie n'est pas valable - + Downloading (0MB) Téléchargement en cours (0MB) - + Please choose a file. Choisissez un fichier. - + Cannot open file '%1'. Impossible d'ouvrir le fichier '%1'. - + Downloading (%1MB) Téléchargement (%1MB) - + Network error: %1. Erreur réseau : %1. - + Parsing file Traitement du fichier. - + Xz extraction failed. L'extraction du zip à échoué. - + Sorry, this version of Oracle does not support xz compressed files. Désolé, cette version d'Oracle ne supporte pas les fichiers zip. - + Failed to open Zip archive: %1. Impossible d'ouvrir l'archive zip: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Extraction zip échouée: l'archive zip contient plus qu'un fichier. - + Zip extraction failed: %1. L'extraction du zip a échoué : %1. - + Sorry, this version of Oracle does not support zipped files. Désolé, cette version d'Oracle ne supporte pas les fichiers zip. - + Failed to interpret downloaded data. Échec lors de l'interprétation des données téléchargées. - + Do you want to download the uncompressed file instead? Voulez-vous télécharger le fichier non compressé à la place ? - + The file was retrieved successfully, but it does not contain any sets data. Le fichier a été trouvé, mais ne contient aucune éditions. @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database Enregistrer la base de données des spoilers - + XML; spoiler database (*.xml) XML ; base de données des spoilers (*.xml) - + + spoiler + + + + Spoilers import Importation des spoilers - + Please specify a compatible source for spoiler data. Veuillez spécifier une source compatible pour les spoilers. - + Download URL: URL de téléchargement : - + + Local file: + + + + Restore default URL Restaurer l'URL par défaut - + + Choose file... + + + + The spoiler database will be saved at the following location: La base de données des spoilers sera enregistrée à l'emplacement suivant : - + Save to a custom path (not recommended) Sauvegarder à un emplacement personnalisé (non recommandé) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database Enregistrer la base de données des jetons - + XML; token database (*.xml) XML ; base de données des jetons (*.xml) - + + tokens + + + + Tokens import Importation des jetons - + Please specify a compatible source for token data. Veuillez spécifier une source compatible pour les jetons. - + Download URL: URL de téléchargement: - + + Local file: + + + + Restore default URL Restaurer l'URL par défaut - + + Choose file... + + + + The token database will be saved at the following location: La base de données des jetons sera enregistrée à l'emplacement suivant : - + Save to a custom path (not recommended) Sauvegarder à un emplacement personnalisé (non recommandé) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Fausse édition contenant les jetons @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer Importateur Oracle @@ -262,22 +292,22 @@ OutroPage - + Finished Terminé - + The wizard has finished. L'assistant a terminé. - + You can now start using Cockatrice with the newly updated cards. Vous pouvez maintenant commencer à utiliser Cockatrice avec les cartes mises à jour. - + If the card databases don't reload automatically, restart the Cockatrice client. Si les bases de données de cartes ne rechargent pas automatiquement, redémarrez le client Cockatrice. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error Erreur - + No set has been imported. Aucune édition n'a été importé. - + Sets imported Éditions importées - + A cockatrice database file of %1 MB has been downloaded. Un fichier de base de données Cockatrice de %1 MB a été téléchargé. - + The following sets have been found: Les éditions suivantes ont été trouvées : - + Press "Save" to store the imported cards in the Cockatrice database. Cliquez sur « Enregistrer » pour enregistrer les cartes importées dans la base de données de Cockatrice. - + The card database will be saved at the following location: La base de données des cartes sera enregistrée à l'emplacement suivant : - + Save to a custom path (not recommended) Sauvegarder à un emplacement personnalisé (non recommandé) - + &Save Sauvegarder - + Import finished: %1 cards. Import terminé: %1 cartes. - + %1: %2 cards imported %1: %2 cartes ajoutées. - + Save card database Sauvegarder la base de carte - + XML; card database (*.xml) XML ; base de données de cartes (*.xml) - + The file could not be saved to %1 Le fichier n'a pu être sauvegarder au chemin '%1' @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error Erreur - + The provided URL is not valid: L'URL fournie n'est pas valide : - + Downloading (0MB) Téléchargement (0 Mo) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) Téléchargement (%1 Mo) - + Network error: %1. Erreur réseau : %1. - + The file could not be saved to %1 Le fichier n'a pas pu être enregistré dans %1 @@ -535,7 +587,7 @@ i18n - + English Français (French) diff --git a/oracle/translations/oracle_it.ts b/oracle/translations/oracle_it.ts index fe925eba6..1765bbbca 100644 --- a/oracle/translations/oracle_it.ts +++ b/oracle/translations/oracle_it.ts @@ -2,23 +2,23 @@ IntroPage - + Introduction Introduzione - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Questo wizard importerà la lista di set, carte e pedine che verranno usate da Cockatrice. - + Interface language: Lingua dell'interfaccia: - + Version: Versione: @@ -26,134 +26,134 @@ e pedine che verranno usate da Cockatrice. LoadSetsPage - + Source selection Selezione sorgente - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Specifica una sorgente compatibile per la lista dei set e delle carte. Puoi specificare un indirizzo URL da cui scaricare il file o selezionare un file già presente nel tuo computer. - + Download URL: Indirizzo download: - + Local file: File nel pc: - + Restore default URL Usa l'indirizzo predefinito - + Choose file... Scegli file... - + Load sets file Carica file dei set - + Sets file (%1) Sets JSON file (%1) File dei set (%1) - - - - - - - + + + + + + + Error Errore - + The provided URL is not valid. L'indirizzo specificato non è valido. - + Downloading (0MB) Scaricamento (0MB) - + Please choose a file. Seleziona un file. - + Cannot open file '%1'. Impossibile aprire il file '%1'. - + Downloading (%1MB) Scaricamento (%1MB) - + Network error: %1. Errore di rete: %1 - + Parsing file Analisi dei file - + Xz extraction failed. Estrazione da file xz fallita. - + Sorry, this version of Oracle does not support xz compressed files. Spiacente, ma questa versione di Oracle non supporta i file xz. - + Failed to open Zip archive: %1. Impossibile aprire il file Zip: %1 - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Estrazione file Zip fallita: lo Zip non contiene un solo file. - + Zip extraction failed: %1. Estrazione file Zip fallita: %1 - + Sorry, this version of Oracle does not support zipped files. Spiacente, ma questa versione di Oracle non supporta i file zip. - + Failed to interpret downloaded data. Impossibile interpretare i dati scaricati. - + Do you want to download the uncompressed file instead? Vuoi provare a scaricare il file non compresso? - + The file was retrieved successfully, but it does not contain any sets data. Il file è stato analizzato correttamente, ma non contiene i dati di nessun set. @@ -161,42 +161,57 @@ e pedine che verranno usate da Cockatrice. LoadSpoilersPage - + Save spoiler database Salva archivio spoiler - + XML; spoiler database (*.xml) XML; archivio spoiler (*.xml) - + + spoiler + + + + Spoilers import Importazione spoiler - + Please specify a compatible source for spoiler data. Specifica una sorgente compatibile per gli spoiler. - + Download URL: Indirizzo download: - + + Local file: + + + + Restore default URL Usa l'indirizzo predefinito - + + Choose file... + + + + The spoiler database will be saved at the following location: L'archivio degli spoiler verrà salvato nel seguente percorso: - + Save to a custom path (not recommended) Salva in un percorso diverso (sconsigliato) @@ -204,42 +219,57 @@ e pedine che verranno usate da Cockatrice. LoadTokensPage - + Save token database Salva archivio pedine - + XML; token database (*.xml) XML; archivio pedine (*.xml) - + + tokens + + + + Tokens import Importazione pedine - + Please specify a compatible source for token data. Specifica una sorgente compatibile per le pedine. - + Download URL: Indirizzo download: - + + Local file: + + + + Restore default URL Usa l'indirizzo predefinito - + + Choose file... + + + + The token database will be saved at the following location: L'archivio delle pedine verrà salvato nel seguente percorso: - + Save to a custom path (not recommended) Salva in un percorso diverso (sconsigliato) @@ -247,7 +277,7 @@ e pedine che verranno usate da Cockatrice. OracleImporter - + Dummy set containing tokens Set finto contenente i token @@ -255,7 +285,7 @@ e pedine che verranno usate da Cockatrice. OracleWizard - + Oracle Importer Oracle Importer @@ -263,22 +293,22 @@ e pedine che verranno usate da Cockatrice. OutroPage - + Finished Finito - + The wizard has finished. Il wizard è completato. - + You can now start using Cockatrice with the newly updated cards. Adesso puoi iniziare ad usare Cockatrice con le nuove carte aggiornate. - + If the card databases don't reload automatically, restart the Cockatrice client. Se il database delle carte non si ricarica in automatico, riavvia il programma Cockatrice. @@ -286,73 +316,73 @@ e pedine che verranno usate da Cockatrice. SaveSetsPage - - + + Error Errore - + No set has been imported. Nessun set importato. - + Sets imported Set importati - + A cockatrice database file of %1 MB has been downloaded. È stato scaricato un file database di Cockatrice di %1 MB. - + The following sets have been found: Sono stati trovati i seguenti set: - + Press "Save" to store the imported cards in the Cockatrice database. Premi "Salva" per salvare le carte importate nel database di Cockatrice. - + The card database will be saved at the following location: L'archivio delle carte verrà salvato nel seguente percorso: - + Save to a custom path (not recommended) Salva in un percorso diverso (sconsigliato) - + &Save &Salva - + Import finished: %1 cards. Importazione conclusa: %1 carte. - + %1: %2 cards imported %1: %2 carte importate - + Save card database Salva archivio carte - + XML; card database (*.xml) XML; archivio carte (*.xml) - + The file could not be saved to %1 Impossibile salvare il file su %1 @@ -360,34 +390,56 @@ e pedine che verranno usate da Cockatrice. SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error Errore - + The provided URL is not valid: L'indirizzo fornito non è valido: - + Downloading (0MB) Scaricamento (0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) Scaricamento (%1MB) - + Network error: %1. Errore di rete: %1. - + The file could not be saved to %1 Impossibile salvare il file su %1 @@ -536,7 +588,7 @@ e pedine che verranno usate da Cockatrice. i18n - + English Italiano (Italian) diff --git a/oracle/translations/oracle_ja.ts b/oracle/translations/oracle_ja.ts index 6a4aae75b..8d15cec34 100644 --- a/oracle/translations/oracle_ja.ts +++ b/oracle/translations/oracle_ja.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction はじめに - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. このウィザードでは、Cockatriceで使用されるカードやトークン、セットのリストをインポートします。 - + Interface language: インターフェース言語: - + Version: バージョン: @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection ソース選択 - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. セットとカードのリストの互換性のあるソースを指定してください。ダウンロードするURLアドレスを指定するか、コンピューターから既存のファイルを使用できます。 - + Download URL: ダウンロードURL: - + Local file: ローカルファイル: - + Restore default URL デフォルトのURLを復元 - + Choose file... ファイルを選択... - + Load sets file カードセットファイルを開く - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + + + Error エラー - + The provided URL is not valid. 指定されたURLは無効です。 - + Downloading (0MB) ダウンロード中 (0MB) - + Please choose a file. ファイルを選択してください。 - + Cannot open file '%1'. '%1'を開けませんでした。 - + Downloading (%1MB) ダウンロード中 (%1MB) - + Network error: %1. ネットワークエラー: %1。 - + Parsing file ファイルの解析 - + Xz extraction failed. Xz展開に失敗。 - + Sorry, this version of Oracle does not support xz compressed files. このバージョンのOracleはxz圧縮ファイルをサポートしていません。 - + Failed to open Zip archive: %1. ZIPアーカイブの展開に失敗: %1。 - + Zip extraction failed: the Zip archive doesn't contain exactly one file. ZIP展開に失敗:Zipアーカイブに含まれるファイルが1つだけではありません。 - + Zip extraction failed: %1. ZIP展開に失敗: %1。 - + Sorry, this version of Oracle does not support zipped files. 申し訳ありませんが現バージョンのOracleはzip形式のファイルをサポートしていません。 - + Failed to interpret downloaded data. - + Do you want to download the uncompressed file instead? 代わりに非圧縮ファイルをダウンロードしますか? - + The file was retrieved successfully, but it does not contain any sets data. ファイルは正常に取得されましたが、カードセットのデータが含まれていませんでした。 @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database スポイラーデータベースを保存 - + XML; spoiler database (*.xml) XML; スポイラーデータベース (*.xml) - + + spoiler + + + + Spoilers import スポイラーインポート - + Please specify a compatible source for spoiler data. 互換性のあるスポイラーデータのソースを指定してください。 - + Download URL: ダウンロードURL: - + + Local file: + + + + Restore default URL デフォルトのURLを復元 - + + Choose file... + + + + The spoiler database will be saved at the following location: スポイラーデータベースは、以下の場所に保存されます: - + Save to a custom path (not recommended) 別のパスに保存(非推奨) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database トークンデータベースを保存 - + XML; token database (*.xml) XML; トークンデータベース (*.xml) - + + tokens + + + + Tokens import トークンのインポート - + Please specify a compatible source for token data. トークンデータのソースを選択してください。 - + Download URL: ダウンロードURL: - + + Local file: + + + + Restore default URL デフォルトのURLを復元 - + + Choose file... + + + + The token database will be saved at the following location: トークンデータベースは以下の場所に保存されます。 - + Save to a custom path (not recommended) 別のパスに保存(非推奨) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens ダミーセットを含むトークン @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle Importer - オラクル・インポーター @@ -262,22 +292,22 @@ OutroPage - + Finished 完了しました! - + The wizard has finished. ウィザードが完了しました。 - + You can now start using Cockatrice with the newly updated cards. Cockatriceで新しく更新されたカードを使うことが出来ます。 - + If the card databases don't reload automatically, restart the Cockatrice client. カードデータベースが自動的に再読込されない場合は、Cockatriceを再起動して下さい。 @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error エラー - + No set has been imported. セットはインポートされませんでした。 - + Sets imported カードセットインポート - + A cockatrice database file of %1 MB has been downloaded. - + The following sets have been found: 次のセットが見つかりました: - + Press "Save" to store the imported cards in the Cockatrice database. ”保存”をクリックするとインポートしたカードをCockatriceデータベースに保存します。 - + The card database will be saved at the following location: カードデータベースは以下の場所に保存されます: - + Save to a custom path (not recommended) 別のパスに保存(非推奨) - + &Save 保存 - + Import finished: %1 cards. %1枚のカードがインポートされました。 - + %1: %2 cards imported %1: %2枚のカードがインポートされました。 - + Save card database カードデータベースを保存 - + XML; card database (*.xml) XML; card database (*.xml) - + The file could not be saved to %1 %1に保存できませんでした。 @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error エラー - + The provided URL is not valid: - + Downloading (0MB) ダウンロード中 (0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) ダウンロード中 (%1MB) - + Network error: %1. ネットワークエラー: %1。 - + The file could not be saved to %1 %1に保存できませんでした。 @@ -535,7 +587,7 @@ i18n - + English 日本語 (Japanese) diff --git a/oracle/translations/oracle_ko.ts b/oracle/translations/oracle_ko.ts index 3712cfe87..f69ad2fab 100644 --- a/oracle/translations/oracle_ko.ts +++ b/oracle/translations/oracle_ko.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction 개요 - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. - + Interface language: - + Version: @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection 확장판 목록 파일 주소 입력 - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. - + Download URL: 다운로드 주소: - + Local file: 파일 위치: - + Restore default URL 기본 주소로 복원 - + Choose file... 파일 선택... - + Load sets file 확장판 목록 파일 불러오기 - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + + + Error 오류 - + The provided URL is not valid. 잘못된 주소를 입력하셨습니다. - + Downloading (0MB) 다운로드 중 (0MB) - + Please choose a file. 확장판 목록 파일을 선택해 주세요. - + Cannot open file '%1'. 파일 '%1'을(를) 열 수 없습니다. - + Downloading (%1MB) 다운로드 중 (%1MB) - + Network error: %1. 네트워크 오류 : %1. - + Parsing file 목록 파싱중 - + Xz extraction failed. - + Sorry, this version of Oracle does not support xz compressed files. - + Failed to open Zip archive: %1. 압축파일 열기 실패 : %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. 압축 풀기 실패 : 압축 파일에 확장판 목록 파일 이외의 파일이 있습니다. - + Zip extraction failed: %1. 압축 풀기 실패 : %1. - + Sorry, this version of Oracle does not support zipped files. 죄송합니다. 본 버전에서는 압축 파일을 지원하지 않습니다. - + Failed to interpret downloaded data. - + Do you want to download the uncompressed file instead? - + The file was retrieved successfully, but it does not contain any sets data. 파일을 성공적으로 다운로드 하였으나 확장판 정보가 들어있지 않습니다. @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database - + XML; spoiler database (*.xml) - + + spoiler + + + + Spoilers import - + Please specify a compatible source for spoiler data. - + Download URL: - + + Local file: + + + + Restore default URL - + + Choose file... + + + + The spoiler database will be saved at the following location: - + Save to a custom path (not recommended) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database - + XML; token database (*.xml) - + + tokens + + + + Tokens import - + Please specify a compatible source for token data. - + Download URL: 웹 주소: - + + Local file: + + + + Restore default URL 기본 주소로 복원 - + + Choose file... + + + + The token database will be saved at the following location: - + Save to a custom path (not recommended) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens 토큰 정보가 들어있는 더미 확장판 @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer 오라클 @@ -262,22 +292,22 @@ OutroPage - + Finished - + The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error 오류 - + No set has been imported. 아무 확장판도 불러오지 못했습니다. - + Sets imported 확장판 불러오기 완료 - + A cockatrice database file of %1 MB has been downloaded. - + The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: - + Save to a custom path (not recommended) - + &Save - + Import finished: %1 cards. 총 %1장의 카드 불러오기 완료 - + %1: %2 cards imported %1에서 %2장의 카드 불러옴 - + Save card database 카드 데이터베이스 저장 - + XML; card database (*.xml) 카드 데이터베이스 XML 파일 (*.xml) - + The file could not be saved to %1 파일을 %1에 저장 할 수 없습니다. @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error - + The provided URL is not valid: - + Downloading (0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) - + Network error: %1. - + The file could not be saved to %1 @@ -535,7 +587,7 @@ i18n - + English 한국어 (Korean) diff --git a/oracle/translations/oracle_nb.ts b/oracle/translations/oracle_nb.ts index 86318983b..191e156ef 100644 --- a/oracle/translations/oracle_nb.ts +++ b/oracle/translations/oracle_nb.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Introduksjon - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. - + Interface language: - + Version: @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection Kilde valg - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. - + Download URL: Nedlastings URL - + Local file: Lokal fil - + Restore default URL Gjenopprett standard URL - + Choose file... Velg fil... - + Load sets file Last set fil... - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + + + Error Feil - + The provided URL is not valid. URL du anga er ikke gyldig. - + Downloading (0MB) Laster ned (0MB) - + Please choose a file. Vennligst velg en fil - + Cannot open file '%1'. Kunne ikke åpme '%1' - + Downloading (%1MB) Laster ned (%1MB) - + Network error: %1. Nettverks feil: %1 - + Parsing file Tolker fil. - + Xz extraction failed. - + Sorry, this version of Oracle does not support xz compressed files. - + Failed to open Zip archive: %1. Kunne ikke åpne Zip Arkiv: %1 - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Zip ekstraksjon feilet: Zip arkivet inneholder ikke nøyaktig en fil - + Zip extraction failed: %1. Zip ekstraksjon feilet: %1 - + Sorry, this version of Oracle does not support zipped files. Beklager, denne versjonen av Oracle støtter ikke Zip filer. - + Failed to interpret downloaded data. - + Do you want to download the uncompressed file instead? - + The file was retrieved successfully, but it does not contain any sets data. Filen ble hentet riktig, men den inneholder ikke noe set data @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database - + XML; spoiler database (*.xml) - + + spoiler + + + + Spoilers import - + Please specify a compatible source for spoiler data. - + Download URL: - + + Local file: + + + + Restore default URL - + + Choose file... + + + + The spoiler database will be saved at the following location: - + Save to a custom path (not recommended) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database - + XML; token database (*.xml) - + + tokens + + + + Tokens import - + Please specify a compatible source for token data. - + Download URL: Nedlastings URL - + + Local file: + + + + Restore default URL Gjenopprett standard URL - + + Choose file... + + + + The token database will be saved at the following location: - + Save to a custom path (not recommended) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Dummy sett som inneholder tokens @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle importerer @@ -262,22 +292,22 @@ OutroPage - + Finished - + The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error Feil - + No set has been imported. Ingen set har blitt importert. - + Sets imported Set importert - + A cockatrice database file of %1 MB has been downloaded. - + The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: - + Save to a custom path (not recommended) - + &Save - + Import finished: %1 cards. Importering fullført: %1 kort. - + %1: %2 cards imported %1: %2 kort importert. - + Save card database Lagre kort database. - + XML; card database (*.xml) XML; kort database (*.xml) - + The file could not be saved to %1 Filen kunne ikke lagres til %1 @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error - + The provided URL is not valid: - + Downloading (0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) - + Network error: %1. - + The file could not be saved to %1 @@ -535,7 +587,7 @@ i18n - + English Norsk Bokmål (Norwegian Bokmål) diff --git a/oracle/translations/oracle_nl.ts b/oracle/translations/oracle_nl.ts index c48b65964..18ca92dc2 100644 --- a/oracle/translations/oracle_nl.ts +++ b/oracle/translations/oracle_nl.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Introductie - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Deze wizard importeert de lijst met sets, kaarten en tokens die door Cockatrice zullen worden gebruikt. - + Interface language: Interface taal: - + Version: Versie: @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection Bron selectie - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Gelieve een compatibele bron te vermelden voor de lijst van sets en kaarten. U kunt een URL-adres opgeven dat wordt gedownload of een bestaand bestand van uw computer gebruiken. - + Download URL: Download URL: - + Local file: Lokaal bestand - + Restore default URL Herstel standaard URL - + Choose file... Bestand kiezen... - + Load sets file Bestand laden - + Sets file (%1) Sets JSON file (%1) Sets bestand (%1) - - - - - - - + + + + + + + Error Fout - + The provided URL is not valid. De ingevoerde URL is niet geldig. - + Downloading (0MB) Downloaden (0MB) - + Please choose a file. Gelieve een bestand te kiezen. - + Cannot open file '%1'. Bestand '%1' kan niet geopend worden. - + Downloading (%1MB) Downloaden (%1MB) - + Network error: %1. Netwerk fout: %1. - + Parsing file Parsen van bestand - + Xz extraction failed. Xz extractie mislukt. - + Sorry, this version of Oracle does not support xz compressed files. Sorry, deze versie van Oracle ondersteunt geen xz gecomprimeerde bestanden. - + Failed to open Zip archive: %1. Zip archief kan niet geopend worden: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Uitpakken van Zip niet gelukt: het archief moet precies één bestand bevatten. - + Zip extraction failed: %1. Uitpakken van Zip niet gelukt: %1. - + Sorry, this version of Oracle does not support zipped files. Sorry, deze versie van Oracle ondersteunt geen gecomprimeerde bestanden. - + Failed to interpret downloaded data. Lezen van gedownloade data mislukt. - + Do you want to download the uncompressed file instead? Wilt u het ongecomprimeerde bestand downloaden? - + The file was retrieved successfully, but it does not contain any sets data. Het bestand is succesvol binnengehaald, maar bevat geen set data. @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database Opslaan spoiler database - + XML; spoiler database (*.xml) XML; spoiler database (*.xml) - + + spoiler + + + + Spoilers import Spoilers import - + Please specify a compatible source for spoiler data. Gelieve een compatibele bron voor spoilergegevens te specificeren. - + Download URL: Download URL: - + + Local file: + + + + Restore default URL Herstel standaard URL - + + Choose file... + + + + The spoiler database will be saved at the following location: De spoiler database wordt op de volgende locatie opgeslagen: - + Save to a custom path (not recommended) Opslaan naar een aangepaste locatie (niet aanbevolen) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database token database opslaan - + XML; token database (*.xml) XML; token database (*.xml) - + + tokens + + + + Tokens import Tokens import - + Please specify a compatible source for token data. Gelieve een compatibele bron voor tokengegevens op te geven. - + Download URL: Download URL: - + + Local file: + + + + Restore default URL Herstel standaard URL - + + Choose file... + + + + The token database will be saved at the following location: De token database wordt op de volgende locatie opgeslagen: - + Save to a custom path (not recommended) Opslaan naar een aangepaste locatie (niet aanbevolen) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Token voorbeeldset @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle importer @@ -262,22 +292,22 @@ OutroPage - + Finished Klaar - + The wizard has finished. De wizard is klaar. - + You can now start using Cockatrice with the newly updated cards. U kunt nu beginnen met het gebruik van Cockatrice met de nieuw bijgewerkte kaarten. - + If the card databases don't reload automatically, restart the Cockatrice client. Als de kaartendatabases niet automatisch herladen, start dan de Cockatrice client opnieuw op. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error Fout - + No set has been imported. Er zijn geen sets geïmporteerd. - + Sets imported Sets geïmporteerd - + A cockatrice database file of %1 MB has been downloaded. Een cockatrice database bestand van %1 MB is gedownload. - + The following sets have been found: De volgende sets zijn gevonden: - + Press "Save" to store the imported cards in the Cockatrice database. Druk op "Opslaan" om de geïmporteerde kaarten op te slaan in de Cockatrice database. - + The card database will be saved at the following location: De kaartendatabase wordt op de volgende locatie opgeslagen: - + Save to a custom path (not recommended) Opslaan naar een aangepaste locatie (niet aanbevolen) - + &Save &Opslaan - + Import finished: %1 cards. Import klaar: %1 kaarten. - + %1: %2 cards imported %1: %2 kaarten geïmporteerd - + Save card database Kaartendatabase opslaan - + XML; card database (*.xml) XML; kaart database (*.xml) - + The file could not be saved to %1 Het bestand kon niet worden opgeslagen in %1 @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error Fout - + The provided URL is not valid: De ingevoerde URL is niet geldig: - + Downloading (0MB) Downloaden (0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) Downloaden (%1MB) - + Network error: %1. Netwerk fout: %1. - + The file could not be saved to %1 Het bestand kon niet worden opgeslagen naar %1 @@ -535,7 +587,7 @@ i18n - + English Nederlands (Dutch) diff --git a/oracle/translations/oracle_pl.ts b/oracle/translations/oracle_pl.ts index 72a28dec4..643bb6669 100644 --- a/oracle/translations/oracle_pl.ts +++ b/oracle/translations/oracle_pl.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Wprowadzenie - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Ten kreator zaimportuje listę dodatków, kart oraz tokenów które zostaną użyte przez Cockatrice. - + Interface language: Język interfejsu: - + Version: Wersja: @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection Wybór źródła - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Proszę podać źródło listy edycji i kart. Można podać adres URL, z którego zostanie pobrana, lub istniejący plik na komputerze. - + Download URL: Pobierz URL - + Local file: Plik lokalny: - + Restore default URL Przywróć domyślny URL - + Choose file... Wybierz plik… - + Load sets file Wczytaj listę dodatków - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + + + Error Błąd - + The provided URL is not valid. Podano nieprawidłowy URL. - + Downloading (0MB) Pobieranie (0MB) - + Please choose a file. Proszę wybrać plik. - + Cannot open file '%1'. Nie można otworzyć pliku '%1'. - + Downloading (%1MB) Pobieranie (%1MB) - + Network error: %1. Błąd sieci: %1. - + Parsing file Analizowanie pliku - + Xz extraction failed. Rozpakowanie XZ nie udało się. - + Sorry, this version of Oracle does not support xz compressed files. Przepraszamy, ta wersja Oracle nie obsługuje plików spakowanych w archiwum xz. - + Failed to open Zip archive: %1. Otwieranie archiwum Zip zakończone niepowodzeniem: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Rozpakowanie pliku Zip nieudane: archiwum nie zawiera dokładnie jednego pliku. - + Zip extraction failed: %1. Rozpakowanie Zip nieudane: %1. - + Sorry, this version of Oracle does not support zipped files. Przepraszamy, ta wersja Oracle nie obsługuje plików spakowanych w archiwum Zip. - + Failed to interpret downloaded data. Interpretacja pobranych danych nie udała się. - + Do you want to download the uncompressed file instead? Czy chcesz zamiast tego pobrać plik nieskompresowany? - + The file was retrieved successfully, but it does not contain any sets data. Plik został pobrany z powodzeniem, ale nie zawiera informacji o dodatkach. @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database Zapisz bazę danych spoilerów. - + XML; spoiler database (*.xml) XML; baza danych spoilerów (*.xml) - + + spoiler + + + + Spoilers import Import spoilerów - + Please specify a compatible source for spoiler data. Proszę wybrać kompatybilne źródło danych ze spoilerami. - + Download URL: URL pobierania: - + + Local file: + + + + Restore default URL Przywróć domyślny URL - + + Choose file... + + + + The spoiler database will be saved at the following location: Baza danych spoilerów zostanie zapisana w tym miejscu: - + Save to a custom path (not recommended) Zapisz do niestandardowej ścieżki (niezalecane) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database Zapisz bazę danych tokenów. - + XML; token database (*.xml) XML; baza danych tokenów (*.xml) - + + tokens + + + + Tokens import Import tokenów - + Please specify a compatible source for token data. Proszę wybrać kompatybilne źródło danych z tokenami. - + Download URL: Pobierz odnośnik: - + + Local file: + + + + Restore default URL Przywróć domyślny URL - + + Choose file... + + + + The token database will be saved at the following location: Baza danych tokenów zostanie zapisana w tym miejscu: - + Save to a custom path (not recommended) Zapisz do niestandardowej ścieżki (niezalecane) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Dodatek-atrapa, zawierający tokeny. @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle – kreator importu @@ -262,22 +292,22 @@ OutroPage - + Finished Zakończone - + The wizard has finished. Kreator zakończył swoją pracę. - + You can now start using Cockatrice with the newly updated cards. Możesz teraz korzystać z Cockatrice z nowo zaktualizowanymi kartami. - + If the card databases don't reload automatically, restart the Cockatrice client. Jeżeli bazy kart nie przeładują się automatycznie, zrestartuj klienta Cockatrice. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error Błąd - + No set has been imported. Nie zaimportowano żadnego dodatku. - + Sets imported Zaimportowane dodatki - + A cockatrice database file of %1 MB has been downloaded. Baza danych Cockatrice mająca %1 MB została pobrana. - + The following sets have been found: Następujące dodatki zostały znalezione: - + Press "Save" to store the imported cards in the Cockatrice database. Kliknij "Zapisz" aby zapisać zaimportowane karty w bazie danych Cockatrice. - + The card database will be saved at the following location: Baza danych kart zostanie zapisana w tym miejscu: - + Save to a custom path (not recommended) Zapisz do niestandardowej ścieżki (niezalecane) - + &Save Zapisz - + Import finished: %1 cards. Import zakończony: %1 kart. - + %1: %2 cards imported %1: zaimportowano %2 kart - + Save card database Zapisz bazę kart - + XML; card database (*.xml) XML, baza kart (*.xml) - + The file could not be saved to %1 Plik nie mógł zostać zapisany do %1 @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error Błąd - + The provided URL is not valid: - + Downloading (0MB) Pobieranie (0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) Pobieranie (%1MB) - + Network error: %1. Błąd sieci: %1. - + The file could not be saved to %1 Plik nie mógł zostać zapisany do %1 @@ -535,7 +587,7 @@ i18n - + English Polski (Polish) diff --git a/oracle/translations/oracle_pt.ts b/oracle/translations/oracle_pt.ts index 6c43458f0..309696e8c 100644 --- a/oracle/translations/oracle_pt.ts +++ b/oracle/translations/oracle_pt.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Introdução - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. - + Interface language: - + Version: @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection Selecção da fonte - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. - + Download URL: URL de download: - + Local file: Ficheiro local: - + Restore default URL URL para repor definições de origem - + Choose file... Escolher ficheiro... - + Load sets file Carregar ficheiro das edições - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + + + Error Erro - + The provided URL is not valid. O URL fornecido não é válido. - + Downloading (0MB) A efectuar download (0MB) - + Please choose a file. Por favor escolha um ficheiro. - + Cannot open file '%1'. Impossível abrir ficheiro '%1'. - + Downloading (%1MB) A efectuar download (%1MB) - + Network error: %1. Erro da rede: %1. - + Parsing file Ficheiro de análise - + Xz extraction failed. - + Sorry, this version of Oracle does not support xz compressed files. - + Failed to open Zip archive: %1. Abrir archivo zip falhou: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Extracção do ZIP falhada: o arquivo ZIP não contem exactamente um ficheiro. - + Zip extraction failed: %1. Extração do Zip falhada: %1. - + Sorry, this version of Oracle does not support zipped files. Pedimos desculpa, mas esta versão do Oracle não suporta ficheiros zipados. - + Failed to interpret downloaded data. - + Do you want to download the uncompressed file instead? - + The file was retrieved successfully, but it does not contain any sets data. O ficheiro foi recuperado com sucesso, mas não contem nenhum dado sobre expansões. @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database - + XML; spoiler database (*.xml) - + + spoiler + + + + Spoilers import - + Please specify a compatible source for spoiler data. - + Download URL: - + + Local file: + + + + Restore default URL - + + Choose file... + + + + The spoiler database will be saved at the following location: - + Save to a custom path (not recommended) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database - + XML; token database (*.xml) - + + tokens + + + + Tokens import - + Please specify a compatible source for token data. - + Download URL: URL de download: - + + Local file: + + + + Restore default URL URL para repor definições de origem - + + Choose file... + + + + The token database will be saved at the following location: - + Save to a custom path (not recommended) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Set básico contendo fichas @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer Importar Oracle @@ -262,22 +292,22 @@ OutroPage - + Finished - + The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error Erro - + No set has been imported. Nenhuma expansão foi importada. - + Sets imported Edições importadas - + A cockatrice database file of %1 MB has been downloaded. - + The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: - + Save to a custom path (not recommended) - + &Save - + Import finished: %1 cards. Importação terminada: %1 cartas. - + %1: %2 cards imported %1. %2 cartas importadas - + Save card database Guardar base de dados das cartas - + XML; card database (*.xml) XML; Base de dados de cartas (*.xml) - + The file could not be saved to %1 O ficheiro não pode ser gravado em %1 @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error - + The provided URL is not valid: - + Downloading (0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) - + Network error: %1. - + The file could not be saved to %1 @@ -535,7 +587,7 @@ i18n - + English Português (Portuguese) diff --git a/oracle/translations/oracle_pt_BR.ts b/oracle/translations/oracle_pt_BR.ts index f4cc27e75..b72de5c27 100644 --- a/oracle/translations/oracle_pt_BR.ts +++ b/oracle/translations/oracle_pt_BR.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Introdução - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Esse recurso irá importar a lista de coleções, cartas e fichas que serão utilizadas pelo Cokatrice. - + Interface language: Idioma da interface: - + Version: Versão: @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection Selecione a origem - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Por favor especifique uma fonte compatível de expansões e cartas. Você pode especificar um endereço URL que será baixado ou usar um arquivo existente no seu computador. - + Download URL: Endereço para download: - + Local file: Arquivo local: - + Restore default URL Restaurar URL padrão - + Choose file... Escolha o arquivo... - + Load sets file Carregar arquivos de expansão - + Sets file (%1) Sets JSON file (%1) Arquivo de expansões (%1) - - - - - - - + + + + + + + Error Erro - + The provided URL is not valid. A URL escolhida não é valida - + Downloading (0MB) Baixando (0MB) - + Please choose a file. Por favor, escolha um arquivo; - + Cannot open file '%1'. Não pode abrir arquivo '%1' - + Downloading (%1MB) Baixando (%1MB) - + Network error: %1. Erro na conexão com a internet: %1. - + Parsing file Analisando arquivo - + Xz extraction failed. Extração de arquivo .xz falhou - + Sorry, this version of Oracle does not support xz compressed files. Desculpe, esta versão do Oracle não suporta arquivos comprimidos no formato .xz - + Failed to open Zip archive: %1. Falhou em abrir o arquivo Zip: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. A extração do arquivo ZIP falhou: o arquivo ZIP não contém exatamente um arquivo. - + Zip extraction failed: %1. Extração do arquivo Zip falhou: %1. - + Sorry, this version of Oracle does not support zipped files. Desculpe, esta versão do Oracle não suporta arquivos compactados. - + Failed to interpret downloaded data. Falha ao interpretar dados baixados - + Do you want to download the uncompressed file instead? Gostaria de baixar os arquivos descomprimidos, em vez disso? - + The file was retrieved successfully, but it does not contain any sets data. O arquivo foi recebido com sucesso, mas não contém qualquer informação de expansão. @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database Salvar base de dados de spoiler - + XML; spoiler database (*.xml) XML; base de dados de spoilers (*.xml) - + + spoiler + + + + Spoilers import Importar spoilers - + Please specify a compatible source for spoiler data. Por favor, selecione uma fonte compatível para os dados de spoilers. - + Download URL: Endereço para download: - + + Local file: + + + + Restore default URL Restaurar URL padrão - + + Choose file... + + + + The spoiler database will be saved at the following location: A base de dados de spoilers será salva no seguinte diretório: - + Save to a custom path (not recommended) Salvar em diretório customizado (não recomendado) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database Salvar base de dados de tokens - + XML; token database (*.xml) XML; base de dados de token (*.xml) - + + tokens + + + + Tokens import Importar tokens - + Please specify a compatible source for token data. Por favor especifique uma fonte compatível para os dados de tokens. - + Download URL: Endereço para download: - + + Local file: + + + + Restore default URL Restaurar URL padrão - + + Choose file... + + + + The token database will be saved at the following location: A base de dados de tokens será salva no seguinte diretório: - + Save to a custom path (not recommended) Salvar em diretório customizado (não recomendado) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Esta expansão contém fichas. @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer Importador Oracle @@ -262,22 +292,22 @@ OutroPage - + Finished Finalizado - + The wizard has finished. O recurso terminou. - + You can now start using Cockatrice with the newly updated cards. Agora você pode começar a usar o Cockatrice com as novas cartas atualizadas. - + If the card databases don't reload automatically, restart the Cockatrice client. Se a base de dados das cartas não for atualizada automaticamente, reinicie o cliente do Cockatrice. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error Erro - + No set has been imported. O set não foi importado. - + Sets imported Expansões importadas - + A cockatrice database file of %1 MB has been downloaded. Um arquivo de %1 MB de banco de dados do cockatrice foi baixado. - + The following sets have been found: As seguintes expansões foram encontradas: - + Press "Save" to store the imported cards in the Cockatrice database. Aperte "Salvar" para guardar as cartas importadas na base de dados do Cockatrice. - + The card database will be saved at the following location: A base de dados de cartas será salva no seguinte diretório: - + Save to a custom path (not recommended) Salvar em diretório customizado (não recomendado) - + &Save &Salvar - + Import finished: %1 cards. Importação finalizada: %1 cartas. - + %1: %2 cards imported %1: %2 cartas importadas - + Save card database Carta salva no banco de dados - + XML; card database (*.xml) XML; banco de dados de cartas (*.xml) - + The file could not be saved to %1 O arquivo não pode ser salvo para %1 @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error Erro - + The provided URL is not valid: - + Downloading (0MB) Baixando (0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) Baixando (%1MB) - + Network error: %1. Erro de rede: %1 - + The file could not be saved to %1 O arquivo não pode ser salvo em %1 @@ -535,7 +587,7 @@ i18n - + English Português do Brasil (Brazilian Portuguese) diff --git a/oracle/translations/oracle_ru.ts b/oracle/translations/oracle_ru.ts index 013f5dfce..2029ee7e9 100644 --- a/oracle/translations/oracle_ru.ts +++ b/oracle/translations/oracle_ru.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Введение - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Эта программа импортирует перечень выпусков, карт и фишек, которые будут использоваться в Cockatrice. - + Interface language: Язык интерфейса: - + Version: Версия @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection Выбор источника - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Пожалуйста, укажите источник для перечня сетов и карт. Вы можете использовать ссылку или уже существующий локальный файл. - + Download URL: Ссылка на скачивание: - + Local file: Локальный файл: - + Restore default URL Восстановить ссылку по умолчанию - + Choose file... Выбрать файл.. - + Load sets file Загрузить файл сетов - + Sets file (%1) Sets JSON file (%1) Файл (%1) - - - - - - - + + + + + + + Error Ошибка - + The provided URL is not valid. Предоставленная ссылка некорректна. - + Downloading (0MB) Загрузка (0MB) - + Please choose a file. Пожалуйста, выберите файл. - + Cannot open file '%1'. Не удалось открыть файл '%1'. - + Downloading (%1MB) Загрузка (%1MB) - + Network error: %1. Ошибка сети: %1. - + Parsing file Ожидание файла - + Xz extraction failed. Ошибка распаковки архива - + Sorry, this version of Oracle does not support xz compressed files. Данная версия Oracle не поддерживает архивы xz - + Failed to open Zip archive: %1. Не удалось загрузить zip-архив: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Ошибка распаковки: zip-архив содержит больше одного файла. - + Zip extraction failed: %1. Ошибка распаковки zip-архива: %1. - + Sorry, this version of Oracle does not support zipped files. Данная версия Oracle не поддерживает zip-архивы. - + Failed to interpret downloaded data. Ошибка в обработке загруженных данных - + Do you want to download the uncompressed file instead? Хотите вместо этого загрузить новую несжатую копию? - + The file was retrieved successfully, but it does not contain any sets data. Файл успешно получен, но в нем не содержится данных о сетах. @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database Сохранить базу карт - + XML; spoiler database (*.xml) XML; база карт (*.xml) - + + spoiler + + + + Spoilers import Импорт карт - + Please specify a compatible source for spoiler data. Укажите совместимый ресурс для импорта базы карт - + Download URL: Ссылка на скачивание: - + + Local file: + + + + Restore default URL Восстановить ссылку по умолчанию - + + Choose file... + + + + The spoiler database will be saved at the following location: База карт будет сохранена в следующей директории: - + Save to a custom path (not recommended) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database - + XML; token database (*.xml) - + + tokens + + + + Tokens import - + Please specify a compatible source for token data. - + Download URL: Ссылка на скачивание: - + + Local file: + + + + Restore default URL Восстановить ссылку по умолчанию - + + Choose file... + + + + The token database will be saved at the following location: - + Save to a custom path (not recommended) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens Пример сета с фишками @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer Импортер Oracle @@ -262,22 +292,22 @@ OutroPage - + Finished - + The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error Ошибка - + No set has been imported. Не было импортировано ни одного сета. - + Sets imported Импортировано сетов - + A cockatrice database file of %1 MB has been downloaded. - + The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: - + Save to a custom path (not recommended) - + &Save Сохранить - + Import finished: %1 cards. Импорт завершен: %1 карт. - + %1: %2 cards imported %1: %2 карт импортировано - + Save card database Сохранить базу карт - + XML; card database (*.xml) База карт (*.xml) - + The file could not be saved to %1 Не удалось сохранить файл в %1 @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error - + The provided URL is not valid: - + Downloading (0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) - + Network error: %1. - + The file could not be saved to %1 @@ -535,7 +587,7 @@ i18n - + English Русский (Russian) diff --git a/oracle/translations/oracle_sr.ts b/oracle/translations/oracle_sr.ts index 2e6cfa1be..de8122316 100644 --- a/oracle/translations/oracle_sr.ts +++ b/oracle/translations/oracle_sr.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Uvod - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. - + Interface language: - + Version: @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection Odabir izvora - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. - + Download URL: URL za preuzimanje: - + Local file: Lokalni fajl: - + Restore default URL Povrati uobičajeni URL - + Choose file... Izaberite fajl... - + Load sets file - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + + + Error Greška - + The provided URL is not valid. Dati URL nije važeći. - + Downloading (0MB) Preuzimanje (0MB) - + Please choose a file. Molimo Vas izaberite fajl. - + Cannot open file '%1'. Nemoguće otvori fajl '%1'. - + Downloading (%1MB) Preuzimanje (%1MB) - + Network error: %1. Mrežna greška: %1. - + Parsing file Parsiranje fajla - + Xz extraction failed. - + Sorry, this version of Oracle does not support xz compressed files. - + Failed to open Zip archive: %1. Neuspeh u otvaranju Zip arhive: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. - + Zip extraction failed: %1. - + Sorry, this version of Oracle does not support zipped files. Izvinite, ova verzija Oracle-a ne podržava zip fajlove. - + Failed to interpret downloaded data. - + Do you want to download the uncompressed file instead? - + The file was retrieved successfully, but it does not contain any sets data. @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database - + XML; spoiler database (*.xml) - + + spoiler + + + + Spoilers import - + Please specify a compatible source for spoiler data. - + Download URL: - + + Local file: + + + + Restore default URL - + + Choose file... + + + + The spoiler database will be saved at the following location: - + Save to a custom path (not recommended) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database - + XML; token database (*.xml) - + + tokens + + + + Tokens import - + Please specify a compatible source for token data. - + Download URL: URL za preuzimanje: - + + Local file: + + + + Restore default URL - + + Choose file... + + + + The token database will be saved at the following location: - + Save to a custom path (not recommended) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer @@ -262,22 +292,22 @@ OutroPage - + Finished - + The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error Greška - + No set has been imported. - + Sets imported - + A cockatrice database file of %1 MB has been downloaded. - + The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: - + Save to a custom path (not recommended) - + &Save - + Import finished: %1 cards. - + %1: %2 cards imported - + Save card database - + XML; card database (*.xml) - + The file could not be saved to %1 @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error - + The provided URL is not valid: - + Downloading (0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) - + Network error: %1. - + The file could not be saved to %1 @@ -535,7 +587,7 @@ i18n - + English Srpski (Serbian) diff --git a/oracle/translations/oracle_tr.ts b/oracle/translations/oracle_tr.ts index 06b4e1359..345de6f46 100644 --- a/oracle/translations/oracle_tr.ts +++ b/oracle/translations/oracle_tr.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Açıklama - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Bu sihirbaz Cockatrice tarafından kullanılacak setlerin, kartların ve tokenlerin listesini içe aktaracaktır. - + Interface language: Arayüz dili: - + Version: Sürüm: @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection Kaynak seçimi - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Lütfen set ve kart listesi için uyumlu bir kaynak belirtin. İndirilecek bir URL adresi belirtebilir veya bilgisayarınızdaki mevcut bir dosyayı kullanabilirsiniz. - + Download URL: İndirme Bağlantısı: - + Local file: Yerel dosya: - + Restore default URL Varsayılan URL'yi geri yükle - + Choose file... Dosya seç... - + Load sets file Set dosyasını yükle - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + + + Error Hata - + The provided URL is not valid. Paylaşılan bağlantı geçerli değil. - + Downloading (0MB) İndiriliyor (0MB) - + Please choose a file. Lütfen dosya seçin. - + Cannot open file '%1'. Dosya açılamıyor '1%'. - + Downloading (%1MB) İndiriliyor (%1MB) - + Network error: %1. Bağlantı hatası: %1 - + Parsing file Dosya ayrıştırılıyor - + Xz extraction failed. Xz çıkarma işlemi başarısız oldu. - + Sorry, this version of Oracle does not support xz compressed files. Maalesef, Oracle'ın bu sürümü xz sıkıştırılmış dosyaları desteklemiyor. - + Failed to open Zip archive: %1. Zip dosyası açılamadı: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Zip çıkarma işlemi başarısız oldu: Zip arşivi tam olarak bir dosya içermiyor. - + Zip extraction failed: %1. Zip çıkarma işlemi başarısız oldu: %1. - + Sorry, this version of Oracle does not support zipped files. Maalesef, Oracle'ın bu sürümü zip dosyalarını desteklemiyor. - + Failed to interpret downloaded data. İndirilen veriler işlenemedi. - + Do you want to download the uncompressed file instead? Bunun yerine sıkıştırılmamış dosyayı indirmek ister misiniz? - + The file was retrieved successfully, but it does not contain any sets data. Dosya başarıyla alındı, ancak herhangi bir set verisi içermiyor. @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database Spoiler veritabanını kaydet - + XML; spoiler database (*.xml) XML; spoiler veritabanı (*.xml) - + + spoiler + + + + Spoilers import Spoilers içe aktar - + Please specify a compatible source for spoiler data. Lütfen spoiler verileri için uyumlu bir kaynak belirtin. - + Download URL: İndirme Bağlantısı: - + + Local file: + + + + Restore default URL Varsayılan bağlantıyı geri yükle - + + Choose file... + + + + The spoiler database will be saved at the following location: - + Save to a custom path (not recommended) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database - + XML; token database (*.xml) - + + tokens + + + + Tokens import - + Please specify a compatible source for token data. - + Download URL: - + + Local file: + + + + Restore default URL - + + Choose file... + + + + The token database will be saved at the following location: - + Save to a custom path (not recommended) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer @@ -262,22 +292,22 @@ OutroPage - + Finished - + The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error - + No set has been imported. - + Sets imported - + A cockatrice database file of %1 MB has been downloaded. - + The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: - + Save to a custom path (not recommended) - + &Save - + Import finished: %1 cards. - + %1: %2 cards imported - + Save card database - + XML; card database (*.xml) - + The file could not be saved to %1 @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error - + The provided URL is not valid: - + Downloading (0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) - + Network error: %1. - + The file could not be saved to %1 @@ -535,7 +587,7 @@ i18n - + English Türkçe (Turkish) diff --git a/oracle/translations/oracle_yue.ts b/oracle/translations/oracle_yue.ts index 822a012fb..173ba8075 100644 --- a/oracle/translations/oracle_yue.ts +++ b/oracle/translations/oracle_yue.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction 介绍 - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. 此嚮導將會導入Cockatrice中用到的系列,卡牌和衍生物列表。 - + Interface language: 介面語言: - + Version: 版本: @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection 選擇來源 - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. 請選擇合適的牌組和卡片清單來源。您可以輸入下載URL或使用電腦中已有的檔案。 - + Download URL: 下载URL: - + Local file: 本地檔案: - + Restore default URL 恢復常用URL - + Choose file... 選擇檔案... - + Load sets file 载入牌组檔案 - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + + + Error 出現錯誤 - + The provided URL is not valid. 所提供的url無效. - + Downloading (0MB) 下載中 (0MB) - + Please choose a file. 請選擇檔案 - + Cannot open file '%1'. 不能開啟檔案 '%1 - + Downloading (%1MB) 下載中 (%1MB) - + Network error: %1. 網路出錯: %1. - + Parsing file 分析檔案中 - + Xz extraction failed. xz解壓縮失敗. - + Sorry, this version of Oracle does not support xz compressed files. 很抱歉,現前Oracle版本不支援xc壓縮檔案 - + Failed to open Zip archive: %1. 未能開啟壓縮文件: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. 解壓縮失敗:這壓縮文件擁有超過一個檔案. - + Zip extraction failed: %1. 解壓縮失敗: %1. - + Sorry, this version of Oracle does not support zipped files. 很抱歉,現前Oracle版本不支援壓縮檔案. - + Failed to interpret downloaded data. 不能分析下載資料. - + Do you want to download the uncompressed file instead? 你是否想另再下載未經壓縮的檔案? - + The file was retrieved successfully, but it does not contain any sets data. 雖然檔案成功取回, 但是檔案並未含有任何牌組資料. @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database 儲存預覽資料庫 - + XML; spoiler database (*.xml) XML ; 預覽資料庫(*.XML) - + + spoiler + + + + Spoilers import 導入預覽卡牌 - + Please specify a compatible source for spoiler data. 請指定預覽資料合用來源. - + Download URL: 下载URL: - + + Local file: + + + + Restore default URL 恢復常用URL - + + Choose file... + + + + The spoiler database will be saved at the following location: 預覽資料庫將會儲藏於以下位置 - + Save to a custom path (not recommended) 儲存到自訂路徑(不建議) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database 儲存衍生物資料庫 - + XML; token database (*.xml) XML; 衍生物資料庫 (*.xml) - + + tokens + + + + Tokens import 導入衍生物 - + Please specify a compatible source for token data. 請指定衍生物資料合用來源. - + Download URL: 下载URL: - + + Local file: + + + + Restore default URL 恢復常用URL - + + Choose file... + + + + The token database will be saved at the following location: 衍生物資料庫將會儲藏於以下位置: - + Save to a custom path (not recommended) 儲存到自訂路徑(不建議) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens 包含衍生物的虚拟牌組 @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle導入器 @@ -262,22 +292,22 @@ OutroPage - + Finished 完成 - + The wizard has finished. 嚮導程序工作完成 - + You can now start using Cockatrice with the newly updated cards. Cockatrice已載入更新了卡牌, 現在已經可以使用. - + If the card databases don't reload automatically, restart the Cockatrice client. 如果卡牌資料庫尚未能自動更新,請把cockatrice用戶端重新開啟. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error 出現錯誤 - + No set has been imported. 沒有牌組被導入。 - + Sets imported 已導入牌組 - + A cockatrice database file of %1 MB has been downloaded. 有%1MB大小的Cockatrice資料庫檔案已被下載 - + The following sets have been found: 已找到以下牌组: - + Press "Save" to store the imported cards in the Cockatrice database. 要將導入卡牌保存於Cockatrice資料庫內請按"儲藏". - + The card database will be saved at the following location: 卡牌資料庫將被儲藏於以下位置 - + Save to a custom path (not recommended) 儲存到自訂路徑(不建議) - + &Save &儲存 - + Import finished: %1 cards. 導入過程完成:成功導入%1張卡牌. - + %1: %2 cards imported %1:成功導入%2張卡牌. - + Save card database 儲存卡牌資料庫 - + XML; card database (*.xml) XML; 卡牌資料庫 (*.xml) - + The file could not be saved to %1 檔案未能儲藏於%1。 @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error 出現錯誤 - + The provided URL is not valid: - + Downloading (0MB) 下載中(0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) 下載中 (%1MB) - + Network error: %1. 網路出錯: %1. - + The file could not be saved to %1 檔案未能儲藏於%1 @@ -535,7 +587,7 @@ i18n - + English diff --git a/oracle/translations/oracle_zh-Hans.ts b/oracle/translations/oracle_zh-Hans.ts index 62a49d3c6..d3927fff8 100644 --- a/oracle/translations/oracle_zh-Hans.ts +++ b/oracle/translations/oracle_zh-Hans.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction 介绍 - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. 此向导将会导入Cockatrice中用到的系列,卡牌和衍生物列表. - + Interface language: 界面语言: - + Version: 版本: @@ -25,134 +25,134 @@ LoadSetsPage - + Source selection 选择来源 - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. 请选择合适的牌组和卡片清单来源。您可以输入下载URL或使用电脑中已有的档案。 - + Download URL: 下载URL: - + Local file: 本地文件: - + Restore default URL 恢复常用URL - + Choose file... 选择档案... - + Load sets file 载入牌组档案 - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + + + Error 出现错误 - + The provided URL is not valid. 所提供的url无效. - + Downloading (0MB) 下载中(0MB) - + Please choose a file. 请选择档案. - + Cannot open file '%1'. 不能开启档案 '%1 - + Downloading (%1MB) 下载中(%1MB) - + Network error: %1. 网路出错: %1. - + Parsing file 分析档案中 - + Xz extraction failed. xz解压缩失败。 - + Sorry, this version of Oracle does not support xz compressed files. 很抱歉,现前Oracle版本不支援xc压缩档案 - + Failed to open Zip archive: %1. 未能开启压缩文件: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. 解压缩失败:这压缩文件拥有超过一个档案. - + Zip extraction failed: %1. 解压缩失败:%1。 - + Sorry, this version of Oracle does not support zipped files. 很抱歉,现前Oracle版本不支援压缩档案. - + Failed to interpret downloaded data. 不能分析下载资料. - + Do you want to download the uncompressed file instead? 你是否想另再下载未经压缩的档案? - + The file was retrieved successfully, but it does not contain any sets data. 虽然档案成功取回, 但是档案并未含有任何牌组资料. @@ -160,42 +160,57 @@ LoadSpoilersPage - + Save spoiler database 储存预览资料库 - + XML; spoiler database (*.xml) XML ; 预览资料库(*.XML) - + + spoiler + + + + Spoilers import 导入预览卡牌 - + Please specify a compatible source for spoiler data. 请指定预览资料合用来源. - + Download URL: 下载URL: - + + Local file: + + + + Restore default URL 恢复常用URL - + + Choose file... + + + + The spoiler database will be saved at the following location: 预览资料库将会储藏于以下位置: - + Save to a custom path (not recommended) 保存到自定义路径(不推荐) @@ -203,42 +218,57 @@ LoadTokensPage - + Save token database 储存衍生物资料库 - + XML; token database (*.xml) XML; 衍生物资料库 (*.xml) - + + tokens + + + + Tokens import 导入衍生物 - + Please specify a compatible source for token data. 请指定衍生物资料合用来源. - + Download URL: 下载URL: - + + Local file: + + + + Restore default URL 恢复常用URL - + + Choose file... + + + + The token database will be saved at the following location: 衍生物资料库将会储藏于以下位置: - + Save to a custom path (not recommended) 保存到自定义路径(不推荐) @@ -246,7 +276,7 @@ OracleImporter - + Dummy set containing tokens 包含衍生物的虚拟牌组 @@ -254,7 +284,7 @@ OracleWizard - + Oracle Importer Oracle导入器 @@ -262,22 +292,22 @@ OutroPage - + Finished 完成 - + The wizard has finished. 向导程式工作完成. - + You can now start using Cockatrice with the newly updated cards. Cockatrice已载入更新了卡牌, 现在已经可以使用. - + If the card databases don't reload automatically, restart the Cockatrice client. 如果卡牌资料库尚未能自动更新,请把cockatrice用户端重新开启. @@ -285,73 +315,73 @@ SaveSetsPage - - + + Error 出现错误 - + No set has been imported. 没有牌组被导入。 - + Sets imported 已导入牌组 - + A cockatrice database file of %1 MB has been downloaded. 有%1MB大小的Cockatrice资料库档案已被下载. - + The following sets have been found: 已找到以下牌组: - + Press "Save" to store the imported cards in the Cockatrice database. 要将导入卡牌保存于Cockatrice资料库内请按"储藏". - + The card database will be saved at the following location: 卡牌资料库将被储藏于以下位置 - + Save to a custom path (not recommended) 保存到自定义路径(不推荐) - + &Save &储存 - + Import finished: %1 cards. 导入过程完成:成功导入%1张卡牌. - + %1: %2 cards imported %1:成功导入%2张卡牌. - + Save card database 储存卡牌资料库 - + XML; card database (*.xml) XML; 卡牌资料库 (*.xml) - + The file could not be saved to %1 档案未能储藏于%1。 @@ -359,34 +389,56 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error 出现错误 - + The provided URL is not valid: - + Downloading (0MB) 下载中(0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) 下载中(%1MB) - + Network error: %1. 网路出错: %1. - + The file could not be saved to %1 档案未能储藏于%1 @@ -535,7 +587,7 @@ i18n - + English 简体中文 (Chinese Simplified) From 57e6c91689d7b7b86485621985449c908e5a90a3 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 15 Jan 2026 18:05:19 -0800 Subject: [PATCH 145/325] [TabDeckEditor] Automatically sync view menu actions (#6522) --- .../deck_editor_card_database_dock_widget.cpp | 1 - .../deck_editor_card_info_dock_widget.cpp | 1 - .../deck_editor_deck_dock_widget.cpp | 1 - .../deck_editor_filter_dock_widget.cpp | 1 - ...k_editor_printing_selector_dock_widget.cpp | 1 - .../widgets/tabs/abstract_tab_deck_editor.cpp | 41 ++++++++- .../widgets/tabs/abstract_tab_deck_editor.h | 26 +++--- .../widgets/tabs/tab_deck_editor.cpp | 89 ++----------------- .../interface/widgets/tabs/tab_deck_editor.h | 3 - .../tab_deck_editor_visual.cpp | 84 ++--------------- .../tab_deck_editor_visual.h | 16 ---- 11 files changed, 65 insertions(+), 199 deletions(-) diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp index 5abc6e619..bacebe385 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp @@ -25,7 +25,6 @@ void DeckEditorCardDatabaseDockWidget::createDatabaseDisplayDock(AbstractTabDeck setWidget(dockContents); installEventFilter(deckEditor); - connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged); // connect signals connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::cardChanged, deckEditor, diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp index 3f12636a8..a78a9c9c5 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp @@ -31,7 +31,6 @@ void DeckEditorCardInfoDockWidget::createCardInfoDock() setWidget(cardInfoDockContents); installEventFilter(deckEditor); - connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged); } void DeckEditorCardInfoDockWidget::updateCard(const ExactCard &_card) 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 73bafe54f..2ecb4f46a 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 @@ -46,7 +46,6 @@ DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable); installEventFilter(deckEditor); - connect(this, &DeckEditorDeckDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged); createDeckDock(); } 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 5790131c5..8a9a6cdaa 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 @@ -80,7 +80,6 @@ void DeckEditorFilterDockWidget::createFiltersDock() setWidget(filterDockContents); installEventFilter(deckEditor); - connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged); } void DeckEditorFilterDockWidget::filterViewCustomContextMenu(const QPoint &point) diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp index 03760c22d..7c4e0de18 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp @@ -32,7 +32,6 @@ void DeckEditorPrintingSelectorDockWidget::createPrintingSelectorDock() setWidget(printingSelectorDockContents); installEventFilter(deckEditor); - connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged); connect(printingSelector, &PrintingSelector::prevCardRequested, deckEditor->getDeckDockWidget(), &DeckEditorDeckDockWidget::selectPrevCard); connect(printingSelector, &PrintingSelector::nextCardRequested, deckEditor->getDeckDockWidget(), 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 e41feb0c1..5e6db71f3 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -77,16 +77,30 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta &AbstractTabDeckEditor::refreshShortcuts); } -void AbstractTabDeckEditor::registerDockWidget(QDockWidget *widget) +void AbstractTabDeckEditor::registerDockWidget(QMenu *_viewMenu, QDockWidget *widget) { - QMenu *menu = viewMenu->addMenu(QString()); + QMenu *menu = _viewMenu->addMenu(QString()); QAction *aVisible = menu->addAction(QString()); aVisible->setCheckable(true); - connect(aVisible, &QAction::triggered, this, &AbstractTabDeckEditor::dockVisibleTriggered); + QAction *aFloating = menu->addAction(QString()); aFloating->setCheckable(true); - connect(aFloating, &QAction::triggered, this, &AbstractTabDeckEditor::dockFloatingTriggered); + aFloating->setEnabled(false); + + // user interaction + connect(aVisible, &QAction::triggered, widget, [widget](bool checked) { widget->setVisible(checked); }); + connect(aFloating, &QAction::triggered, this, [widget](bool checked) { widget->setFloating(checked); }); + + // sync aFloating's enabled state with aVisible's checked state + connect(aVisible, &QAction::toggled, aFloating, [aFloating](bool checked) { aFloating->setEnabled(checked); }); + + // sync aFloating with dockWidget's floating state + connect(widget, &QDockWidget::topLevelChanged, aFloating, + [aFloating](bool topLevel) { aFloating->setChecked(topLevel); }); + + // sync aVisible with dockWidget's visible state + widget->installEventFilter(new DockWidgetVisibilityFilter(widget, aVisible)); dockToActions.insert(widget, {menu, aVisible, aFloating}); } @@ -594,3 +608,22 @@ bool AbstractTabDeckEditor::closeRequest() return false; return close(); } + +DockWidgetVisibilityFilter::DockWidgetVisibilityFilter(QDockWidget *dockWidget, QAction *aVisible) + : QObject(dockWidget), dockWidget(dockWidget), aVisible(aVisible) +{ +} + +bool DockWidgetVisibilityFilter::eventFilter(QObject *o, QEvent *e) +{ + if (o == dockWidget && !e->spontaneous()) { + if (e->type() == QEvent::Show) { + aVisible->setChecked(true); + } + + if (e->type() == QEvent::Hide) { + aVisible->setChecked(false); + } + } + return false; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h index 66a56f39b..eb0b3755c 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h @@ -169,9 +169,6 @@ public slots: /** @brief Shows the printing selector dock. Pure virtual. */ virtual void showPrintingSelector() = 0; - /** @brief Slot for when a dock's top-level state changes. Pure virtual. */ - virtual void dockTopLevelChanged(bool topLevel) = 0; - signals: /** @brief Emitted when a deck should be opened in a new editor tab. */ void openDeckEditor(const LoadedDeck &deck); @@ -246,12 +243,6 @@ protected slots: /** @brief Handles dock close events. */ void closeEvent(QCloseEvent *event) override; - /** @brief Slot triggered when a dock visibility changes. Pure virtual. */ - virtual void dockVisibleTriggered() = 0; - - /** @brief Slot triggered when a dock floating state changes. Pure virtual. */ - virtual void dockFloatingTriggered() = 0; - private: /** @brief Sets the deck for this tab. * @param _deck The deck object. @@ -289,7 +280,7 @@ protected: * @brief registers a QDockWidget as a managed dock widget. Creates the associated actions and menu, adds them to * the viewMenu, and connects those actions to the tab's slots. */ - void registerDockWidget(QDockWidget *widget); + void registerDockWidget(QMenu *_viewMenu, QDockWidget *widget); /** @brief Confirms deck open action based on settings and modified state. * @param openInSameTabIfBlank Whether to reuse same tab if blank. @@ -316,4 +307,19 @@ protected: QMap dockToActions; }; +/** + * This filter syncs the dock widget's visibility with the viewMenu visibility action's check state. + */ +class DockWidgetVisibilityFilter : public QObject +{ + Q_OBJECT + + QDockWidget *dockWidget; + QAction *aVisible; + +public: + explicit DockWidgetVisibilityFilter(QDockWidget *dockWidget, QAction *aVisible); + bool eventFilter(QObject *o, QEvent *e) override; +}; + #endif // TAB_GENERIC_DECK_EDITOR_H diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index 9c7cc0a84..4625decd8 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -54,11 +54,11 @@ void TabDeckEditor::createMenus() viewMenu = new QMenu(this); - registerDockWidget(cardInfoDockWidget); - registerDockWidget(cardDatabaseDockWidget); - registerDockWidget(deckDockWidget); - registerDockWidget(filterDockWidget); - registerDockWidget(printingSelectorDockWidget); + registerDockWidget(viewMenu, cardInfoDockWidget); + registerDockWidget(viewMenu, cardDatabaseDockWidget); + registerDockWidget(viewMenu, deckDockWidget); + registerDockWidget(viewMenu, filterDockWidget); + registerDockWidget(viewMenu, printingSelectorDockWidget); if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { dockToActions[printingSelectorDockWidget].menu->setEnabled(false); @@ -130,7 +130,6 @@ void TabDeckEditor::showPrintingSelector() { printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr()); printingSelectorDockWidget->printingSelector->updateDisplay(); - dockToActions[printingSelectorDockWidget].aVisible->setChecked(true); printingSelectorDockWidget->setVisible(true); } @@ -152,19 +151,9 @@ void TabDeckEditor::loadLayout() if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { if (!printingSelectorDockWidget->isHidden()) { printingSelectorDockWidget->setHidden(true); - dockToActions[printingSelectorDockWidget].aVisible->setChecked(true); } } - for (auto it = dockToActions.begin(); it != dockToActions.end(); ++it) { - QDockWidget *dockWidget = it.key(); - const DockActions &actions = it.value(); - - actions.aVisible->setChecked(!dockWidget->isHidden()); - actions.aFloating->setEnabled(actions.aVisible->isChecked()); - actions.aFloating->setChecked(dockWidget->isFloating()); - } - cardInfoDockWidget->setMinimumSize(layouts.getDeckEditorCardSize()); cardInfoDockWidget->setMaximumSize(layouts.getDeckEditorCardSize()); @@ -185,23 +174,13 @@ void TabDeckEditor::loadLayout() */ void TabDeckEditor::restartLayout() { - - // Update menu checkboxes - for (auto it = dockToActions.begin(); it != dockToActions.end(); ++it) { - QDockWidget *dockWidget = it.key(); - const DockActions &actions = it.value(); - - actions.aVisible->setEnabled(true); - actions.aFloating->setEnabled(false); - - // Show/hide and reset floating + // Show/hide and reset floating + for (auto dockWidget : dockToActions.keys()) { dockWidget->setVisible(true); dockWidget->setFloating(false); } // Printing selector special case - dockToActions[printingSelectorDockWidget].aVisible->setChecked( - !SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); printingSelectorDockWidget->setVisible(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); addDockWidget(Qt::LeftDockWidgetArea, cardDatabaseDockWidget); @@ -230,51 +209,6 @@ void TabDeckEditor::freeDocksSize() } } -/** @brief Handles dock visibility toggling from menu actions. */ -void TabDeckEditor::dockVisibleTriggered() -{ - QObject *o = sender(); - - for (auto it = dockToActions.begin(); it != dockToActions.end(); ++it) { - QDockWidget *dockWidget = it.key(); - const DockActions &actions = it.value(); - - if (o == actions.aVisible) { - dockWidget->setHidden(!actions.aVisible->isChecked()); - actions.aFloating->setEnabled(actions.aVisible->isChecked()); - return; - } - } -} - -/** @brief Handles dock floating toggling from menu actions. */ -void TabDeckEditor::dockFloatingTriggered() -{ - QObject *o = sender(); - - for (auto it = dockToActions.begin(); it != dockToActions.end(); ++it) { - QDockWidget *dockWidget = it.key(); - const DockActions &actions = it.value(); - - if (o == actions.aFloating) { - dockWidget->setFloating(actions.aFloating->isChecked()); - return; - } - } -} - -/** @brief Syncs menu state with dock floating changes. */ -void TabDeckEditor::dockTopLevelChanged(bool topLevel) -{ - QObject *o = sender(); - - auto dockWidget = qobject_cast(o); - if (dockToActions.contains(dockWidget)) { - DockActions actions = dockToActions.value(dockWidget); - actions.aFloating->setChecked(topLevel); - } -} - /** * @brief Handles close/hide events to update menu state and save layout. * @param o Object sending the event. @@ -283,15 +217,6 @@ void TabDeckEditor::dockTopLevelChanged(bool topLevel) */ bool TabDeckEditor::eventFilter(QObject *o, QEvent *e) { - if (e->type() == QEvent::Close) { - auto dockWidget = qobject_cast(o); - if (dockToActions.contains(dockWidget)) { - DockActions actions = dockToActions.value(dockWidget); - actions.aVisible->setChecked(false); - actions.aFloating->setEnabled(false); - } - } - if (o == this && e->type() == QEvent::Hide) { LayoutsSettings &layouts = SettingsCache::instance().layouts(); layouts.setDeckEditorLayoutState(saveState()); diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h index b3ffb8d90..285d241d2 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h @@ -70,9 +70,6 @@ protected slots: /** @brief Handles dock visibility, floating, and top-level changes. */ bool eventFilter(QObject *o, QEvent *e) override; - void dockVisibleTriggered() override; - void dockFloatingTriggered() override; - void dockTopLevelChanged(bool topLevel) override; public: /** diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index a3c18da7c..745ec22a8 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -97,10 +97,10 @@ void TabDeckEditorVisual::createMenus() viewMenu = new QMenu(this); - registerDockWidget(cardInfoDockWidget); - registerDockWidget(deckDockWidget); - registerDockWidget(filterDockWidget); - registerDockWidget(printingSelectorDockWidget); + registerDockWidget(viewMenu, cardInfoDockWidget); + registerDockWidget(viewMenu, deckDockWidget); + registerDockWidget(viewMenu, filterDockWidget); + registerDockWidget(viewMenu, printingSelectorDockWidget); if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { dockToActions[printingSelectorDockWidget].menu->setEnabled(false); @@ -241,7 +241,6 @@ void TabDeckEditorVisual::showPrintingSelector() { printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getCard().getCardPtr()); printingSelectorDockWidget->printingSelector->updateDisplay(); - dockToActions[printingSelectorDockWidget].aVisible->setChecked(true); printingSelectorDockWidget->setVisible(true); } @@ -283,19 +282,9 @@ void TabDeckEditorVisual::loadLayout() if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { if (!printingSelectorDockWidget->isHidden()) { printingSelectorDockWidget->setHidden(true); - dockToActions[printingSelectorDockWidget].aVisible->setChecked(false); } } - for (auto it = dockToActions.begin(); it != dockToActions.end(); ++it) { - QDockWidget *dockWidget = it.key(); - const DockActions &actions = it.value(); - - actions.aVisible->setChecked(dockWidget->isHidden()); - actions.aFloating->setEnabled(actions.aVisible->isChecked()); - actions.aFloating->setChecked(dockWidget->isFloating()); - } - cardInfoDockWidget->setMinimumSize(layouts.getDeckEditorCardSize()); cardInfoDockWidget->setMaximumSize(layouts.getDeckEditorCardSize()); @@ -314,20 +303,10 @@ void TabDeckEditorVisual::loadLayout() /** @brief Resets the layout to default positions and dock states. */ void TabDeckEditorVisual::restartLayout() { - for (auto it = dockToActions.begin(); it != dockToActions.end(); ++it) { - QDockWidget *dockWidget = it.key(); - const DockActions &actions = it.value(); - - actions.aFloating->setEnabled(false); + for (auto dockWidget : dockToActions.keys()) { dockWidget->setFloating(false); } - dockToActions[cardInfoDockWidget].aVisible->setChecked(true); - dockToActions[deckDockWidget].aVisible->setChecked(true); - dockToActions[filterDockWidget].aVisible->setChecked(false); - dockToActions[printingSelectorDockWidget].aVisible->setChecked( - !SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); - deckDockWidget->setVisible(true); cardInfoDockWidget->setVisible(true); filterDockWidget->setVisible(false); @@ -377,15 +356,6 @@ void TabDeckEditorVisual::retranslateUi() */ bool TabDeckEditorVisual::eventFilter(QObject *o, QEvent *e) { - if (e->type() == QEvent::Close) { - auto dockWidget = qobject_cast(o); - if (dockToActions.contains(dockWidget)) { - DockActions actions = dockToActions.value(dockWidget); - actions.aVisible->setChecked(false); - actions.aFloating->setEnabled(false); - } - } - if (o == this && e->type() == QEvent::Hide) { LayoutsSettings &layouts = SettingsCache::instance().layouts(); layouts.setDeckEditorLayoutState(saveState()); @@ -397,47 +367,3 @@ bool TabDeckEditorVisual::eventFilter(QObject *o, QEvent *e) } return false; } - -/** @brief Toggles dock visibility based on the corresponding menu action. */ -void TabDeckEditorVisual::dockVisibleTriggered() -{ - QObject *o = sender(); - for (auto it = dockToActions.begin(); it != dockToActions.end(); ++it) { - QDockWidget *dockWidget = it.key(); - const DockActions &actions = it.value(); - - if (o == actions.aVisible) { - dockWidget->setHidden(!actions.aVisible->isChecked()); - actions.aFloating->setEnabled(actions.aVisible->isChecked()); - return; - } - } -} - -/** @brief Toggles dock floating state based on the corresponding menu action. */ -void TabDeckEditorVisual::dockFloatingTriggered() -{ - QObject *o = sender(); - - for (auto it = dockToActions.begin(); it != dockToActions.end(); ++it) { - QDockWidget *dockWidget = it.key(); - const DockActions &actions = it.value(); - - if (o == actions.aFloating) { - dockWidget->setFloating(actions.aFloating->isChecked()); - return; - } - } -} - -/** @brief Updates menu checkboxes when a dock's top-level/floating state changes. */ -void TabDeckEditorVisual::dockTopLevelChanged(bool topLevel) -{ - QObject *o = sender(); - - auto dockWidget = qobject_cast(o); - if (dockToActions.contains(dockWidget)) { - DockActions actions = dockToActions.value(dockWidget); - actions.aFloating->setChecked(topLevel); - } -} diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h index 2f1d11d82..371699c4d 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h @@ -84,22 +84,6 @@ protected slots: */ bool eventFilter(QObject *o, QEvent *e) override; - /** - * @brief Triggered when a dock visibility menu item is clicked. - */ - void dockVisibleTriggered() override; - - /** - * @brief Triggered when a dock floating menu item is clicked. - */ - void dockFloatingTriggered() override; - - /** - * @brief Triggered when a dock top-level state changes. - * @param topLevel True if the dock became floating. - */ - void dockTopLevelChanged(bool topLevel) override; - protected: TabDeckEditorVisualTabWidget *tabContainer; ///< Tab container holding different visual widgets. From 93f0715d0232aa500f50f58ba41409419a2e88e3 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 15 Jan 2026 18:54:50 -0800 Subject: [PATCH 146/325] [TabDeckEditor] Save cardDatabase dock size in settings (#6524) --- .../src/interface/widgets/tabs/tab_deck_editor.cpp | 4 ++++ .../libcockatrice/settings/layouts_settings.cpp | 11 +++++++++++ .../libcockatrice/settings/layouts_settings.h | 2 ++ 3 files changed, 17 insertions(+) diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index 4625decd8..64b78ee0b 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -154,6 +154,9 @@ void TabDeckEditor::loadLayout() } } + cardDatabaseDockWidget->setMinimumSize(layouts.getDeckEditorCardDatabaseSize()); + cardDatabaseDockWidget->setMaximumSize(layouts.getDeckEditorCardDatabaseSize()); + cardInfoDockWidget->setMinimumSize(layouts.getDeckEditorCardSize()); cardInfoDockWidget->setMaximumSize(layouts.getDeckEditorCardSize()); @@ -221,6 +224,7 @@ bool TabDeckEditor::eventFilter(QObject *o, QEvent *e) LayoutsSettings &layouts = SettingsCache::instance().layouts(); layouts.setDeckEditorLayoutState(saveState()); layouts.setDeckEditorGeometry(saveGeometry()); + layouts.setDeckEditorCardDatabaseSize(cardDatabaseDockWidget->size()); layouts.setDeckEditorCardSize(cardInfoDockWidget->size()); layouts.setDeckEditorFilterSize(filterDockWidget->size()); layouts.setDeckEditorDeckSize(deckDockWidget->size()); diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp index 4b1400e0c..97629d385 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp @@ -25,6 +25,17 @@ void LayoutsSettings::setDeckEditorGeometry(const QByteArray &value) setValue(value, "layouts/deckEditor_geometry"); } +QSize LayoutsSettings::getDeckEditorCardDatabaseSize() +{ + QVariant previous = getValue("layouts/deckEditor_CardDatabaseSize"); + return previous == QVariant() ? QSize(500, 500) : previous.toSize(); +} + +void LayoutsSettings::setDeckEditorCardDatabaseSize(const QSize &value) +{ + setValue(value, "layouts/deckEditor_CardDatabaseSize"); +} + QSize LayoutsSettings::getDeckEditorCardSize() { QVariant previous = getValue("layouts/deckEditor_CardSize"); diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h index 5feb0f4f6..9c4df77df 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h @@ -19,6 +19,7 @@ class LayoutsSettings : public SettingsManager public: void setDeckEditorLayoutState(const QByteArray &value); void setDeckEditorGeometry(const QByteArray &value); + void setDeckEditorCardDatabaseSize(const QSize &value); void setDeckEditorCardSize(const QSize &value); void setDeckEditorDeckSize(const QSize &value); void setDeckEditorPrintingSelectorSize(const QSize &value); @@ -41,6 +42,7 @@ public: const QByteArray getDeckEditorLayoutState(); const QByteArray getDeckEditorGeometry(); + QSize getDeckEditorCardDatabaseSize(); QSize getDeckEditorCardSize(); QSize getDeckEditorDeckSize(); QSize getDeckEditorPrintingSelectorSize(); From 154b9ace921f5b82a084b6ca6405d7945364c27f Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 16 Jan 2026 07:10:36 -0800 Subject: [PATCH 147/325] [TabDeckEditor] Move cardDatabase dock action to top of menu (#6523) --- cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index 64b78ee0b..1ab7942e2 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -54,8 +54,8 @@ void TabDeckEditor::createMenus() viewMenu = new QMenu(this); - registerDockWidget(viewMenu, cardInfoDockWidget); registerDockWidget(viewMenu, cardDatabaseDockWidget); + registerDockWidget(viewMenu, cardInfoDockWidget); registerDockWidget(viewMenu, deckDockWidget); registerDockWidget(viewMenu, filterDockWidget); registerDockWidget(viewMenu, printingSelectorDockWidget); @@ -94,16 +94,16 @@ QString TabDeckEditor::getTabText() const void TabDeckEditor::retranslateUi() { deckMenu->retranslateUi(); - cardInfoDockWidget->retranslateUi(); cardDatabaseDockWidget->retranslateUi(); + cardInfoDockWidget->retranslateUi(); deckDockWidget->retranslateUi(); filterDockWidget->retranslateUi(); printingSelectorDockWidget->retranslateUi(); viewMenu->setTitle(tr("&View")); - dockToActions[cardInfoDockWidget].menu->setTitle(tr("Card Info")); dockToActions[cardDatabaseDockWidget].menu->setTitle(tr("Card Database")); + dockToActions[cardInfoDockWidget].menu->setTitle(tr("Card Info")); dockToActions[deckDockWidget].menu->setTitle(tr("Deck")); dockToActions[filterDockWidget].menu->setTitle(tr("Filters")); dockToActions[printingSelectorDockWidget].menu->setTitle(tr("Printing")); From 1b71519ec6d4ff721edc113c6561faa716888e43 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 16 Jan 2026 07:11:39 -0800 Subject: [PATCH 148/325] [VDE] Make sample hand widget look nicer (#6525) --- .../draw_probability/draw_probability_widget.cpp | 2 ++ .../visual_deck_editor_sample_hand_widget.cpp | 11 +++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp index 146ab6f1c..4931aeaa4 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp @@ -22,6 +22,7 @@ DrawProbabilityWidget::DrawProbabilityWidget(QWidget *parent, DeckListStatistics { controls = new QWidget(this); controlLayout = new QHBoxLayout(controls); + controlLayout->setContentsMargins(11, 0, 11, 0); labelPrefix = new QLabel(this); controlLayout->addWidget(labelPrefix); @@ -65,6 +66,7 @@ DrawProbabilityWidget::DrawProbabilityWidget(QWidget *parent, DeckListStatistics resultTable = new QTableWidget(this); resultTable->setColumnCount(3); resultTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + resultTable->setEditTriggers(QAbstractItemView::NoEditTriggers); layout->addWidget(resultTable); // Connections diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp index fb5ae780c..9e7fb0208 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp @@ -15,10 +15,13 @@ VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *pare : QWidget(parent), deckListModel(_deckListModel), statsAnalyzer(_statsAnalyzer) { layout = new QVBoxLayout(this); + layout->setSpacing(0); setLayout(layout); - resetAndHandSizeContainerWidget = new QWidget(this); resetAndHandSizeLayout = new QHBoxLayout(resetAndHandSizeContainerWidget); + resetAndHandSizeLayout->setContentsMargins(11, 0, 11, 0); + + resetAndHandSizeContainerWidget = new QWidget(this); resetAndHandSizeContainerWidget->setLayout(resetAndHandSizeLayout); resetButton = new QPushButton(this); @@ -39,12 +42,12 @@ VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *pare flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); layout->addWidget(flowWidget); - drawProbabilityWidget = new DrawProbabilityWidget(this, statsAnalyzer); - layout->addWidget(drawProbabilityWidget); - cardSizeWidget = new CardSizeWidget(this, flowWidget); layout->addWidget(cardSizeWidget); + drawProbabilityWidget = new DrawProbabilityWidget(this, statsAnalyzer); + layout->addWidget(drawProbabilityWidget); + for (const ExactCard &card : getRandomCards(handSizeSpinBox->value())) { auto displayWidget = new CardInfoPictureWidget(this); displayWidget->setCard(card); From 84483c56d7ca8b5147a6362354e1aa329b356828 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 16 Jan 2026 07:12:46 -0800 Subject: [PATCH 149/325] [TabDeckEditor] Generalize visibility filter and extract it to a separate file (#6526) * create class * use new class in old code --- cockatrice/CMakeLists.txt | 2 ++ .../widgets/tabs/abstract_tab_deck_editor.cpp | 24 +++---------- .../widgets/tabs/abstract_tab_deck_editor.h | 15 -------- .../utility/visibility_change_listener.cpp | 26 ++++++++++++++ .../utility/visibility_change_listener.h | 35 +++++++++++++++++++ 5 files changed, 67 insertions(+), 35 deletions(-) create mode 100644 cockatrice/src/interface/widgets/utility/visibility_change_listener.cpp create mode 100644 cockatrice/src/interface/widgets/utility/visibility_change_listener.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index c6d962ffb..54e61ed77 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -227,6 +227,8 @@ set(cockatrice_SOURCES src/interface/widgets/utility/custom_line_edit.cpp src/interface/widgets/utility/get_text_with_max.cpp src/interface/widgets/utility/sequence_edit.cpp + src/interface/widgets/utility/visibility_change_listener.cpp + src/interface/widgets/utility/visibility_change_listener.h src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp 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 5e6db71f3..c20eb650d 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -18,6 +18,7 @@ #include "../interface/widgets/dialogs/dlg_load_deck.h" #include "../interface/widgets/dialogs/dlg_load_deck_from_clipboard.h" #include "../interface/widgets/dialogs/dlg_load_deck_from_website.h" +#include "../utility/visibility_change_listener.h" #include "tab_supervisor.h" #include @@ -100,7 +101,9 @@ void AbstractTabDeckEditor::registerDockWidget(QMenu *_viewMenu, QDockWidget *wi [aFloating](bool topLevel) { aFloating->setChecked(topLevel); }); // sync aVisible with dockWidget's visible state - widget->installEventFilter(new DockWidgetVisibilityFilter(widget, aVisible)); + auto filter = new VisibilityChangeListener(widget); + connect(filter, &VisibilityChangeListener::visibilityChanged, aVisible, + [aVisible](bool visible) { aVisible->setChecked(visible); }); dockToActions.insert(widget, {menu, aVisible, aFloating}); } @@ -608,22 +611,3 @@ bool AbstractTabDeckEditor::closeRequest() return false; return close(); } - -DockWidgetVisibilityFilter::DockWidgetVisibilityFilter(QDockWidget *dockWidget, QAction *aVisible) - : QObject(dockWidget), dockWidget(dockWidget), aVisible(aVisible) -{ -} - -bool DockWidgetVisibilityFilter::eventFilter(QObject *o, QEvent *e) -{ - if (o == dockWidget && !e->spontaneous()) { - if (e->type() == QEvent::Show) { - aVisible->setChecked(true); - } - - if (e->type() == QEvent::Hide) { - aVisible->setChecked(false); - } - } - return false; -} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h index eb0b3755c..9c62a5c93 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h @@ -307,19 +307,4 @@ protected: QMap dockToActions; }; -/** - * This filter syncs the dock widget's visibility with the viewMenu visibility action's check state. - */ -class DockWidgetVisibilityFilter : public QObject -{ - Q_OBJECT - - QDockWidget *dockWidget; - QAction *aVisible; - -public: - explicit DockWidgetVisibilityFilter(QDockWidget *dockWidget, QAction *aVisible); - bool eventFilter(QObject *o, QEvent *e) override; -}; - #endif // TAB_GENERIC_DECK_EDITOR_H diff --git a/cockatrice/src/interface/widgets/utility/visibility_change_listener.cpp b/cockatrice/src/interface/widgets/utility/visibility_change_listener.cpp new file mode 100644 index 000000000..38696f775 --- /dev/null +++ b/cockatrice/src/interface/widgets/utility/visibility_change_listener.cpp @@ -0,0 +1,26 @@ +#include "visibility_change_listener.h" + +#include +#include + +VisibilityChangeListener::VisibilityChangeListener(QWidget *targetWidget) + : QObject(targetWidget), targetWidget(targetWidget) +{ + if (targetWidget) { + targetWidget->installEventFilter(this); + } +} + +bool VisibilityChangeListener::eventFilter(QObject *o, QEvent *e) +{ + if (o == targetWidget && !e->spontaneous()) { + if (e->type() == QEvent::Show) { + emit visibilityChanged(true); + } + + if (e->type() == QEvent::Hide) { + emit visibilityChanged(false); + } + } + return false; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/utility/visibility_change_listener.h b/cockatrice/src/interface/widgets/utility/visibility_change_listener.h new file mode 100644 index 000000000..10928caf6 --- /dev/null +++ b/cockatrice/src/interface/widgets/utility/visibility_change_listener.h @@ -0,0 +1,35 @@ +#ifndef COCKATRICE_VISIBILITY_LISTENER_H +#define COCKATRICE_VISIBILITY_LISTENER_H + +#include + +/** + * @brief This filter listens to the visibility changes of a target widget, emitting signals whenever the visibility of + * that widget changes. + */ +class VisibilityChangeListener : public QObject +{ + Q_OBJECT + + QWidget *targetWidget; + +public: + /** + * Creates a new instance of this class, watching the targetWidget. + * This class automatically installs itself as an eventFilter to the targetWidget. + * + * @param targetWidget The widget to watch. Sets that widget as this object's parent. + */ + explicit VisibilityChangeListener(QWidget *targetWidget); + + bool eventFilter(QObject *o, QEvent *e) override; + +signals: + /** + * Emitted whenever the target widget's visibility changes + * @param visible The widget's new visibility + */ + void visibilityChanged(bool visible); +}; + +#endif // COCKATRICE_VISIBILITY_LISTENER_H From c7c7bf550af97c0b41157952b46def2988ad3d6e Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 16 Jan 2026 09:45:10 -0800 Subject: [PATCH 150/325] [TabGame] Don't create replay dock if not replay tab (#6527) * [TabGame] Don't create replay dock if not replay tab * use replayDock to determine if replay tab * null check replayManager in dtor --- .../src/interface/widgets/tabs/tab_game.cpp | 27 ++++++++++--------- .../src/interface/widgets/tabs/tab_game.h | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index 5b6c8d52a..977020923 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -97,12 +97,11 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, createMessageDock(); createPlayAreaWidget(); createDeckViewContainerWidget(); - createReplayDock(nullptr); + replayDock = nullptr; addDockWidget(Qt::RightDockWidgetArea, cardInfoDock); addDockWidget(Qt::RightDockWidgetArea, playerListDock); addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock); - replayDock->setHidden(true); mainWidget = new QStackedWidget(this); mainWidget->addWidget(deckViewContainerWidget); @@ -256,13 +255,15 @@ void TabGame::emitUserEvent() TabGame::~TabGame() { - delete replayManager->replay; + if (replayManager) { + delete replayManager->replay; + } } void TabGame::updatePlayerListDockTitle() { - QString tabText = " | " + (replayManager->replay ? tr("Replay") : tr("Game")) + " #" + - QString::number(game->getGameMetaInfo()->gameId()); + QString type = replayDock ? tr("Replay") : tr("Game"); + QString tabText = " | " + type + " #" + QString::number(game->getGameMetaInfo()->gameId()); QString userCountInfo = QString(" %1/%2").arg(game->getPlayerManager()->getPlayerCount()).arg(game->getGameMetaInfo()->maxPlayers()); playerListDock->setWindowTitle(tr("Player List") + userCountInfo + @@ -271,8 +272,8 @@ void TabGame::updatePlayerListDockTitle() void TabGame::retranslateUi() { - QString tabText = " | " + (replayManager->replay ? tr("Replay") : tr("Game")) + " #" + - QString::number(game->getGameMetaInfo()->gameId()); + QString type = replayDock ? tr("Replay") : tr("Game"); + QString tabText = " | " + type + " #" + QString::number(game->getGameMetaInfo()->gameId()); updatePlayerListDockTitle(); cardInfoDock->setWindowTitle(tr("Card Info") + (cardInfoDock->isWindow() ? tabText : QString())); @@ -318,7 +319,7 @@ void TabGame::retranslateUi() } } if (aLeaveGame) { - if (replayManager->replay) { + if (replayDock) { aLeaveGame->setText(tr("C&lose replay")); } else { aLeaveGame->setText(tr("&Leave game")); @@ -517,7 +518,7 @@ bool TabGame::leaveGame() return false; } - if (!replayManager->replay) + if (!replayDock) emit gameLeft(); } return true; @@ -904,7 +905,7 @@ QString TabGame::getTabText() const QString gameId(QString::number(game->getGameMetaInfo()->gameId())); QString tabText; - if (replayManager->replay) + if (replayDock) tabText.append(tr("Replay") + " "); if (!gameTypeInfo.isEmpty()) tabText.append(gameTypeInfo + " "); @@ -1085,7 +1086,7 @@ void TabGame::createViewMenuItems() void TabGame::loadLayout() { LayoutsSettings &layouts = SettingsCache::instance().layouts(); - if (replayManager->replay) { + if (replayDock) { restoreGeometry(layouts.getReplayPlayAreaGeometry()); restoreState(layouts.getReplayPlayAreaLayoutState()); @@ -1121,7 +1122,7 @@ void TabGame::loadLayout() aMessageLayoutDockFloating->setChecked(messageLayoutDock->isFloating()); aPlayerListDockFloating->setChecked(playerListDock->isFloating()); - if (replayManager->replay) { + if (replayDock) { aReplayDockVisible->setChecked(replayDock->isVisible()); aReplayDockFloating->setEnabled(aReplayDockVisible->isChecked()); aReplayDockFloating->setChecked(replayDock->isFloating()); @@ -1380,7 +1381,7 @@ void TabGame::createMessageDock(bool bReplay) void TabGame::hideEvent(QHideEvent *event) { LayoutsSettings &layouts = SettingsCache::instance().layouts(); - if (replayManager->replay) { + if (replayDock) { layouts.setReplayPlayAreaState(saveState()); layouts.setReplayPlayAreaGeometry(saveGeometry()); layouts.setReplayCardInfoSize(cardInfoDock->size()); diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.h b/cockatrice/src/interface/widgets/tabs/tab_game.h index 27374ef2a..23640b578 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.h +++ b/cockatrice/src/interface/widgets/tabs/tab_game.h @@ -58,7 +58,7 @@ class TabGame : public Tab private: AbstractGame *game; const UserListProxy *userListProxy; - ReplayManager *replayManager; + ReplayManager *replayManager = nullptr; QStringList gameTypes; QCompleter *completer; QStringList autocompleteUserList; From d579c82cb9f36905da36f47234ce0387e12a0099 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:20:36 -0800 Subject: [PATCH 151/325] [DeckLoader] Make save/load methods static (#6476) * const * [DeckLoader] make methods static * use static methods * add docs * add docs --- .../src/game/deckview/deck_view_container.cpp | 7 +- .../src/interface/deck_loader/deck_loader.cpp | 208 +++++++++--------- .../src/interface/deck_loader/deck_loader.h | 69 +++++- .../interface/widgets/general/home_widget.cpp | 7 +- .../widgets/tabs/abstract_tab_deck_editor.cpp | 23 +- .../widgets/tabs/tab_deck_storage.cpp | 23 +- .../tab_deck_storage_visual.cpp | 7 +- .../deck_preview_deck_tags_display_widget.cpp | 8 +- .../deck_preview/deck_preview_widget.cpp | 6 +- .../libcockatrice/deck_list/deck_list.cpp | 8 +- .../libcockatrice/deck_list/deck_list.h | 9 +- 11 files changed, 209 insertions(+), 166 deletions(-) diff --git a/cockatrice/src/game/deckview/deck_view_container.cpp b/cockatrice/src/game/deckview/deck_view_container.cpp index c7876a12f..44b2be6d1 100644 --- a/cockatrice/src/game/deckview/deck_view_container.cpp +++ b/cockatrice/src/game/deckview/deck_view_container.cpp @@ -260,16 +260,15 @@ void DeckViewContainer::loadLocalDeck() void DeckViewContainer::loadDeckFromFile(const QString &filePath) { DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(filePath); - DeckLoader deck(this); - bool success = deck.loadFromFile(filePath, fmt, true); + std::optional deckOpt = DeckLoader::loadFromFile(filePath, fmt, true); - if (!success) { + if (!deckOpt) { QMessageBox::critical(this, tr("Error"), tr("The selected file could not be loaded.")); return; } - loadDeckFromDeckList(deck.getDeck().deckList); + loadDeckFromDeckList(deckOpt.value().deckList); } void DeckViewContainer::loadDeckFromDeckList(const DeckList &deck) diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index 45c3ec1ba..305cd34d9 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -29,24 +29,26 @@ DeckLoader::DeckLoader(QObject *parent) : QObject(parent) { } -bool DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest) +std::optional +DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - return false; + qCWarning(DeckLoaderLog) << "File does not exist:" << fileName; + return std::nullopt; } bool result = false; - DeckList deckList = DeckList(); + DeckList deckList; switch (fmt) { case DeckFileFormat::PlainText: result = deckList.loadFromFile_Plain(&file); break; case DeckFileFormat::Cockatrice: { result = deckList.loadFromFile_Native(&file); - qCInfo(DeckLoaderLog) << "Loaded from" << fileName << "-" << result; if (!result) { - qCInfo(DeckLoaderLog) << "Retrying as plain format"; + qCInfo(DeckLoaderLog) << "Failed to load " << fileName + << "as cockatrice format; retrying as plain format"; file.seek(0); result = deckList.loadFromFile_Plain(&file); fmt = DeckFileFormat::PlainText; @@ -58,120 +60,112 @@ bool DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fm break; } - if (result) { - loadedDeck.deckList = deckList; - loadedDeck.lastLoadInfo = { - .fileName = fileName, - .fileFormat = fmt, - }; - if (userRequest) { - updateLastLoadedTimestamp(fileName, fmt); - } - - emit deckLoaded(); + if (!result) { + qCWarning(DeckLoaderLog) << "Failed to load " << fileName << "as" << fmt; + return std::nullopt; } - qCInfo(DeckLoaderLog) << "Deck was loaded -" << result; - return result; -} + LoadedDeck::LoadInfo lastLoadInfo = { + .fileName = fileName, + .fileFormat = fmt, + }; + LoadedDeck loadedDeck = {deckList, lastLoadInfo}; -bool DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest) -{ - auto *watcher = new QFutureWatcher(this); - - connect(watcher, &QFutureWatcher::finished, this, [this, watcher, fileName, fmt, userRequest]() { - const bool result = watcher->result(); - watcher->deleteLater(); - - if (result) { - loadedDeck.lastLoadInfo = { - .fileName = fileName, - .fileFormat = fmt, - }; - if (userRequest) { - updateLastLoadedTimestamp(fileName, fmt); - } - emit deckLoaded(); - } - - emit loadFinished(result); - }); - - QFuture future = QtConcurrent::run([=, this]() { - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - return false; - } - - switch (fmt) { - case DeckFileFormat::PlainText: - return loadedDeck.deckList.loadFromFile_Plain(&file); - case DeckFileFormat::Cockatrice: { - bool result = false; - result = loadedDeck.deckList.loadFromFile_Native(&file); - if (!result) { - file.seek(0); - return loadedDeck.deckList.loadFromFile_Plain(&file); - } - return result; - } - default: - return false; - break; - } - }); - - watcher->setFuture(future); - return true; // Return immediately to indicate the async task was started -} - -bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId) -{ - bool result = loadedDeck.deckList.loadFromString_Native(nativeString); - if (result) { - loadedDeck.lastLoadInfo = { - .remoteDeckId = remoteDeckId, - }; - - emit deckLoaded(); + if (userRequest) { + updateLastLoadedTimestamp(loadedDeck); } - return result; + + qCDebug(DeckLoaderLog) << "Loaded deck" << fileName << "with userRequest:" << userRequest; + + return loadedDeck; } -bool DeckLoader::saveToFile(const QString &fileName, DeckFileFormat::Format fmt) +void DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest) +{ + QFuture future = QtConcurrent::run([=, this] { + std::optional deckOpt = loadFromFile(fileName, fmt, userRequest); + if (deckOpt) { + loadedDeck = deckOpt.value(); + } + emit loadFinished(deckOpt.has_value()); + }); +} + +std::optional DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId) +{ + DeckList deckList; + bool success = deckList.loadFromString_Native(nativeString); + + if (!success) { + qCWarning(DeckLoaderLog) << "Failed to load remote deck with id" << remoteDeckId << ":" << nativeString; + return std::nullopt; + } + + LoadedDeck::LoadInfo lastLoadInfo = {.remoteDeckId = remoteDeckId}; + LoadedDeck loadedDeck = {deckList, lastLoadInfo}; + + qCDebug(DeckLoaderLog) << "Loaded remote deck with id" << remoteDeckId; + + return loadedDeck; +} + +std::optional +DeckLoader::saveToFile(const DeckList &deck, const QString &fileName, DeckFileFormat::Format fmt) { QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { - return false; + qCWarning(DeckLoaderLog) << "Could not create or open file:" << fileName; + return std::nullopt; } - bool result = false; + bool success = false; switch (fmt) { case DeckFileFormat::PlainText: - result = loadedDeck.deckList.saveToFile_Plain(&file); + success = deck.saveToFile_Plain(&file); break; case DeckFileFormat::Cockatrice: - result = loadedDeck.deckList.saveToFile_Native(&file); - qCInfo(DeckLoaderLog) << "Saving to " << fileName << "-" << result; + success = deck.saveToFile_Native(&file); break; } - if (result) { - loadedDeck.lastLoadInfo = { - .fileName = fileName, - .fileFormat = fmt, - }; - qCInfo(DeckLoaderLog) << "Deck was saved -" << result; - } - file.flush(); file.close(); - return result; + qCInfo(DeckLoaderLog) << "Saved deck to " << fileName << "with format" << fmt << "-" << success; + + if (!success) { + return std::nullopt; + } + + LoadedDeck::LoadInfo lastLoadInfo = {fileName, fmt}; + return lastLoadInfo; } -bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, DeckFileFormat::Format fmt) +bool DeckLoader::saveToFile(const LoadedDeck &deck) { + auto opt = saveToFile(deck.deckList, deck.lastLoadInfo.fileName, deck.lastLoadInfo.fileFormat); + return opt.has_value(); +} + +bool DeckLoader::saveToNewFile(LoadedDeck &deck, const QString &fileName, DeckFileFormat::Format fmt) +{ + std::optional infoOpt = saveToFile(deck.deckList, fileName, fmt); + + if (infoOpt) { + deck.lastLoadInfo = infoOpt.value(); + } + + return infoOpt.has_value(); +} + +/** + * @brief Updates the lastLoadedTimestamp field in the file corresponding to the deck, without changing the + * FileModificationTime of the file. + */ +bool DeckLoader::updateLastLoadedTimestamp(LoadedDeck &deck) +{ + QString fileName = deck.lastLoadInfo.fileName; + QFileInfo fileInfo(fileName); if (!fileInfo.exists()) { qCWarning(DeckLoaderLog) << "File does not exist:" << fileName; @@ -190,24 +184,19 @@ bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, DeckFileForm bool result = false; // Perform file modifications - switch (fmt) { + switch (deck.lastLoadInfo.fileFormat) { case DeckFileFormat::PlainText: - result = loadedDeck.deckList.saveToFile_Plain(&file); + result = deck.deckList.saveToFile_Plain(&file); break; case DeckFileFormat::Cockatrice: - loadedDeck.deckList.setLastLoadedTimestamp(QDateTime::currentDateTime().toString()); - result = loadedDeck.deckList.saveToFile_Native(&file); + deck.deckList.setLastLoadedTimestamp(QDateTime::currentDateTime().toString()); + result = deck.deckList.saveToFile_Native(&file); break; } file.close(); // Close the file to ensure changes are flushed if (result) { - loadedDeck.lastLoadInfo = { - .fileName = fileName, - .fileFormat = fmt, - }; - // Re-open the file and set the original timestamp if (!file.open(QIODevice::ReadWrite)) { qCWarning(DeckLoaderLog) << "Failed to re-open file to set timestamp:" << fileName; @@ -434,8 +423,13 @@ void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out, } } -bool DeckLoader::convertToCockatriceFormat(const QString &fileName) +bool DeckLoader::convertToCockatriceFormat(LoadedDeck &deck) { + QString fileName = deck.lastLoadInfo.fileName; + if (fileName.isEmpty()) { + return false; + } + // Change the file extension to .cod QFileInfo fileInfo(fileName); QString newFileName = QDir::toNativeSeparators(fileInfo.path() + "/" + fileInfo.completeBaseName() + ".cod"); @@ -453,7 +447,7 @@ bool DeckLoader::convertToCockatriceFormat(const QString &fileName) switch (DeckFileFormat::getFormatFromName(fileName)) { case DeckFileFormat::PlainText: // Save in Cockatrice's native format - result = loadedDeck.deckList.saveToFile_Native(&file); + result = deck.deckList.saveToFile_Native(&file); break; case DeckFileFormat::Cockatrice: qCInfo(DeckLoaderLog) << "File is already in Cockatrice format. No conversion needed."; @@ -474,7 +468,7 @@ bool DeckLoader::convertToCockatriceFormat(const QString &fileName) } else { qCInfo(DeckLoaderLog) << "Original file deleted successfully:" << fileName; } - loadedDeck.lastLoadInfo = { + deck.lastLoadInfo = { .fileName = newFileName, .fileFormat = DeckFileFormat::Cockatrice, }; diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h index ec5636995..1780e2706 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.h +++ b/cockatrice/src/interface/deck_loader/deck_loader.h @@ -20,7 +20,6 @@ class DeckLoader : public QObject { Q_OBJECT signals: - void deckLoaded(); void loadFinished(bool success); public: @@ -53,11 +52,60 @@ public: return loadedDeck.lastLoadInfo.isEmpty(); } - bool loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest = false); - bool loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest); - bool loadFromRemote(const QString &nativeString, int remoteDeckId); - bool saveToFile(const QString &fileName, DeckFileFormat::Format fmt); - bool updateLastLoadedTimestamp(const QString &fileName, DeckFileFormat::Format fmt); + /** + * @brief Asynchronously loads a deck from a local file into this DeckLoader. + * The `loadFinished` signal will be emitted when the load finishes. + * Once the loading finishes, the deck can be accessed with `getDeck` + * @param fileName The file to load + * @param fmt The format of the file to load + * @param userRequest Whether the load was manually requested by the user, instead of being done in the background. + */ + void loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest); + + /** + * @brief Loads a deck from a local file. + * @param fileName The file to load + * @param fmt The format of the file to load + * @param userRequest Whether the load was manually requested by the user, instead of being done in the background. + * @return An optional containing the LoadedDeck, or empty if the load failed. + */ + static std::optional + loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest = false); + + /** + * @brief Loads a deck from the response of a remote deck request + * @param nativeString The deck string, in cod format + * @param remoteDeckId The remote deck id + * @return An optional containing the LoadedDeck, or empty if the load failed. + */ + static std::optional loadFromRemote(const QString &nativeString, int remoteDeckId); + + /** + * @brief Saves a DeckList to a local file. + * @param deck The DeckList + * @param fileName The file to write to + * @param fmt The deck file format to use + * @return An optional containing the LoadInfo for the new file, or empty if the save failed. + */ + static std::optional + saveToFile(const DeckList &deck, const QString &fileName, DeckFileFormat::Format fmt); + + /** + * @brief Saves a LoadedDeck to a local file. + * Uses the lastLoadInfo in the LoadedDeck to determine where to save to. + * @param deck The LoadedDeck to save. Should have valid lastLoadInfo. + * @return Whether the save succeeded. + */ + static bool saveToFile(const LoadedDeck &deck); + + /** + * @brief Saves a LoadedDeck to a new local file. + * @param deck The LoadedDeck to save. Will update the lastLoadInfo. + * @param fileName The file to write to + * @param fmt The deck file format to use + * @return Whether the save succeeded. + */ + static bool saveToNewFile(LoadedDeck &deck, const QString &fileName, DeckFileFormat::Format fmt); static QString exportDeckToDecklist(const DeckList &deckList, DecklistWebsite website); @@ -74,7 +122,13 @@ public: */ static void printDeckList(QPrinter *printer, const DeckList &deckList); - bool convertToCockatriceFormat(const QString &fileName); + /** + * 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. + */ + static bool convertToCockatriceFormat(LoadedDeck &deck); LoadedDeck &getDeck() { @@ -90,6 +144,7 @@ public: } private: + static bool updateLastLoadedTimestamp(LoadedDeck &deck); static void printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node); static void saveToStream_DeckHeader(QTextStream &out, const DeckList &deckList); diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index 803a93108..269326257 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -78,10 +78,9 @@ void HomeWidget::initializeBackgroundFromSource() void HomeWidget::loadBackgroundSourceDeck() { - DeckLoader deckLoader = DeckLoader(this); - deckLoader.loadFromFile(SettingsCache::instance().getDeckPath() + "background.cod", DeckFileFormat::Cockatrice, - false); - backgroundSourceDeck = deckLoader.getDeck().deckList; + std::optional deckOpt = DeckLoader::loadFromFile( + SettingsCache::instance().getDeckPath() + "background.cod", DeckFileFormat::Cockatrice, false); + backgroundSourceDeck = deckOpt.has_value() ? deckOpt.value().deckList : DeckList(); } void HomeWidget::updateRandomCard() 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 c20eb650d..0043c0496 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -313,13 +313,13 @@ void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLo { DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName); - auto l = DeckLoader(this); - if (l.loadFromFile(fileName, fmt, true)) { + std::optional deckOpt = DeckLoader::loadFromFile(fileName, fmt, true); + if (deckOpt) { if (deckOpenLocation == NEW_TAB) { - emit openDeckEditor(l.getDeck()); + emit openDeckEditor(deckOpt.value()); } else { deckMenu->setSaveStatus(false); - openDeck(l.getDeck()); + openDeck(deckOpt.value()); } } else { QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(fileName)); @@ -355,9 +355,7 @@ bool AbstractTabDeckEditor::actSaveDeck() if (loadedDeck.lastLoadInfo.fileName.isEmpty()) return actSaveDeckAs(); - auto deckLoader = DeckLoader(this); - deckLoader.setDeck(loadedDeck); - if (deckLoader.saveToFile(loadedDeck.lastLoadInfo.fileName, loadedDeck.lastLoadInfo.fileFormat)) { + if (DeckLoader::saveToFile(loadedDeck)) { deckStateManager->setModified(false); return true; } @@ -374,14 +372,14 @@ bool AbstractTabDeckEditor::actSaveDeck() */ bool AbstractTabDeckEditor::actSaveDeckAs() { - LoadedDeck loadedDeck = deckStateManager->toLoadedDeck(); + DeckList deckList = deckStateManager->getDeckList(); QFileDialog dialog(this, tr("Save deck")); dialog.setDirectory(SettingsCache::instance().getDeckPath()); dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setDefaultSuffix("cod"); dialog.setNameFilters(DeckLoader::FILE_NAME_FILTERS); - dialog.selectFile(loadedDeck.deckList.getName().trimmed()); + dialog.selectFile(deckList.getName().trimmed()); if (!dialog.exec()) return false; @@ -389,16 +387,15 @@ bool AbstractTabDeckEditor::actSaveDeckAs() QString fileName = dialog.selectedFiles().at(0); DeckFileFormat::Format fmt = DeckFileFormat::getFormatFromName(fileName); - DeckLoader deckLoader = DeckLoader(this); - deckLoader.setDeck(loadedDeck); - if (!deckLoader.saveToFile(fileName, fmt)) { + std::optional infoOpt = DeckLoader::saveToFile(deckList, fileName, fmt); + if (!infoOpt) { QMessageBox::critical( this, tr("Error"), tr("The deck could not be saved.\nPlease check that the directory is writable and try again.")); return false; } - deckStateManager->setLastLoadInfo({.fileName = fileName, .fileFormat = fmt}); + deckStateManager->setLastLoadInfo(infoOpt.value()); deckStateManager->setModified(false); SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(fileName); diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp index 73b3fc0ac..26e3f2ecf 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp @@ -241,11 +241,11 @@ void TabDeckStorage::actOpenLocalDeck() continue; QString filePath = localDirModel->filePath(curLeft); - auto deckLoader = new DeckLoader(this); - if (!deckLoader->loadFromFile(filePath, DeckFileFormat::Cockatrice, true)) + std::optional deckOpt = DeckLoader::loadFromFile(filePath, DeckFileFormat::Cockatrice, true); + if (!deckOpt) continue; - emit openDeckEditor(deckLoader->getDeck()); + emit openDeckEditor(deckOpt.value()); } } @@ -307,13 +307,13 @@ void TabDeckStorage::uploadDeck(const QString &filePath, const QString &targetPa QFile deckFile(filePath); QFileInfo deckFileInfo(deckFile); - DeckLoader deckLoader(this); - if (!deckLoader.loadFromFile(filePath, DeckFileFormat::Cockatrice)) { + std::optional deckOpt = DeckLoader::loadFromFile(filePath, DeckFileFormat::Cockatrice, true); + if (!deckOpt) { QMessageBox::critical(this, tr("Error"), tr("Invalid deck file")); return; } - DeckList deck = deckLoader.getDeck().deckList; + DeckList deck = deckOpt.value().deckList; if (deck.getName().isEmpty()) { bool ok; @@ -434,11 +434,11 @@ void TabDeckStorage::openRemoteDeckFinished(const Response &r, const CommandCont const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext); const Command_DeckDownload &cmd = commandContainer.session_command(0).GetExtension(Command_DeckDownload::ext); - DeckLoader loader(this); - if (!loader.loadFromRemote(QString::fromStdString(resp.deck()), cmd.deck_id())) + std::optional deckOpt = DeckLoader::loadFromRemote(QString::fromStdString(resp.deck()), cmd.deck_id()); + if (!deckOpt) return; - emit openDeckEditor(loader.getDeck()); + emit openDeckEditor(deckOpt.value()); } void TabDeckStorage::actDownload() @@ -496,10 +496,7 @@ void TabDeckStorage::downloadFinished(const Response &r, DeckList deckList = DeckList(QString::fromStdString(resp.deck())); - DeckLoader deckLoader(this); - deckLoader.setDeck({deckList, {}}); - - deckLoader.saveToFile(filePath, DeckFileFormat::Cockatrice); + DeckLoader::saveToFile(deckList, filePath, DeckFileFormat::Cockatrice); } void TabDeckStorage::actNewFolder() diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp index 9570702ed..0cbcb641a 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp @@ -24,11 +24,12 @@ TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor) void TabDeckStorageVisual::actOpenLocalDeck(const QString &filePath) { - auto deckLoader = DeckLoader(this); - if (!deckLoader.loadFromFile(filePath, DeckFileFormat::getFormatFromName(filePath), true)) { + std::optional deckOpt = + DeckLoader::loadFromFile(filePath, DeckFileFormat::getFormatFromName(filePath), true); + if (!deckOpt) { QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(filePath)); return; } - emit openDeckEditor(deckLoader.getDeck()); + emit openDeckEditor(deckOpt.value()); } diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp index d57cda1c6..8cd1004de 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp @@ -77,10 +77,10 @@ static QStringList findAllKnownTags() QStringList allFiles = getAllFiles(SettingsCache::instance().getDeckPath()); QStringList knownTags; - auto loader = DeckLoader(nullptr); for (const QString &file : allFiles) { - loader.loadFromFile(file, DeckFileFormat::getFormatFromName(file), false); - QStringList tags = loader.getDeck().deckList.getTags(); + std::optional deckOpt = + DeckLoader::loadFromFile(file, DeckFileFormat::getFormatFromName(file), false); + QStringList tags = deckOpt.has_value() ? deckOpt->deckList.getTags() : QStringList(); knownTags.append(tags); knownTags.removeDuplicates(); } @@ -124,7 +124,7 @@ static bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath) static void convertFileToCockatriceFormat(DeckPreviewWidget *deckPreviewWidget) { - deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath); + DeckLoader::convertToCockatriceFormat(deckPreviewWidget->deckLoader->getDeck()); deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getDeck().lastLoadInfo.fileName; deckPreviewWidget->refreshBannerCardText(); } 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 f98e37e16..5c55db456 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 @@ -288,7 +288,7 @@ void DeckPreviewWidget::setBannerCard(int /* changedIndex */) auto [name, id] = bannerCardComboBox->currentData().value>(); CardRef cardRef = {name, id}; deckLoader->getDeck().deckList.setBannerCard(cardRef); - deckLoader->saveToFile(filePath, DeckFileFormat::getFormatFromName(filePath)); + DeckLoader::saveToFile(deckLoader->getDeck()); bannerCardDisplayWidget->setCard(CardDatabaseManager::query()->getCard(cardRef)); } @@ -311,7 +311,7 @@ void DeckPreviewWidget::imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewC void DeckPreviewWidget::setTags(const QStringList &tags) { deckLoader->getDeck().deckList.setTags(tags); - deckLoader->saveToFile(filePath, DeckFileFormat::Cockatrice); + DeckLoader::saveToFile(deckLoader->getDeck()); } QMenu *DeckPreviewWidget::createRightClickMenu() @@ -386,7 +386,7 @@ void DeckPreviewWidget::actRenameDeck() // write change deckLoader->getDeck().deckList.setName(newName); - deckLoader->saveToFile(filePath, DeckFileFormat::getFormatFromName(filePath)); + DeckLoader::saveToFile(deckLoader->getDeck()); // update VDS refreshBannerCardText(); diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index a294601fb..71a04cce3 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -182,7 +182,7 @@ bool DeckList::loadFromFile_Native(QIODevice *device) return loadFromXml(&xml); } -bool DeckList::saveToFile_Native(QIODevice *device) +bool DeckList::saveToFile_Native(QIODevice *device) const { QXmlStreamWriter xml(device); xml.setAutoFormatting(true); @@ -393,7 +393,7 @@ bool DeckList::loadFromFile_Plain(QIODevice *device) return loadFromStream_Plain(in, false); } -bool DeckList::saveToStream_Plain(QTextStream &stream, bool prefixSideboardCards, bool slashTappedOutSplitCards) +bool DeckList::saveToStream_Plain(QTextStream &stream, bool prefixSideboardCards, bool slashTappedOutSplitCards) const { auto writeToStream = [&stream, prefixSideboardCards, slashTappedOutSplitCards](const auto node, const auto card) { if (prefixSideboardCards && node->getName() == DECK_ZONE_SIDE) { @@ -410,13 +410,13 @@ bool DeckList::saveToStream_Plain(QTextStream &stream, bool prefixSideboardCards return true; } -bool DeckList::saveToFile_Plain(QIODevice *device, bool prefixSideboardCards, bool slashTappedOutSplitCards) +bool DeckList::saveToFile_Plain(QIODevice *device, bool prefixSideboardCards, bool slashTappedOutSplitCards) const { QTextStream out(device); return saveToStream_Plain(out, prefixSideboardCards, slashTappedOutSplitCards); } -QString DeckList::writeToString_Plain(bool prefixSideboardCards, bool slashTappedOutSplitCards) +QString DeckList::writeToString_Plain(bool prefixSideboardCards, bool slashTappedOutSplitCards) const { QString result; QTextStream out(&result); diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index d0ca55342..808733b09 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -201,16 +201,17 @@ public: bool loadFromString_Native(const QString &nativeString); QString writeToString_Native() const; bool loadFromFile_Native(QIODevice *device); - bool saveToFile_Native(QIODevice *device); + bool saveToFile_Native(QIODevice *device) const; ///@} /// @name Serialization (Plain text) ///@{ bool loadFromStream_Plain(QTextStream &stream, bool preserveMetadata); bool loadFromFile_Plain(QIODevice *device); - bool saveToStream_Plain(QTextStream &stream, bool prefixSideboardCards, bool slashTappedOutSplitCards); - bool saveToFile_Plain(QIODevice *device, bool prefixSideboardCards = true, bool slashTappedOutSplitCards = false); - QString writeToString_Plain(bool prefixSideboardCards = true, bool slashTappedOutSplitCards = false); + bool saveToStream_Plain(QTextStream &stream, bool prefixSideboardCards, bool slashTappedOutSplitCards) const; + bool + saveToFile_Plain(QIODevice *device, bool prefixSideboardCards = true, bool slashTappedOutSplitCards = false) const; + QString writeToString_Plain(bool prefixSideboardCards = true, bool slashTappedOutSplitCards = false) const; ///@} /// @name Deck manipulation From 9c07c7a96377c67c40574da72586b0be3e8c0a44 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 16 Jan 2026 17:22:48 -0800 Subject: [PATCH 152/325] [TabGame] Automatically sync view menu actions (#6529) --- .../src/interface/widgets/tabs/tab_game.cpp | 230 ++++-------------- .../src/interface/widgets/tabs/tab_game.h | 21 +- 2 files changed, 58 insertions(+), 193 deletions(-) diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index 977020923..2df6e5ac5 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -20,6 +20,7 @@ #include "../interface/widgets/utility/line_edit_completer.h" #include "../interface/window_main.h" #include "../main.h" +#include "../utility/visibility_change_listener.h" #include "tab_supervisor.h" #include @@ -337,23 +338,18 @@ void TabGame::retranslateUi() } viewMenu->setTitle(tr("&View")); - cardInfoDockMenu->setTitle(tr("Card Info")); - messageLayoutDockMenu->setTitle(tr("Messages")); - playerListDockMenu->setTitle(tr("Player List")); - aCardInfoDockVisible->setText(tr("Visible")); - aCardInfoDockFloating->setText(tr("Floating")); - - aMessageLayoutDockVisible->setText(tr("Visible")); - aMessageLayoutDockFloating->setText(tr("Floating")); - - aPlayerListDockVisible->setText(tr("Visible")); - aPlayerListDockFloating->setText(tr("Floating")); + dockToActions[cardInfoDock].menu->setTitle(tr("Card Info")); + dockToActions[messageLayoutDock].menu->setTitle(tr("Messages")); + dockToActions[playerListDock].menu->setTitle(tr("Player List")); if (replayDock) { - replayDockMenu->setTitle(tr("Replay Timeline")); - aReplayDockVisible->setText(tr("Visible")); - aReplayDockFloating->setText(tr("Floating")); + dockToActions[replayDock].menu->setTitle(tr("Replay Timeline")); + } + + for (auto &actions : dockToActions.values()) { + actions.aVisible->setText(tr("Visible")); + actions.aFloating->setText(tr("Floating")); } aResetLayout->setText(tr("Reset layout")); @@ -1038,40 +1034,12 @@ void TabGame::createViewMenuItems() { viewMenu = new QMenu(this); - cardInfoDockMenu = viewMenu->addMenu(QString()); - messageLayoutDockMenu = viewMenu->addMenu(QString()); - playerListDockMenu = viewMenu->addMenu(QString()); - - aCardInfoDockVisible = cardInfoDockMenu->addAction(QString()); - aCardInfoDockVisible->setCheckable(true); - connect(aCardInfoDockVisible, &QAction::triggered, this, &TabGame::dockVisibleTriggered); - aCardInfoDockFloating = cardInfoDockMenu->addAction(QString()); - aCardInfoDockFloating->setCheckable(true); - connect(aCardInfoDockFloating, &QAction::triggered, this, &TabGame::dockFloatingTriggered); - - aMessageLayoutDockVisible = messageLayoutDockMenu->addAction(QString()); - aMessageLayoutDockVisible->setCheckable(true); - connect(aMessageLayoutDockVisible, &QAction::triggered, this, &TabGame::dockVisibleTriggered); - aMessageLayoutDockFloating = messageLayoutDockMenu->addAction(QString()); - aMessageLayoutDockFloating->setCheckable(true); - connect(aMessageLayoutDockFloating, &QAction::triggered, this, &TabGame::dockFloatingTriggered); - - aPlayerListDockVisible = playerListDockMenu->addAction(QString()); - aPlayerListDockVisible->setCheckable(true); - connect(aPlayerListDockVisible, &QAction::triggered, this, &TabGame::dockVisibleTriggered); - aPlayerListDockFloating = playerListDockMenu->addAction(QString()); - aPlayerListDockFloating->setCheckable(true); - connect(aPlayerListDockFloating, &QAction::triggered, this, &TabGame::dockFloatingTriggered); + registerDockWidget(viewMenu, cardInfoDock); + registerDockWidget(viewMenu, messageLayoutDock); + registerDockWidget(viewMenu, playerListDock); if (replayDock) { - replayDockMenu = viewMenu->addMenu(QString()); - - aReplayDockVisible = replayDockMenu->addAction(QString()); - aReplayDockVisible->setCheckable(true); - connect(aReplayDockVisible, &QAction::triggered, this, &TabGame::dockVisibleTriggered); - aReplayDockFloating = replayDockMenu->addAction(QString()); - aReplayDockFloating->setCheckable(true); - connect(aReplayDockFloating, &QAction::triggered, this, &TabGame::dockFloatingTriggered); + registerDockWidget(viewMenu, replayDock); } viewMenu->addSeparator(); @@ -1083,6 +1051,36 @@ void TabGame::createViewMenuItems() addTabMenu(viewMenu); } +void TabGame::registerDockWidget(QMenu *_viewMenu, QDockWidget *widget) +{ + QMenu *menu = _viewMenu->addMenu(QString()); + + QAction *aVisible = menu->addAction(QString()); + aVisible->setCheckable(true); + + QAction *aFloating = menu->addAction(QString()); + aFloating->setCheckable(true); + aFloating->setEnabled(false); + + // user interaction + connect(aVisible, &QAction::triggered, widget, [widget](bool checked) { widget->setVisible(checked); }); + connect(aFloating, &QAction::triggered, this, [widget](bool checked) { widget->setFloating(checked); }); + + // sync aFloating's enabled state with aVisible's checked state + connect(aVisible, &QAction::toggled, aFloating, [aFloating](bool checked) { aFloating->setEnabled(checked); }); + + // sync aFloating with dockWidget's floating state + connect(widget, &QDockWidget::topLevelChanged, aFloating, + [aFloating](bool topLevel) { aFloating->setChecked(topLevel); }); + + // sync aVisible with dockWidget's visible state + auto filter = new VisibilityChangeListener(widget); + connect(filter, &VisibilityChangeListener::visibilityChanged, aVisible, + [aVisible](bool visible) { aVisible->setChecked(visible); }); + + dockToActions.insert(widget, {menu, aVisible, aFloating}); +} + void TabGame::loadLayout() { LayoutsSettings &layouts = SettingsCache::instance().layouts(); @@ -1110,24 +1108,6 @@ void TabGame::loadLayout() playerListDock->setMaximumSize(layouts.getGamePlayerListSize()); } - aCardInfoDockVisible->setChecked(cardInfoDock->isVisible()); - aMessageLayoutDockVisible->setChecked(messageLayoutDock->isVisible()); - aPlayerListDockVisible->setChecked(playerListDock->isVisible()); - - aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked()); - aMessageLayoutDockFloating->setEnabled(aMessageLayoutDockVisible->isChecked()); - aPlayerListDockFloating->setEnabled(aPlayerListDockVisible->isChecked()); - - aCardInfoDockFloating->setChecked(cardInfoDock->isFloating()); - aMessageLayoutDockFloating->setChecked(messageLayoutDock->isFloating()); - aPlayerListDockFloating->setChecked(playerListDock->isFloating()); - - if (replayDock) { - aReplayDockVisible->setChecked(replayDock->isVisible()); - aReplayDockFloating->setEnabled(aReplayDockVisible->isChecked()); - aReplayDockFloating->setChecked(replayDock->isFloating()); - } - QTimer::singleShot(100, this, &TabGame::freeDocksSize); } @@ -1158,14 +1138,6 @@ void TabGame::actResetLayout() playerListDock->setFloating(false); messageLayoutDock->setFloating(false); - aCardInfoDockVisible->setChecked(true); - aPlayerListDockVisible->setChecked(true); - aMessageLayoutDockVisible->setChecked(true); - - aCardInfoDockFloating->setChecked(false); - aPlayerListDockFloating->setChecked(false); - aMessageLayoutDockFloating->setChecked(false); - addDockWidget(Qt::RightDockWidgetArea, cardInfoDock); addDockWidget(Qt::RightDockWidgetArea, playerListDock); addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock); @@ -1174,8 +1146,6 @@ void TabGame::actResetLayout() replayDock->setVisible(true); replayDock->setFloating(false); addDockWidget(Qt::BottomDockWidgetArea, replayDock); - aReplayDockVisible->setChecked(true); - aReplayDockFloating->setChecked(false); cardInfoDock->setMinimumSize(250, 360); cardInfoDock->setMaximumSize(250, 360); @@ -1227,9 +1197,6 @@ void TabGame::createReplayDock(GameReplay *replay) QDockWidget::DockWidgetMovable); replayDock->setWidget(replayManager); replayDock->setFloating(false); - - replayDock->installEventFilter(this); - connect(replayDock, &QDockWidget::topLevelChanged, this, &TabGame::dockTopLevelChanged); } void TabGame::createDeckViewContainerWidget(bool bReplay) @@ -1268,9 +1235,6 @@ void TabGame::createCardInfoDock(bool bReplay) QDockWidget::DockWidgetMovable); cardInfoDock->setWidget(cardBoxLayoutWidget); cardInfoDock->setFloating(false); - - cardInfoDock->installEventFilter(this); - connect(cardInfoDock, &QDockWidget::topLevelChanged, this, &TabGame::dockTopLevelChanged); } void TabGame::createPlayerListDock(bool bReplay) @@ -1290,9 +1254,6 @@ void TabGame::createPlayerListDock(bool bReplay) QDockWidget::DockWidgetMovable); playerListDock->setWidget(playerListWidget); playerListDock->setFloating(false); - - playerListDock->installEventFilter(this); - connect(playerListDock, &QDockWidget::topLevelChanged, this, &TabGame::dockTopLevelChanged); } void TabGame::createMessageDock(bool bReplay) @@ -1373,9 +1334,6 @@ void TabGame::createMessageDock(bool bReplay) QDockWidget::DockWidgetMovable); messageLayoutDock->setWidget(messageLogLayoutWidget); messageLayoutDock->setFloating(false); - - messageLayoutDock->installEventFilter(this); - connect(messageLayoutDock, &QDockWidget::topLevelChanged, this, &TabGame::dockTopLevelChanged); } void TabGame::hideEvent(QHideEvent *event) @@ -1398,103 +1356,3 @@ void TabGame::hideEvent(QHideEvent *event) Tab::hideEvent(event); } - -// Method uses to sync docks state with menu items state -bool TabGame::eventFilter(QObject *o, QEvent *e) -{ - if (e->type() == QEvent::Close) { - if (o == cardInfoDock) { - aCardInfoDockVisible->setChecked(false); - aCardInfoDockFloating->setEnabled(false); - } else if (o == messageLayoutDock) { - aMessageLayoutDockVisible->setChecked(false); - aMessageLayoutDockFloating->setEnabled(false); - } else if (o == playerListDock) { - aPlayerListDockVisible->setChecked(false); - aPlayerListDockFloating->setEnabled(false); - } else if (o == replayDock) { - aReplayDockVisible->setChecked(false); - aReplayDockFloating->setEnabled(false); - } - } - - return false; -} - -void TabGame::dockVisibleTriggered() -{ - QObject *o = sender(); - if (o == aCardInfoDockVisible) { - cardInfoDock->setVisible(aCardInfoDockVisible->isChecked()); - aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked()); - return; - } - - if (o == aMessageLayoutDockVisible) { - messageLayoutDock->setVisible(aMessageLayoutDockVisible->isChecked()); - aMessageLayoutDockFloating->setEnabled(aMessageLayoutDockVisible->isChecked()); - return; - } - - if (o == aPlayerListDockVisible) { - playerListDock->setVisible(aPlayerListDockVisible->isChecked()); - aPlayerListDockFloating->setEnabled(aPlayerListDockVisible->isChecked()); - return; - } - - if (o == aReplayDockVisible) { - replayDock->setVisible(aReplayDockVisible->isChecked()); - aReplayDockFloating->setEnabled(aReplayDockVisible->isChecked()); - return; - } -} - -void TabGame::dockFloatingTriggered() -{ - QObject *o = sender(); - if (o == aCardInfoDockFloating) { - cardInfoDock->setFloating(aCardInfoDockFloating->isChecked()); - return; - } - - if (o == aMessageLayoutDockFloating) { - messageLayoutDock->setFloating(aMessageLayoutDockFloating->isChecked()); - return; - } - - if (o == aPlayerListDockFloating) { - playerListDock->setFloating(aPlayerListDockFloating->isChecked()); - return; - } - - if (o == aReplayDockFloating) { - replayDock->setFloating(aReplayDockFloating->isChecked()); - return; - } -} - -void TabGame::dockTopLevelChanged(bool topLevel) -{ - retranslateUi(); - - QObject *o = sender(); - if (o == cardInfoDock) { - aCardInfoDockFloating->setChecked(topLevel); - return; - } - - if (o == messageLayoutDock) { - aMessageLayoutDockFloating->setChecked(topLevel); - return; - } - - if (o == playerListDock) { - aPlayerListDockFloating->setChecked(topLevel); - return; - } - - if (o == replayDock) { - aReplayDockFloating->setChecked(topLevel); - return; - } -} diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.h b/cockatrice/src/interface/widgets/tabs/tab_game.h index 23640b578..4f944bf87 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.h +++ b/cockatrice/src/interface/widgets/tabs/tab_game.h @@ -78,16 +78,26 @@ private: QWidget *gamePlayAreaWidget, *deckViewContainerWidget; QDockWidget *cardInfoDock, *messageLayoutDock, *playerListDock, *replayDock; QAction *playersSeparator; - QMenu *gameMenu, *viewMenu, *cardInfoDockMenu, *messageLayoutDockMenu, *playerListDockMenu, *replayDockMenu; + QMenu *gameMenu, *viewMenu; TearOffMenu *phasesMenu; QAction *aGameInfo, *aConcede, *aLeaveGame, *aNextPhase, *aNextPhaseAction, *aNextTurn, *aReverseTurn, *aRemoveLocalArrows, *aRotateViewCW, *aRotateViewCCW, *aResetLayout, *aResetReplayLayout; - QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aMessageLayoutDockVisible, *aMessageLayoutDockFloating, - *aPlayerListDockVisible, *aPlayerListDockFloating, *aReplayDockVisible, *aReplayDockFloating; QAction *aFocusChat; QList phaseActions; QAction *aCardMenu; + /** + * @brief The actions associated with managing a QDockWidget + */ + struct DockActions + { + QMenu *menu; + QAction *aVisible; + QAction *aFloating; + }; + + QMap dockToActions; + Player *addPlayer(Player *newPlayer); void addLocalPlayer(Player *newPlayer, int playerId); void processRemotePlayerDeckSelect(QString deckList, int playerId, QString playerName); @@ -107,6 +117,7 @@ private: void createMenuItems(); void createReplayMenuItems(); void createViewMenuItems(); + void registerDockWidget(QMenu *_viewMenu, QDockWidget *widget); void createCardInfoDock(bool bReplay = false); void createPlayerListDock(bool bReplay = false); void createMessageDock(bool bReplay = false); @@ -156,10 +167,6 @@ private slots: void freeDocksSize(); void hideEvent(QHideEvent *event) override; - bool eventFilter(QObject *o, QEvent *e) override; - void dockVisibleTriggered(); - void dockFloatingTriggered(); - void dockTopLevelChanged(bool topLevel); protected slots: void closeEvent(QCloseEvent *event) override; From 792f0770711fe6f4a5b1e244afc427ba29c58c0d Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 16 Jan 2026 17:23:41 -0800 Subject: [PATCH 153/325] [VDE] Use splitter in sample hand widget (#6528) * [VDE] Use splitter in sample hand widget * remove unused code --- .../visual_deck_editor_sample_hand_widget.cpp | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp index 9e7fb0208..81f2c18bb 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp @@ -6,6 +6,7 @@ #include "../deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.h" #include "../deck_analytics/deck_list_statistics_analyzer.h" +#include #include #include @@ -18,6 +19,10 @@ VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *pare layout->setSpacing(0); setLayout(layout); + auto upperLayout = new QVBoxLayout(this); + upperLayout->setContentsMargins(0, 0, 0, 0); + upperLayout->setSpacing(0); + resetAndHandSizeLayout = new QHBoxLayout(resetAndHandSizeContainerWidget); resetAndHandSizeLayout->setContentsMargins(11, 0, 11, 0); @@ -37,23 +42,27 @@ VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *pare &VisualDeckEditorSampleHandWidget::updateDisplay); resetAndHandSizeLayout->addWidget(handSizeSpinBox); - layout->addWidget(resetAndHandSizeContainerWidget); + upperLayout->addWidget(resetAndHandSizeContainerWidget); flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); - layout->addWidget(flowWidget); + upperLayout->addWidget(flowWidget); cardSizeWidget = new CardSizeWidget(this, flowWidget); - layout->addWidget(cardSizeWidget); + upperLayout->addWidget(cardSizeWidget); + + auto upperLayoutWidget = new QWidget(this); + upperLayoutWidget->setLayout(upperLayout); drawProbabilityWidget = new DrawProbabilityWidget(this, statsAnalyzer); - layout->addWidget(drawProbabilityWidget); - for (const ExactCard &card : getRandomCards(handSizeSpinBox->value())) { - auto displayWidget = new CardInfoPictureWidget(this); - displayWidget->setCard(card); - displayWidget->setScaleFactor(cardSizeWidget->getSlider()->value()); - flowWidget->addWidget(displayWidget); - } + auto *splitter = new QSplitter(this); + splitter->setObjectName("splitter"); + splitter->setOrientation(Qt::Vertical); + + splitter->addWidget(upperLayoutWidget); + splitter->addWidget(drawProbabilityWidget); + + layout->addWidget(splitter); retranslateUi(); } From f7ffcc58fe3dd0b2060f45bdba39ac9431bd4ef0 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sat, 17 Jan 2026 18:18:13 +0100 Subject: [PATCH 154/325] [Sample hand widget] Create container widget before declaring it as parent (#6530) --- .../visual_deck_editor_sample_hand_widget.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp index 81f2c18bb..3a34e07a7 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp @@ -23,10 +23,9 @@ VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *pare upperLayout->setContentsMargins(0, 0, 0, 0); upperLayout->setSpacing(0); + resetAndHandSizeContainerWidget = new QWidget(this); resetAndHandSizeLayout = new QHBoxLayout(resetAndHandSizeContainerWidget); resetAndHandSizeLayout->setContentsMargins(11, 0, 11, 0); - - resetAndHandSizeContainerWidget = new QWidget(this); resetAndHandSizeContainerWidget->setLayout(resetAndHandSizeLayout); resetButton = new QPushButton(this); From af2995ba96eb78195b20343aeec947bdff7ae3cd Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 18 Jan 2026 17:51:57 -0800 Subject: [PATCH 155/325] [VDS] Ignore tokens when calculating color identity (#6532) --- .../deck_preview/deck_preview_widget.cpp | 2 +- .../libcockatrice/deck_list/deck_list.cpp | 8 ++++---- .../libcockatrice/deck_list/deck_list.h | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) 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 5c55db456..1de49dbdf 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 @@ -153,7 +153,7 @@ void DeckPreviewWidget::updateTagsVisibility(bool visible) QString DeckPreviewWidget::getColorIdentity() { - QStringList cardList = deckLoader->getDeck().deckList.getCardList(); + QStringList cardList = deckLoader->getDeck().deckList.getCardList({DECK_ZONE_MAIN, DECK_ZONE_SIDE}); if (cardList.isEmpty()) { return {}; } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index 71a04cce3..173988796 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -438,9 +438,9 @@ void DeckList::cleanList(bool preserveMetadata) refreshDeckHash(); } -QStringList DeckList::getCardList() const +QStringList DeckList::getCardList(const QSet &restrictToZones) const { - auto nodes = tree.getCardNodes(); + auto nodes = tree.getCardNodes(restrictToZones); QStringList result; std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(result), [](auto node) { return node->getName(); }); @@ -448,9 +448,9 @@ QStringList DeckList::getCardList() const return result; } -QList DeckList::getCardRefList() const +QList DeckList::getCardRefList(const QSet &restrictToZones) const { - auto nodes = tree.getCardNodes(); + auto nodes = tree.getCardNodes(restrictToZones); QList result; std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(result), diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index 808733b09..8f7efbe2f 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -221,8 +221,8 @@ public: { return tree.isEmpty() && metadata.isEmpty() && sideboardPlans.isEmpty(); } - QStringList getCardList() const; - QList getCardRefList() const; + QStringList getCardList(const QSet &restrictToZones = {}) const; + QList getCardRefList(const QSet &restrictToZones = {}) const; QList getCardNodes(const QSet &restrictToZones = {}) const; QList getZoneNodes() const; int getSideboardSize() const; From f7e71a086888e63136f45b59f9015da2f770c243 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Mon, 19 Jan 2026 00:27:58 -0800 Subject: [PATCH 156/325] [DeckList] Add optional restrictToZone param to getZoneNodes (#6534) --- .../libcockatrice/deck_list/deck_list.cpp | 4 ++-- .../libcockatrice/deck_list/deck_list.h | 2 +- .../libcockatrice/deck_list/deck_list_node_tree.cpp | 10 +++++----- .../libcockatrice/deck_list/deck_list_node_tree.h | 7 ++++++- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index 173988796..3d1070f15 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -464,9 +464,9 @@ QList DeckList::getCardNodes(const QSet &rest return tree.getCardNodes(restrictToZones); } -QList DeckList::getZoneNodes() const +QList DeckList::getZoneNodes(const QSet &restrictToZones) const { - return tree.getZoneNodes(); + return tree.getZoneNodes(restrictToZones); } int DeckList::getSideboardSize() const diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index 8f7efbe2f..91e56fc9f 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -224,7 +224,7 @@ public: QStringList getCardList(const QSet &restrictToZones = {}) const; QList getCardRefList(const QSet &restrictToZones = {}) const; QList getCardNodes(const QSet &restrictToZones = {}) const; - QList getZoneNodes() const; + QList getZoneNodes(const QSet &restrictToZones = {}) const; int getSideboardSize() const; DecklistCardNode *addCard(const QString &cardName, 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 8f651a061..644e0851a 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 @@ -41,10 +41,7 @@ QList DecklistNodeTree::getCardNodes(const QSet result; - for (auto *zoneNode : getZoneNodes()) { - if (!restrictToZones.isEmpty() && !restrictToZones.contains(zoneNode->getName())) { - continue; - } + for (auto *zoneNode : getZoneNodes(restrictToZones)) { for (auto *cardNode : *zoneNode) { auto *cardCardNode = dynamic_cast(cardNode); if (cardCardNode) { @@ -56,13 +53,16 @@ QList DecklistNodeTree::getCardNodes(const QSet DecklistNodeTree::getZoneNodes() const +QList DecklistNodeTree::getZoneNodes(const QSet &restrictToZones) const { QList zones; for (auto *node : *root) { InnerDecklistNode *currentZone = dynamic_cast(node); if (!currentZone) continue; + if (!restrictToZones.isEmpty() && !restrictToZones.contains(currentZone->getName())) { + continue; + } zones.append(currentZone); } 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 5cfd4944d..6de760634 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 @@ -45,7 +45,12 @@ public: */ QList getCardNodes(const QSet &restrictToZones = {}) const; - QList getZoneNodes() const; + /** + * Gets all zone nodes in the tree + * @param restrictToZones If not empty, only get the zone nodes with these names. + * @return A QList containing all the zone nodes in the tree. + */ + QList getZoneNodes(const QSet &restrictToZones = {}) const; /** * @brief Computes the deck hash From 485d5a8b4850cd89648c279ef516f3eda34beee2 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Mon, 19 Jan 2026 00:28:13 -0800 Subject: [PATCH 157/325] [DeckListModel] Fix exception precedence in legality check (#6535) --- .../libcockatrice/models/deck_list/deck_list_model.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) 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 447d868a8..aa95b444e 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -709,8 +709,9 @@ static bool isCardQuantityLegalForFormat(const QString &format, const CardInfo & auto formatRules = CardDatabaseManager::query()->getFormat(format); + // if format has no custom rules, then just do the default check if (!formatRules) { - return true; + return cardInfo.isLegalInFormat(format); } // Exceptions always win @@ -758,11 +759,7 @@ void DeckListModel::refreshCardFormatLegalities() } QString format = deckList->getGameFormat(); - bool legal = exactCard.getInfo().isLegalInFormat(format); - - if (legal) { - legal = isCardQuantityLegalForFormat(format, exactCard.getInfo(), currentCard->getNumber()); - } + bool legal = isCardQuantityLegalForFormat(format, exactCard.getInfo(), currentCard->getNumber()); currentCard->setFormatLegality(legal); } From d9b9c79112e86064c0da4f43d45355045f6a1372 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Mon, 19 Jan 2026 00:38:48 -0800 Subject: [PATCH 158/325] [VDS] Add option to hide color identity (#6533) --- cockatrice/src/client/settings/cache_settings.cpp | 8 ++++++++ cockatrice/src/client/settings/cache_settings.h | 8 +++++++- .../deck_preview/deck_preview_widget.cpp | 12 ++++++++++++ .../deck_preview/deck_preview_widget.h | 1 + .../visual_deck_storage_quick_settings_widget.cpp | 15 +++++++++++++++ .../visual_deck_storage_quick_settings_widget.h | 3 +++ 6 files changed, 46 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 2f0eb98dc..8a981ce14 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -310,6 +310,7 @@ SettingsCache::SettingsCache() visualDeckStorageDefaultTagsList = settings->value("interface/visualdeckstoragedefaulttagslist", defaultTags).toStringList(); visualDeckStorageSearchFolderNames = settings->value("interface/visualdeckstoragesearchfoldernames", true).toBool(); + visualDeckStorageShowColorIdentity = settings->value("interface/visualdeckstorageshowcoloridentity", true).toBool(); visualDeckStorageShowBannerCardComboBox = settings->value("interface/visualdeckstorageshowbannercardcombobox", true).toBool(); visualDeckStorageShowTagsOnDeckPreviews = @@ -829,6 +830,13 @@ void SettingsCache::setVisualDeckStorageSearchFolderNames(QT_STATE_CHANGED_T val settings->setValue("interface/visualdeckstoragesearchfoldernames", visualDeckStorageSearchFolderNames); } +void SettingsCache::setVisualDeckStorageShowColorIdentity(QT_STATE_CHANGED_T value) +{ + visualDeckStorageShowColorIdentity = value; + settings->setValue("interface/visualdeckstorageshowcoloridentity", visualDeckStorageShowColorIdentity); + emit visualDeckStorageShowColorIdentityChanged(visualDeckStorageShowColorIdentity); +} + void SettingsCache::setVisualDeckStorageShowBannerCardComboBox(QT_STATE_CHANGED_T _showBannerCardComboBox) { visualDeckStorageShowBannerCardComboBox = _showBannerCardComboBox; diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index f4e1000be..57adb394a 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -158,6 +158,7 @@ signals: void deckEditorTagsWidgetVisibleChanged(bool _visible); void visualDeckStorageShowTagFilterChanged(bool _visible); void visualDeckStorageDefaultTagsListChanged(); + void visualDeckStorageShowColorIdentityChanged(bool _visible); void visualDeckStorageShowBannerCardComboBoxChanged(bool _visible); void visualDeckStorageShowTagsOnDeckPreviewsChanged(bool _visible); void visualDeckStorageCardSizeChanged(); @@ -251,6 +252,7 @@ private: bool deckEditorTagsWidgetVisible; int visualDeckStorageSortingOrder; bool visualDeckStorageShowFolders; + bool visualDeckStorageShowColorIdentity; bool visualDeckStorageShowBannerCardComboBox; bool visualDeckStorageShowTagsOnDeckPreviews; bool visualDeckStorageShowTagFilter; @@ -621,6 +623,10 @@ public: { return visualDeckStorageSearchFolderNames; } + [[nodiscard]] bool getVisualDeckStorageShowColorIdentity() const + { + return visualDeckStorageShowColorIdentity; + } [[nodiscard]] bool getVisualDeckStorageShowBannerCardComboBox() const { return visualDeckStorageShowBannerCardComboBox; @@ -1045,6 +1051,7 @@ public slots: void setVisualDeckStorageShowTagFilter(QT_STATE_CHANGED_T _showTags); void setVisualDeckStorageDefaultTagsList(QStringList _defaultTagsList); void setVisualDeckStorageSearchFolderNames(QT_STATE_CHANGED_T value); + void setVisualDeckStorageShowColorIdentity(QT_STATE_CHANGED_T value); void setVisualDeckStorageShowBannerCardComboBox(QT_STATE_CHANGED_T _showBannerCardComboBox); void setVisualDeckStorageShowTagsOnDeckPreviews(QT_STATE_CHANGED_T _showTags); void setVisualDeckStorageCardSize(int _visualDeckStorageCardSize); @@ -1117,5 +1124,4 @@ public slots: void setMaxFontSize(int _max); void setRoundCardCorners(bool _roundCardCorners); }; - #endif 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 1de49dbdf..a9c1f0933 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 @@ -42,6 +42,8 @@ DeckPreviewWidget::DeckPreviewWidget(QWidget *_parent, connect(bannerCardDisplayWidget, &DeckPreviewCardPictureWidget::imageDoubleClicked, this, &DeckPreviewWidget::imageDoubleClickedEvent); + connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageShowColorIdentityChanged, this, + &DeckPreviewWidget::updateColorIdentityVisibility); connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageShowTagsOnDeckPreviewsChanged, this, &DeckPreviewWidget::updateTagsVisibility); connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageShowBannerCardComboBoxChanged, this, @@ -96,6 +98,7 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess) connect(bannerCardComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &DeckPreviewWidget::setBannerCard); + updateColorIdentityVisibility(SettingsCache::instance().getVisualDeckStorageShowColorIdentity()); updateBannerCardComboBox(); updateBannerCardComboBoxVisibility(SettingsCache::instance().getVisualDeckStorageShowBannerCardComboBox()); updateTagsVisibility(SettingsCache::instance().getVisualDeckStorageShowTagsOnDeckPreviews()); @@ -123,6 +126,15 @@ bool DeckPreviewWidget::checkVisibility() const return true; } +void DeckPreviewWidget::updateColorIdentityVisibility(bool visible) +{ + if (colorIdentityWidget == nullptr) { + return; + } + + colorIdentityWidget->setVisible(visible); +} + void DeckPreviewWidget::updateBannerCardComboBoxVisibility(bool visible) { if (bannerCardComboBox == nullptr) { diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h index 85f0ce76e..1dc67d158 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h @@ -63,6 +63,7 @@ public slots: void imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); void initializeUi(bool deckLoadSuccess); void updateVisibility(); + void updateColorIdentityVisibility(bool visible); void updateBannerCardComboBoxVisibility(bool visible); void updateTagsVisibility(bool visible); void resizeEvent(QResizeEvent *event) override; diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp index a396b8cd3..a0e8512ac 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp @@ -26,6 +26,14 @@ VisualDeckStorageQuickSettingsWidget::VisualDeckStorageQuickSettingsWidget(QWidg connect(showTagFilterCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setVisualDeckStorageShowTagFilter); + // show color identity on DeckPreviewWidget checkbox + showColorIdentityCheckBox = new QCheckBox(this); + showColorIdentityCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowColorIdentity()); + connect(showColorIdentityCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + &VisualDeckStorageQuickSettingsWidget::showColorIdentityChanged); + connect(showColorIdentityCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageShowColorIdentity); + // show tags on DeckPreviewWidget checkbox showTagsOnDeckPreviewsCheckBox = new QCheckBox(this); showTagsOnDeckPreviewsCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowTagsOnDeckPreviews()); @@ -103,6 +111,7 @@ VisualDeckStorageQuickSettingsWidget::VisualDeckStorageQuickSettingsWidget(QWidg // putting everything together this->addSettingsWidget(showFoldersCheckBox); this->addSettingsWidget(showTagFilterCheckBox); + this->addSettingsWidget(showColorIdentityCheckBox); this->addSettingsWidget(showTagsOnDeckPreviewsCheckBox); this->addSettingsWidget(showBannerCardComboBoxCheckBox); this->addSettingsWidget(drawUnusedColorIdentitiesCheckBox); @@ -119,6 +128,7 @@ void VisualDeckStorageQuickSettingsWidget::retranslateUi() { showFoldersCheckBox->setText(tr("Show Folders")); showTagFilterCheckBox->setText(tr("Show Tag Filter")); + showColorIdentityCheckBox->setText(tr("Show Color Identity")); showTagsOnDeckPreviewsCheckBox->setText(tr("Show Tags On Deck Previews")); showBannerCardComboBoxCheckBox->setText(tr("Show Banner Card Selection Option")); drawUnusedColorIdentitiesCheckBox->setText(tr("Draw unused Color Identities")); @@ -140,6 +150,11 @@ bool VisualDeckStorageQuickSettingsWidget::getDrawUnusedColorIdentities() const return drawUnusedColorIdentitiesCheckBox->isChecked(); } +bool VisualDeckStorageQuickSettingsWidget::getShowColorIdentity() const +{ + return showColorIdentityCheckBox->isChecked(); +} + bool VisualDeckStorageQuickSettingsWidget::getShowBannerCardComboBox() const { return showBannerCardComboBoxCheckBox->isChecked(); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.h index 5bb7e4110..ea4330a15 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.h @@ -22,6 +22,7 @@ class VisualDeckStorageQuickSettingsWidget : public SettingsButtonWidget Q_OBJECT QCheckBox *showFoldersCheckBox; + QCheckBox *showColorIdentityCheckBox; QCheckBox *drawUnusedColorIdentitiesCheckBox; QCheckBox *showBannerCardComboBoxCheckBox; QCheckBox *showTagFilterCheckBox; @@ -49,6 +50,7 @@ public: [[nodiscard]] bool getShowFolders() const; [[nodiscard]] bool getDrawUnusedColorIdentities() const; + [[nodiscard]] bool getShowColorIdentity() const; [[nodiscard]] bool getShowBannerCardComboBox() const; [[nodiscard]] bool getShowTagFilter() const; [[nodiscard]] bool getShowTagsOnDeckPreviews() const; @@ -59,6 +61,7 @@ public: signals: void showFoldersChanged(bool enabled); void drawUnusedColorIdentitiesChanged(bool enabled); + void showColorIdentityChanged(bool enabled); void showBannerCardComboBoxChanged(bool enabled); void showTagFilterChanged(bool enabled); void showTagsOnDeckPreviewsChanged(bool enabled); From 39ddaa0c355ee6d9aa7e227b1f3f409fbc1c666c Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 22 Jan 2026 20:31:39 -0800 Subject: [PATCH 159/325] [VDS] Reload deck on hover if file has been modified since last load (#6507) * add reload to DeckLoader * [VDS] Reload deck on hover if file has been modified since last load * fix version incompatibility --- .../src/interface/deck_loader/deck_loader.cpp | 16 ++++ .../src/interface/deck_loader/deck_loader.h | 7 ++ .../additional_info/color_identity_widget.cpp | 10 ++ .../additional_info/color_identity_widget.h | 1 + .../deck_preview_deck_tags_display_widget.h | 2 +- .../deck_preview/deck_preview_widget.cpp | 94 +++++++++++++++---- .../deck_preview/deck_preview_widget.h | 14 ++- 7 files changed, 124 insertions(+), 20 deletions(-) diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index 305cd34d9..3481e245b 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -91,6 +91,22 @@ void DeckLoader::loadFromFileAsync(const QString &fileName, DeckFileFormat::Form }); } +bool DeckLoader::reload() +{ + QString lastFileName = loadedDeck.lastLoadInfo.fileName; + if (lastFileName.isEmpty()) { + return false; + } + std::optional deck = loadFromFile(lastFileName, loadedDeck.lastLoadInfo.fileFormat, false); + + if (!deck) { + return false; + } + + loadedDeck = *deck; + return true; +} + std::optional DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId) { DeckList deckList; diff --git a/cockatrice/src/interface/deck_loader/deck_loader.h b/cockatrice/src/interface/deck_loader/deck_loader.h index 1780e2706..2530b7f5d 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.h +++ b/cockatrice/src/interface/deck_loader/deck_loader.h @@ -62,6 +62,13 @@ public: */ void loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest); + /** + * @brief Loads the file that the lastLoadInfo currently points to into this instance. + * No-ops if the lastLoadInfo is missing the required info or the load fails. + * @return Whether the loaded succeeded. + */ + bool reload(); + /** * @brief Loads a deck from a local file. * @param fileName The file to load diff --git a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp index 98f5a306b..1c38214bd 100644 --- a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp @@ -53,6 +53,16 @@ void ColorIdentityWidget::populateManaSymbolWidgets() } } +void ColorIdentityWidget::setColorIdentity(const QString &_colorIdentity) +{ + if (colorIdentity == _colorIdentity) { + return; + } + + colorIdentity = _colorIdentity; + populateManaSymbolWidgets(); +} + void ColorIdentityWidget::toggleUnusedVisibility() { populateManaSymbolWidgets(); diff --git a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.h b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.h index d9746b956..6512ba890 100644 --- a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.h +++ b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.h @@ -23,6 +23,7 @@ public: static QStringList parseColorIdentity(const QString &manaString); public slots: + void setColorIdentity(const QString &_colorIdentity); void resizeEvent(QResizeEvent *event) override; void toggleUnusedVisibility(); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h index 74ea1cbdf..bfd0a170d 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h @@ -21,7 +21,7 @@ class DeckPreviewDeckTagsDisplayWidget : public QWidget FlowWidget *flowWidget; public: - explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, const QStringList &_tags); + explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, const QStringList &_tags = {}); void setTags(const QStringList &_tags); void refreshTags(); 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 a9c1f0933..a2adb732c 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 @@ -71,21 +71,48 @@ void DeckPreviewWidget::resizeEvent(QResizeEvent *event) } } +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +void DeckPreviewWidget::enterEvent(QEnterEvent *event) +#else +void DeckPreviewWidget::enterEvent(QEvent *event) +#endif +{ + QWidget::enterEvent(event); + reloadIfModified(); +} + +/** + * @brief Sets the lastModifiedTime to the value given by the file. + */ +void DeckPreviewWidget::updateLastModifiedTime() +{ + QFileInfo fileInfo(filePath); + lastModifiedTime = fileInfo.lastModified(); +} + +/** + * @brief Writes the current contents of the deck to file. Updates the lastModifiedTime afterward. + */ +void DeckPreviewWidget::writeDeckToFile() +{ + DeckLoader::saveToFile(deckLoader->getDeck()); + updateLastModifiedTime(); +} + void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess) { if (!deckLoadSuccess) { return; } - auto bannerCard = deckLoader->getDeck().deckList.getBannerCard().name.isEmpty() - ? ExactCard() - : CardDatabaseManager::query()->getCard(deckLoader->getDeck().deckList.getBannerCard()); - bannerCardDisplayWidget->setCard(bannerCard); + QFileInfo fileInfo(filePath); + lastModifiedTime = fileInfo.lastModified(); + bannerCardDisplayWidget->setFontSize(24); setFilePath(deckLoader->getDeck().lastLoadInfo.fileName); - colorIdentityWidget = new ColorIdentityWidget(this, getColorIdentity()); - deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeck().deckList.getTags()); + colorIdentityWidget = new ColorIdentityWidget(this); + deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this); connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this, &DeckPreviewWidget::setTags); bannerCardLabel = new QLabel(this); @@ -93,13 +120,11 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess) bannerCardComboBox = new QComboBox(this); bannerCardComboBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); bannerCardComboBox->setObjectName("bannerCardComboBox"); - bannerCardComboBox->setCurrentText(deckLoader->getDeck().deckList.getBannerCard().name); bannerCardComboBox->installEventFilter(new NoScrollFilter(bannerCardComboBox)); connect(bannerCardComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &DeckPreviewWidget::setBannerCard); updateColorIdentityVisibility(SettingsCache::instance().getVisualDeckStorageShowColorIdentity()); - updateBannerCardComboBox(); updateBannerCardComboBoxVisibility(SettingsCache::instance().getVisualDeckStorageShowBannerCardComboBox()); updateTagsVisibility(SettingsCache::instance().getVisualDeckStorageShowTagsOnDeckPreviews()); @@ -108,9 +133,44 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess) layout->addWidget(bannerCardLabel); layout->addWidget(bannerCardComboBox); - refreshBannerCardText(); - retranslateUi(); + resyncWidgets(); +} + +/** + * @brief Syncs the contents of the child widgets with the current deck. + */ +void DeckPreviewWidget::resyncWidgets() +{ + auto bannerCardRef = deckLoader->getDeck().deckList.getBannerCard(); + auto bannerCard = bannerCardRef.name.isEmpty() ? ExactCard() : CardDatabaseManager::query()->getCard(bannerCardRef); + + bannerCardDisplayWidget->setCard(bannerCard); + refreshBannerCardText(); + updateBannerCardComboBox(bannerCardRef.name); + colorIdentityWidget->setColorIdentity(getColorIdentity()); + deckTagsDisplayWidget->setTags(deckLoader->getDeck().deckList.getTags()); +} + +/** + * @brief Reloads the deck if the file's last modified time has increased since we last checked. + */ +void DeckPreviewWidget::reloadIfModified() +{ + QFileInfo fileInfo(filePath); + QDateTime newLastModifiedTime = fileInfo.lastModified(); + + if (!newLastModifiedTime.isValid() || newLastModifiedTime <= lastModifiedTime) { + return; + } + + bool success = deckLoader->reload(); + + if (success) { + fileInfo.refresh(); + lastModifiedTime = fileInfo.lastModified(); + resyncWidgets(); + } } void DeckPreviewWidget::updateVisibility() @@ -232,11 +292,8 @@ void DeckPreviewWidget::refreshBannerCardToolTip() } } -void DeckPreviewWidget::updateBannerCardComboBox() +void DeckPreviewWidget::updateBannerCardComboBox(const QString ¤tText) { - // Store the current text of the combo box - QString currentText = bannerCardComboBox->currentText(); - // Block signals temporarily bool wasBlocked = bannerCardComboBox->blockSignals(true); bannerCardComboBox->setUpdatesEnabled(false); @@ -300,7 +357,7 @@ void DeckPreviewWidget::setBannerCard(int /* changedIndex */) auto [name, id] = bannerCardComboBox->currentData().value>(); CardRef cardRef = {name, id}; deckLoader->getDeck().deckList.setBannerCard(cardRef); - DeckLoader::saveToFile(deckLoader->getDeck()); + writeDeckToFile(); bannerCardDisplayWidget->setCard(CardDatabaseManager::query()->getCard(cardRef)); } @@ -323,7 +380,7 @@ void DeckPreviewWidget::imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewC void DeckPreviewWidget::setTags(const QStringList &tags) { deckLoader->getDeck().deckList.setTags(tags); - DeckLoader::saveToFile(deckLoader->getDeck()); + writeDeckToFile(); } QMenu *DeckPreviewWidget::createRightClickMenu() @@ -398,7 +455,7 @@ void DeckPreviewWidget::actRenameDeck() // write change deckLoader->getDeck().deckList.setName(newName); - DeckLoader::saveToFile(deckLoader->getDeck()); + writeDeckToFile(); // update VDS refreshBannerCardText(); @@ -429,9 +486,10 @@ void DeckPreviewWidget::actRenameFile() } deckLoader->getDeck().lastLoadInfo.fileName = newFilePath; + setFilePath(newFilePath); // update VDS - setFilePath(newFilePath); + updateLastModifiedTime(); refreshBannerCardText(); } diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h index 1dc67d158..98116cabe 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h @@ -38,6 +38,7 @@ public: VisualDeckStorageWidget *visualDeckStorageWidget; QVBoxLayout *layout; QString filePath; + QDateTime lastModifiedTime; DeckLoader *deckLoader; DeckPreviewCardPictureWidget *bannerCardDisplayWidget = nullptr; ColorIdentityWidget *colorIdentityWidget = nullptr; @@ -57,18 +58,29 @@ public slots: void setFilePath(const QString &filePath); void refreshBannerCardText(); void refreshBannerCardToolTip(); - void updateBannerCardComboBox(); + void updateBannerCardComboBox(const QString ¤tText); void setBannerCard(int); void imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); void imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); void initializeUi(bool deckLoadSuccess); + void resyncWidgets(); + void reloadIfModified(); void updateVisibility(); void updateColorIdentityVisibility(bool visible); void updateBannerCardComboBoxVisibility(bool visible); void updateTagsVisibility(bool visible); void resizeEvent(QResizeEvent *event) override; +protected: +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + void enterEvent(QEnterEvent *event) override; // Qt6 signature +#else + void enterEvent(QEvent *event) override; // Qt5 signature +#endif + private: + void updateLastModifiedTime(); + void writeDeckToFile(); QMenu *createRightClickMenu(); void addSetBannerCardMenu(QMenu *menu); From 2e1a0bec93353d0aadba7bb79c95f739fd430c67 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 22 Jan 2026 20:33:21 -0800 Subject: [PATCH 160/325] [CardInfo] Refactor: add getLegalityProp method (#6536) --- libcockatrice_card/libcockatrice/card/card_info.cpp | 7 ++++++- libcockatrice_card/libcockatrice/card/card_info.h | 8 ++++++++ .../libcockatrice/filters/filter_string.cpp | 5 ++--- .../libcockatrice/filters/filter_tree.cpp | 2 +- .../libcockatrice/models/deck_list/deck_list_model.cpp | 7 +++---- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/libcockatrice_card/libcockatrice/card/card_info.cpp b/libcockatrice_card/libcockatrice/card/card_info.cpp index ae73ef2db..1aaec85b8 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.cpp +++ b/libcockatrice_card/libcockatrice/card/card_info.cpp @@ -75,13 +75,18 @@ QString CardInfo::getCorrectedName() const return result.remove(rmrx).replace(spacerx, space); } +QString CardInfo::getLegalityProp(const QString &format) const +{ + return getProperty("format-" + format); +} + bool CardInfo::isLegalInFormat(const QString &format) const { if (format.isEmpty()) { return true; } - QString formatLegality = getProperty("format-" + format); + QString formatLegality = getLegalityProp(format); return formatLegality == "legal" || formatLegality == "restricted"; } diff --git a/libcockatrice_card/libcockatrice/card/card_info.h b/libcockatrice_card/libcockatrice/card/card_info.h index b81cf51dc..654ac1f63 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.h +++ b/libcockatrice_card/libcockatrice/card/card_info.h @@ -291,6 +291,14 @@ public: */ [[nodiscard]] QString getCorrectedName() const; + /** + * @brief Gets the card's legality value for the given format. + * The legality prop for a format is stored in the property map under the key "format-" + * @param format The format's name. + * @return The card's legality value for the format. Empty if not found. + */ + [[nodiscard]] QString getLegalityProp(const QString &format) const; + /** * @brief Checks if the card is legal in the given format. * A card is considered legal in a format if its properties map contains an entry for "format-", with value diff --git a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp index 3615729f0..0d29f7897 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp @@ -143,13 +143,12 @@ static void setupParserRules() search["FormatQuery"] = [](const peg::SemanticValues &sv) -> Filter { if (sv.choice() == 0) { const auto format = std::any_cast(sv[0]); - return - [=](const CardData &x) -> bool { return x->getProperty(QString("format-%1").arg(format)) == "legal"; }; + return [=](const CardData &x) -> bool { return x->getLegalityProp(format) == "legal"; }; } const auto format = std::any_cast(sv[1]); const auto legality = std::any_cast(sv[0]); - return [=](const CardData &x) -> bool { return x->getProperty(QString("format-%1").arg(format)) == legality; }; + return [=](const CardData &x) -> bool { return x->getLegalityProp(format) == legality; }; }; search["Legality"] = [](const peg::SemanticValues &sv) -> QString { switch (tolower(std::string(sv.sv())[0])) { diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp index afb2d8563..19e8c2d8d 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp @@ -275,7 +275,7 @@ bool FilterItem::acceptCmc(const CardInfoPtr info) const bool FilterItem::acceptFormat(const CardInfoPtr info) const { - return info->getProperty(QString("format-%1").arg(term.toLower())) == "legal"; + return info->getLegalityProp(term.toLower()) == "legal"; } bool FilterItem::acceptLoyalty(const CardInfoPtr info) const 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 aa95b444e..8d03dc015 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -719,13 +719,12 @@ static bool isCardQuantityLegalForFormat(const QString &format, const CardInfo & return true; } - const QString legalityProp = "format-" + format; - if (!cardInfo.getProperties().contains(legalityProp)) { + // check legality prop + const QString legality = cardInfo.getLegalityProp(format); + if (legality.isEmpty()) { return false; } - const QString legality = cardInfo.getProperty(legalityProp); - int maxAllowed = maxAllowedForLegality(*formatRules, legality); if (maxAllowed == -1) { From 8d274c19248ec34a1306777736775315a9040626 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 22 Jan 2026 20:34:24 -0800 Subject: [PATCH 161/325] [DeckListModel] Correctly refresh legality on add card (#6537) --- .../libcockatrice/models/deck_list/deck_list_model.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 8d03dc015..8459f306c 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -435,9 +435,8 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam // Determine the correct index int insertRow = findSortedInsertRow(groupNode, cardInfo); - auto *decklistCard = - deckList->addCard(cardInfo->getName(), zoneName, insertRow, cardSetName, printingInfo.getProperty("num"), - printingInfo.getProperty("uuid"), cardInfo->isLegalInFormat(deckList->getGameFormat())); + auto *decklistCard = deckList->addCard(cardInfo->getName(), zoneName, insertRow, cardSetName, + printingInfo.getProperty("num"), printingInfo.getProperty("uuid")); beginInsertRows(parentIndex, insertRow, insertRow); cardNode = new DecklistModelCardNode(decklistCard, groupNode, insertRow); @@ -453,6 +452,7 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam emit deckHashChanged(); } sort(lastKnownColumn, lastKnownOrder); + refreshCardFormatLegalities(); emitRecursiveUpdates(parentIndex); auto index = nodeToIndex(cardNode); From 999733fc0fa92728d6ebd70bcab8ae3efa6266d1 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 23 Jan 2026 00:18:05 -0800 Subject: [PATCH 162/325] [VDE] Fix right click to remove card not working (#6549) * fix typo * fix crash --- .../widgets/deck_editor/deck_state_manager.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 412954bb8..09254608e 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp @@ -201,17 +201,19 @@ QModelIndex DeckStateManager::decrementCard(const ExactCard &card, const QString return {}; } - bool success = offsetCountAtIndex(idx, false); + bool success = offsetCountAtIndex(idx, -1); if (!success) { return {}; } - if (idx.isValid()) { - emit focusIndexChanged(idx, true); + // old index is no longer safe since rows could have been removed + QModelIndex newIdx = deckListModel->findCard(card.getName(), zoneName, providerId, collectorNumber); + if (newIdx.isValid()) { + emit focusIndexChanged(newIdx, true); } - return idx; + return newIdx; } static bool doSwapCard(DeckListModel *model, From d363ec51546b1dbdc0ea0aaeca940be1b237ecb6 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 23 Jan 2026 00:58:46 -0800 Subject: [PATCH 163/325] [VDS] Fix crash when tab is opened before card database is loaded (#6553) --- .../deck_preview/deck_preview_widget.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 a2adb732c..77ea8f865 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 @@ -78,7 +78,11 @@ void DeckPreviewWidget::enterEvent(QEvent *event) #endif { QWidget::enterEvent(event); - reloadIfModified(); + + // don't do reloads until widgets have been created + if (bannerCardComboBox != nullptr) { + reloadIfModified(); + } } /** From bfeb3a7ca964acf9fa466cd2598969676d292bd8 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 23 Jan 2026 01:05:25 -0800 Subject: [PATCH 164/325] [DeckListModel] Refactor to use forEachCard in legality check (#6550) --- .../models/deck_list/deck_list_model.cpp | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) 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 8459f306c..9ccb0b1ad 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -738,29 +738,26 @@ static bool isCardQuantityLegalForFormat(const QString &format, const CardInfo & return quantity <= maxAllowed; } +static bool isCardNodeLegalForFormat(const QString &format, const InnerDecklistNode *zone, const DecklistCardNode *card) +{ + Q_UNUSED(zone) + + // unknown cards are not legal + ExactCard exactCard = CardDatabaseManager::query()->getCard(card->toCardRef()); + if (!exactCard) { + return false; + } + + // actual check + return isCardQuantityLegalForFormat(format, exactCard.getInfo(), card->getNumber()); +} + void DeckListModel::refreshCardFormatLegalities() { - InnerDecklistNode *listRoot = deckList->getTree()->getRoot(); + QString format = deckList->getGameFormat(); - for (int i = 0; i < listRoot->size(); i++) { - auto *currentZone = static_cast(listRoot->at(i)); - for (int j = 0; j < currentZone->size(); j++) { - auto *currentCard = static_cast(currentZone->at(j)); - - // TODO: better sanity checking - if (currentCard == nullptr) { - continue; - } - - ExactCard exactCard = CardDatabaseManager::query()->getCard(currentCard->toCardRef()); - if (!exactCard) { - continue; - } - - QString format = deckList->getGameFormat(); - bool legal = isCardQuantityLegalForFormat(format, exactCard.getInfo(), currentCard->getNumber()); - - currentCard->setFormatLegality(legal); - } - } + deckList->forEachCard([&format](const InnerDecklistNode *zone, DecklistCardNode *card) { + bool legal = isCardNodeLegalForFormat(format, zone, card); + card->setFormatLegality(legal); + }); } From 5a274fdbed270a646f8c3913d8582e83d6489e70 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 23 Jan 2026 02:35:36 -0800 Subject: [PATCH 165/325] [DeckListModel] Mark all cards in token zone as legal (#6551) --- .../libcockatrice/models/deck_list/deck_list_model.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 9ccb0b1ad..4d36164b0 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -740,7 +740,10 @@ static bool isCardQuantityLegalForFormat(const QString &format, const CardInfo & static bool isCardNodeLegalForFormat(const QString &format, const InnerDecklistNode *zone, const DecklistCardNode *card) { - Q_UNUSED(zone) + // Don't check legality for tokens + if (zone->getName() == DECK_ZONE_TOKENS) { + return true; + } // unknown cards are not legal ExactCard exactCard = CardDatabaseManager::query()->getCard(card->toCardRef()); From 948ec9e0424fddd03ea8f50a97b50dd33809aac6 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Fri, 23 Jan 2026 14:47:08 +0100 Subject: [PATCH 166/325] [VDE] Accurately represent card amounts (#6547) --- .../card_group_display_widget.cpp | 178 +++++++++++++++--- .../card_group_display_widget.h | 4 +- .../flat_card_group_display_widget.cpp | 8 +- .../overlapped_card_group_display_widget.cpp | 7 +- .../models/deck_list/deck_list_model.cpp | 4 + 5 files changed, 172 insertions(+), 29 deletions(-) diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp index 8e6ebed63..7239a7c3f 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp @@ -37,6 +37,7 @@ CardGroupDisplayWidget::CardGroupDisplayWidget(QWidget *parent, &CardGroupDisplayWidget::onSelectionChanged); } connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &CardGroupDisplayWidget::onCardRemoval); + connect(deckListModel, &QAbstractItemModel::dataChanged, this, &CardGroupDisplayWidget::onDataChanged); } // Just here so it can get overwritten in subclasses. @@ -81,8 +82,11 @@ void CardGroupDisplayWidget::onSelectionChanged(const QItemSelection &selected, auto it = indexToWidgetMap.find(QPersistentModelIndex(idx)); if (it != indexToWidgetMap.end()) { - if (auto displayWidget = qobject_cast(it.value())) { - displayWidget->setHighlighted(true); + // Highlight all copies of this card + for (auto widget : it.value()) { + if (auto displayWidget = qobject_cast(widget)) { + displayWidget->setHighlighted(true); + } } } } @@ -96,23 +100,51 @@ void CardGroupDisplayWidget::onSelectionChanged(const QItemSelection &selected, auto it = indexToWidgetMap.find(QPersistentModelIndex(idx)); if (it != indexToWidgetMap.end()) { - if (auto displayWidget = qobject_cast(it.value())) { - displayWidget->setHighlighted(false); + // Un-highlight all copies of this card + for (auto widget : it.value()) { + if (auto displayWidget = qobject_cast(widget)) { + displayWidget->setHighlighted(false); + } } } } } } +void CardGroupDisplayWidget::refreshSelectionForIndex(const QPersistentModelIndex &persistent) +{ + if (!selectionModel || !indexToWidgetMap.contains(persistent)) { + return; + } + + // Convert persistent index to regular index for selection check + QModelIndex idx = QModelIndex(persistent); + + // Check if this index is selected + // We need to check against the selection model's model (which might be a proxy) + bool isSelected = false; + if (auto proxyModel = qobject_cast(selectionModel->model())) { + // Map source index to proxy + QModelIndex proxyIdx = proxyModel->mapFromSource(idx); + isSelected = selectionModel->isSelected(proxyIdx); + } else { + isSelected = selectionModel->isSelected(idx); + } + + // Apply selection state to all widgets for this index + for (auto widget : indexToWidgetMap[persistent]) { + if (auto displayWidget = qobject_cast(widget)) { + displayWidget->setHighlighted(isSelected); + } + } +} + // ===================================================================================================================== // Display Widget Management // ===================================================================================================================== QWidget *CardGroupDisplayWidget::constructWidgetForIndex(QPersistentModelIndex index) { - if (indexToWidgetMap.contains(index)) { - return indexToWidgetMap[index]; - } auto cardName = index.sibling(index.row(), DeckListModelColumns::CARD_NAME).data(Qt::EditRole).toString(); auto cardProviderId = index.sibling(index.row(), DeckListModelColumns::CARD_PROVIDER_ID).data(Qt::EditRole).toString(); @@ -125,7 +157,8 @@ QWidget *CardGroupDisplayWidget::constructWidgetForIndex(QPersistentModelIndex i connect(widget, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this, &CardGroupDisplayWidget::onHover); connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, widget, &CardInfoPictureWidget::setScaleFactor); - indexToWidgetMap.insert(index, widget); + indexToWidgetMap[index].append(widget); + return widget; } @@ -152,17 +185,26 @@ void CardGroupDisplayWidget::updateCardDisplays() // 4. persist the source index QPersistentModelIndex persistent(sourceIndex); - addToLayout(constructWidgetForIndex(persistent)); + // Get the card amount + int cardAmount = + sourceIndex.sibling(sourceIndex.row(), DeckListModelColumns::CARD_AMOUNT).data(Qt::EditRole).toInt(); + + // Create multiple widgets for the card count + for (int copy = 0; copy < cardAmount; ++copy) { + addToLayout(constructWidgetForIndex(persistent)); + } } } void CardGroupDisplayWidget::clearAllDisplayWidgets() { - for (auto idx : indexToWidgetMap.keys()) { - auto displayWidget = indexToWidgetMap.value(idx); - removeFromLayout(displayWidget); - indexToWidgetMap.remove(idx); - delete displayWidget; + auto it = indexToWidgetMap.begin(); + while (it != indexToWidgetMap.end()) { + for (auto displayWidget : it.value()) { + removeFromLayout(displayWidget); + delete displayWidget; + } + it = indexToWidgetMap.erase(it); } } @@ -184,7 +226,13 @@ void CardGroupDisplayWidget::onCardAddition(const QModelIndex &parent, int first // Persist the index QPersistentModelIndex persistent(child); - insertIntoLayout(constructWidgetForIndex(persistent), row); + // Get the card amount for the newly added card + int cardAmount = child.sibling(child.row(), DeckListModelColumns::CARD_AMOUNT).data(Qt::EditRole).toInt(); + + // Insert multiple copies + for (int copy = 0; copy < cardAmount; ++copy) { + insertIntoLayout(constructWidgetForIndex(persistent), row); + } } } } @@ -193,18 +241,100 @@ void CardGroupDisplayWidget::onCardRemoval(const QModelIndex &parent, int first, { Q_UNUSED(first); Q_UNUSED(last); - if (parent == trackedIndex) { - for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) { - if (!idx.isValid()) { - removeFromLayout(indexToWidgetMap.value(idx)); - indexToWidgetMap.value(idx)->deleteLater(); - indexToWidgetMap.remove(idx); + + if (parent != trackedIndex) { + return; + } + + // Use iterator so we can remove while iterating + auto it = indexToWidgetMap.begin(); + while (it != indexToWidgetMap.end()) { + const QPersistentModelIndex &idx = it.key(); + bool shouldRemove = !idx.isValid() || it.value().isEmpty(); + + if (shouldRemove) { + // Clean up widgets + for (auto widget : it.value()) { + removeFromLayout(widget); + widget->deleteLater(); + } + + // Erase and advance iterator + it = indexToWidgetMap.erase(it); + } else { + ++it; + } + } + + if (!trackedIndex.isValid()) { + emit cleanupRequested(this); + } +} + +void CardGroupDisplayWidget::onDataChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight, + const QVector &roles) +{ + if (topLeft.parent() != trackedIndex && bottomRight.parent() != trackedIndex) { + return; + } + + // Check if CARD_AMOUNT column changed + bool amountChanged = (topLeft.column() <= DeckListModelColumns::CARD_AMOUNT && + bottomRight.column() >= DeckListModelColumns::CARD_AMOUNT) || + roles.isEmpty() || roles.contains(Qt::EditRole); + + if (!amountChanged) { + return; + } + + // For each affected row, adjust widget count + for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { + QModelIndex idx = deckListModel->index(row, 0, trackedIndex); + + if (!idx.isValid()) { + continue; + } + + QPersistentModelIndex persistent(idx); + int newAmount = idx.sibling(idx.row(), DeckListModelColumns::CARD_AMOUNT).data(Qt::EditRole).toInt(); + + // Get current widget count + int currentWidgetCount = indexToWidgetMap.contains(persistent) ? indexToWidgetMap.value(persistent).count() : 0; + + if (newAmount == currentWidgetCount) { + // Still refresh selection even if count didn't change + refreshSelectionForIndex(persistent); + continue; + } + + if (newAmount < currentWidgetCount) { + // Remove excess widgets + int toRemove = currentWidgetCount - newAmount; + + for (int i = 0; i < toRemove; ++i) { + if (!indexToWidgetMap[persistent].isEmpty()) { + QWidget *widget = indexToWidgetMap[persistent].takeLast(); + removeFromLayout(widget); + widget->deleteLater(); + } + } + + // If all widgets removed, clean up the map entry + if (indexToWidgetMap[persistent].isEmpty()) { + indexToWidgetMap.remove(persistent); + } + } else { + // Add new widgets + int toAdd = newAmount - currentWidgetCount; + + for (int i = 0; i < toAdd; ++i) { + addToLayout(constructWidgetForIndex(persistent)); } } - if (!trackedIndex.isValid()) { - emit cleanupRequested(this); - } + // Always refresh selection state after modifying widgets + refreshSelectionForIndex(persistent); } } diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h index 73c43abbc..0f1209b29 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.h @@ -33,12 +33,13 @@ public: int bannerOpacity, CardSizeWidget *cardSizeWidget); void onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + void refreshSelectionForIndex(const QPersistentModelIndex &persistent); void clearAllDisplayWidgets(); DeckListModel *deckListModel; QItemSelectionModel *selectionModel; QPersistentModelIndex trackedIndex; - QHash indexToWidgetMap; + QMap> indexToWidgetMap; QString zoneName; QString cardGroupCategory; QString activeGroupCriteria; @@ -53,6 +54,7 @@ public slots: virtual void updateCardDisplays(); virtual void onCardAddition(const QModelIndex &parent, int first, int last); virtual void onCardRemoval(const QModelIndex &parent, int first, int last); + void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); void onActiveSortCriteriaChanged(QStringList activeSortCriteria); void resizeEvent(QResizeEvent *event) override; diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp index e52242c7b..a192d13d8 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp @@ -31,13 +31,17 @@ FlatCardGroupDisplayWidget::FlatCardGroupDisplayWidget(QWidget *parent, layout->addWidget(flowWidget); + // Clear all existing widgets for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) { - FlatCardGroupDisplayWidget::removeFromLayout(indexToWidgetMap.value(idx)); - indexToWidgetMap.value(idx)->deleteLater(); + for (auto widget : indexToWidgetMap.value(idx)) { + FlatCardGroupDisplayWidget::removeFromLayout(widget); + widget->deleteLater(); + } indexToWidgetMap.remove(idx); } FlatCardGroupDisplayWidget::updateCardDisplays(); + disconnect(deckListModel, &QAbstractItemModel::rowsInserted, this, &CardGroupDisplayWidget::onCardAddition); disconnect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &CardGroupDisplayWidget::onCardRemoval); diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp index 27741a79c..dca8ee2a5 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp @@ -30,9 +30,12 @@ OverlappedCardGroupDisplayWidget::OverlappedCardGroupDisplayWidget(QWidget *pare layout->addWidget(overlapWidget); + // Clear all existing widgets for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) { - OverlappedCardGroupDisplayWidget::removeFromLayout(indexToWidgetMap.value(idx)); - indexToWidgetMap.value(idx)->deleteLater(); + for (auto widget : indexToWidgetMap.value(idx)) { + OverlappedCardGroupDisplayWidget::removeFromLayout(widget); + widget->deleteLater(); + } indexToWidgetMap.remove(idx); } 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 4d36164b0..a0624f858 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -450,6 +450,10 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam cardNode->setCardProviderId(printingInfo.getProperty("uuid")); deckList->refreshDeckHash(); emit deckHashChanged(); + // Emit dataChanged for the amount column since we modified it + QModelIndex cardIndex = nodeToIndex(cardNode); + QModelIndex amountIndex = cardIndex.sibling(cardIndex.row(), DeckListModelColumns::CARD_AMOUNT); + emit dataChanged(amountIndex, amountIndex, {Qt::EditRole}); } sort(lastKnownColumn, lastKnownOrder); refreshCardFormatLegalities(); From 3c48d926634b43d96cdf3b008703ed2c2fa9f070 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 24 Jan 2026 02:20:16 -0800 Subject: [PATCH 167/325] [DeckEditor] Show info in PrintingSelector dock when override printings enabled (#6554) * don't hide printing selector dock * extract warning message to separate file * create printing disabled info widget --- cockatrice/CMakeLists.txt | 2 + .../deck_editor_database_display_widget.cpp | 6 +- .../deck_editor_deck_dock_widget.cpp | 10 ++-- ...k_editor_printing_selector_dock_widget.cpp | 20 ++++++- ...eck_editor_printing_selector_dock_widget.h | 8 +++ .../printing_disabled_info_widget.cpp | 56 +++++++++++++++++++ .../printing_disabled_info_widget.h | 24 ++++++++ .../widgets/dialogs/dlg_settings.cpp | 28 +--------- .../dialogs/override_printing_warning.cpp | 38 +++++++++++++ .../dialogs/override_printing_warning.h | 20 +++++++ .../widgets/tabs/abstract_tab_deck_editor.cpp | 3 - .../widgets/tabs/tab_deck_editor.cpp | 13 ----- .../tab_deck_editor_visual.cpp | 15 +---- 13 files changed, 176 insertions(+), 67 deletions(-) create mode 100644 cockatrice/src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp create mode 100644 cockatrice/src/interface/widgets/deck_editor/printing_disabled_info_widget.h create mode 100644 cockatrice/src/interface/widgets/dialogs/override_printing_warning.cpp create mode 100644 cockatrice/src/interface/widgets/dialogs/override_printing_warning.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 54e61ed77..d675b809d 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -47,6 +47,7 @@ set(cockatrice_SOURCES src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp src/interface/widgets/dialogs/dlg_update.cpp src/interface/widgets/dialogs/dlg_view_log.cpp + src/interface/widgets/dialogs/override_printing_warning.cpp src/interface/widgets/dialogs/tip_of_the_day.cpp src/filters/deck_filter_string.cpp src/filters/filter_builder.cpp @@ -178,6 +179,7 @@ set(cockatrice_SOURCES src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp src/interface/widgets/deck_editor/deck_list_style_proxy.cpp src/interface/widgets/deck_editor/deck_state_manager.cpp + src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp src/interface/widgets/general/background_sources.cpp src/interface/widgets/general/display/background_plate_widget.cpp src/interface/widgets/general/display/banner_widget.cpp 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 590fd1d1b..c625ff1d9 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 @@ -187,10 +187,8 @@ void DeckEditorDatabaseDisplayWidget::databaseCustomMenu(QPoint point) QAction *addToDeck, *addToSideboard, *selectPrinting, *edhRecCommander, *edhRecCard; addToDeck = menu.addAction(tr("Add to Deck")); addToSideboard = menu.addAction(tr("Add to Sideboard")); - if (!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { - selectPrinting = menu.addAction(tr("Select Printing")); - connect(selectPrinting, &QAction::triggered, this, [this, card] { deckEditor->showPrintingSelector(); }); - } + selectPrinting = menu.addAction(tr("Select Printing")); + connect(selectPrinting, &QAction::triggered, this, [this, card] { deckEditor->showPrintingSelector(); }); if (canBeCommander(card.getInfo())) { edhRecCommander = menu.addAction(tr("Show on EDHRec (Commander)")); connect(edhRecCommander, &QAction::triggered, this, 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 2ecb4f46a..f939ae99d 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 @@ -713,14 +713,12 @@ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, bool i void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point) { - if (!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { - QMenu menu; + QMenu menu; - QAction *selectPrinting = menu.addAction(tr("Select Printing")); - connect(selectPrinting, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::showPrintingSelector); + QAction *selectPrinting = menu.addAction(tr("Select Printing")); + connect(selectPrinting, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::showPrintingSelector); - menu.exec(deckView->mapToGlobal(point)); - } + menu.exec(deckView->mapToGlobal(point)); } void DeckEditorDeckDockWidget::refreshShortcuts() diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp index 7c4e0de18..7c2e1bec4 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp @@ -1,6 +1,8 @@ #include "deck_editor_printing_selector_dock_widget.h" +#include "../../../client/settings/cache_settings.h" #include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h" +#include "printing_disabled_info_widget.h" #include @@ -14,6 +16,11 @@ DeckEditorPrintingSelectorDockWidget::DeckEditorPrintingSelectorDockWidget(Abstr setFloating(false); createPrintingSelectorDock(); + printingDisabledInfoWidget = new PrintingDisabledInfoWidget(this); + + setVisibleWidget(SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); + connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this, + &DeckEditorPrintingSelectorDockWidget::setVisibleWidget); retranslateUi(); } @@ -26,10 +33,9 @@ void DeckEditorPrintingSelectorDockWidget::createPrintingSelectorDock() printingSelectorFrame->setObjectName("printingSelectorFrame"); printingSelectorFrame->addWidget(printingSelector); - auto *printingSelectorDockContents = new QWidget(); + printingSelectorDockContents = new QWidget(); printingSelectorDockContents->setObjectName("printingSelectorDockContents"); printingSelectorDockContents->setLayout(printingSelectorFrame); - setWidget(printingSelectorDockContents); installEventFilter(deckEditor); connect(printingSelector, &PrintingSelector::prevCardRequested, deckEditor->getDeckDockWidget(), @@ -42,3 +48,13 @@ void DeckEditorPrintingSelectorDockWidget::retranslateUi() { setWindowTitle(tr("Printing Selector")); } + +void DeckEditorPrintingSelectorDockWidget::setVisibleWidget(bool overridePrintings) +{ + if (overridePrintings) { + setWidget(printingDisabledInfoWidget); + } else { + setWidget(printingSelectorDockContents); + printingSelector->updateDisplay(); + } +} diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.h index d7836a938..1037b55fb 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.h @@ -12,7 +12,9 @@ #include +class PrintingDisabledInfoWidget; class TabDeckEditor; + class DeckEditorPrintingSelectorDockWidget : public QDockWidget { Q_OBJECT @@ -20,10 +22,16 @@ public: explicit DeckEditorPrintingSelectorDockWidget(AbstractTabDeckEditor *parent); void createPrintingSelectorDock(); void retranslateUi(); + PrintingSelector *printingSelector; private: AbstractTabDeckEditor *deckEditor; + QWidget *printingSelectorDockContents; + PrintingDisabledInfoWidget *printingDisabledInfoWidget; + +private slots: + void setVisibleWidget(bool overridePrintings); }; #endif // DECK_EDITOR_PRINTING_SELECTOR_DOCK_WIDGET_H diff --git a/cockatrice/src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp new file mode 100644 index 000000000..7c48d1e81 --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp @@ -0,0 +1,56 @@ +#include "printing_disabled_info_widget.h" + +#include "../dialogs/override_printing_warning.h" + +#include +#include +#include +#include +#include + +PrintingDisabledInfoWidget::PrintingDisabledInfoWidget(QWidget *parent) : QWidget(parent) +{ + auto layout = new QVBoxLayout(this); + layout->setObjectName("PrintingDisabledInfoWidgetFrame"); + setLayout(layout); + + QLabel *imageLabel = new QLabel(this); + imageLabel->setAlignment(Qt::AlignCenter); + QIcon icon = style()->standardIcon(QStyle::SP_MessageBoxWarning); + imageLabel->setPixmap(icon.pixmap({60, 60})); + + textLabel = new QLabel(this); + textLabel->setWordWrap(true); + textLabel->setAlignment(Qt::AlignCenter); + + settingsButton = new QPushButton(this); + connect(settingsButton, &QPushButton::clicked, this, &PrintingDisabledInfoWidget::disableOverridePrintings); + + auto buttonLayout = new QHBoxLayout(this); + buttonLayout->addStretch(); + buttonLayout->addWidget(settingsButton); + buttonLayout->addStretch(); + + layout->addStretch(); + layout->addWidget(imageLabel); + layout->addWidget(textLabel); + layout->addLayout(buttonLayout); + layout->addStretch(); + + retranslateUi(); +} + +void PrintingDisabledInfoWidget::retranslateUi() +{ + textLabel->setText( + tr("The Printing Selector is disabled because you have currently enabled the setting to override all " + "selected printings with personal set preferences.\n\n" + "This setting means you'll only see the default printing for each card, instead of being able to select a " + "printing, and will not see the printings other people have selected.\n\n")); + settingsButton->setText(tr("Enable printings again")); +} + +void PrintingDisabledInfoWidget::disableOverridePrintings() +{ + OverridePrintingWarning::execMessageBox(this, false); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/deck_editor/printing_disabled_info_widget.h b/cockatrice/src/interface/widgets/deck_editor/printing_disabled_info_widget.h new file mode 100644 index 000000000..0851ccfad --- /dev/null +++ b/cockatrice/src/interface/widgets/deck_editor/printing_disabled_info_widget.h @@ -0,0 +1,24 @@ +#ifndef COCKATRICE_PRINTING_DISABLED_INFO_WIDGET_H +#define COCKATRICE_PRINTING_DISABLED_INFO_WIDGET_H +#include + +class QPushButton; +class QLabel; + +class PrintingDisabledInfoWidget : public QWidget +{ + Q_OBJECT + + QLabel *textLabel; + QPushButton *settingsButton; + +private slots: + void disableOverridePrintings(); + +public: + explicit PrintingDisabledInfoWidget(QWidget *parent); + + void retranslateUi(); +}; + +#endif // COCKATRICE_PRINTING_DISABLED_INFO_WIDGET_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp index e97ca8921..e7286f078 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp @@ -13,6 +13,7 @@ #include "../interface/widgets/utility/get_text_with_max.h" #include "../interface/widgets/utility/sequence_edit.h" #include "../main.h" +#include "override_printing_warning.h" #include <../../client/settings/card_counter_settings.h> #include @@ -680,32 +681,9 @@ void AppearanceSettingsPage::overrideAllCardArtWithPersonalPreferenceToggled(QT_ { bool enable = static_cast(value); - QString message; - if (enable) { - message = tr("Enabling this feature will disable the use of the Printing Selector.\n\n" - "You will not be able to manage printing preferences on a per-deck basis, " - "or see printings other people have selected for their decks.\n\n" - "You will have to use the Set Manager, available through Card Database -> Manage Sets.\n\n" - "Are you sure you would like to enable this feature?"); - } else { - message = - tr("Disabling this feature will enable the Printing Selector.\n\n" - "You can now choose printings on a per-deck basis in the Deck Editor and configure which printing " - "gets added to a deck by default by pinning it in the Printing Selector.\n\n" - "You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector" - " (other sort orders like alphabetical or release date are available).\n\n" - "Are you sure you would like to disable this feature?"); - } + bool accepted = OverridePrintingWarning::execMessageBox(this, enable); - QMessageBox::StandardButton result = - QMessageBox::question(this, tr("Confirm Change"), message, QMessageBox::Yes | QMessageBox::No); - - if (result == QMessageBox::Yes) { - SettingsCache::instance().setOverrideAllCardArtWithPersonalPreference(value); - // Caches are now invalid. - CardPictureLoader::clearPixmapCache(); - CardPictureLoader::clearNetworkCache(); - } else { + if (!accepted) { // If user cancels, revert the checkbox/state back QTimer::singleShot(0, this, [this, enable]() { overrideAllCardArtWithPersonalPreferenceCheckBox.blockSignals(true); diff --git a/cockatrice/src/interface/widgets/dialogs/override_printing_warning.cpp b/cockatrice/src/interface/widgets/dialogs/override_printing_warning.cpp new file mode 100644 index 000000000..d7699fbe4 --- /dev/null +++ b/cockatrice/src/interface/widgets/dialogs/override_printing_warning.cpp @@ -0,0 +1,38 @@ +#include "override_printing_warning.h" + +#include "../../card_picture_loader/card_picture_loader.h" +#include "../../client/settings/cache_settings.h" + +bool OverridePrintingWarning::execMessageBox(QWidget *parent, bool enable) +{ + QString message; + if (enable) { + message = + QObject::tr("Enabling this feature will disable the use of the Printing Selector.\n\n" + "You will not be able to manage printing preferences on a per-deck basis, " + "or see printings other people have selected for their decks.\n\n" + "You will have to use the Set Manager, available through Card Database -> Manage Sets.\n\n" + "Are you sure you would like to enable this feature?"); + } else { + message = QObject::tr( + "Disabling this feature will enable the Printing Selector.\n\n" + "You can now choose printings on a per-deck basis in the Deck Editor and configure which printing " + "gets added to a deck by default by pinning it in the Printing Selector.\n\n" + "You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector" + " (other sort orders like alphabetical or release date are available).\n\n" + "Are you sure you would like to disable this feature?"); + } + + QMessageBox::StandardButton result = + QMessageBox::question(parent, QObject::tr("Confirm Change"), message, QMessageBox::Yes | QMessageBox::No); + + if (result == QMessageBox::Yes) { + SettingsCache::instance().setOverrideAllCardArtWithPersonalPreference(static_cast(enable)); + // Caches are now invalid. + CardPictureLoader::clearPixmapCache(); + CardPictureLoader::clearNetworkCache(); + return true; + } + + return false; +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/dialogs/override_printing_warning.h b/cockatrice/src/interface/widgets/dialogs/override_printing_warning.h new file mode 100644 index 000000000..f2c926fc3 --- /dev/null +++ b/cockatrice/src/interface/widgets/dialogs/override_printing_warning.h @@ -0,0 +1,20 @@ +#ifndef COCKATRICE_OVERRIDE_PRINTING_WARN_H +#define COCKATRICE_OVERRIDE_PRINTING_WARN_H +#include + +namespace OverridePrintingWarning +{ + +/** + * @brief Pops up the warning message for the changing the override printing setting. + * If the user clicks accept, then this also handles changing the setting and resetting the card image cache. + * + * @param parent The parent widget + * @param enable Whether the user is trying to enable or disable the setting + * @return Whether the user clicked accept. All other ways of closing the window returns false. + */ +bool execMessageBox(QWidget *parent, bool enable); + +} // namespace OverridePrintingWarning + +#endif // COCKATRICE_OVERRIDE_PRINTING_WARN_H 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 0043c0496..44fa46bb1 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -61,9 +61,6 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta cardInfoDockWidget = new DeckEditorCardInfoDockWidget(this); filterDockWidget = new DeckEditorFilterDockWidget(this); printingSelectorDockWidget = new DeckEditorPrintingSelectorDockWidget(this); - connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this, [this] { - printingSelectorDockWidget->setHidden(SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); - }); // Connect deck signals to this tab connect(deckStateManager, &DeckStateManager::isModifiedChanged, this, &AbstractTabDeckEditor::onDeckModified); diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index 1ab7942e2..1d667640d 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -60,10 +60,6 @@ void TabDeckEditor::createMenus() registerDockWidget(viewMenu, filterDockWidget); registerDockWidget(viewMenu, printingSelectorDockWidget); - if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { - dockToActions[printingSelectorDockWidget].menu->setEnabled(false); - } - connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this, [this](bool enabled) { dockToActions[printingSelectorDockWidget].menu->setEnabled(!enabled); }); @@ -148,12 +144,6 @@ void TabDeckEditor::loadLayout() restoreGeometry(layouts.getDeckEditorGeometry()); } - if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { - if (!printingSelectorDockWidget->isHidden()) { - printingSelectorDockWidget->setHidden(true); - } - } - cardDatabaseDockWidget->setMinimumSize(layouts.getDeckEditorCardDatabaseSize()); cardDatabaseDockWidget->setMaximumSize(layouts.getDeckEditorCardDatabaseSize()); @@ -183,9 +173,6 @@ void TabDeckEditor::restartLayout() dockWidget->setFloating(false); } - // Printing selector special case - printingSelectorDockWidget->setVisible(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); - addDockWidget(Qt::LeftDockWidgetArea, cardDatabaseDockWidget); addDockWidget(Qt::RightDockWidgetArea, deckDockWidget); addDockWidget(Qt::RightDockWidgetArea, cardInfoDockWidget); diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 745ec22a8..60fcb0f3f 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -102,13 +102,6 @@ void TabDeckEditorVisual::createMenus() registerDockWidget(viewMenu, filterDockWidget); registerDockWidget(viewMenu, printingSelectorDockWidget); - if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { - dockToActions[printingSelectorDockWidget].menu->setEnabled(false); - } - - connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this, - [this](bool enabled) { dockToActions[printingSelectorDockWidget].menu->setEnabled(!enabled); }); - viewMenu->addSeparator(); aResetLayout = viewMenu->addAction(QString()); @@ -279,12 +272,6 @@ void TabDeckEditorVisual::loadLayout() restoreGeometry(layouts.getDeckEditorGeometry()); } - if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { - if (!printingSelectorDockWidget->isHidden()) { - printingSelectorDockWidget->setHidden(true); - } - } - cardInfoDockWidget->setMinimumSize(layouts.getDeckEditorCardSize()); cardInfoDockWidget->setMaximumSize(layouts.getDeckEditorCardSize()); @@ -310,7 +297,7 @@ void TabDeckEditorVisual::restartLayout() deckDockWidget->setVisible(true); cardInfoDockWidget->setVisible(true); filterDockWidget->setVisible(false); - printingSelectorDockWidget->setVisible(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); + printingSelectorDockWidget->setVisible(true); setCentralWidget(centralWidget); addDockWidget(Qt::RightDockWidgetArea, deckDockWidget); From 12b5525a2d578fd356e26fd6f4eeb64550f43f1e Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sat, 24 Jan 2026 11:21:12 +0100 Subject: [PATCH 168/325] [TabArchidekt] Cleaner filters, infinite scrolling, and a "go back button" (#6545) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [TabArchidekt] Cleaner filters, infinite scrolling, and a "go back button" Took 46 minutes Took 5 seconds * Fix infinite scroll triggering in detail view. Took 25 minutes Took 3 seconds * Use setLabelText() so it's white Took 2 minutes --------- Co-authored-by: Lukas Brübach --- ...idekt_api_response_deck_display_widget.cpp | 15 +- ...chidekt_api_response_deck_display_widget.h | 10 +- ...api_response_deck_entry_display_widget.cpp | 2 +- ..._response_deck_listings_display_widget.cpp | 14 + ...pi_response_deck_listings_display_widget.h | 1 + .../tabs/api/archidekt/tab_archidekt.cpp | 636 ++++++++++-------- .../tabs/api/archidekt/tab_archidekt.h | 210 +++--- 7 files changed, 534 insertions(+), 354 deletions(-) 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 3a2468368..f40b9094f 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 @@ -20,12 +20,22 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi layout = new QVBoxLayout(this); setLayout(layout); - openInEditorButton = new QPushButton(this); - layout->addWidget(openInEditorButton); + navigationContainer = new QWidget(this); + navigationContainerLayout = new QHBoxLayout(navigationContainer); + + homeButton = new QPushButton(navigationContainer); + navigationContainerLayout->addWidget(homeButton); + + connect(homeButton, &QPushButton::clicked, this, &ArchidektApiResponseDeckDisplayWidget::requestSearch); + + openInEditorButton = new QPushButton(navigationContainer); + navigationContainerLayout->addWidget(openInEditorButton); connect(openInEditorButton, &QPushButton::clicked, this, &ArchidektApiResponseDeckDisplayWidget::actOpenInDeckEditor); + layout->addWidget(navigationContainer); + displayOptionsWidget = new VisualDeckDisplayOptionsWidget(this); layout->addWidget(displayOptionsWidget); @@ -80,6 +90,7 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi void ArchidektApiResponseDeckDisplayWidget::retranslateUi() { + homeButton->setText(tr("Back to results")); openInEditorButton->setText(tr("Open Deck in Deck Editor")); } diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h index 58aca429e..aaf272718 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.h @@ -49,6 +49,7 @@ signals: * @param url URL of the deck on Archidekt. */ void requestNavigation(QString url); + void requestSearch(); /** * @brief Emitted when the deck should be opened in the deck editor. @@ -102,9 +103,12 @@ private slots: void onGroupCriteriaChange(const QString &activeGroupCriteria); private: - ArchidektApiResponseDeck response; ///< API deck data container - CardSizeWidget *cardSizeSlider; ///< Slider for adjusting card sizes - QVBoxLayout *layout; ///< Main vertical layout + ArchidektApiResponseDeck response; ///< API deck data container + CardSizeWidget *cardSizeSlider; ///< Slider for adjusting card sizes + QVBoxLayout *layout; ///< Main vertical layout + QWidget *navigationContainer; + QHBoxLayout *navigationContainerLayout; + QPushButton *homeButton; QPushButton *openInEditorButton; ///< Button to open deck in editor VisualDeckDisplayOptionsWidget *displayOptionsWidget; ///< Controls grouping/sorting/display QScrollArea *scrollArea; ///< Scrollable area for deck zones diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp index 736b69ea2..c26220869 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp @@ -213,7 +213,7 @@ void ArchidektApiResponseDeckEntryDisplayWidget::updateScaledPreview() int textMaxWidth = int(newWidth * 0.7); // allow 70% of width for text QFontMetrics fm(previewWidget->topLeftLabel->font()); QString elided = fm.elidedText(response.getName(), Qt::ElideRight, textMaxWidth); - previewWidget->topLeftLabel->setText(elided); + previewWidget->topLeftLabel->setLabelText(elided); previewWidget->topLeftLabel->setToolTip(response.getName()); setFixedWidth(newWidth); diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp index 8746093c7..4d3dfd660 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp @@ -35,6 +35,20 @@ ArchidektApiResponseDeckListingsDisplayWidget::ArchidektApiResponseDeckListingsD layout->addWidget(flowWidget); } +void ArchidektApiResponseDeckListingsDisplayWidget::append(const ArchidektDeckListingApiResponse &data) +{ + for (const auto &deckListing : data.results) { + auto cardListDisplayWidget = + new ArchidektApiResponseDeckEntryDisplayWidget(this, deckListing, imageNetworkManager); + cardListDisplayWidget->setScaleFactor(cardSizeSlider->getSlider()->value()); + connect(cardListDisplayWidget, &ArchidektApiResponseDeckEntryDisplayWidget::requestNavigation, this, + &ArchidektApiResponseDeckListingsDisplayWidget::requestNavigation); + connect(cardSizeSlider->getSlider(), &QSlider::valueChanged, cardListDisplayWidget, + &ArchidektApiResponseDeckEntryDisplayWidget::setScaleFactor); + flowWidget->addWidget(cardListDisplayWidget); + } +} + void ArchidektApiResponseDeckListingsDisplayWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h index f00941239..479de5fc8 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.h @@ -69,6 +69,7 @@ public: explicit ArchidektApiResponseDeckListingsDisplayWidget(QWidget *parent, ArchidektDeckListingApiResponse response, CardSizeWidget *cardSizeSlider); + void append(const ArchidektDeckListingApiResponse &data); /** * @brief Ensures FlowWidget layout properly recomputes on resize. 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 3769cd9a2..352d55c79 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp @@ -9,6 +9,9 @@ #include #include +#include +#include +#include #include #include #include @@ -18,129 +21,258 @@ #include #include #include +#include +#include #include #include #include #include #include -TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) +TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) + : Tab(_tabSupervisor), currentPage(1), isLoadingMore(false), isListMode(true) { + // Initialize network networkManager = new QNetworkAccessManager(this); - networkManager->setTransferTimeout(); // Use Qt's default timeout + networkManager->setTransferTimeout(); networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); - connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(processApiJson(QNetworkReply *))); + connect(networkManager, &QNetworkAccessManager::finished, this, &TabArchidekt::processApiJson); + // Initialize debounce timer searchDebounceTimer = new QTimer(this); - searchDebounceTimer->setSingleShot(true); // We only want it to fire once after inactivity - searchDebounceTimer->setInterval(300); // 300ms debounce + searchDebounceTimer->setSingleShot(true); + searchDebounceTimer->setInterval(300); + connect(searchDebounceTimer, &QTimer::timeout, this, &TabArchidekt::doSearchImmediate); - connect(searchDebounceTimer, &QTimer::timeout, this, [this]() { doSearchImmediate(); }); + initializeUi(); + setupFilterWidgets(); + connectSignals(); + retranslateUi(); + getTopDecks(); +} + +void TabArchidekt::initializeUi() +{ + // Main container container = new QWidget(this); mainLayout = new QVBoxLayout(container); mainLayout->setContentsMargins(0, 0, 0, 0); - container->setLayout(mainLayout); + mainLayout->setSpacing(0); - navigationContainer = new QWidget(container); - navigationContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); - navigationLayout = new QHBoxLayout(navigationContainer); - navigationLayout->setSpacing(3); - navigationContainer->setLayout(navigationLayout); + // Primary toolbar (most important filters) + primaryToolbar = new QWidget(container); + primaryToolbar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + primaryToolbarLayout = new QHBoxLayout(primaryToolbar); + primaryToolbarLayout->setContentsMargins(6, 6, 6, 6); + primaryToolbarLayout->setSpacing(6); - // Sort by - - orderByCombo = new QComboBox(navigationContainer); + // Sort controls + sortByLabel = new QLabel(primaryToolbar); + orderByCombo = new QComboBox(primaryToolbar); orderByCombo->addItems({"name", "updatedAt", "createdAt", "viewCount", "size", "edhBracket"}); - orderByCombo->setCurrentText("updatedAt"); // Pre-select updatedAt + orderByCombo->setCurrentText("updatedAt"); + orderByCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); - // Asc/Desc toggle - orderDirButton = new QPushButton(tr("Desc."), navigationContainer); - orderDirButton->setCheckable(true); // checked = DESC, unchecked = ASC + orderDirButton = new QPushButton(tr("Desc."), primaryToolbar); + orderDirButton->setCheckable(true); orderDirButton->setChecked(true); + orderDirButton->setFixedWidth(60); - connect(orderByCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch); - connect(orderDirButton, &QPushButton::clicked, this, [this](bool checked) { - orderDirButton->setText(checked ? tr("Desc.") : tr("Asc.")); - doSearch(); - }); - - // Colors - QHBoxLayout *colorLayout = new QHBoxLayout(); - QString colorIdentity = "WUBRG"; // Optionally include "C" for colorless once we have a symbol for it + // Color filter (inline) + QWidget *colorWidget = new QWidget(primaryToolbar); + QHBoxLayout *colorLayout = new QHBoxLayout(colorWidget); + colorLayout->setContentsMargins(0, 0, 0, 0); + colorLayout->setSpacing(2); + QString colorIdentity = "WUBRG"; for (const QChar &color : colorIdentity) { - auto *manaSymbol = new ManaSymbolWidget(navigationContainer, color, false, true); - manaSymbol->setFixedWidth(25); + auto *manaSymbol = new ManaSymbolWidget(colorWidget, color, false, true); + manaSymbol->setFixedSize(28, 28); + colorSymbols.append(manaSymbol); colorLayout->addWidget(manaSymbol); connect(manaSymbol, &ManaSymbolWidget::colorToggled, this, [this](QChar c, bool active) { - if (active) { + if (active) activeColors.insert(c); - } else { + else activeColors.remove(c); - } doSearch(); }); } - logicalAndCheck = new QCheckBox("Require ALL colors", navigationContainer); + logicalAndCheck = new QCheckBox(tr("AND"), primaryToolbar); + logicalAndCheck->setToolTip(tr("Require ALL selected colors")); - // Formats + // Common search fields + nameField = new QLineEdit(primaryToolbar); + nameField->setPlaceholderText(tr("Deck name...")); + nameField->setMinimumWidth(150); - formatLabel = new QLabel(this); + ownerField = new QLineEdit(primaryToolbar); + ownerField->setPlaceholderText(tr("Owner...")); + ownerField->setMinimumWidth(120); - formatSettingsWidget = new SettingsButtonWidget(this); + // Filter by label + filterByLabel = new QLabel(primaryToolbar); + + // Package toggle + packagesCheck = new QCheckBox(tr("Packages"), primaryToolbar); + + // Search button + searchButton = new QPushButton(tr("Search"), primaryToolbar); + searchButton->setDefault(true); + + // Advanced filters toggle button + advancedFiltersButton = new QPushButton(tr("Advanced Filters"), primaryToolbar); + advancedFiltersButton->setCheckable(true); + advancedFiltersButton->setChecked(false); + + // Settings + settingsButton = new SettingsButtonWidget(primaryToolbar); + cardSizeSlider = new CardSizeWidget(primaryToolbar, nullptr, SettingsCache::instance().getArchidektPreviewSize()); + settingsButton->addSettingsWidget(cardSizeSlider); + + // Assemble primary toolbar + primaryToolbarLayout->addWidget(sortByLabel); + primaryToolbarLayout->addWidget(orderByCombo); + primaryToolbarLayout->addWidget(orderDirButton); + + // Add separator/spacing + primaryToolbarLayout->addSpacing(12); + + primaryToolbarLayout->addWidget(filterByLabel); + primaryToolbarLayout->addWidget(colorWidget); + primaryToolbarLayout->addWidget(logicalAndCheck); + primaryToolbarLayout->addWidget(nameField, 1); + primaryToolbarLayout->addWidget(ownerField, 1); + primaryToolbarLayout->addWidget(packagesCheck); + primaryToolbarLayout->addWidget(searchButton, 1); + primaryToolbarLayout->addWidget(advancedFiltersButton); + primaryToolbarLayout->addWidget(settingsButton); + + // Secondary toolbar (advanced filters - initially hidden) + secondaryToolbar = new QWidget(container); + secondaryToolbar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + secondaryToolbar->setVisible(false); // Start hidden + secondaryToolbarLayout = new QHBoxLayout(secondaryToolbar); + secondaryToolbarLayout->setContentsMargins(6, 3, 6, 6); + secondaryToolbarLayout->setSpacing(6); + + // Scrollable results area + scrollArea = new QScrollArea(container); + scrollArea->setWidgetResizable(true); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + + resultsContainer = new QWidget(); + resultsLayout = new QVBoxLayout(resultsContainer); + resultsLayout->setContentsMargins(0, 0, 0, 0); + resultsLayout->setSpacing(0); + + scrollArea->setWidget(resultsContainer); + + scrollArea->viewport()->installEventFilter(this); + + mainLayout->addWidget(primaryToolbar); + mainLayout->addWidget(secondaryToolbar); + mainLayout->addWidget(scrollArea); + + setCentralWidget(container); +} + +bool TabArchidekt::eventFilter(QObject *obj, QEvent *event) +{ + if (obj == scrollArea->viewport() && event->type() == QEvent::Wheel) { + auto *wheelEvent = static_cast(event); + + if (wheelEvent->angleDelta().y() < 0 && !isLoadingMore && isListMode) { + loadNextPage(); + wheelEvent->accept(); + return false; // allow scrolling + } + } + + // Always pass the event to the parent to handle normal scrolling + return QWidget::eventFilter(obj, event); +} + +void TabArchidekt::setupFilterWidgets() +{ + // Advanced filters (in secondary toolbar) + + // EDH Bracket + auto *bracketLabel = new QLabel(tr("Bracket:"), secondaryToolbar); + edhBracketCombo = new QComboBox(secondaryToolbar); + edhBracketCombo->addItem(tr("Any")); + edhBracketCombo->addItems({"1", "2", "3", "4", "5"}); + edhBracketCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); + + // Format filter (collapsible) + formatButton = new SettingsButtonWidget(secondaryToolbar); + formatButton->setButtonText(tr("Formats")); + formatButton->setButtonIcon(QPixmap("theme:icons/scale_balanced")); + + QWidget *formatContainer = new QWidget(secondaryToolbar); + QGridLayout *formatLayout = new QGridLayout(formatContainer); + formatLayout->setContentsMargins(4, 4, 4, 4); QStringList formatNames = {"Standard", "Modern", "Commander", "Legacy", "Vintage", "Pauper", "Custom", "Frontier", "Future Std", "Penny Dreadful", "1v1 Commander", "Dual Commander", "Brawl"}; - for (int i = 0; i < formatNames.size(); ++i) { - QCheckBox *formatCheckBox = new QCheckBox(formatNames[i], navigationContainer); - connect(formatCheckBox, &QCheckBox::clicked, this, &TabArchidekt::doSearch); + int row = 0, col = 0; + for (const QString &formatName : formatNames) { + auto *formatCheckBox = new QCheckBox(formatName, formatContainer); formatChecks << formatCheckBox; - formatSettingsWidget->addSettingsWidget(formatCheckBox); + formatLayout->addWidget(formatCheckBox, row, col); + connect(formatCheckBox, &QCheckBox::clicked, this, &TabArchidekt::doSearch); + + col++; + if (col >= 3) { + col = 0; + row++; + } } - // EDH Bracket - edhBracketCombo = new QComboBox(navigationContainer); - edhBracketCombo->addItem(tr("Any Bracket")); - edhBracketCombo->addItems({"1", "2", "3", "4", "5"}); + formatButton->addSettingsWidget(formatContainer); - connect(edhBracketCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch); + cardsField = new QLineEdit(secondaryToolbar); + cardsField->setPlaceholderText(tr("Contains card...")); + cardsField->setMinimumWidth(140); - // Search for Card Packages instead of Decks - packagesCheck = new QCheckBox("Packages", navigationContainer); + commandersField = new QLineEdit(secondaryToolbar); + commandersField->setPlaceholderText(tr("Commander...")); + commandersField->setMinimumWidth(140); - connect(packagesCheck, &QCheckBox::clicked, this, [this]() { - bool disable = packagesCheck->isChecked(); - for (auto *cb : formatChecks) - cb->setEnabled(!disable); - commandersField->setEnabled(!disable); - deckTagNameField->setEnabled(!disable); - edhBracketCombo->setCurrentIndex(0); - edhBracketCombo->setEnabled(!disable); - doSearch(); - }); + deckTagNameField = new QLineEdit(secondaryToolbar); + deckTagNameField->setPlaceholderText(tr("Tag...")); + deckTagNameField->setMinimumWidth(100); - // Deck Name - nameField = new QLineEdit(navigationContainer); - nameField->setPlaceholderText(tr("Deck name contains...")); + // Deck size filter (collapsible) + deckSizeButton = new SettingsButtonWidget(secondaryToolbar); + deckSizeButton->setButtonText(tr("Deck Size")); - // Owner Name - ownerField = new QLineEdit(navigationContainer); - ownerField->setPlaceholderText(tr("Owner name contains...")); + QWidget *sizeContainer = new QWidget(secondaryToolbar); + QHBoxLayout *sizeLayout = new QHBoxLayout(sizeContainer); + sizeLayout->setContentsMargins(4, 4, 4, 4); - // Contained cards - cardsField = new QLineEdit(navigationContainer); - cardsField->setPlaceholderText("Deck contains card..."); + minDeckSizeSpin = new QSpinBox(sizeContainer); + minDeckSizeSpin->setSpecialValueText(tr("Any")); + minDeckSizeSpin->setRange(0, 200); + minDeckSizeSpin->setValue(0); - // Commanders - commandersField = new QLineEdit(navigationContainer); - commandersField->setPlaceholderText("Deck has commander..."); + minDeckSizeLogicCombo = new QComboBox(sizeContainer); + minDeckSizeLogicCombo->addItems({"Exact", "≥", "≤"}); + minDeckSizeLogicCombo->setCurrentIndex(1); - // DB supplemented card search + sizeLayout->addWidget(new QLabel(tr("Cards:"), sizeContainer)); + sizeLayout->addWidget(minDeckSizeSpin); + sizeLayout->addWidget(minDeckSizeLogicCombo); + + deckSizeButton->addSettingsWidget(sizeContainer); + + // Setup card name autocomplete auto cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this); auto displayModel = new CardDatabaseDisplayModel(this); displayModel->setSourceModel(cardDatabaseModel); @@ -161,144 +293,119 @@ TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) cardsField->setCompleter(completer); commandersField->setCompleter(completer); - connect(cardsField, &QLineEdit::textChanged, searchModel, &CardSearchModel::updateSearchResults); - + // Keep autocomplete working for both fields connect(cardsField, &QLineEdit::textChanged, this, [=](const QString &text) { + searchModel->updateSearchResults(text); QString pattern = ".*" + QRegularExpression::escape(text) + ".*"; proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption)); if (!text.isEmpty()) completer->complete(); }); - connect(commandersField, &QLineEdit::textChanged, searchModel, &CardSearchModel::updateSearchResults); - connect(commandersField, &QLineEdit::textChanged, this, [=](const QString &text) { + searchModel->updateSearchResults(text); QString pattern = ".*" + QRegularExpression::escape(text) + ".*"; proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption)); if (!text.isEmpty()) completer->complete(); }); - // Tag Name - deckTagNameField = new QLineEdit(navigationContainer); - deckTagNameField->setPlaceholderText("Deck tag"); + // Assemble secondary toolbar + secondaryToolbarLayout->addWidget(bracketLabel); + secondaryToolbarLayout->addWidget(edhBracketCombo); + secondaryToolbarLayout->addWidget(formatButton); + secondaryToolbarLayout->addWidget(cardsField); + secondaryToolbarLayout->addWidget(commandersField); + secondaryToolbarLayout->addWidget(deckTagNameField); + secondaryToolbarLayout->addWidget(deckSizeButton); + secondaryToolbarLayout->addStretch(); +} - connect(deckTagNameField, &QLineEdit::textChanged, this, &TabArchidekt::doSearch); +void TabArchidekt::connectSignals() +{ + // Advanced filters toggle + connect(advancedFiltersButton, &QPushButton::clicked, this, + [this](bool checked) { secondaryToolbar->setVisible(checked); }); - // Search button - searchPushButton = new QPushButton(navigationContainer); - searchPushButton->setText("Search"); + // These trigger immediate search (no debounce needed) + connect(orderByCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch); + connect(orderDirButton, &QPushButton::clicked, [this](bool checked) { + orderDirButton->setText(checked ? tr("Desc.") : tr("Asc.")); + doSearch(); + }); - connect(searchPushButton, &QPushButton::clicked, this, &TabArchidekt::doSearch); - - // Card Size settings - settingsButton = new SettingsButtonWidget(this); - cardSizeSlider = new CardSizeWidget(this, nullptr, SettingsCache::instance().getArchidektPreviewSize()); connect(cardSizeSlider, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(), &SettingsCache::setArchidektPreviewCardSize); - settingsButton->addSettingsWidget(cardSizeSlider); - // Min deck size - minDeckSizeLabel = new QLabel(navigationContainer); + // Search button triggers immediate search + connect(searchButton, &QPushButton::clicked, this, &TabArchidekt::doSearchImmediate); - minDeckSizeSpin = new QSpinBox(navigationContainer); - minDeckSizeSpin->setSpecialValueText(tr("Disabled")); - minDeckSizeSpin->setRange(0, 200); - minDeckSizeSpin->setValue(0); - - // Size logic - minDeckSizeLogicCombo = new QComboBox(navigationContainer); - minDeckSizeLogicCombo->addItems({"Exact", "≥", "≤"}); // Exact = unset, ≥ = GTE, ≤ = LTE - minDeckSizeLogicCombo->setCurrentIndex(1); // default GTE + // These trigger search (but not text fields) + connect(logicalAndCheck, &QCheckBox::clicked, this, &TabArchidekt::doSearch); + connect(packagesCheck, &QCheckBox::clicked, [this]() { + updatePackageModeState(packagesCheck->isChecked()); + doSearch(); + }); + // Format filters trigger search + connect(edhBracketCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch); connect(minDeckSizeSpin, qOverload(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch); connect(minDeckSizeLogicCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch); - // Page number - pageLabel = new QLabel(navigationContainer); + // Allow Enter key in text fields to trigger search + connect(nameField, &QLineEdit::returnPressed, this, &TabArchidekt::doSearchImmediate); + connect(ownerField, &QLineEdit::returnPressed, this, &TabArchidekt::doSearchImmediate); + connect(cardsField, &QLineEdit::returnPressed, this, &TabArchidekt::doSearchImmediate); + connect(commandersField, &QLineEdit::returnPressed, this, &TabArchidekt::doSearchImmediate); + connect(deckTagNameField, &QLineEdit::returnPressed, this, &TabArchidekt::doSearchImmediate); - pageSpin = new QSpinBox(navigationContainer); - pageSpin->setRange(1, 9999); - pageSpin->setValue(1); + // Format checkboxes trigger search + for (auto *formatCheck : formatChecks) { + connect(formatCheck, &QCheckBox::clicked, this, &TabArchidekt::doSearch); + } +} - connect(pageSpin, qOverload(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch); +void TabArchidekt::updatePackageModeState(bool isPackageMode) +{ + // Disable format-specific and commander-specific filters in package mode + for (auto *cb : formatChecks) { + cb->setEnabled(!isPackageMode); + } - // Page display - currentPageDisplay = new QWidget(container); - currentPageLayout = new QVBoxLayout(currentPageDisplay); - currentPageLayout->setContentsMargins(0, 0, 0, 0); - currentPageDisplay->setLayout(currentPageLayout); + edhBracketCombo->setEnabled(!isPackageMode); + if (isPackageMode) { + edhBracketCombo->setCurrentIndex(0); + } - // Layout composition - - // Sort section - navigationLayout->addWidget(orderByCombo); - navigationLayout->addWidget(orderDirButton); - - // Colors section - navigationLayout->addLayout(colorLayout); - navigationLayout->addWidget(logicalAndCheck); - - // Formats section - navigationLayout->addWidget(formatSettingsWidget); - navigationLayout->addWidget(formatLabel); - - // EDH Bracket - navigationLayout->addWidget(edhBracketCombo); - - // Packages toggle - navigationLayout->addWidget(packagesCheck); - - // Deck name - navigationLayout->addWidget(nameField); - - // Owner name - navigationLayout->addWidget(ownerField); - - // Contained cards - navigationLayout->addWidget(cardsField); - - // Commanders - navigationLayout->addWidget(commandersField); - - // Deck tag - navigationLayout->addWidget(deckTagNameField); - - // Search button - navigationLayout->addWidget(searchPushButton); - - // Card size settings - navigationLayout->addWidget(settingsButton); - - // Min. # of cards in deck - navigationLayout->addWidget(minDeckSizeLabel); - navigationLayout->addWidget(minDeckSizeSpin); - navigationLayout->addWidget(minDeckSizeLogicCombo); - - // Page number - navigationLayout->addWidget(pageLabel); - navigationLayout->addWidget(pageSpin); - - mainLayout->addWidget(navigationContainer); - mainLayout->addWidget(currentPageDisplay); - - // Ensure navigation stays at the top and currentPageDisplay takes remaining space - mainLayout->setStretch(0, 0); // navigationContainer gets minimum space - mainLayout->setStretch(1, 1); // currentPageDisplay expands as much as possible - - setCentralWidget(container); - - TabArchidekt::retranslateUi(); - - getTopDecks(); + commandersField->setEnabled(!isPackageMode); + deckTagNameField->setEnabled(!isPackageMode); } void TabArchidekt::retranslateUi() { - searchPushButton->setText(tr("Search")); - formatLabel->setText(tr("Formats")); - minDeckSizeLabel->setText(tr("Min. # of Cards:")); - pageLabel->setText(tr("Page:")); + sortByLabel->setText(tr("Sort by:")); + orderDirButton->setText(orderDirButton->isChecked() ? tr("Desc.") : tr("Asc.")); + + filterByLabel->setText(tr("Filter by:")); + + logicalAndCheck->setText(tr("AND")); + logicalAndCheck->setToolTip(tr("Require ALL selected colors")); + + nameField->setPlaceholderText(tr("Deck name...")); + ownerField->setPlaceholderText(tr("Owner...")); + packagesCheck->setText(tr("Packages")); + advancedFiltersButton->setText(tr("Advanced Filters")); + + cardsField->setPlaceholderText(tr("Contains card...")); + commandersField->setPlaceholderText(tr("Commander...")); + deckTagNameField->setPlaceholderText(tr("Tag...")); + + formatButton->setButtonText(tr("Formats")); + deckSizeButton->setButtonText(tr("Deck Size")); + + searchButton->setText(tr("Search")); + + settingsButton->setToolTip(tr("Display Settings")); } QString TabArchidekt::buildSearchUrl() @@ -306,13 +413,11 @@ QString TabArchidekt::buildSearchUrl() QUrlQuery query; // orderBy (field + direction) - { - QString field = orderByCombo->currentText(); - if (!field.isEmpty()) { - bool desc = orderDirButton->isChecked(); - QString final = desc ? "-" + field : field; - query.addQueryItem("orderBy", final); - } + QString field = orderByCombo->currentText(); + if (!field.isEmpty()) { + bool desc = orderDirButton->isChecked(); + QString final = desc ? "-" + field : field; + query.addQueryItem("orderBy", final); } // Colors @@ -329,29 +434,26 @@ QString TabArchidekt::buildSearchUrl() query.addQueryItem("logicalAnd", "true"); } - // Formats + // Formats (disabled in package mode) if (!packagesCheck->isChecked()) { QStringList formatIds; - for (int i = 0; i < formatChecks.size(); ++i) + for (int i = 0; i < formatChecks.size(); ++i) { if (formatChecks[i]->isChecked()) { formatIds << QString::number(i + 1); } + } if (!formatIds.isEmpty()) { query.addQueryItem("deckFormat", formatIds.join(",")); } - } - // edhBracket - if (!packagesCheck->isChecked()) { - if (!edhBracketCombo->currentText().isEmpty()) { - if (edhBracketCombo->currentText() != tr("Any Bracket")) { - query.addQueryItem("edhBracket", edhBracketCombo->currentText()); - } + // edhBracket + if (edhBracketCombo->currentIndex() > 0) { + query.addQueryItem("edhBracket", edhBracketCombo->currentText()); } } - // Search for card packages instead of decks + // Package mode if (packagesCheck->isChecked()) { query.addQueryItem("packages", "true"); } @@ -361,54 +463,47 @@ QString TabArchidekt::buildSearchUrl() query.addQueryItem("name", nameField->text()); } - // owner + // Owner if (!ownerField->text().isEmpty()) { query.addQueryItem("ownerUsername", ownerField->text()); } - // cards + // Cards if (!cardsField->text().isEmpty()) { - query.addQueryItem("cardName", cardsField->text()); + query.addQueryItem("cards", cardsField->text()); } - // Commander Name - if (!packagesCheck->isChecked()) { - if (!commandersField->text().isEmpty()) { - query.addQueryItem("commanderName", commandersField->text()); - } + // Commander (disabled in package mode) + if (!packagesCheck->isChecked() && !commandersField->text().isEmpty()) { + query.addQueryItem("commanderName", commandersField->text()); } - // deckTagName - if (!packagesCheck->isChecked()) { - if (!deckTagNameField->text().isEmpty()) { - query.addQueryItem("deckTagName", deckTagNameField->text()); - } + // Deck tag (disabled in package mode) + if (!packagesCheck->isChecked() && !deckTagNameField->text().isEmpty()) { + query.addQueryItem("deckTagName", deckTagNameField->text()); } - // page number - if (pageSpin->value() <= 1) { - query.addQueryItem("page", QString::number(pageSpin->value())); - } + // Page number (for infinite scroll) + query.addQueryItem("page", QString::number(currentPage)); // Min deck size if (minDeckSizeSpin->value() != 0) { query.addQueryItem("size", QString::number(minDeckSizeSpin->value())); - QString logic = "GTE"; // default + QString logic = "GTE"; QString selected = minDeckSizeLogicCombo->currentText(); if (selected == "≥") logic = "GTE"; else if (selected == "≤") logic = "LTE"; else - logic = ""; // Exact = unset + logic = ""; if (!logic.isEmpty()) { query.addQueryItem("sizeLogic", logic); } } - // build final URL QUrl url("https://archidekt.com/api/decks/v3/"); url.setQuery(query); @@ -417,7 +512,12 @@ QString TabArchidekt::buildSearchUrl() void TabArchidekt::doSearch() { - searchDebounceTimer->start(); + // Reset to first page on new search + currentPage = 1; + // We're searching, so we'll be in list mode + isListMode = true; + // Don't debounce - only called by explicit user actions now + doSearchImmediate(); } void TabArchidekt::doSearchImmediate() @@ -428,6 +528,21 @@ void TabArchidekt::doSearchImmediate() networkManager->get(req); } +void TabArchidekt::loadNextPage() +{ + if (isLoadingMore) { + return; + } + + isLoadingMore = true; + currentPage++; + + QString url = buildSearchUrl(); + QNetworkRequest req{QUrl(url)}; + req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); + networkManager->get(req); +} + void TabArchidekt::actNavigatePage(QString url) { QNetworkRequest request{QUrl(url)}; @@ -437,6 +552,7 @@ void TabArchidekt::actNavigatePage(QString url) void TabArchidekt::getTopDecks() { + currentPage = 1; QNetworkRequest request{QUrl(buildSearchUrl())}; request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); networkManager->get(request); @@ -445,7 +561,7 @@ void TabArchidekt::getTopDecks() void TabArchidekt::processApiJson(QNetworkReply *reply) { if (reply->error() != QNetworkReply::NoError) { - qDebug() << "Network error occurred:" << reply->errorString(); + isLoadingMore = false; reply->deleteLater(); return; } @@ -454,17 +570,14 @@ void TabArchidekt::processApiJson(QNetworkReply *reply) QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData); if (!jsonDoc.isObject()) { - qDebug() << "Invalid JSON response received."; + isLoadingMore = false; reply->deleteLater(); return; } QJsonObject jsonObj = jsonDoc.object(); - - // Get the actual URL from the reply QString responseUrl = reply->url().toString(); - // Check if the response URL matches a commander request if (responseUrl.startsWith("https://archidekt.com/api/decks/v3/")) { processTopDecksResponse(jsonObj); } else if (responseUrl.startsWith("https://archidekt.com/api/decks/")) { @@ -473,6 +586,7 @@ void TabArchidekt::processApiJson(QNetworkReply *reply) prettyPrintJson(jsonObj, 4); } + isLoadingMore = false; reply->deleteLater(); } @@ -481,28 +595,27 @@ void TabArchidekt::processTopDecksResponse(QJsonObject reply) ArchidektDeckListingApiResponse deckData; deckData.fromJson(reply); - // **Remove previous page display to prevent stacking** - if (currentPageDisplay) { - mainLayout->removeWidget(currentPageDisplay); - delete currentPageDisplay; - currentPageDisplay = nullptr; + // New search → clear everything + if (currentPage == 1) { + QLayoutItem *item; + while ((item = resultsLayout->takeAt(0)) != nullptr) { + delete item->widget(); + delete item; + } + + listingsWidget = new ArchidektApiResponseDeckListingsDisplayWidget(resultsContainer, deckData, cardSizeSlider); + + connect(listingsWidget, &ArchidektApiResponseDeckListingsDisplayWidget::requestNavigation, this, + &TabArchidekt::actNavigatePage); + + resultsLayout->addWidget(listingsWidget); + return; } - // **Create new currentPageDisplay** - currentPageDisplay = new QWidget(container); - currentPageLayout = new QVBoxLayout(currentPageDisplay); - currentPageDisplay->setLayout(currentPageLayout); - - auto display = new ArchidektApiResponseDeckListingsDisplayWidget(currentPageDisplay, deckData, cardSizeSlider); - connect(display, &ArchidektApiResponseDeckListingsDisplayWidget::requestNavigation, this, - &TabArchidekt::actNavigatePage); - currentPageLayout->addWidget(display); - - mainLayout->addWidget(currentPageDisplay); - - // **Ensure layout stays correct** - mainLayout->setStretch(0, 0); // Keep navigationContainer at the top - mainLayout->setStretch(1, 1); // Make sure currentPageDisplay takes remaining space + // Infinite scroll → append + if (listingsWidget) { + listingsWidget->append(deckData); + } } void TabArchidekt::processDeckResponse(QJsonObject reply) @@ -510,54 +623,47 @@ void TabArchidekt::processDeckResponse(QJsonObject reply) ArchidektApiResponseDeck deckData; deckData.fromJson(reply); - // **Remove previous page display to prevent stacking** - if (currentPageDisplay) { - mainLayout->removeWidget(currentPageDisplay); - delete currentPageDisplay; - currentPageDisplay = nullptr; + // We're in single deck mode - disable infinite scroll + isListMode = false; + + // Clear existing results for single deck view + QLayoutItem *item; + while ((item = resultsLayout->takeAt(0)) != nullptr) { + delete item->widget(); + delete item; } - // **Create new currentPageDisplay** - currentPageDisplay = new QWidget(container); - currentPageLayout = new QVBoxLayout(currentPageDisplay); - currentPageDisplay->setLayout(currentPageLayout); - - auto display = new ArchidektApiResponseDeckDisplayWidget(currentPageDisplay, deckData, cardSizeSlider); + auto display = new ArchidektApiResponseDeckDisplayWidget(resultsContainer, deckData, cardSizeSlider); connect(display, &ArchidektApiResponseDeckDisplayWidget::requestNavigation, this, &TabArchidekt::actNavigatePage); + connect(display, &ArchidektApiResponseDeckDisplayWidget::requestSearch, this, &TabArchidekt::doSearchImmediate); connect(display, &ArchidektApiResponseDeckDisplayWidget::openInDeckEditor, tabSupervisor, &TabSupervisor::openDeckInNewTab); - currentPageLayout->addWidget(display); - - mainLayout->addWidget(currentPageDisplay); - - // **Ensure layout stays correct** - mainLayout->setStretch(0, 0); // Keep navigationContainer at the top - mainLayout->setStretch(1, 1); // Make sure currentPageDisplay takes remaining space + resultsLayout->addWidget(display); } void TabArchidekt::prettyPrintJson(const QJsonValue &value, int indentLevel) { - const QString indent(indentLevel * 2, ' '); // Adjust spacing as needed for pretty printing + const QString indent(indentLevel * 2, ' '); if (value.isObject()) { QJsonObject obj = value.toObject(); for (auto it = obj.begin(); it != obj.end(); ++it) { - qDebug().noquote() << indent + it.key() + ":"; + qInfo().noquote() << indent + it.key() + ":"; prettyPrintJson(it.value(), indentLevel + 1); } } else if (value.isArray()) { QJsonArray array = value.toArray(); for (int i = 0; i < array.size(); ++i) { - qDebug().noquote() << indent + QString("[%1]:").arg(i); + qInfo().noquote() << indent + QString("[%1]:").arg(i); prettyPrintJson(array[i], indentLevel + 1); } } else if (value.isString()) { - qDebug().noquote() << indent + "\"" + value.toString() + "\""; + qInfo().noquote() << indent + "\"" + value.toString() + "\""; } else if (value.isDouble()) { - qDebug().noquote() << indent + QString::number(value.toDouble()); + qInfo().noquote() << indent + QString::number(value.toDouble()); } else if (value.isBool()) { - qDebug().noquote() << indent + (value.toBool() ? "true" : "false"); + qInfo().noquote() << indent + (value.toBool() ? "true" : "false"); } else if (value.isNull()) { - qDebug().noquote() << indent + "null"; + qInfo().noquote() << indent + "null"; } -} +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h index 68a5b78c9..e837e672b 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h @@ -4,26 +4,35 @@ #include "../../interface/widgets/cards/card_size_widget.h" #include "../../interface/widgets/quick_settings/settings_button_widget.h" #include "../../tab.h" +#include "display/archidekt_api_response_deck_listings_display_widget.h" #include #include #include +#include #include #include #include +#include +#include #include #include +#include +#include +#include #include /** Base API link for Archidekt deck search */ inline QString archidektApiLink = "https://archidekt.com/api/decks/v3/?name="; +class ManaSymbolWidget; + /** * @brief Tab for browsing, searching, and filtering Archidekt decks. * * This class provides a comprehensive interface for querying decks from the Archidekt API. * Users can filter decks by name, owner, included cards, commanders, deck tags, colors, EDH bracket, - * and formats. It also provides sorting and pagination, as well as a card size adjustment widget. + * and formats. It supports infinite scroll pagination for seamless browsing. */ class TabArchidekt : public Tab { @@ -63,7 +72,7 @@ public: * - Packages toggle * - Sorting field and direction * - Minimum amount of cards in the deck - * - Pagination (page) + * - Current page (for infinite scroll) */ QString buildSearchUrl(); @@ -90,32 +99,45 @@ public: return cardSizeSlider; } - /** @brief Network manager for handling API requests */ - QNetworkAccessManager *networkManager; - public slots: /** - * @brief Trigger a search using the current filters + * @brief Trigger a debounced search using the current filters * - * Sends a network request to the Archidekt API using the URL generated by buildSearchUrl(). - * Updates the current page display with results asynchronously. + * Resets to page 1 and starts the debounce timer. The actual search will execute + * after 300ms of inactivity. */ void doSearch(); + + /** + * @brief Immediately trigger a search using the current filters + * + * Sends a network request to the Archidekt API using the URL generated by buildSearchUrl(). + * Updates the results display asynchronously. + */ void doSearchImmediate(); + + /** + * @brief Load the next page of results for infinite scroll + * + * Increments the current page and fetches additional results, which are appended + * to the existing results display. + */ + void loadNextPage(); + /** * @brief Process a network reply containing JSON data * @param reply QNetworkReply object with the API response * - * Determines whether the response corresponds to a top decks query or a single deck, + * Determines whether the response corresponds to a deck listing or a single deck, * and dispatches it to the appropriate handler. */ void processApiJson(QNetworkReply *reply); /** * @brief Handle a JSON response containing multiple decks - * @param reply QJsonObject containing top deck listings + * @param reply QJsonObject containing deck listings * - * Clears the previous page display and creates a new display widget for the results. + * If this is page 1, clears previous results. Appends new results to the display. */ void processTopDecksResponse(QJsonObject reply); @@ -123,7 +145,7 @@ public slots: * @brief Handle a JSON response for a single deck * @param reply QJsonObject containing deck data * - * Clears the previous page display and creates a new display widget for the deck details. + * Clears the results area and displays the single deck details. */ void processDeckResponse(QJsonObject reply); @@ -138,107 +160,129 @@ public slots: * @brief Navigate to a specified page URL * @param url The URL to request * - * Typically called when a navigation button is clicked in a deck listing. + * Typically called when a deck card is clicked in the listing. */ void actNavigatePage(QString url); /** * @brief Fetch top decks from the Archidekt API * - * Called on initialization to populate the initial page display. + * Called on initialization to populate the initial results display. */ void getTopDecks(); +protected: + /** + * @brief Event filter to catch wheel events for infinite scroll + * @param obj The object that received the event + * @param event The event to filter + * @return bool Whether the event was handled + */ + bool eventFilter(QObject *obj, QEvent *event) override; + private: - QTimer *searchDebounceTimer; ///< Timer to debounce search requests by spin-boxes etc. + /** + * @brief Initialize the main UI layout and toolbars + * + * Creates the container, main layout, primary toolbar (sort, colors, name, owner, packages), + * secondary toolbar (advanced filters), and scrollable results area. + */ + void initializeUi(); + + /** + * @brief Set up all filter widgets + * + * Creates filter widgets for: + * - Card search with autocomplete + * - Commander search with autocomplete + * - Deck tags + * - Format selection (collapsible) + * - Deck size filter (collapsible) + */ + void setupFilterWidgets(); + + /** + * @brief Connect all signals and slots for UI interactions + * + * Links all widget signals to their appropriate handlers, including + * search triggers, filter changes, package mode toggling, and infinite scroll. + */ + void connectSignals(); + + /** + * @brief Update UI state when package mode is toggled + * @param isPackageMode Whether package mode is currently enabled + * + * Disables format-specific and commander-specific filters when searching + * for card packages instead of full decks. + */ + void updatePackageModeState(bool isPackageMode); + + // --------------------------------------------------------------------- + // Network & Timing + // --------------------------------------------------------------------- + + QNetworkAccessManager *networkManager; ///< Network manager for handling API requests + QTimer *searchDebounceTimer; ///< Timer to debounce search requests + int currentPage; ///< Current page number for infinite scroll + bool isLoadingMore; ///< Flag to prevent multiple simultaneous page loads + bool isListMode; + ArchidektApiResponseDeckListingsDisplayWidget *listingsWidget = nullptr; // --------------------------------------------------------------------- // Layout Containers // --------------------------------------------------------------------- - QWidget *container; ///< Root container for the entire tab - QVBoxLayout *mainLayout; ///< Outer vertical layout containing navigation and page display - QWidget *navigationContainer; ///< Container for all navigation/filter controls - QHBoxLayout *navigationLayout; ///< Layout for horizontal arrangement of filter widgets - QWidget *currentPageDisplay; ///< Widget containing the currently displayed deck(s) - QVBoxLayout *currentPageLayout; ///< Layout for deck display widgets + QWidget *container; ///< Root container for the entire tab + QVBoxLayout *mainLayout; ///< Outer vertical layout containing toolbars and results + + QWidget *primaryToolbar; ///< Primary toolbar with most important filters + QHBoxLayout *primaryToolbarLayout; ///< Layout for primary toolbar + + QWidget *secondaryToolbar; ///< Secondary toolbar with advanced filters + QHBoxLayout *secondaryToolbarLayout; ///< Layout for secondary toolbar + + QScrollArea *scrollArea; ///< Scrollable area for results (enables infinite scroll) + QWidget *resultsContainer; ///< Container widget inside scroll area + QVBoxLayout *resultsLayout; ///< Layout for results (decks appended here) // --------------------------------------------------------------------- - // Sorting Controls + // Primary Toolbar Controls (Most Important) // --------------------------------------------------------------------- + QLabel *sortByLabel; ///< Label for sort controls QComboBox *orderByCombo; ///< Dropdown for selecting the sort field QPushButton *orderDirButton; ///< Toggle button for ascending/descending sort - // --------------------------------------------------------------------- - // Color Filters - // --------------------------------------------------------------------- + QLabel *filterByLabel; ///< Label for filter controls + QList colorSymbols; ///< Mana symbol toggle buttons + QSet activeColors; ///< Set of currently active mana colors + QCheckBox *logicalAndCheck; ///< Require ALL selected colors instead of ANY - QSet activeColors; ///< Set of currently active mana colors - QCheckBox *logicalAndCheck; ///< Require ALL selected colors instead of ANY + QLineEdit *nameField; ///< Input for deck name filter + QLineEdit *ownerField; ///< Input for owner name filter + QCheckBox *packagesCheck; ///< Toggle for searching card packages instead of full decks + QPushButton *searchButton; ///< Button to trigger search + QPushButton *advancedFiltersButton; ///< Button to show/hide advanced filters - // --------------------------------------------------------------------- - // Format Filters - // --------------------------------------------------------------------- - - QLabel *formatLabel; ///< Label displaying "Formats" - SettingsButtonWidget *formatSettingsWidget; ///< Collapsible widget containing format checkboxes - QVector formatChecks; ///< Individual checkboxes for each format - - // --------------------------------------------------------------------- - // EDH Bracket / Package Toggle - // --------------------------------------------------------------------- - - QComboBox *edhBracketCombo; ///< Dropdown for EDH bracket selection - QCheckBox *packagesCheck; ///< Toggle for searching card packages instead of full decks - - // --------------------------------------------------------------------- - // Basic Search Fields - // --------------------------------------------------------------------- - - QLineEdit *nameField; ///< Input for deck name filter - QLineEdit *ownerField; ///< Input for owner name filter - - // --------------------------------------------------------------------- - // Card Filters - // --------------------------------------------------------------------- - - QLineEdit *cardsField; ///< Input for cards included in the deck (comma-separated) - QLineEdit *commandersField; ///< Input for commander cards (comma-separated) - - // --------------------------------------------------------------------- - // Deck Tag - // --------------------------------------------------------------------- - - QLineEdit *deckTagNameField; ///< Input for deck tag filtering - - // --------------------------------------------------------------------- - // Search Trigger - // --------------------------------------------------------------------- - - QPushButton *searchPushButton; ///< Button to trigger the search manually - - // --------------------------------------------------------------------- - // UI Settings (Card Size) - // --------------------------------------------------------------------- - - SettingsButtonWidget *settingsButton; ///< Container for additional UI settings + SettingsButtonWidget *settingsButton; ///< Container for card size settings CardSizeWidget *cardSizeSlider; ///< Slider to adjust card size in results // --------------------------------------------------------------------- - // Minimum Cards in Deck + // Secondary Toolbar Controls (Advanced Filters) // --------------------------------------------------------------------- - QLabel *minDeckSizeLabel; ///< Label for minimum number of cards per deck - QSpinBox *minDeckSizeSpin; ///< Spinner to select minimum deck size - QComboBox *minDeckSizeLogicCombo; ///< Combo box for the size logic to apply + QLineEdit *cardsField; ///< Input for cards included in the deck + QLineEdit *commandersField; ///< Input for commander cards + QLineEdit *deckTagNameField; ///< Input for deck tag filtering - // --------------------------------------------------------------------- - // Pagination - // --------------------------------------------------------------------- + SettingsButtonWidget *formatButton; ///< Collapsible button for format filters + QVector formatChecks; ///< Individual checkboxes for each format + QComboBox *edhBracketCombo; ///< Dropdown for EDH bracket selection - QLabel *pageLabel; ///< Label for current page selection - QSpinBox *pageSpin; ///< Spinner to select the page number for results + SettingsButtonWidget *deckSizeButton; ///< Collapsible button for deck size filter + QSpinBox *minDeckSizeSpin; ///< Spinner to select minimum deck size + QComboBox *minDeckSizeLogicCombo; ///< Combo box for size comparison logic // --------------------------------------------------------------------- // Optional Context @@ -247,4 +291,4 @@ private: CardInfoPtr cardToQuery; ///< Optional pre-selected card for initial filtering }; -#endif // COCKATRICE_TAB_ARCHIDEKT_H +#endif // COCKATRICE_TAB_ARCHIDEKT_H \ No newline at end of file From 2b372c14e424b2073323a66352f09e5a8c70a325 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sat, 24 Jan 2026 11:22:43 +0100 Subject: [PATCH 169/325] Docs: Use `doxygen-awesome-css` theme (#6512) * add doxygen-theme-css submodule * enable theme and disable not needed code references * hide nav sync button * css and cleanup * Move comments to dedicated README to not fail config check --- .gitmodules | 3 ++ Doxyfile | 12 ++++--- cockatrice/resources/help/deck_search.md | 2 +- cockatrice/resources/help/search.md | 2 +- doc/doxygen/DoxygenLayout.xml | 13 ++++---- doc/doxygen/README.md | 14 ++++++++ doc/doxygen/css/cockatrice_docs_style.css | 33 +++++++++++++++++++ ...oxygen_style.css => doxygen_style_old.css} | 0 doc/doxygen/css/hide_nav_sync.css | 10 ++++++ doc/doxygen/extra-pages/index.md | 12 +++---- .../extra-pages/user_documentation/index.md | 8 ++--- doc/doxygen/theme | 1 + 12 files changed, 86 insertions(+), 24 deletions(-) create mode 100644 doc/doxygen/README.md create mode 100644 doc/doxygen/css/cockatrice_docs_style.css rename doc/doxygen/css/{doxygen_style.css => doxygen_style_old.css} (100%) create mode 100644 doc/doxygen/css/hide_nav_sync.css create mode 160000 doc/doxygen/theme diff --git a/.gitmodules b/.gitmodules index a0a57f3d7..732fec7ca 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "vcpkg"] path = vcpkg url = https://github.com/microsoft/vcpkg.git +[submodule "doxygen-awesome-css"] + path = doc/doxygen/theme + url = https://github.com/jothepro/doxygen-awesome-css.git diff --git a/Doxyfile b/Doxyfile index ecacc6b8f..0f2642fd8 100644 --- a/Doxyfile +++ b/Doxyfile @@ -54,7 +54,7 @@ PROJECT_NUMBER = $(COCKATRICE_REF) # 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 = "A cross-platform virtual tabletop for multiplayer card games" +PROJECT_BRIEF = "A 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 @@ -1068,6 +1068,8 @@ RECURSIVE = YES EXCLUDE = build/ \ cmake/ \ + doc/doxygen/theme/docs/ \ + doc/doxygen/theme/include/ \ vcpkg/ \ webclient/ @@ -1430,7 +1432,9 @@ HTML_STYLESHEET = # documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = doc/doxygen/css/doxygen_style.css +HTML_EXTRA_STYLESHEET = doc/doxygen/theme/doxygen-awesome.css \ + doc/doxygen/css/hide_nav_sync.css \ + doc/doxygen/css/cockatrice_docs_style.css # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -1453,7 +1457,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 = AUTO_DARK +HTML_COLORSTYLE = LIGHT # 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 @@ -1764,7 +1768,7 @@ ECLIPSE_DOC_ID = org.doxygen.Project # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -DISABLE_INDEX = YES +DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag diff --git a/cockatrice/resources/help/deck_search.md b/cockatrice/resources/help/deck_search.md index 2ddad9e66..3263daf04 100644 --- a/cockatrice/resources/help/deck_search.md +++ b/cockatrice/resources/help/deck_search.md @@ -49,4 +49,4 @@ searches are case insensitive.
    Grouping:
    red -([[]]:100 or aggro) (Any deck that has red in its filename but is not 100 cards or has aggro in its filename)
    -
    +
    \ No newline at end of file diff --git a/cockatrice/resources/help/search.md b/cockatrice/resources/help/search.md index 6f25e4013..63e6e74c3 100644 --- a/cockatrice/resources/help/search.md +++ b/cockatrice/resources/help/search.md @@ -67,4 +67,4 @@ In this list of examples below, each entry has an explanation and can be clicked
    [o:/counter target .* spell/](#o:/counter target .* spell/) (Any card text with "counter target *something* spell")
    [o:/for each .* and\/or .*/](#o:/for each .* and\/or .*/) (/'s can be escaped with a \)
    - + \ No newline at end of file diff --git a/doc/doxygen/DoxygenLayout.xml b/doc/doxygen/DoxygenLayout.xml index d6847755b..551391db3 100644 --- a/doc/doxygen/DoxygenLayout.xml +++ b/doc/doxygen/DoxygenLayout.xml @@ -7,7 +7,7 @@ - + @@ -15,9 +15,8 @@ - - - + + @@ -28,11 +27,11 @@ - + - + @@ -41,7 +40,7 @@ - + diff --git a/doc/doxygen/README.md b/doc/doxygen/README.md new file mode 100644 index 000000000..988147159 --- /dev/null +++ b/doc/doxygen/README.md @@ -0,0 +1,14 @@ +## Doxygen Documentation Theme + +Required changes to the `Doxyfile` config from the [theme docs](https://jothepro.github.io/doxygen-awesome-css/index.html#autotoc_md16) to make [doxygen-awesome-css](https://github.com/jothepro/doxygen-awesome-css) work: +``` +HTML_EXTRA_STYLESHEET = doxygen-awesome.css # Main CSS file of the theme +GENERATE_TREEVIEW = YES # Optional, also works without +HTML_COLORSTYLE = LIGHT # Required with doxygen-awesome-css theme, Auto Dark Mode will still work +DISABLE_INDEX = NO # YES is bugged in the theme, see jothepro/doxygen-awesome-css/issues/201 +FULL_SIDEBAR = NO # Required for doxygen-awesome-css theme +``` + +
    + +Cockatrice dedicated color adjustments are configured in the [cockatrice_docs_style.css](https://github.com/Cockatrice/Cockatrice/blob/master/doc/doxygen/css/cockatrice_docs_style.css) file that uses & overrides the theme definitions. diff --git a/doc/doxygen/css/cockatrice_docs_style.css b/doc/doxygen/css/cockatrice_docs_style.css new file mode 100644 index 000000000..e0177bdbb --- /dev/null +++ b/doc/doxygen/css/cockatrice_docs_style.css @@ -0,0 +1,33 @@ +/* + +See "Awesome Doxygen CSS" theme docs: +https://jothepro.github.io/doxygen-awesome-css/md_docs_2customization.html#autotoc_md36 + +Adjustments here are based on the css file of the theme and variables defined there. + +*/ + + +/* Light Mode overrides */ +html { + --primary-color: #33a946; + --primary-dark-color: #33a946; + --primary-light-color: #33a946; +} + +/* Dark Mode overrides */ +@media (prefers-color-scheme: dark) { + html:not(.light-mode) { + --primary-color: #33a946; + --primary-dark-color: #33a946; + --primary-light-color: #33a946; + } +} + +/* Dark Mode overrides, defined twice to support both the dark-mode without and with doxygen-awesome-darkmode-toggle.js */ +html.dark-mode { + color-scheme: dark; + --primary-color: #33a946; + --primary-dark-color: #33a946; + --primary-light-color: #33a946; +} diff --git a/doc/doxygen/css/doxygen_style.css b/doc/doxygen/css/doxygen_style_old.css similarity index 100% rename from doc/doxygen/css/doxygen_style.css rename to doc/doxygen/css/doxygen_style_old.css diff --git a/doc/doxygen/css/hide_nav_sync.css b/doc/doxygen/css/hide_nav_sync.css new file mode 100644 index 000000000..ad06ce6ed --- /dev/null +++ b/doc/doxygen/css/hide_nav_sync.css @@ -0,0 +1,10 @@ +/* hide navigation sync control and icons */ +#nav-sync, +#nav-sync * { + display: none !important; +} + +div.nav-sync-icon, +div.nav-sync-icon * { + display: none !important; +} \ No newline at end of file diff --git a/doc/doxygen/extra-pages/index.md b/doc/doxygen/extra-pages/index.md index b5c2b61e5..b3363d3f4 100644 --- a/doc/doxygen/extra-pages/index.md +++ b/doc/doxygen/extra-pages/index.md @@ -1,10 +1,8 @@ -@mainpage Cockatrice Documentation +@mainpage Documentation -# Welcome +Welcome to the Cockatrice code documentation. -This is the **main landing page** of the Cockatrice documentation. +- @subpage user_reference +- @subpage developer_reference -- Go to the @subpage user_reference page -- Review the @subpage developer_reference - -Or check out the Cockatrice Webpage. +Please also check the Cockatrice Webpage or our Code Repository for general information. diff --git a/doc/doxygen/extra-pages/user_documentation/index.md b/doc/doxygen/extra-pages/user_documentation/index.md index 29e76eff6..3f10a0ac6 100644 --- a/doc/doxygen/extra-pages/user_documentation/index.md +++ b/doc/doxygen/extra-pages/user_documentation/index.md @@ -1,17 +1,17 @@ @page user_reference User Reference -# Deck Management +## Deck Management - @subpage creating_decks - @subpage importing_decks - @subpage editing_decks - @subpage exporting_decks -# Release Channels +## Release Channels - @subpage beta_release -# Syntax Help +## Syntax Help - @subpage search_syntax_help -- @subpage deck_search_syntax_help \ No newline at end of file +- @subpage deck_search_syntax_help diff --git a/doc/doxygen/theme b/doc/doxygen/theme new file mode 160000 index 000000000..1f3620084 --- /dev/null +++ b/doc/doxygen/theme @@ -0,0 +1 @@ +Subproject commit 1f3620084ff75734ed192101acf40e9dff01d848 From ffc55aff104ba42cefe606226a419270873d9de2 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sat, 24 Jan 2026 11:28:06 +0100 Subject: [PATCH 170/325] [VDE] Add shortcut to increment cards [Alt + LMB] (#6555) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 13 minutes Co-authored-by: Lukas Brübach --- .../tab_deck_editor_visual.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 60fcb0f3f..74c1021fb 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -156,23 +156,32 @@ void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event, QItemSelectionModel *sel = deckDockWidget->getSelectionModel(); // Double click = swap - if (event->type() == QEvent::MouseButtonDblClick && event->button() == Qt::LeftButton) { + if (event->type() == QEvent::MouseButtonDblClick && event->button() == Qt::LeftButton && + !event->modifiers().testFlag(Qt::AltModifier)) { deckStateManager->swapCardAtIndex(idx); idx = deckStateManager->getModel()->findCard(card.getName(), zoneName); sel->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect); return; } - // Right-click = decrement - if (event->button() == Qt::RightButton) { - actDecrementCard(card); + // Alt + Right-click = decrement + if (event->button() == Qt::RightButton && event->modifiers().testFlag(Qt::AltModifier)) { + if (zoneName == DECK_ZONE_MAIN) { + actDecrementCard(card); + } else { + actDecrementCardFromSideboard(card); + } // Keep selection intact. return; } // Alt + Left click = increment if (event->button() == Qt::LeftButton && event->modifiers().testFlag(Qt::AltModifier)) { - // actIncrementCard(card); + if (zoneName == DECK_ZONE_MAIN) { + actAddCard(card); + } else { + actAddCardToSideboard(card); + } // Keep selection intact. return; } From 5b8897231d50e3098c55d7ab5ce74d424c977d35 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sat, 24 Jan 2026 13:24:19 +0100 Subject: [PATCH 171/325] CI: Include submodules in repo checkout (Docs deployment) (#6557) * Enable submodule checkout in documentation build * Change submodule checkout to recursive --- .github/workflows/documentation-build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml index 396ccd62b..9837ce164 100644 --- a/.github/workflows/documentation-build.yml +++ b/.github/workflows/documentation-build.yml @@ -22,6 +22,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v6 + with: + submodules: recursive - name: Install Graphviz run: | From afdb385770a299854b865f532b66044f3fdd4fda Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Sat, 24 Jan 2026 18:54:29 +0100 Subject: [PATCH 172/325] move returning cards to server_game (#6561) lock the game mutex instead of player mutex when returning cards --- .../remote/game/server_abstract_player.cpp | 45 +------------------ .../server/remote/game/server_game.cpp | 45 +++++++++++++++++++ .../network/server/remote/game/server_game.h | 1 + 3 files changed, 47 insertions(+), 44 deletions(-) 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 a5bb16d13..b08495bad 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 @@ -575,51 +575,8 @@ Server_AbstractPlayer::cmdConcede(const Command_Concede & /*cmd*/, ResponseConta setConceded(true); game->removeArrowsRelatedToPlayer(ges, this); game->unattachCards(ges, this); + game->returnCardsFromPlayer(ges, this); - playerMutex.lock(); - - // Return cards to their rightful owners before conceding the game - static const QRegularExpression ownerRegex{"Owner: ?([^\n]+)"}; - for (const auto &card : zones.value("table")->getCards()) { - if (card == nullptr) { - continue; - } - - const auto ®exResult = ownerRegex.match(card->getAnnotation()); - if (!regexResult.hasMatch()) { - continue; - } - - CardToMove cardToMove; - cardToMove.set_card_id(card->getId()); - - for (const auto *player : game->getPlayers()) { - if (player == nullptr || player->getUserInfo() == nullptr) { - continue; - } - - const auto &ownerToReturnTo = regexResult.captured(1); - const auto &correctOwner = QString::compare(QString::fromStdString(player->getUserInfo()->name()), - ownerToReturnTo, Qt::CaseInsensitive) == 0; - if (!correctOwner) { - continue; - } - - const auto &startZone = zones.value("table"); - const auto &targetZone = player->getZones().value("table"); - - if (startZone == nullptr || targetZone == nullptr) { - continue; - } - - moveCard(ges, startZone, QList() << &cardToMove, targetZone, 0, 0, false); - break; - } - } - - playerMutex.unlock(); - - // All borrowed cards have been returned, can now continue cleanup process clearZones(); Event_PlayerPropertiesChanged event; 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 c04876696..eae23dacf 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -23,6 +23,7 @@ #include "../server_database_interface.h" #include "../server_protocolhandler.h" #include "../server_room.h" +#include "libcockatrice/protocol/pb/command_move_card.pb.h" #include "server_abstract_player.h" #include "server_arrow.h" #include "server_card.h" @@ -31,6 +32,7 @@ #include "server_spectator.h" #include +#include #include #include #include @@ -824,3 +826,46 @@ void Server_Game::getInfo(ServerInfo_Game &result) const result.set_start_time(startTime.toSecsSinceEpoch()); } } + +void Server_Game::returnCardsFromPlayer(GameEventStorage &ges, Server_AbstractPlayer *player) +{ + QMutexLocker locker(&gameMutex); + // Return cards to their rightful owners before conceding the game + static const QRegularExpression ownerRegex{"Owner: ?([^\n]+)"}; + const auto &playerTable = player->getZones().value("table"); + for (const auto &card : playerTable->getCards()) { + if (card == nullptr) { + continue; + } + + const auto ®exResult = ownerRegex.match(card->getAnnotation()); + if (!regexResult.hasMatch()) { + continue; + } + + CardToMove cardToMove; + cardToMove.set_card_id(card->getId()); + + for (const auto *otherPlayer : getPlayers()) { + if (otherPlayer == nullptr || otherPlayer->getUserInfo() == nullptr) { + continue; + } + + const auto &ownerToReturnTo = regexResult.captured(1); + const auto &correctOwner = QString::compare(QString::fromStdString(otherPlayer->getUserInfo()->name()), + ownerToReturnTo, Qt::CaseInsensitive) == 0; + if (!correctOwner) { + continue; + } + + const auto &targetZone = otherPlayer->getZones().value("table"); + + if (playerTable == nullptr || targetZone == nullptr) { + continue; + } + + player->moveCard(ges, playerTable, QList() << &cardToMove, targetZone, 0, 0, false); + break; + } + } +} 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 64374019c..1c658f2ba 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h @@ -218,6 +218,7 @@ public: GameEventStorageItem::EventRecipients recipients = GameEventStorageItem::SendToPrivate | GameEventStorageItem::SendToOthers, int privatePlayerId = -1); + void returnCardsFromPlayer(GameEventStorage &ges, Server_AbstractPlayer *player); }; #endif From 8a126263a99d85ab7f41f3eb0ca8e108d144cba9 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Sat, 24 Jan 2026 21:05:26 +0100 Subject: [PATCH 173/325] do not ignore return value of opening log in servatrice when rotating (#6564) --- servatrice/src/server_logger.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/servatrice/src/server_logger.cpp b/servatrice/src/server_logger.cpp index 79d8cdfe0..de0befacb 100644 --- a/servatrice/src/server_logger.cpp +++ b/servatrice/src/server_logger.cpp @@ -116,7 +116,9 @@ void ServerLogger::rotateLogs() flushBuffer(); logFile->close(); - logFile->open(QIODevice::Append); + if (!logFile->open(QIODevice::Append)) { + std::cerr << "ERROR: Failed to open log file for writing!" << std::endl; + } } QFile *ServerLogger::logFile; From 49e6cf95c4c1738e132869860089d664eefe3ac0 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Sun, 25 Jan 2026 04:34:53 +0100 Subject: [PATCH 174/325] fix package description (#6565) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 574ccecb3..fb4f3bd2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -254,7 +254,7 @@ endif() set(CPACK_PACKAGE_CONTACT "Zach Halpern ") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_NAME}") set(CPACK_PACKAGE_VENDOR "Cockatrice Development Team") -set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/README.md") +set(CPACK_PACKAGE_DESCRIPTION "Cockatrice is an open-source, multiplatform application for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline.") set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}") From 92f02fa4ee4c45d1da203b9dcb7008a6495e0b38 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Sun, 25 Jan 2026 07:37:19 +0100 Subject: [PATCH 175/325] mess with the font rendering of the home screen until it works (#6563) * mess with the font rendering of the home screen until it works * add more fonts --- .../interface/widgets/general/home_styled_button.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cockatrice/src/interface/widgets/general/home_styled_button.cpp b/cockatrice/src/interface/widgets/general/home_styled_button.cpp index 07c9894c1..e8f2722cd 100644 --- a/cockatrice/src/interface/widgets/general/home_styled_button.cpp +++ b/cockatrice/src/interface/widgets/general/home_styled_button.cpp @@ -41,6 +41,8 @@ QString HomeStyledButton::generateButtonStylesheet(const QPair & return QString(R"( QPushButton { font-size: 34px; + font-weight: bold; + font-family: sans-serif, "Segoe UI", "Helvetica Neue"; padding: 30px; color: white; border: 2px solid %1; @@ -88,16 +90,12 @@ void HomeStyledButton::paintEvent(QPaintEvent *event) painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::TextAntialiasing); - QFont font = this->font(); - font.setBold(true); - painter.setFont(font); - - QFontMetrics fm(font); - QSize textSize = fm.size(Qt::TextSingleLine, this->text()); + QFontMetrics fm(font()); + QSize textSize = fm.size(Qt::TextSingleLine, text()); QPointF center((width() - textSize.width()) / 2.0, (height() + textSize.height() / 2.0) / 2.0); QPainterPath path; - path.addText(center, font, this->text()); + path.addText(center, font(), text()); painter.setPen(QPen(Qt::black, 2.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); painter.setBrush(Qt::white); From c02cf5e89e1d8cc227f3fce8856dacd4d960150f Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 25 Jan 2026 01:36:10 -0800 Subject: [PATCH 176/325] [VDE] Fix crash from alt-click when card has unknown set (#6566) --- .../interface/widgets/deck_editor/deck_state_manager.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 09254608e..8afbfaaa2 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp @@ -175,9 +175,11 @@ QModelIndex DeckStateManager::addCard(const ExactCard &card, const QString &zone QString zone = card.getInfo().getIsToken() ? DECK_ZONE_TOKENS : zoneName; - QString reason = tr("Added (%1): %2 (%3) %4") - .arg(zone, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(), - card.getPrinting().getProperty("num")); + CardSetPtr set = card.getPrinting().getSet(); + QString setName = set ? set->getCorrectedShortName() : ""; + + QString reason = + tr("Added (%1): %2 (%3) %4").arg(zone, card.getName(), setName, card.getPrinting().getProperty("num")); QModelIndex idx = modifyDeck(reason, [&card, &zone](auto model) { return model->addCard(card, zone); }); From 303bd8b60752ff6fae029e44f47dbd4bad5ec979 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Sun, 25 Jan 2026 22:05:19 +0100 Subject: [PATCH 177/325] detect recursive redirects (#6570) * detect recursive redirects * handle failure with normal failure handler --- .../card_picture_loader_worker_work.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp index d5aac645e..cd41ac27f 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp @@ -70,16 +70,22 @@ void CardPictureLoaderWorkerWork::picDownloadFailed() void CardPictureLoaderWorkerWork::handleNetworkReply(QNetworkReply *reply) { QVariant redirectTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); + bool redirectFailure = false; if (redirectTarget.isValid()) { QUrl url = reply->request().url(); QUrl redirectUrl = redirectTarget.toUrl(); if (redirectUrl.isRelative()) { redirectUrl = url.resolved(redirectUrl); } - emit urlRedirected(url, redirectUrl); + if (url == redirectUrl) { + qCWarning(CardPictureLoaderWorkerWorkLog) << "recusive redirect detected!"; + redirectFailure = true; + } else { + emit urlRedirected(url, redirectUrl); + } } - if (reply->error()) { + if (redirectFailure || reply->error()) { handleFailedReply(reply); } else { handleSuccessfulReply(reply); From 0b4e7be59616a7e548a1a18ece3db19bfac1d925 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Sun, 25 Jan 2026 22:05:53 +0100 Subject: [PATCH 178/325] followup to #6563 (#6569) * mess with the font rendering of the home screen until it works * add more fonts * increase font weight * fix outline on the text --- .../src/interface/widgets/general/home_styled_button.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cockatrice/src/interface/widgets/general/home_styled_button.cpp b/cockatrice/src/interface/widgets/general/home_styled_button.cpp index e8f2722cd..c7bdd1e5e 100644 --- a/cockatrice/src/interface/widgets/general/home_styled_button.cpp +++ b/cockatrice/src/interface/widgets/general/home_styled_button.cpp @@ -97,7 +97,7 @@ void HomeStyledButton::paintEvent(QPaintEvent *event) QPainterPath path; path.addText(center, font(), text()); - painter.setPen(QPen(Qt::black, 2.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); - painter.setBrush(Qt::white); - painter.drawPath(path); + QPen pen(Qt::black, 4.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); + painter.strokePath(path, pen); + painter.fillPath(path, Qt::white); } From 630c71f1236f817fac66814bc3e5e226e3dadc61 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sun, 25 Jan 2026 22:06:06 +0100 Subject: [PATCH 179/325] [VDE] Insert at correct index onDataChanged() instead of just appending. (#6556) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 25 minutes Took 3 seconds Co-authored-by: Lukas Brübach --- .../card_group_display_widget.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp index 7239a7c3f..fa304816f 100644 --- a/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp @@ -325,11 +325,22 @@ void CardGroupDisplayWidget::onDataChanged(const QModelIndex &topLeft, indexToWidgetMap.remove(persistent); } } else { - // Add new widgets int toAdd = newAmount - currentWidgetCount; + int insertBase = 0; + // Count widgets belonging to rows before this one + for (int r = 0; r < row; ++r) { + QModelIndex prevIdx = deckListModel->index(r, 0, trackedIndex); + QPersistentModelIndex prevPersistent(prevIdx); + + if (indexToWidgetMap.contains(prevPersistent)) { + insertBase += indexToWidgetMap.value(prevPersistent).size(); + } + } + + // Insert after existing copies of this card for (int i = 0; i < toAdd; ++i) { - addToLayout(constructWidgetForIndex(persistent)); + insertIntoLayout(constructWidgetForIndex(persistent), insertBase + currentWidgetCount + i); } } From 915da79cadb651f72764eb33adb3427abf3056fa Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sun, 25 Jan 2026 16:50:40 -0500 Subject: [PATCH 180/325] Translate cockatrice/cockatrice_en@source.ts in de (#6541) 100% translated source file: 'cockatrice/cockatrice_en@source.ts' on 'de'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- cockatrice/translations/cockatrice_de.ts | 334 ++++++++++++----------- 1 file changed, 173 insertions(+), 161 deletions(-) diff --git a/cockatrice/translations/cockatrice_de.ts b/cockatrice/translations/cockatrice_de.ts index b24a93992..ed1a88be8 100644 --- a/cockatrice/translations/cockatrice_de.ts +++ b/cockatrice/translations/cockatrice_de.ts @@ -156,7 +156,13 @@ You will not be able to manage printing preferences on a per-deck basis, or see You will have to use the Set Manager, available through Card Database -> Manage Sets. Are you sure you would like to enable this feature? - + Diese Funktion aktivieren wird die Nutzung der Druckauswahl deaktivieren. + +Es wird ihnen nicht möglich sein ihre Druckeinstellungen auf einer Per-Deck Basis zu verwalten, oder die Drucke, die andere Personen für ihre Decks ausgewählt haben, zu sehen. + +Sie werden den Set-Manager verwenden müssen, der über Kartendatenbank -> Sets verwalten verfügbar ist. + +Sind sie sicher, dass diese Funktion aktiviert werden soll? @@ -167,12 +173,18 @@ You can now choose printings on a per-deck basis in the Deck Editor and configur You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). Are you sure you would like to disable this feature? - + Diese Funktion deaktivieren wird die Nutzung der Druckauswahl aktivieren. + +Es wird ihnen nun möglich sein ihre Druckeinstellungen auf einer Per-Deck Basis im Deck-Editor zu verwalten und zu konfigurieren, welcher Druck standardmäßig in ein Deck hinzugefügt wird, indem sie es anheften in der Druckauswahl + +Sie können auch den Set-Manager verwenden, um selbstständig die Sortierungsreihenfolge für Drucke in der Druckauswahl festzulegen (andere Sortierungsoptionen wie alphabetisch oder Veröffentlichkeitsdatum sind verfügbar). + +Sind sie sicher, dass diese Funktion deaktiviert werden soll? Confirm Change - + Änderung bestätigen @@ -217,7 +229,7 @@ Are you sure you would like to disable this feature? Show game filter toolbar above list in room tab - + Spiel-Filter Symbolleiste über der Liste im Raum-Tab anzeigen @@ -237,7 +249,7 @@ Are you sure you would like to disable this feature? Override all card art with personal set preference (Pre-ProviderID change behavior) - + Überschreiben aller Kartenbilder mit persönlichen Set-Präferenzen (Vor-AnbieterID Wechselverhalten) @@ -326,7 +338,7 @@ Are you sure you would like to disable this feature? Open Deck in Deck Editor - + Deck im Deck-Editor öffnen @@ -550,7 +562,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Name (Exact) - + Name (Exakt) @@ -674,12 +686,12 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Set: - + Set: Collector Number: - + Sammlernummer: @@ -995,22 +1007,22 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Add Panel - + Feld hinzufügen Remove Panel - + Feld entfernen Save Layout - + Layout speichern Load Layout - + Layout laden @@ -1018,7 +1030,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Card Database - + Kartendatenbank @@ -1082,7 +1094,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Loading Database... - + Datenbank wird geladen... @@ -1147,7 +1159,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Format: - + Format: @@ -1488,32 +1500,32 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Undo - + Rückgängig machen Redo - + Wiederholen Undo/Redo history - + Rückgängig machen/Wiederholen Verlauf Click on an entry to revert to that point in the history. - + Einen Eintrag anklicken um zu diesem Zeitpunkt im Verlauf zurückzukehren [redo] - + [wiederholen]  [undo] - + [rückgängig machen]  @@ -1746,57 +1758,57 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Rename deck to "%1" from "%2" - + Deck umbenennen von "%1" zu "%2" Updated comments (was %1 chars, now %2 chars) - + Kommentare aktualisiert (davor %1 Zeichen, jetzt %2 Zeichen) Set banner card to %1 (%2) - + Bannerkarte setzen als %1 (%2) Tags changed - + Tags geändert Set format to %1 - + Format setzen zu %1 Added (%1): %2 (%3) %4 - + (%1): %2 (%3) %4 hinzugefügt Moved to %1 1 × "%2" (%3) - + Nach %1 1 × "%2" (%3) bewegt Removed "%1" (all copies) - + "%1" entfernt (alle Kopien) %1 1 × "%2" (%3) - + %1 1 × "%2" (%3) Added - + Hinzugefügt Removed - + Entfernt @@ -2150,7 +2162,7 @@ Möchten sie das Deck ins .cod Format konvertieren? Create game as judge - + Spiel als Richter erstellen @@ -2618,7 +2630,7 @@ Aktivieren Sie die Edition „Token" im „Editionen verwalten...“ Menü Hide games not created by buddies Hide games not created by buddy - + Nicht von Freunden erstellte Spiele verstecken @@ -3150,17 +3162,17 @@ Die E-Mail-Adresse wird zur Accountverifikation genutzt. Bulk modified printings. - + Drucke in Masse modifizieren. Cleared all printing information. - + Alle Druckinformation gelöscht. Set all printings to preferred. - + Alle Drucke auf bevorzugt setzen. @@ -3556,63 +3568,63 @@ Eventuell müssen sie manuell eine neue Version herunterladen. Draw Probability Settings - + Zieh-Wahrscheinlichkeitseinstellungen Criteria: - + Kriterien: Card Name - + Kartenname Type - + Typ Subtype - + Untertyp Mana Value - + Manawert Exactness: - + Genauheit: At least - + mindestens Exactly - + genau Quantity (N): - + Menge (N): Cards drawn (M): - + Karten die gezogen wurden (M): cards - + Karten @@ -3620,67 +3632,67 @@ Eventuell müssen sie manuell eine neue Version herunterladen. Draw Probability - + Zieh-Wahrscheinlichkeit Probability of drawing - + Wahrscheinlichkeit des Ziehens Card Name - + Kartenname Type - + Typ Subtype - + Untertyp Mana Value - + Manawert At least - + mindestens Exactly - + genau card(s) having drawn at least - + Karte(n) gezogen haben mindestens cards - + Karten Category - + Kategorie Qty - + Menge Odds (%) - + Wahrscheinlichkeit (%) @@ -3757,7 +3769,7 @@ Eventuell müssen sie manuell eine neue Version herunterladen. Game Changers - + Spielwechsler @@ -3765,7 +3777,7 @@ Eventuell müssen sie manuell eine neue Version herunterladen. Budget - + Budget @@ -3916,12 +3928,12 @@ Eventuell müssen sie manuell eine neue Version herunterladen. Join Game as Judge - + Spiel als Richter beitreten Spectate Game as Judge - + Spiel als Richter zuschauen @@ -3961,7 +3973,7 @@ Eventuell müssen sie manuell eine neue Version herunterladen. Join as judge - + Als Richter beitreten @@ -3971,7 +3983,7 @@ Eventuell müssen sie manuell eine neue Version herunterladen. Join as judge spectator - + Als Richter-Zuschauer beitreten @@ -3989,32 +4001,32 @@ Eventuell müssen sie manuell eine neue Version herunterladen. All types - + Alle Typen Filter by game name... - + Nach Spielnamen filtern... Filter by game type/format - + Nach Spieltyp/Format filtern Hide games not created by buddies - + Nicht von Freunden erstellte Spiele ausblenden Hide full games - + Volle Spiele ausblenden Hide started games - + Gestartete Spiele ausblenden @@ -4297,7 +4309,7 @@ Eventuell müssen sie manuell eine neue Version herunterladen. &All players - + &Alle Spieler @@ -4330,37 +4342,37 @@ Eventuell müssen sie manuell eine neue Version herunterladen. Sort hand by... - + Hand nach ... sortieren Name - + Name Type - + Typ Mana Value - + Manawert Take &mulligan (Choose hand size) - + &Mulligan nehmen (Handgröße wählen) Take mulligan (Same hand size) - + Mulligan nehmen (Gleiche Handgröße) Take mulligan (Hand size - 1) - + Mulligan nehmen (Handgröße -1) @@ -4396,7 +4408,7 @@ Eventuell müssen sie manuell eine neue Version herunterladen. All players - + Alle Spieler @@ -4429,7 +4441,7 @@ Eventuell müssen sie manuell eine neue Version herunterladen. Browse Archidekt - + Archidekt durchsuchen @@ -4638,17 +4650,17 @@ Eventuell müssen sie manuell eine neue Version herunterladen. &All players - + &Alle Spieler Reveal top cards of library - + Oberste Karten der Bibliothek vorzeigen Number of cards: (max. %1) - + Anzahl der Karten: (max. %1) @@ -5484,42 +5496,42 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. Mana Base Configuration - + Mana-Basis Konfiguration Display type: - + Anzeigetyp: pie - + Torte bar - + Leiste combinedBar - + kombinierteLeiste Filter Colors (optional): - + Farben filtern (optional): OK - + OK Cancel - + Abbrechen @@ -5535,47 +5547,47 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. Group By: - + Gruppieren nach: type - + Typ color - + Farbe subtype - + Untertyp power - + Stärke toughness - + Widerstandskraft Filters (optional): - + Filter (optional): Show main bar row - + Anzeigen der Hauptleistenreihe Show per-category rows - + Anzeige der per-Kategoriereihen @@ -5591,27 +5603,27 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. Display type: - + Anzeigetyp: pie - + Torte bar - + Leiste combinedBar - + kombinierteLeiste Filter Colors (optional): - + Farben filtern (optional): @@ -5627,27 +5639,27 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. Top display type: - + Oberster Anzeigetyp: pie - + Torte bar - + Leiste Colors: - + Farben: Show per-color rows - + Anzeige der per-Farbenreihen @@ -5655,12 +5667,12 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. %1 pips (%2 cards) - + %1 Pips (%2 Karten) %1 mana (%2 cards) - + %1 Mana (%2 Karten) @@ -5668,12 +5680,12 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. Mana Production + Devotion - + Manaerzeugung + Hingabe Mana Distribution Settings - + Manaverteilungseinstellungen @@ -7633,58 +7645,58 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! Desc. - + Beschreibung. Asc. - + Aufsteigend. Any Bracket - + Jede Klammer Deck name contains... - + Deckname enthält... Owner name contains... - + Besitzername enthält... Disabled - + Deaktiviert Search - + Suche Formats - + Formate Min. # of Cards: - + Min. # an Karten: Page: - + Seite: Archidekt: - + Archidekt: @@ -7712,7 +7724,7 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! Card Database - + Kartendatenbank @@ -8726,7 +8738,7 @@ Je mehr Informationen Sie angeben, desto spezifischer fallen die Ergebnisse aus. Archidekt - + Archidekt @@ -8854,7 +8866,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Database Display - + Datenbankanzeige @@ -9350,7 +9362,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Do not delete &arrows inside of subphases - + Keine &Pfeile löschen in Unterphasen @@ -9552,7 +9564,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Search filter... - + Suchfilter.... @@ -9575,22 +9587,22 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Do not display formats with less than this amount of cards in the database - + Keine Formate mit weniger als dieser Anzahl an Karten in der Datenbank anzeigen Filter mode (AND/OR/NOT conjunctions of filters) - + Filtermodus (UND/ODER/NICHT Verbindungen von Filtern) Mode: Exact Match - + Modus: Exakte Übereinstimmung Mode: Includes - + Modus: Enthält @@ -9621,7 +9633,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Filter by name... (Exact match) - + Nach Namen filtern... (Exakte Übereinstimmung) @@ -9711,7 +9723,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Visual - + Visuell @@ -9726,12 +9738,12 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Sort by: - + Sortieren nach: Filter by: - + Filtern nach: @@ -9756,7 +9768,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Table - + Tisch @@ -9764,43 +9776,43 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Group by: - + Gruppieren nach: Change how cards are divided into categories/groups. - + Wie Karten innerhalb ihrer Kategorien/Gruppierungen sortiert werden ändern Sort by: - + Sortieren nach: Click and drag to change the sort order within the groups - + Klicken und ziehen, um die Sortierungsreihenfolge innerhalb von Gruppen zu ändern Configure how cards are sorted within their groups - + Wie Karten innerhalb ihrer Gruppierung sortiert werden einstellen Toggle Layout: Overlap - + Umschalten des Layouts: Überlappung Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Wie Karten innerhalb von Zonen angezeigt werden ändern ( z.B. überlappend oder vollständig sichtbar) Toggle Layout: Flat - + Umschalten des Layouts: Flach @@ -9821,7 +9833,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Type a card name here for suggestions from the database... - + Hier einen Kartennahmen eingeben für Vorschläge aus der Datenbank... @@ -10945,7 +10957,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Reveal Selected Cards to All Players - + Ausgewählte Karten allen Spielern vorzeigen @@ -11100,12 +11112,12 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Mulligan (Same hand size) - + Mulligan nehmen (Gleiche Handgröße) Mulligan (Hand size - 1) - + Mulligan nehmen (Handgröße -1) @@ -11135,27 +11147,27 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Sort Hand by Name - + Hand sortieren nach Name Sort Hand by Type - + Hand sortieren nach Typ Sort Hand by Mana Value - + Hand sortieren nach Manawert Reveal Hand to All Players - + Hand allen Spielern vorzeigen Reveal Random Card to All Players - + Zufällige Karte allen Spielern vorzeigen From 364470b3c81d4fc1c5bca89e20975282d4f9986a Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sun, 25 Jan 2026 16:51:03 -0500 Subject: [PATCH 181/325] Updates for project Cockatrice and language en_US (#6543) * Translate oracle/oracle_en@source.ts in en_US 100% translated source file: 'oracle/oracle_en@source.ts' on 'en_US'. * Translate cockatrice_en@source.ts in en_US 100% translated source file: 'cockatrice_en@source.ts' on 'en_US'. --------- Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- cockatrice/translations/cockatrice_en_US.ts | 308 ++++++++++---------- oracle/translations/oracle_en_US.ts | 20 +- 2 files changed, 164 insertions(+), 164 deletions(-) diff --git a/cockatrice/translations/cockatrice_en_US.ts b/cockatrice/translations/cockatrice_en_US.ts index 58ca883c8..c74e63114 100644 --- a/cockatrice/translations/cockatrice_en_US.ts +++ b/cockatrice/translations/cockatrice_en_US.ts @@ -101,7 +101,7 @@ Please check that the directory is writable and try again. Add Analytics Panel - + Add Analytics Panel @@ -229,7 +229,7 @@ Are you sure you would like to disable this feature? Show game filter toolbar above list in room tab - + Show game filter toolbar above list in room tab @@ -338,7 +338,7 @@ Are you sure you would like to disable this feature? Open Deck in Deck Editor - + Open Deck in Deck Editor @@ -562,7 +562,7 @@ This is only saved for moderators and cannot be seen by the banned person. Name (Exact) - + Name (Exact) @@ -686,12 +686,12 @@ This is only saved for moderators and cannot be seen by the banned person. Set: - + Set: Collector Number: - + Collector Number: @@ -1007,22 +1007,22 @@ This is only saved for moderators and cannot be seen by the banned person. Add Panel - + Add Panel Remove Panel - + Remove Panel Save Layout - + Save Layout Load Layout - + Load Layout @@ -1030,7 +1030,7 @@ This is only saved for moderators and cannot be seen by the banned person. Card Database - + Card Database @@ -1094,7 +1094,7 @@ This is only saved for moderators and cannot be seen by the banned person. Loading Database... - + Loading Database... @@ -1159,7 +1159,7 @@ This is only saved for moderators and cannot be seen by the banned person. Format: - + Format: @@ -1500,32 +1500,32 @@ This is only saved for moderators and cannot be seen by the banned person. Undo - + Undo Redo - + Redo Undo/Redo history - + Undo/Redo history Click on an entry to revert to that point in the history. - + Click on an entry to revert to that point in the history. [redo] - + [redo] [undo] - + [undo] @@ -1758,57 +1758,57 @@ This is only saved for moderators and cannot be seen by the banned person. Rename deck to "%1" from "%2" - + Rename deck to "%1" from "%2" Updated comments (was %1 chars, now %2 chars) - + Updated comments (was %1 chars, now %2 chars) Set banner card to %1 (%2) - + Set banner card to %1 (%2) Tags changed - + Tags changed Set format to %1 - + Set format to %1 Added (%1): %2 (%3) %4 - + Added (%1): %2 (%3) %4 Moved to %1 1 × "%2" (%3) - + Moved to %1 1 × "%2" (%3) Removed "%1" (all copies) - + Removed "%1" (all copies) %1 1 × "%2" (%3) - + %1 1 × "%2" (%3) Added - + Added Removed - + Removed @@ -2162,7 +2162,7 @@ This will kick all non-ready players from the game. Create game as judge - + Create game as judge @@ -2630,7 +2630,7 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Hide games not created by buddies Hide games not created by buddy - + Hide games not created by buddies @@ -3162,17 +3162,17 @@ Your email will be used to verify your account. Bulk modified printings. - + Bulk modified printings. Cleared all printing information. - + Cleared all printing information. Set all printings to preferred. - + Set all printings to preferred. @@ -3568,63 +3568,63 @@ You may have to manually download the new version. Draw Probability Settings - + Draw Probability Settings Criteria: - + Criteria: Card Name - + Card Name Type - + Type Subtype - + Subtype Mana Value - + Mana Value Exactness: - + Exactness: At least - + At least Exactly - + Exactly Quantity (N): - + Quantity (N): Cards drawn (M): - + Cards drawn (M): cards - + cards @@ -3632,67 +3632,67 @@ You may have to manually download the new version. Draw Probability - + Draw Probability Probability of drawing - + Probability of drawing Card Name - + Card Name Type - + Type Subtype - + Subtype Mana Value - + Mana Value At least - + At least Exactly - + Exactly card(s) having drawn at least - + card(s) having drawn at least cards - + cards Category - + Category Qty - + Qty Odds (%) - + Odds (%) @@ -3769,7 +3769,7 @@ You may have to manually download the new version. Game Changers - + Game Changers @@ -3777,7 +3777,7 @@ You may have to manually download the new version. Budget - + Budget @@ -3928,12 +3928,12 @@ You may have to manually download the new version. Join Game as Judge - + Join Game as Judge Spectate Game as Judge - + Spectate Game as Judge @@ -3973,7 +3973,7 @@ You may have to manually download the new version. Join as judge - + Join as judge @@ -3983,7 +3983,7 @@ You may have to manually download the new version. Join as judge spectator - + Join as judge spectator @@ -4001,32 +4001,32 @@ You may have to manually download the new version. All types - + All types Filter by game name... - + Filter by game name... Filter by game type/format - + Filter by game type/format Hide games not created by buddies - + Hide games not created by buddies Hide full games - + Hide full games Hide started games - + Hide started games @@ -4342,37 +4342,37 @@ You may have to manually download the new version. Sort hand by... - + Sort hand by... Name - + Name Type - + Type Mana Value - + Mana Value Take &mulligan (Choose hand size) - + Take &mulligan (Choose hand size) Take mulligan (Same hand size) - + Take mulligan (Same hand size) Take mulligan (Hand size - 1) - + Take mulligan (Hand size - 1) @@ -4408,7 +4408,7 @@ You may have to manually download the new version. All players - + All players @@ -4441,7 +4441,7 @@ You may have to manually download the new version. Browse Archidekt - + Browse Archidekt @@ -5496,42 +5496,42 @@ Cockatrice will now reload the card database. Mana Base Configuration - + Mana Base Configuration Display type: - + Display type: pie - + pie bar - + bar combinedBar - + combinedBar Filter Colors (optional): - + Filter Colors (optional): OK - + OK Cancel - + Cancel @@ -5547,47 +5547,47 @@ Cockatrice will now reload the card database. Group By: - + Group By: type - + type color - + color subtype - + subtype power - + power toughness - + toughness Filters (optional): - + Filters (optional): Show main bar row - + Show main bar row Show per-category rows - + Show per-category rows @@ -5603,27 +5603,27 @@ Cockatrice will now reload the card database. Display type: - + Display type: pie - + pie bar - + bar combinedBar - + combinedBar Filter Colors (optional): - + Filter Colors (optional): @@ -5639,27 +5639,27 @@ Cockatrice will now reload the card database. Top display type: - + Top display type: pie - + pie bar - + bar Colors: - + Colors: Show per-color rows - + Show per-color rows @@ -5667,12 +5667,12 @@ Cockatrice will now reload the card database. %1 pips (%2 cards) - + %1 pips (%2 cards) %1 mana (%2 cards) - + %1 mana (%2 cards) @@ -5680,12 +5680,12 @@ Cockatrice will now reload the card database. Mana Production + Devotion - + Mana Production + Devotion Mana Distribution Settings - + Mana Distribution Settings @@ -7645,58 +7645,58 @@ Please check your shortcut settings! Desc. - + Desc. Asc. - + Asc. Any Bracket - + Any Bracket Deck name contains... - + Deck name contains... Owner name contains... - + Owner name contains... Disabled - + Disabled Search - + Search Formats - + Formats Min. # of Cards: - + Min. # of Cards: Page: - + Page: Archidekt: - + Archidekt: @@ -7724,7 +7724,7 @@ Please check your shortcut settings! Card Database - + Card Database @@ -8738,7 +8738,7 @@ The more information you put in, the more specific your results will be. Archidekt - + Archidekt @@ -8866,7 +8866,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Database Display - + Database Display @@ -9362,7 +9362,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Do not delete &arrows inside of subphases - + Do not delete &arrows inside of subphases @@ -9564,7 +9564,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Search filter... - + Search filter... @@ -9587,22 +9587,22 @@ Please refrain from engaging in this activity or further actions may be taken ag Do not display formats with less than this amount of cards in the database - + Do not display formats with less than this amount of cards in the database Filter mode (AND/OR/NOT conjunctions of filters) - + Filter mode (AND/OR/NOT conjunctions of filters) Mode: Exact Match - + Mode: Exact Match Mode: Includes - + Mode: Includes @@ -9633,7 +9633,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Filter by name... (Exact match) - + Filter by name... (Exact match) @@ -9723,7 +9723,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Visual - + Visual @@ -9738,12 +9738,12 @@ Please refrain from engaging in this activity or further actions may be taken ag Sort by: - + Sort by: Filter by: - + Filter by: @@ -9768,7 +9768,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Table - + Table @@ -9776,43 +9776,43 @@ Please refrain from engaging in this activity or further actions may be taken ag Group by: - + Group by: Change how cards are divided into categories/groups. - + Change how cards are divided into categories/groups. Sort by: - + Sort by: Click and drag to change the sort order within the groups - + Click and drag to change the sort order within the groups Configure how cards are sorted within their groups - + Configure how cards are sorted within their groups Toggle Layout: Overlap - + Toggle Layout: Overlap Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) Toggle Layout: Flat - + Toggle Layout: Flat @@ -9833,7 +9833,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Type a card name here for suggestions from the database... - + Type a card name here for suggestions from the database... @@ -10957,7 +10957,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Reveal Selected Cards to All Players - + Reveal Selected Cards to All Players @@ -11112,12 +11112,12 @@ Please refrain from engaging in this activity or further actions may be taken ag Mulligan (Same hand size) - + Mulligan (Same hand size) Mulligan (Hand size - 1) - + Mulligan (Hand size - 1) @@ -11147,27 +11147,27 @@ Please refrain from engaging in this activity or further actions may be taken ag Sort Hand by Name - + Sort Hand by Name Sort Hand by Type - + Sort Hand by Type Sort Hand by Mana Value - + Sort Hand by Mana Value Reveal Hand to All Players - + Reveal Hand to All Players Reveal Random Card to All Players - + Reveal Random Card to All Players diff --git a/oracle/translations/oracle_en_US.ts b/oracle/translations/oracle_en_US.ts index a08b85a1c..348b26323 100644 --- a/oracle/translations/oracle_en_US.ts +++ b/oracle/translations/oracle_en_US.ts @@ -172,7 +172,7 @@ spoiler - + spoiler @@ -192,7 +192,7 @@ Local file: - + Local file: @@ -202,7 +202,7 @@ Choose file... - + Choose file... @@ -230,7 +230,7 @@ tokens - + tokens @@ -250,7 +250,7 @@ Local file: - + Local file: @@ -260,7 +260,7 @@ Choose file... - + Choose file... @@ -391,12 +391,12 @@ Load %1 file - + Load %1 file %1 file (%1) - + %1 file (%1) @@ -420,12 +420,12 @@ Please choose a file. - + Please choose a file. Cannot open file '%1'. - + Cannot open file '%1'. From 5309dd17beb3e78d3885830beba3bc14dbeabb80 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Mon, 26 Jan 2026 02:44:03 +0100 Subject: [PATCH 182/325] fix typo (#6572) --- .../card_picture_loader/card_picture_loader_worker_work.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp index cd41ac27f..4c676c704 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp @@ -78,7 +78,7 @@ void CardPictureLoaderWorkerWork::handleNetworkReply(QNetworkReply *reply) redirectUrl = url.resolved(redirectUrl); } if (url == redirectUrl) { - qCWarning(CardPictureLoaderWorkerWorkLog) << "recusive redirect detected!"; + qCWarning(CardPictureLoaderWorkerWorkLog) << "recursive redirect detected!"; redirectFailure = true; } else { emit urlRedirected(url, redirectUrl); From 7b64970e974319a07d33a96d5b86382555edbbb7 Mon Sep 17 00:00:00 2001 From: Rob Blanckaert Date: Tue, 27 Jan 2026 17:49:40 -0800 Subject: [PATCH 183/325] [App] Add Fusion Theme (#6577) Adds a new default theme that causes the QT Fusion theme to be selected. This theme looks a bit nicer than 'Windows' and supports both light and dark mode out of the box. --- cockatrice/src/interface/theme_manager.cpp | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index ec52a0f6e..0dd8cf30d 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -6,10 +6,15 @@ #include #include #include +#include #include #include +#include +#include +#include #define NONE_THEME_NAME "Default" +#define FUSION_THEME_NAME "Fusion" #define STYLE_CSS_NAME "style.css" #define HANDZONE_BG_NAME "handzone" #define PLAYERZONE_BG_NAME "playerzone" @@ -24,6 +29,9 @@ static const QStringList DEFAULT_RESOURCE_PATHS = {":/resources"}; ThemeManager::ThemeManager(QObject *parent) : QObject(parent) { ensureThemeDirectoryExists(); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) + connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, &ThemeManager::themeChangedSlot); +#endif connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this, &ThemeManager::themeChangedSlot); themeChangedSlot(); } @@ -44,6 +52,7 @@ QStringMap &ThemeManager::getAvailableThemes() // add default value availableThemes.insert(NONE_THEME_NAME, QString()); + availableThemes.insert(FUSION_THEME_NAME, QString()); // load themes from user profile dir dir.setPath(SettingsCache::instance().getThemesPath()); @@ -117,6 +126,20 @@ void ThemeManager::themeChangedSlot() qApp->setStyleSheet(""); } + if (themeName == FUSION_THEME_NAME) { + qApp->setStyle(QStyleFactory::create("Fusion")); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) + QPalette palette; + if (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark) { + palette.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); + } + + qApp->setPalette(palette); +#endif + } else { + qApp->setStyle(QStyleFactory::create(QStyleFactory::keys().first())); + } + if (dirPath.isEmpty()) { // set default values QDir::setSearchPaths("theme", DEFAULT_RESOURCE_PATHS); From 165c4ddd2a487530ed5955267350072e457ee6e8 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:49:54 -0800 Subject: [PATCH 184/325] [PrintingSelector] Properly clamp text size to picture on load (#6576) --- .../widgets/printing_selector/printing_selector.cpp | 1 - .../printing_selector_card_display_widget.cpp | 6 ++++++ .../printing_selector_card_display_widget.h | 2 ++ .../printing_selector_card_overlay_widget.cpp | 6 ------ 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp index 2f18c7116..71b93b297 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp @@ -223,7 +223,6 @@ void PrintingSelector::getAllSetsForCurrentCard() auto *cardDisplayWidget = new PrintingSelectorCardDisplayWidget(this, deckEditor, deckStateManager, cardSizeWidget->getSlider(), card); flowWidget->addWidget(cardDisplayWidget); - cardDisplayWidget->clampSetNameToPicture(); cardDisplayWidget->updateCardAmounts(uuidToAmounts); connect(cardDisplayWidget, &PrintingSelectorCardDisplayWidget::cardPreferenceChanged, this, &PrintingSelector::updateDisplay); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp index b95c25cbd..6ffd112ab 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp @@ -70,3 +70,9 @@ void PrintingSelectorCardDisplayWidget::updateCardAmounts(const QMapupdateCardAmounts(main, side); } + +void PrintingSelectorCardDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + clampSetNameToPicture(); +} diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h index 64bb72e22..ac5c7c05f 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.h @@ -29,6 +29,8 @@ public slots: void clampSetNameToPicture(); void updateCardAmounts(const QMap> &uuidToAmounts); + void resizeEvent(QResizeEvent *event) override; + signals: void cardPreferenceChanged(); 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 4298b6dc3..1508b5243 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 @@ -60,12 +60,6 @@ PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *pa allZonesCardAmountWidget->raise(); // Ensure it's on top of the picture - // Attempt to cast the parent to PrintingSelectorCardDisplayWidget - if (const auto *parentWidget = qobject_cast(parent)) { - connect(cardInfoPicture, &CardInfoPictureWidget::cardScaleFactorChanged, parentWidget, - &PrintingSelectorCardDisplayWidget::clampSetNameToPicture); - } - connect(cardSizeSlider, &QSlider::valueChanged, cardInfoPicture, &CardInfoPictureWidget::setScaleFactor); } From 5cc5767c87314ccb459e0e40d1b3227809e4e541 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:50:19 -0800 Subject: [PATCH 185/325] [CardInfoPictureWidget] Refactor constant fields to static const (#6575) * [CardInfoPictureWidget] Refactor constant fields to static const * rename constants * reformat * comment out unused --- .../cards/card_info_picture_widget.cpp | 46 ++++++++++++------- .../widgets/cards/card_info_picture_widget.h | 12 ++--- 2 files changed, 32 insertions(+), 26 deletions(-) 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 7206c05c9..816940b0f 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.cpp @@ -15,6 +15,18 @@ #include #include +static constexpr qreal MTG_CARD_ASPECT_RATIO = 1.396; +// static constexpr qreal YUGIOH_CARD_ASPECT_RATIO = 1.457; +static constexpr qreal ASPECT_RATIO = MTG_CARD_ASPECT_RATIO; + +static constexpr int BASE_WIDTH = 200; +static constexpr int BASE_HEIGHT = 200; + +static constexpr int ENLARGED_PIXMAP_OFFSET = 10; + +static constexpr int HOVER_ACTIVATE_THRESHOLD_MS = 500; +static constexpr int ANIMATION_OFFSET = 10; // Adjust this for how much the widget moves up + /** * @class CardInfoPictureWidget * @brief Widget that displays an enlarged image of a card, loading the image based on the card's info or showing a @@ -34,7 +46,7 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool _hoverToZoomEnabled, const bool _raiseOnEnter) : QWidget(parent), pixmapDirty(true), hoverToZoomEnabled(_hoverToZoomEnabled), raiseOnEnter(_raiseOnEnter) { - setMinimumHeight(baseHeight); + setMinimumHeight(BASE_HEIGHT); if (hoverToZoomEnabled) { setMouseTracking(true); } @@ -52,7 +64,7 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool _hoverT animation->setEasingCurve(QEasingCurve::OutQuad); animation->setStartValue(originalPos); - animation->setEndValue(originalPos - QPoint(0, animationOffset)); + animation->setEndValue(originalPos - QPoint(0, ANIMATION_OFFSET)); connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) { Q_UNUSED(_roundCardCorners); @@ -119,8 +131,8 @@ void CardInfoPictureWidget::resizeEvent(QResizeEvent *event) */ void CardInfoPictureWidget::setScaleFactor(const int scale) { - const int newWidth = baseWidth * scale / 100; - const int newHeight = static_cast(newWidth * aspectRatio); + const int newWidth = BASE_WIDTH * scale / 100; + const int newHeight = static_cast(newWidth * ASPECT_RATIO); scaleFactor = scale; @@ -226,8 +238,8 @@ void CardInfoPictureWidget::paintEvent(QPaintEvent *event) */ QSize CardInfoPictureWidget::sizeHint() const { - return {static_cast(baseWidth * scaleFactor / 100.0), - static_cast(baseWidth * scaleFactor / 100.0 * aspectRatio)}; + return {static_cast(BASE_WIDTH * scaleFactor / 100.0), + static_cast(BASE_WIDTH * scaleFactor / 100.0 * ASPECT_RATIO)}; } /** @@ -244,7 +256,7 @@ void CardInfoPictureWidget::enterEvent(QEvent *event) // If hover-to-zoom is enabled, start the hover timer if (hoverToZoomEnabled) { - hoverTimer->start(hoverActivateThresholdInMs); + hoverTimer->start(HOVER_ACTIVATE_THRESHOLD_MS); } // Emit signal indicating a card is being hovered on @@ -256,7 +268,7 @@ void CardInfoPictureWidget::enterEvent(QEvent *event) } else { originalPos = this->pos(); // Update the baseline position animation->setStartValue(originalPos); - animation->setEndValue(originalPos - QPoint(0, animationOffset)); + animation->setEndValue(originalPos - QPoint(0, ANIMATION_OFFSET)); } animation->setDirection(QAbstractAnimation::Forward); animation->start(); @@ -311,15 +323,15 @@ void CardInfoPictureWidget::mouseMoveEvent(QMouseEvent *event) const QRect screenGeometry = QGuiApplication::screenAt(cursorPos)->geometry(); const QSize widgetSize = enlargedPixmapWidget->size(); - int newX = cursorPos.x() + enlargedPixmapOffset; - int newY = cursorPos.y() + enlargedPixmapOffset; + int newX = cursorPos.x() + ENLARGED_PIXMAP_OFFSET; + int newY = cursorPos.y() + ENLARGED_PIXMAP_OFFSET; // Adjust if out of bounds if (newX + widgetSize.width() > screenGeometry.right()) { - newX = cursorPos.x() - widgetSize.width() - enlargedPixmapOffset; + newX = cursorPos.x() - widgetSize.width() - ENLARGED_PIXMAP_OFFSET; } if (newY + widgetSize.height() > screenGeometry.bottom()) { - newY = cursorPos.y() - widgetSize.height() - enlargedPixmapOffset; + newY = cursorPos.y() - widgetSize.height() - ENLARGED_PIXMAP_OFFSET; } enlargedPixmapWidget->move(newX, newY); @@ -453,21 +465,21 @@ void CardInfoPictureWidget::showEnlargedPixmap() connect(this, &QObject::destroyed, enlargedPixmapWidget, &CardInfoPictureEnlargedWidget::deleteLater); } - const QSize enlargedSize(static_cast(size().width() * 2), static_cast(size().width() * aspectRatio * 2)); + const QSize enlargedSize(static_cast(size().width() * 2), static_cast(size().width() * ASPECT_RATIO * 2)); enlargedPixmapWidget->setCardPixmap(exactCard, enlargedSize); const QPoint cursorPos = QCursor::pos(); const QRect screenGeometry = QGuiApplication::screenAt(cursorPos)->geometry(); const QSize widgetSize = enlargedPixmapWidget->size(); - int newX = cursorPos.x() + enlargedPixmapOffset; - int newY = cursorPos.y() + enlargedPixmapOffset; + int newX = cursorPos.x() + ENLARGED_PIXMAP_OFFSET; + int newY = cursorPos.y() + ENLARGED_PIXMAP_OFFSET; if (newX + widgetSize.width() > screenGeometry.right()) { - newX = cursorPos.x() - widgetSize.width() - enlargedPixmapOffset; + newX = cursorPos.x() - widgetSize.width() - ENLARGED_PIXMAP_OFFSET; } if (newY + widgetSize.height() > screenGeometry.bottom()) { - newY = cursorPos.y() - widgetSize.height() - enlargedPixmapOffset; + newY = cursorPos.y() - widgetSize.height() - ENLARGED_PIXMAP_OFFSET; } enlargedPixmapWidget->move(newX, newY); diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h index c9dbc47ab..1095d7d74 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_widget.h @@ -68,23 +68,17 @@ protected: private: ExactCard exactCard; - qreal magicTheGatheringCardAspectRatio = 1.396; - qreal yuGiOhCardAspectRatio = 1.457; - qreal aspectRatio = magicTheGatheringCardAspectRatio; - int baseWidth = 200; - int baseHeight = 200; double scaleFactor = 100; QPixmap resizedPixmap; bool pixmapDirty; bool hoverToZoomEnabled; bool raiseOnEnter; - int hoverActivateThresholdInMs = 500; + CardInfoPictureEnlargedWidget *enlargedPixmapWidget = nullptr; - int enlargedPixmapOffset = 10; + QTimer *hoverTimer; QPropertyAnimation *animation; - QPoint originalPos; // Store the original position - const int animationOffset = 10; // Adjust this for how much the widget moves up + QPoint originalPos; // Store the original position QMenu *createRightClickMenu(); QMenu *createViewRelatedCardsMenu(); From 1b29e0bfa8a38d9b7efa2cac1227bc99989aabe9 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:50:46 -0800 Subject: [PATCH 186/325] [PrintingSelector] Reduce spacing (#6574) * [PrintingSelector] Reduce spacing * align top * reduce spacing in flowLayout --- .../printing_selector_card_display_widget.cpp | 2 ++ .../set_name_and_collectors_number_display_widget.cpp | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp index 6ffd112ab..7b545191d 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp @@ -30,6 +30,8 @@ PrintingSelectorCardDisplayWidget::PrintingSelectorCardDisplayWidget(QWidget *pa : QWidget(parent), rootCard(rootCard) { layout = new QVBoxLayout(this); + layout->setContentsMargins(5, 5, 5, 5); + layout->setSpacing(3); setLayout(layout); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); diff --git a/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp index 5962680cd..eb3a96cf8 100644 --- a/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp @@ -17,6 +17,8 @@ SetNameAndCollectorsNumberDisplayWidget::SetNameAndCollectorsNumberDisplayWidget { // Set up the layout for the widget layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(1); setLayout(layout); // Set the widget's size policy and minimum size @@ -26,12 +28,12 @@ SetNameAndCollectorsNumberDisplayWidget::SetNameAndCollectorsNumberDisplayWidget // Create and configure the set name label setName = new QLabel(_setName); setName->setWordWrap(true); - setName->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); + setName->setAlignment(Qt::AlignHCenter | Qt::AlignTop); setName->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); // Create and configure the collectors number label collectorsNumber = new QLabel(_collectorsNumber); - collectorsNumber->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); + collectorsNumber->setAlignment(Qt::AlignHCenter | Qt::AlignTop); collectorsNumber->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); // Store the card size slider and connect its signal to the font size adjustment slot From c7249dfbd91634d180fd83e868f58e3d278e0d3f Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 28 Jan 2026 09:39:54 -0800 Subject: [PATCH 187/325] [PrintingSelector] Don't change font size (#6573) * [PrintingSelector] Don't change font size * remove connection to slider * update comments --- .../printing_selector_card_display_widget.cpp | 4 +- ...e_and_collectors_number_display_widget.cpp | 46 ++----------------- ...ame_and_collectors_number_display_widget.h | 9 +--- 3 files changed, 6 insertions(+), 53 deletions(-) diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp index 7b545191d..7d0b4882f 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp @@ -44,8 +44,8 @@ PrintingSelectorCardDisplayWidget::PrintingSelectorCardDisplayWidget(QWidget *pa // Create the widget to display the set name and collector's number QString combinedSetName = QString(set->getLongName() + " (" + set->getShortName() + ")"); - setNameAndCollectorsNumberDisplayWidget = new SetNameAndCollectorsNumberDisplayWidget( - this, combinedSetName, rootCard.getPrinting().getProperty("num"), cardSizeSlider); + setNameAndCollectorsNumberDisplayWidget = + new SetNameAndCollectorsNumberDisplayWidget(this, combinedSetName, rootCard.getPrinting().getProperty("num")); // Add the widgets to the layout layout->addWidget(overlayWidget, 0, Qt::AlignHCenter); diff --git a/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp index eb3a96cf8..e12775a04 100644 --- a/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp @@ -6,14 +6,12 @@ * @class SetNameAndCollectorsNumberDisplayWidget * @brief A widget to display the set name and collectors number with adjustable font size. * - * This widget displays the set name and collectors number on two separate labels. The font size is resized dynamically - * when the card size is changed. + * This widget displays the set name and collectors number on two separate labels. */ SetNameAndCollectorsNumberDisplayWidget::SetNameAndCollectorsNumberDisplayWidget(QWidget *parent, const QString &_setName, - const QString &_collectorsNumber, - QSlider *_cardSizeSlider) - : QWidget(parent), cardSizeSlider(_cardSizeSlider) + const QString &_collectorsNumber) + : QWidget(parent) { // Set up the layout for the widget layout = new QVBoxLayout(this); @@ -36,49 +34,11 @@ SetNameAndCollectorsNumberDisplayWidget::SetNameAndCollectorsNumberDisplayWidget collectorsNumber->setAlignment(Qt::AlignHCenter | Qt::AlignTop); collectorsNumber->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - // Store the card size slider and connect its signal to the font size adjustment slot - connect(cardSizeSlider, &QSlider::valueChanged, this, &SetNameAndCollectorsNumberDisplayWidget::adjustFontSize); - // Add labels to the layout layout->addWidget(setName); layout->addWidget(collectorsNumber); } -/** - * @brief Adjusts the font size of the labels based on the slider value. - * - * This method adjusts the font size of the set name and collectors number labels - * according to the scale percentage provided by the slider. The font size is clamped - * to a range between the defined minimum and maximum font sizes. - * - * @param scalePercentage The scale percentage from the slider. - */ -void SetNameAndCollectorsNumberDisplayWidget::adjustFontSize(int scalePercentage) -{ - // Define the base font size and the range - const int minFontSize = 8; // Minimum font size - const int maxFontSize = 32; // Maximum font size - const int basePercentage = 100; // Scale at 100% - - // Calculate the new font size - int newFontSize = minFontSize + (scalePercentage - basePercentage) * (maxFontSize - minFontSize) / 225; - - // Clamp the font size to the defined range - newFontSize = std::clamp(newFontSize, minFontSize, maxFontSize); - - // Update the fonts for both labels - QFont setNameFont = setName->font(); - setNameFont.setPointSize(newFontSize); - setName->setFont(setNameFont); - - QFont collectorsNumberFont = collectorsNumber->font(); - collectorsNumberFont.setPointSize(newFontSize); - collectorsNumber->setFont(collectorsNumberFont); - - // Optionally trigger a resize to accommodate new font size - adjustSize(); -} - /** * @brief Handles resize events to adjust the height of the set name label. * diff --git a/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.h b/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.h index 057b42a66..220f57256 100644 --- a/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.h @@ -17,20 +17,13 @@ class SetNameAndCollectorsNumberDisplayWidget : public QWidget { Q_OBJECT public: - SetNameAndCollectorsNumberDisplayWidget(QWidget *parent, - const QString &setName, - const QString &collectorsNumber, - QSlider *cardSizeSlider); + SetNameAndCollectorsNumberDisplayWidget(QWidget *parent, const QString &setName, const QString &collectorsNumber); void resizeEvent(QResizeEvent *event) override; -public slots: - void adjustFontSize(int scalePercentage); - private: QVBoxLayout *layout; QLabel *setName; QLabel *collectorsNumber; - QSlider *cardSizeSlider; }; #endif // SET_NAME_AND_COLLECTORS_NUMBER_DISPLAY_WIDGET_H From bf5891a910ba09967967de827ca272575b3ca4e2 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Wed, 28 Jan 2026 21:36:28 +0100 Subject: [PATCH 188/325] [ThemeManager] Proper Fusion Palettes (#6580) --- cockatrice/src/interface/theme_manager.cpp | 213 ++++++++++++++++++++- 1 file changed, 211 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index 0dd8cf30d..30c3a0974 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -6,15 +6,20 @@ #include #include #include +#include +#include #include #include #include +#include #include #include #include #define NONE_THEME_NAME "Default" -#define FUSION_THEME_NAME "Fusion" +#define FUSION_THEME_NAME "Fusion (System Default)" +#define FUSION_THEME_NAME_LIGHT "Fusion (Light)" +#define FUSION_THEME_NAME_DARK "Fusion (Dark)" #define STYLE_CSS_NAME "style.css" #define HANDZONE_BG_NAME "handzone" #define PLAYERZONE_BG_NAME "playerzone" @@ -26,6 +31,62 @@ static const QColor PLAYERZONE_BG_DEFAULT = QColor(200, 200, 200); static const QColor STACKZONE_BG_DEFAULT = QColor(113, 43, 43); static const QStringList DEFAULT_RESOURCE_PATHS = {":/resources"}; +struct PaletteColorInfo +{ + QPalette::ColorGroup group; + QPalette::ColorRole role; + QColor color; +}; + +[[maybe_unused]] static inline QList queryAllPaletteColors(const QPalette &palette = qApp->palette()) +{ + QList colors; + + // Iterate through relevant color groups (Active, Disabled, Inactive) + const QList groups = {QPalette::Active, QPalette::Disabled, QPalette::Inactive}; + + for (auto group : groups) { + // Iterate through all color roles (excluding NoRole and NColorRoles) + for (int r = 0; r < QPalette::NColorRoles; ++r) { + auto role = static_cast(r); + if (role == QPalette::NoRole) + continue; + + PaletteColorInfo info; + info.group = group; + info.role = role; + info.color = palette.color(group, role); + colors.append(info); + } + } + + return colors; +} + +// Pretty print version +[[maybe_unused]] static inline void printPaletteColors(const QPalette &palette = qApp->palette()) +{ + QMetaEnum groupEnum = QMetaEnum::fromType(); + QMetaEnum roleEnum = QMetaEnum::fromType(); + + const QList groups = {QPalette::Active, QPalette::Disabled, QPalette::Inactive}; + + for (auto group : groups) { + qInfo() << "\n===========" << groupEnum.valueToKey(group) << "==========="; + + for (int r = 0; r < QPalette::NColorRoles; ++r) { + auto role = static_cast(r); + if (role == QPalette::NoRole) + continue; + + QColor color = palette.color(group, role); + qInfo().nospace() << qPrintable(QString("%1").arg(roleEnum.valueToKey(role), -20)) << " : " + << qPrintable(color.name(QColor::HexArgb)) << " (RGBA: " << color.red() << ", " + << color.green() << ", " << color.blue() << ", " << color.alpha() << ")"; + } + } +} + ThemeManager::ThemeManager(QObject *parent) : QObject(parent) { ensureThemeDirectoryExists(); @@ -52,11 +113,14 @@ QStringMap &ThemeManager::getAvailableThemes() // add default value availableThemes.insert(NONE_THEME_NAME, QString()); - availableThemes.insert(FUSION_THEME_NAME, QString()); // load themes from user profile dir dir.setPath(SettingsCache::instance().getThemesPath()); + availableThemes.insert(FUSION_THEME_NAME, dir.filePath("Fusion (System Default)")); + availableThemes.insert(FUSION_THEME_NAME_LIGHT, dir.filePath("Fusion (Light)")); + availableThemes.insert(FUSION_THEME_NAME_DARK, dir.filePath("Fusion (Dark)")); + for (QString themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) { if (!availableThemes.contains(themeName)) { availableThemes.insert(themeName, dir.absoluteFilePath(themeName)); @@ -111,6 +175,145 @@ QBrush ThemeManager::loadExtraBrush(QString fileName, QBrush &fallbackBrush) return brush; } +static inline QPalette createDarkGreenFusionPalette() +{ + QPalette p; + + // ---------- Core backgrounds ---------- + p.setColor(QPalette::Window, QColor(30, 30, 30)); // #ff1e1e1e + p.setColor(QPalette::Base, QColor(45, 45, 45)); // #ff2d2d2d + p.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); // #ff353535 + p.setColor(QPalette::Button, QColor(60, 60, 60)); // #ff3c3c3c + p.setColor(QPalette::ToolTipBase, QColor(60, 60, 60)); // #ff3c3c3c + + // ---------- Core text ---------- + p.setColor(QPalette::WindowText, Qt::white); // #ffffffff + p.setColor(QPalette::Text, Qt::white); // #ffffffff + p.setColor(QPalette::ButtonText, Qt::white); // #ffffffff + p.setColor(QPalette::ToolTipText, QColor(212, 212, 212)); // #ffd4d4d4 + p.setColor(QPalette::PlaceholderText, QColor(255, 255, 255, 128)); // #80ffffff + + // ---------- Selection / focus ---------- + const QColor highlight(20, 140, 60); // #ff148c3c + p.setColor(QPalette::Highlight, highlight); + p.setColor(QPalette::HighlightedText, Qt::white); // #ffffffff + + // ---------- Links ---------- + p.setColor(QPalette::Link, QColor(0, 246, 82)); // #ff00f652 + p.setColor(QPalette::LinkVisited, QColor(0, 211, 70)); // #ff00d346 + + // ---------- Accent (Qt 6) ---------- +#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) + p.setColor(QPalette::Accent, QColor(0, 211, 70)); // #ff00d346 +#endif + + // ---------- Bright text ---------- + p.setColor(QPalette::BrightText, QColor(0, 246, 82)); // #ff00f652 + + // ---------- 3D / frame shading ---------- + p.setColor(QPalette::Light, QColor(120, 120, 120)); // #ff787878 + p.setColor(QPalette::Midlight, QColor(90, 90, 90)); // #ff5a5a5a + p.setColor(QPalette::Mid, QColor(40, 40, 40)); // #ff282828 + p.setColor(QPalette::Dark, QColor(30, 30, 30)); // #ff1e1e1e + p.setColor(QPalette::Shadow, Qt::black); // #ff000000 + + // ---------- Disabled state ---------- + const QColor disabledText(157, 157, 157); // #ff9d9d9d + p.setColor(QPalette::Disabled, QPalette::WindowText, disabledText); + p.setColor(QPalette::Disabled, QPalette::Text, disabledText); + p.setColor(QPalette::Disabled, QPalette::ButtonText, disabledText); + p.setColor(QPalette::Disabled, QPalette::Base, QColor(30, 30, 30)); + p.setColor(QPalette::Disabled, QPalette::Window, QColor(30, 30, 30)); + p.setColor(QPalette::Disabled, QPalette::Link, QColor(48, 140, 198)); // #ff308cc6 + p.setColor(QPalette::Disabled, QPalette::LinkVisited, QColor(255, 0, 255)); // #ffff00ff + p.setColor(QPalette::Disabled, QPalette::ToolTipBase, QColor(255, 255, 220)); + p.setColor(QPalette::Disabled, QPalette::ToolTipText, Qt::black); + +#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) + p.setColor(QPalette::Disabled, QPalette::Accent, disabledText); +#endif + + // ---------- Inactive state ---------- + p.setColor(QPalette::Inactive, QPalette::Highlight, QColor(30, 30, 30)); + p.setColor(QPalette::Inactive, QPalette::HighlightedText, Qt::white); + +#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) + p.setColor(QPalette::Inactive, QPalette::Accent, QColor(30, 30, 30)); +#endif + + return p; +} + +static inline QPalette createLightGreenFusionPalette() +{ + QPalette p; + + // ---------- Core backgrounds ---------- + p.setColor(QPalette::Window, QColor(240, 240, 240)); // #fff0f0f0 + p.setColor(QPalette::Base, Qt::white); // #ffffffff + p.setColor(QPalette::AlternateBase, QColor(233, 231, 227)); // #ffe9e7e3 + p.setColor(QPalette::Button, QColor(240, 240, 240)); // #fff0f0f0 + p.setColor(QPalette::ToolTipBase, QColor(255, 255, 220)); // #ffffffdc + + // ---------- Core text ---------- + p.setColor(QPalette::WindowText, Qt::black); // #ff000000 + p.setColor(QPalette::Text, Qt::black); // #ff000000 + p.setColor(QPalette::ButtonText, Qt::black); // #ff000000 + p.setColor(QPalette::ToolTipText, Qt::black); // #ff000000 + p.setColor(QPalette::PlaceholderText, QColor(0, 0, 0, 128)); // #80000000 + + // ---------- Selection / focus ---------- + const QColor highlight(20, 140, 60); // #ff148c3c + p.setColor(QPalette::Highlight, highlight); + p.setColor(QPalette::HighlightedText, Qt::white); // #ffffffff + + // ---------- Links ---------- + p.setColor(QPalette::Link, QColor(13, 95, 40)); // #ff0d5f28 + p.setColor(QPalette::LinkVisited, QColor(8, 64, 27)); // #ff08401b + + // ---------- Accent (Qt 6) ---------- +#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) + p.setColor(QPalette::Accent, QColor(16, 117, 50)); // #ff107532 +#endif + + // ---------- Bright text ---------- + p.setColor(QPalette::BrightText, Qt::white); // #ffffffff + + // ---------- 3D / frame shading ---------- + p.setColor(QPalette::Light, Qt::white); // #ffffffff + p.setColor(QPalette::Midlight, QColor(227, 227, 227)); // #ffe3e3e3 + p.setColor(QPalette::Mid, QColor(160, 160, 160)); // #ffa0a0a0 + p.setColor(QPalette::Dark, QColor(160, 160, 160)); // #ffa0a0a0 + p.setColor(QPalette::Shadow, QColor(105, 105, 105)); // #ff696969 + + // ---------- Disabled state ---------- + const QColor disabledText(120, 120, 120); // #ff787878 + p.setColor(QPalette::Disabled, QPalette::WindowText, disabledText); + p.setColor(QPalette::Disabled, QPalette::Text, disabledText); + p.setColor(QPalette::Disabled, QPalette::ButtonText, disabledText); + p.setColor(QPalette::Disabled, QPalette::Base, QColor(240, 240, 240)); + p.setColor(QPalette::Disabled, QPalette::Window, QColor(240, 240, 240)); + p.setColor(QPalette::Disabled, QPalette::Midlight, QColor(247, 247, 247)); + p.setColor(QPalette::Disabled, QPalette::AlternateBase, QColor(247, 247, 247)); + p.setColor(QPalette::Disabled, QPalette::Shadow, Qt::black); + p.setColor(QPalette::Disabled, QPalette::Link, QColor(0, 0, 255)); + p.setColor(QPalette::Disabled, QPalette::LinkVisited, QColor(255, 0, 255)); + +#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) + p.setColor(QPalette::Disabled, QPalette::Accent, disabledText); +#endif + + // ---------- Inactive state ---------- + p.setColor(QPalette::Inactive, QPalette::Highlight, QColor(240, 240, 240)); + p.setColor(QPalette::Inactive, QPalette::HighlightedText, Qt::black); + +#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) + p.setColor(QPalette::Inactive, QPalette::Accent, QColor(240, 240, 240)); +#endif + + return p; +} + void ThemeManager::themeChangedSlot() { QString themeName = SettingsCache::instance().getThemeName(); @@ -136,6 +339,12 @@ void ThemeManager::themeChangedSlot() qApp->setPalette(palette); #endif + } else if (themeName == FUSION_THEME_NAME_LIGHT) { + qApp->setStyle(QStyleFactory::create("Fusion")); + qApp->setPalette(createLightGreenFusionPalette()); + } else if (themeName == FUSION_THEME_NAME_DARK) { + qApp->setStyle(QStyleFactory::create("Fusion")); + qApp->setPalette(createDarkGreenFusionPalette()); } else { qApp->setStyle(QStyleFactory::create(QStyleFactory::keys().first())); } From d4100786737579736a6b28aa788d3564ff9b0d9d Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Wed, 28 Jan 2026 21:54:11 +0100 Subject: [PATCH 189/325] Refresh chat view colors on theme changed. (#6581) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 35 minutes Co-authored-by: Lukas Brübach --- .../widgets/server/chat_view/chat_view.cpp | 65 ++++++++++++++----- .../widgets/server/chat_view/chat_view.h | 2 + 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp index 50375c936..adbae45c5 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp @@ -28,21 +28,9 @@ ChatView::ChatView(TabSupervisor *_tabSupervisor, AbstractGame *_game, bool _sho userListProxy(_tabSupervisor->getUserListManager()), evenNumber(true), showTimestamps(_showTimestamps), hoveredItemType(HoveredNothing) { - if (palette().windowText().color().lightness() > 200) { - document()->setDefaultStyleSheet(R"( - a { text-decoration: none; color: rgb(71,158,252); } - .blue { color: rgb(71,158,252); } - )"); - serverMessageColor = QColor(0xFF, 0x73, 0x83); - otherUserColor = otherUserColor.lighter(150); - linkColor = QColor(71, 158, 252); - } else { - document()->setDefaultStyleSheet(R"( - a { text-decoration: none; color: blue; } - .blue { color: blue } - )"); - linkColor = palette().link().color(); - } + adjustColorsToPalette(); + + connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this, &ChatView::adjustColorsToPalette); userContextMenu = new UserContextMenu(tabSupervisor, this, game); connect(userContextMenu, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); @@ -63,6 +51,53 @@ ChatView::ChatView(TabSupervisor *_tabSupervisor, AbstractGame *_game, bool _sho connect(this, &ChatView::anchorClicked, this, &ChatView::openLink); } +void ChatView::adjustColorsToPalette() +{ + if (palette().windowText().color().lightness() > 200) { + document()->setDefaultStyleSheet(R"( + a { text-decoration: none; color: rgb(71,158,252); } + .blue { color: rgb(71,158,252); } + )"); + serverMessageColor = QColor(0xFF, 0x73, 0x83); + otherUserColor = otherUserColor.lighter(150); + linkColor = QColor(71, 158, 252); + } else { + document()->setDefaultStyleSheet(R"( + a { text-decoration: none; color: blue; } + .blue { color: blue } + )"); + linkColor = palette().link().color(); + } + + QTimer::singleShot(0, this, &ChatView::refreshBlockColors); +} + +void ChatView::refreshBlockColors() +{ + QTextDocument *doc = document(); + QTextCursor cursor(doc); + + bool even = true; // start fresh + + for (QTextBlock block = doc->begin(); block.isValid(); block = block.next()) { + QTextBlockFormat fmt = block.blockFormat(); + + if (even) + fmt.setBackground(palette().window()); + else + fmt.setBackground(palette().base()); + + fmt.setForeground(palette().text()); + + cursor.setPosition(block.position()); + cursor.setBlockFormat(fmt); + + even = !even; + } + + evenNumber = even; // keep future rows consistent +} + void ChatView::retranslateUi() { userContextMenu->retranslateUi(); diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.h b/cockatrice/src/interface/widgets/server/chat_view/chat_view.h index 6cf8370ed..d1939fbea 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.h +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.h @@ -85,6 +85,8 @@ private: private slots: void openLink(const QUrl &link); void actMessageClicked(); + void adjustColorsToPalette(); + void refreshBlockColors(); public: ChatView(TabSupervisor *_tabSupervisor, AbstractGame *_game, bool _showTimestamps, QWidget *parent = nullptr); From a096a0e3bbae3007f9febdece203da28fc24b595 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 7 Feb 2026 18:43:41 -0800 Subject: [PATCH 190/325] [VDE] Reduce spacing in deck view toolbar (#6591) --- .../visual_deck_editor/visual_deck_display_options_widget.cpp | 2 ++ .../widgets/visual_deck_editor/visual_deck_editor_widget.cpp | 1 + 2 files changed, 3 insertions(+) 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 f0bee5d7d..79a98fda6 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 @@ -7,6 +7,8 @@ VisualDeckDisplayOptionsWidget::VisualDeckDisplayOptionsWidget(QWidget *parent) : QWidget(parent) { groupAndSortLayout = new QHBoxLayout(this); + groupAndSortLayout->setContentsMargins(0, 0, 0, 0); + groupAndSortLayout->setSpacing(3); groupAndSortLayout->setAlignment(Qt::AlignLeft); this->setLayout(groupAndSortLayout); 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 d6afb5f22..e957eb304 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 @@ -161,6 +161,7 @@ void VisualDeckEditorWidget::initializeDisplayOptionsAndSearchWidget() displayOptionsAndSearch = new QWidget(this); displayOptionsAndSearchLayout = new QHBoxLayout(displayOptionsAndSearch); + displayOptionsAndSearchLayout->setContentsMargins(0, 0, 0, 0); displayOptionsAndSearchLayout->setAlignment(Qt::AlignLeft); displayOptionsAndSearch->setLayout(displayOptionsAndSearchLayout); From 3ada27eae1bb5f427bd22527dcf0fc8e10d50bab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 7 Feb 2026 21:43:51 -0500 Subject: [PATCH 191/325] Bump webpack from 5.99.9 to 5.105.0 in /webclient (#6590) Bumps [webpack](https://github.com/webpack/webpack) from 5.99.9 to 5.105.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Changelog](https://github.com/webpack/webpack/blob/main/CHANGELOG.md) - [Commits](https://github.com/webpack/webpack/compare/v5.99.9...v5.105.0) --- updated-dependencies: - dependency-name: webpack dependency-version: 5.105.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- webclient/package-lock.json | 297 ++++++++++++++++++++---------------- 1 file changed, 169 insertions(+), 128 deletions(-) diff --git a/webclient/package-lock.json b/webclient/package-lock.json index b5d961a75..6b12b4ad6 100644 --- a/webclient/package-lock.json +++ b/webclient/package-lock.json @@ -4866,9 +4866,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" }, "node_modules/@types/express": { "version": "4.17.14", @@ -5641,9 +5641,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "bin": { "acorn": "bin/acorn" }, @@ -5671,6 +5671,17 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -6344,6 +6355,14 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -6481,9 +6500,9 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, "node_modules/browserslist": { - "version": "4.24.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", - "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "funding": [ { "type": "opencollective", @@ -6499,10 +6518,11 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001716", - "electron-to-chromium": "^1.5.149", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -6609,9 +6629,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001718", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", - "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", "funding": [ { "type": "opencollective", @@ -7849,9 +7869,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.155", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", - "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==" + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==" }, "node_modules/emittery": { "version": "0.8.1", @@ -7886,12 +7906,12 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" @@ -8002,9 +8022,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==" }, "node_modules/es-shim-unscopables": { "version": "1.0.0", @@ -14383,11 +14403,15 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/loader-utils": { @@ -14815,9 +14839,9 @@ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -18747,11 +18771,15 @@ } }, "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/temp-dir": { @@ -18823,9 +18851,9 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -19208,9 +19236,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "funding": [ { "type": "opencollective", @@ -19367,9 +19395,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -19395,34 +19423,35 @@ } }, "node_modules/webpack": { - "version": "5.99.9", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz", - "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", + "version": "5.105.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", "dependencies": { "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", + "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.19.0", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.3" }, "bin": { "webpack": "bin/webpack.js" @@ -19670,9 +19699,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "engines": { "node": ">=10.13.0" } @@ -19709,9 +19738,9 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/webpack/node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -23591,9 +23620,9 @@ } }, "@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" }, "@types/express": { "version": "4.17.14", @@ -24245,9 +24274,9 @@ } }, "acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==" + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==" }, "acorn-globals": { "version": "6.0.0", @@ -24265,6 +24294,11 @@ } } }, + "acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==" + }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -24752,6 +24786,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==" + }, "batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -24869,14 +24908,15 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, "browserslist": { - "version": "4.24.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", - "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "requires": { - "caniuse-lite": "^1.0.30001716", - "electron-to-chromium": "^1.5.149", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" } }, "bser": { @@ -24950,9 +24990,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001718", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", - "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==" + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==" }, "case-sensitive-paths-webpack-plugin": { "version": "2.4.0", @@ -25843,9 +25883,9 @@ } }, "electron-to-chromium": { - "version": "1.5.155", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", - "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==" + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==" }, "emittery": { "version": "0.8.1", @@ -25868,12 +25908,12 @@ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, "enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "requires": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" } }, "entities": { @@ -25963,9 +26003,9 @@ } }, "es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==" }, "es-shim-unscopables": { "version": "1.0.0", @@ -30636,9 +30676,9 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==" + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==" }, "loader-utils": { "version": "2.0.4", @@ -30953,9 +30993,9 @@ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" }, "node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==" }, "normalize-path": { "version": "3.0.0", @@ -33582,9 +33622,9 @@ } }, "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==" }, "temp-dir": { "version": "2.0.0", @@ -33637,9 +33677,9 @@ } }, "terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", "requires": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -33910,9 +33950,9 @@ "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" }, "update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "requires": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -34023,9 +34063,9 @@ } }, "watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -34045,34 +34085,35 @@ "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" }, "webpack": { - "version": "5.99.9", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz", - "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", + "version": "5.105.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", "requires": { "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", + "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.19.0", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.3" }, "dependencies": { "ajv": { @@ -34100,9 +34141,9 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "requires": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -34265,9 +34306,9 @@ } }, "webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==" + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==" }, "websocket-driver": { "version": "0.7.4", From 32aa60bb1459663fbb7a7d8a194962d9a0da2915 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 7 Feb 2026 18:44:49 -0800 Subject: [PATCH 192/325] [Oracle] Move oracle settings to separate file (#6588) --- oracle/src/oracleimporter.cpp | 1 - oracle/src/oraclewizard.cpp | 29 ++++++++++++++++++++++++++++- oracle/src/oraclewizard.h | 2 ++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 4b089846f..d585842f6 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -1,6 +1,5 @@ #include "oracleimporter.h" -#include "client/settings/cache_settings.h" #include "libcockatrice/interfaces/noop_card_preference_provider.h" #include "libcockatrice/interfaces/noop_card_set_priority_controller.h" #include "parsehelpers.h" diff --git a/oracle/src/oraclewizard.cpp b/oracle/src/oraclewizard.cpp index 46e4722c6..9d32a993b 100644 --- a/oracle/src/oraclewizard.cpp +++ b/oracle/src/oraclewizard.cpp @@ -21,7 +21,14 @@ OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent) // define a dummy context that will be used where needed QString dummy = QT_TRANSLATE_NOOP("i18n", "English"); - settings = new QSettings(SettingsCache::instance().getSettingsPath() + "global.ini", QSettings::IniFormat, this); + QString oracleSettingsFile = SettingsCache::instance().getSettingsPath() + "oracle.ini"; + settings = new QSettings(oracleSettingsFile, QSettings::IniFormat, this); + + // We moved the oracle-specific settings from global.ini to a separate oracle.ini after 2.10 + if (!QFile::exists(oracleSettingsFile)) { + migrateOracleSettings(); + } + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &OracleWizard::updateLanguage); importer = new OracleImporter(this); @@ -50,6 +57,26 @@ OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent) retranslateUi(); } +/** + * Migrates the oracle-specific settings from global.ini to oracle.ini + */ +void OracleWizard::migrateOracleSettings() +{ + QString filePath = SettingsCache::instance().getSettingsPath() + "global.ini"; + auto globalSettings = QSettings(filePath, QSettings::IniFormat, this); + + auto tryMigrateValue = [this, &globalSettings](const QString &name) { + QVariant variant = globalSettings.value(name); + if (variant.isValid()) { + settings->setValue(name, variant.toString()); + } + }; + + tryMigrateValue("allsetsurl"); + tryMigrateValue("tokensurl"); + tryMigrateValue("spoilersurl"); +} + void OracleWizard::updateLanguage() { qApp->removeTranslator(translator); diff --git a/oracle/src/oraclewizard.h b/oracle/src/oraclewizard.h index c913094e3..78427175c 100644 --- a/oracle/src/oraclewizard.h +++ b/oracle/src/oraclewizard.h @@ -75,6 +75,8 @@ private: QString cardSourceUrl; QString cardSourceVersion; + void migrateOracleSettings(); + protected: void changeEvent(QEvent *event) override; }; From 8d7535c0391ecbd87e6826fafa653663dbd367ab Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 7 Feb 2026 18:45:33 -0800 Subject: [PATCH 193/325] [LayoutSettings] Reorganize hierarchy in settings file (#6586) * [LayoutSettings] Reorganize hierarchy in settings file * rename stuff since we're moving settings later --- .../settings/layouts_settings.cpp | 92 ++++++++++--------- 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp index 97629d385..f5e6da0c2 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp @@ -1,218 +1,228 @@ #include "layouts_settings.h" +const static QString STATE_PROP = "state"; +const static QString GEOMETRY_PROP = "geometry"; +const static QString SIZE_PROP = "widgetSize"; + +const static QString GROUP_DECK_EDITOR = "deckEditor"; +const static QString GROUP_DECK_EDITOR_DB = "deckEditorDb"; +const static QString GROUP_SETS_DIALOG = "setsDialog"; +const static QString GROUP_GAME_PLAY_AREA = "gamePlayArea"; +const static QString GROUP_REPLAY_PLAY_AREA = "replayPlayArea"; + LayoutsSettings::LayoutsSettings(const QString &settingPath, QObject *parent) - : SettingsManager(settingPath + "layouts.ini", "layouts", QString(), parent) + : SettingsManager(settingPath + "layouts.ini", QString(), QString(), parent) { } const QByteArray LayoutsSettings::getDeckEditorLayoutState() { - return getValue("layouts/deckEditor_state").toByteArray(); + return getValue(STATE_PROP, GROUP_DECK_EDITOR).toByteArray(); } void LayoutsSettings::setDeckEditorLayoutState(const QByteArray &value) { - setValue(value, "layouts/deckEditor_state"); + setValue(value, STATE_PROP, GROUP_DECK_EDITOR); } const QByteArray LayoutsSettings::getDeckEditorGeometry() { - return getValue("layouts/deckEditor_geometry").toByteArray(); + return getValue(GEOMETRY_PROP, GROUP_DECK_EDITOR).toByteArray(); } void LayoutsSettings::setDeckEditorGeometry(const QByteArray &value) { - setValue(value, "layouts/deckEditor_geometry"); + setValue(value, GEOMETRY_PROP, GROUP_DECK_EDITOR); } QSize LayoutsSettings::getDeckEditorCardDatabaseSize() { - QVariant previous = getValue("layouts/deckEditor_CardDatabaseSize"); + QVariant previous = getValue("cardDatabase", GROUP_DECK_EDITOR, SIZE_PROP); return previous == QVariant() ? QSize(500, 500) : previous.toSize(); } void LayoutsSettings::setDeckEditorCardDatabaseSize(const QSize &value) { - setValue(value, "layouts/deckEditor_CardDatabaseSize"); + setValue(value, "cardDatabase", GROUP_DECK_EDITOR, SIZE_PROP); } QSize LayoutsSettings::getDeckEditorCardSize() { - QVariant previous = getValue("layouts/deckEditor_CardSize"); + QVariant previous = getValue("card", GROUP_DECK_EDITOR, SIZE_PROP); return previous == QVariant() ? QSize(250, 500) : previous.toSize(); } void LayoutsSettings::setDeckEditorCardSize(const QSize &value) { - setValue(value, "layouts/deckEditor_CardSize"); + setValue(value, "card", GROUP_DECK_EDITOR, SIZE_PROP); } QSize LayoutsSettings::getDeckEditorDeckSize() { - QVariant previous = getValue("layouts/deckEditor_DeckSize"); + QVariant previous = getValue("deck", GROUP_DECK_EDITOR, SIZE_PROP); return previous == QVariant() ? QSize(250, 360) : previous.toSize(); } void LayoutsSettings::setDeckEditorDeckSize(const QSize &value) { - setValue(value, "layouts/deckEditor_DeckSize"); + setValue(value, "deck", GROUP_DECK_EDITOR, SIZE_PROP); } QSize LayoutsSettings::getDeckEditorPrintingSelectorSize() { - QVariant previous = getValue("layouts/deckEditor_PrintingSelectorSize"); + QVariant previous = getValue("printingSelector", GROUP_DECK_EDITOR, SIZE_PROP); return previous == QVariant() ? QSize(525, 250) : previous.toSize(); } void LayoutsSettings::setDeckEditorPrintingSelectorSize(const QSize &value) { - setValue(value, "layouts/deckEditor_PrintingSelectorSize"); + setValue(value, "printingSelector", GROUP_DECK_EDITOR, SIZE_PROP); } QSize LayoutsSettings::getDeckEditorFilterSize() { - QVariant previous = getValue("layouts/deckEditor_FilterSize"); + QVariant previous = getValue("filter", GROUP_DECK_EDITOR, SIZE_PROP); return previous == QVariant() ? QSize(250, 250) : previous.toSize(); } void LayoutsSettings::setDeckEditorFilterSize(const QSize &value) { - setValue(value, "layouts/deckEditor_FilterSize"); + setValue(value, "filter", GROUP_DECK_EDITOR, SIZE_PROP); } const QByteArray LayoutsSettings::getDeckEditorDbHeaderState() { - return getValue("layouts/deckEditorDbHeader_state").toByteArray(); + return getValue(STATE_PROP, GROUP_DECK_EDITOR_DB, "header").toByteArray(); } void LayoutsSettings::setDeckEditorDbHeaderState(const QByteArray &value) { - setValue(value, "layouts/deckEditorDbHeader_state"); + setValue(value, STATE_PROP, GROUP_DECK_EDITOR_DB, "header"); } const QByteArray LayoutsSettings::getSetsDialogHeaderState() { - return getValue("layouts/setsDialogHeader_state").toByteArray(); + return getValue(STATE_PROP, GROUP_SETS_DIALOG, "header").toByteArray(); } void LayoutsSettings::setSetsDialogHeaderState(const QByteArray &value) { - setValue(value, "layouts/setsDialogHeader_state"); + setValue(value, STATE_PROP, GROUP_SETS_DIALOG, "header"); } void LayoutsSettings::setGamePlayAreaGeometry(const QByteArray &value) { - setValue(value, "layouts/gameplayarea_geometry"); + setValue(value, GEOMETRY_PROP, GROUP_GAME_PLAY_AREA); } void LayoutsSettings::setGamePlayAreaState(const QByteArray &value) { - setValue(value, "layouts/gameplayarea_state"); + setValue(value, STATE_PROP, GROUP_GAME_PLAY_AREA); } const QByteArray LayoutsSettings::getGamePlayAreaLayoutState() { - return getValue("layouts/gameplayarea_state").toByteArray(); + return getValue(STATE_PROP, GROUP_GAME_PLAY_AREA).toByteArray(); } const QByteArray LayoutsSettings::getGamePlayAreaGeometry() { - return getValue("layouts/gameplayarea_geometry").toByteArray(); + return getValue(GEOMETRY_PROP, GROUP_GAME_PLAY_AREA).toByteArray(); } const QSize LayoutsSettings::getGameCardInfoSize() { - QVariant previous = getValue("layouts/gameplayarea_CardInfoSize"); + QVariant previous = getValue("cardInfo", GROUP_GAME_PLAY_AREA, SIZE_PROP); return previous == QVariant() ? QSize(250, 360) : previous.toSize(); } void LayoutsSettings::setGameCardInfoSize(const QSize &value) { - setValue(value, "layouts/gameplayarea_CardInfoSize"); + setValue(value, "cardInfo", GROUP_GAME_PLAY_AREA, SIZE_PROP); } const QSize LayoutsSettings::getGameMessageLayoutSize() { - QVariant previous = getValue("layouts/gameplayarea_MessageLayoutSize"); + QVariant previous = getValue("messageLayout", GROUP_GAME_PLAY_AREA, SIZE_PROP); return previous == QVariant() ? QSize(250, 250) : previous.toSize(); } void LayoutsSettings::setGameMessageLayoutSize(const QSize &value) { - setValue(value, "layouts/gameplayarea_MessageLayoutSize"); + setValue(value, "messageLayout", GROUP_GAME_PLAY_AREA, SIZE_PROP); } const QSize LayoutsSettings::getGamePlayerListSize() { - QVariant previous = getValue("layouts/gameplayarea_PlayerListSize"); + QVariant previous = getValue("playerList", GROUP_GAME_PLAY_AREA, SIZE_PROP); return previous == QVariant() ? QSize(250, 50) : previous.toSize(); } void LayoutsSettings::setGamePlayerListSize(const QSize &value) { - setValue(value, "layouts/gameplayarea_PlayerListSize"); + setValue(value, "playerList", GROUP_GAME_PLAY_AREA, SIZE_PROP); } void LayoutsSettings::setReplayPlayAreaGeometry(const QByteArray &value) { - setValue(value, "layouts/replayplayarea_geometry"); + setValue(value, GEOMETRY_PROP, GROUP_REPLAY_PLAY_AREA); } void LayoutsSettings::setReplayPlayAreaState(const QByteArray &value) { - setValue(value, "layouts/replayplayarea_state"); + setValue(value, STATE_PROP, GROUP_REPLAY_PLAY_AREA); } const QByteArray LayoutsSettings::getReplayPlayAreaLayoutState() { - return getValue("layouts/replayplayarea_state").toByteArray(); + return getValue(STATE_PROP, GROUP_REPLAY_PLAY_AREA).toByteArray(); } const QByteArray LayoutsSettings::getReplayPlayAreaGeometry() { - return getValue("layouts/replayplayarea_geometry").toByteArray(); + return getValue(GEOMETRY_PROP, GROUP_REPLAY_PLAY_AREA).toByteArray(); } const QSize LayoutsSettings::getReplayCardInfoSize() { - QVariant previous = getValue("layouts/replayplayarea_CardInfoSize"); + QVariant previous = getValue("cardInfo", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); return previous == QVariant() ? QSize(250, 360) : previous.toSize(); } void LayoutsSettings::setReplayCardInfoSize(const QSize &value) { - setValue(value, "layouts/replayplayarea_CardInfoSize"); + setValue(value, "cardInfo", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); } const QSize LayoutsSettings::getReplayMessageLayoutSize() { - QVariant previous = getValue("layouts/replayplayarea_MessageLayoutSize"); + QVariant previous = getValue("messageLayout", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); return previous == QVariant() ? QSize(250, 200) : previous.toSize(); } void LayoutsSettings::setReplayMessageLayoutSize(const QSize &value) { - setValue(value, "layouts/replayplayarea_MessageLayoutSize"); + setValue(value, "messageLayout", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); } const QSize LayoutsSettings::getReplayPlayerListSize() { - QVariant previous = getValue("layouts/replayplayarea_PlayerListSize"); + QVariant previous = getValue("playerList", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); return previous == QVariant() ? QSize(250, 50) : previous.toSize(); } void LayoutsSettings::setReplayPlayerListSize(const QSize &value) { - setValue(value, "layouts/replayplayarea_PlayerListSize"); + setValue(value, "playerList", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); } const QSize LayoutsSettings::getReplayReplaySize() { - QVariant previous = getValue("layouts/replayplayarea_ReplaySize"); + QVariant previous = getValue("replay", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); return previous == QVariant() ? QSize(900, 100) : previous.toSize(); } void LayoutsSettings::setReplayReplaySize(const QSize &value) { - setValue(value, "layouts/replayplayarea_ReplaySize"); + setValue(value, "replay", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); } From 4884640070c704a51ea2da5ed542644629dc81a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 7 Feb 2026 21:47:41 -0500 Subject: [PATCH 194/325] Bump ssciwr/doxygen-install from 1 to 2 (#6585) Bumps [ssciwr/doxygen-install](https://github.com/ssciwr/doxygen-install) from 1 to 2. - [Release notes](https://github.com/ssciwr/doxygen-install/releases) - [Commits](https://github.com/ssciwr/doxygen-install/compare/v1...v2) --- updated-dependencies: - dependency-name: ssciwr/doxygen-install dependency-version: '2' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/documentation-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml index 9837ce164..ce331d113 100644 --- a/.github/workflows/documentation-build.yml +++ b/.github/workflows/documentation-build.yml @@ -31,7 +31,7 @@ jobs: dot -V - name: Install Doxygen - uses: ssciwr/doxygen-install@v1 + uses: ssciwr/doxygen-install@v2 with: version: "1.14.0" From 24bc713ba8069241fc43aefc8c6ecf291cedd830 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 7 Feb 2026 18:47:50 -0800 Subject: [PATCH 195/325] [DeckListModel] Fix deck hash not updating on card node add/remove (#6584) --- .../libcockatrice/models/deck_list/deck_list_model.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 a0624f858..8cbf23be4 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -340,6 +340,9 @@ bool DeckListModel::removeRows(int row, int count, const QModelIndex &parent) emitRecursiveUpdates(parent); } + deckList->refreshDeckHash(); + emit deckHashChanged(); + return true; } @@ -448,8 +451,6 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam cardNode->setCardSetShortName(cardSetName); cardNode->setCardCollectorNumber(printingInfo.getProperty("num")); cardNode->setCardProviderId(printingInfo.getProperty("uuid")); - deckList->refreshDeckHash(); - emit deckHashChanged(); // Emit dataChanged for the amount column since we modified it QModelIndex cardIndex = nodeToIndex(cardNode); QModelIndex amountIndex = cardIndex.sibling(cardIndex.row(), DeckListModelColumns::CARD_AMOUNT); @@ -458,6 +459,10 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam sort(lastKnownColumn, lastKnownOrder); refreshCardFormatLegalities(); emitRecursiveUpdates(parentIndex); + + deckList->refreshDeckHash(); + emit deckHashChanged(); + auto index = nodeToIndex(cardNode); if (cardNodeAdded) { From a80a0531a62813c808396da88e5a5206cfed47b6 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sat, 7 Feb 2026 21:48:00 -0500 Subject: [PATCH 196/325] Translate oracle/oracle_en@source.ts in fr (#6544) 100% translated source file: 'oracle/oracle_en@source.ts' on 'fr'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- oracle/translations/oracle_fr.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/oracle/translations/oracle_fr.ts b/oracle/translations/oracle_fr.ts index 7f43ad140..d43dfa9f3 100644 --- a/oracle/translations/oracle_fr.ts +++ b/oracle/translations/oracle_fr.ts @@ -172,7 +172,7 @@ spoiler - + spoiler @@ -192,7 +192,7 @@ Local file: - + Fichier local: @@ -202,7 +202,7 @@ Choose file... - + Choisissez un fichier... @@ -230,7 +230,7 @@ tokens - + jetons @@ -250,7 +250,7 @@ Local file: - + Ficher local: @@ -260,7 +260,7 @@ Choose file... - + Choisissez un ficher... @@ -391,12 +391,12 @@ Load %1 file - + Chargez le fichier %1 %1 file (%1) - + fichier %1 (%1) @@ -420,12 +420,12 @@ Please choose a file. - + Merci de choisir un fichier. Cannot open file '%1'. - + Le fichier '%1' ne peut pas être ouvert. From 804a60f1ea50b0feb80512d5ed8e29c89b2a7b18 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 7 Feb 2026 19:51:12 -0800 Subject: [PATCH 197/325] [LayoutSettings] Move over layout settings in global.ini (#6587) * [LayoutSettings] Move over some settings from general * remove unused setting --- .../src/client/settings/cache_settings.cpp | 28 ---------------- .../src/client/settings/cache_settings.h | 24 -------------- .../src/game/dialogs/dlg_create_token.cpp | 8 ++--- .../widgets/dialogs/dlg_manage_sets.cpp | 6 ++-- cockatrice/src/interface/window_main.cpp | 4 +-- .../settings/layouts_settings.cpp | 32 +++++++++++++++++++ .../libcockatrice/settings/layouts_settings.h | 8 +++++ 7 files changed, 49 insertions(+), 61 deletions(-) diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 8a981ce14..30093fd52 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -268,9 +268,6 @@ SettingsCache::SettingsCache() picDownload = settings->value("personal/picturedownload", true).toBool(); showStatusBar = settings->value("personal/showStatusBar", false).toBool(); - mainWindowGeometry = settings->value("interface/main_window_geometry").toByteArray(); - tokenDialogGeometry = settings->value("interface/token_dialog_geometry").toByteArray(); - setsDialogGeometry = settings->value("interface/sets_dialog_geometry").toByteArray(); notificationsEnabled = settings->value("interface/notificationsenabled", true).toBool(); spectatorNotificationsEnabled = settings->value("interface/specnotificationsenabled", false).toBool(); buddyConnectNotificationsEnabled = settings->value("interface/buddyconnectnotificationsenabled", true).toBool(); @@ -280,7 +277,6 @@ SettingsCache::SettingsCache() doNotDeleteArrowsInSubPhases = settings->value("interface/doNotDeleteArrowsInSubPhases", true).toBool(); startingHandSize = settings->value("interface/startinghandsize", 7).toInt(); annotateTokens = settings->value("interface/annotatetokens", false).toBool(); - tabGameSplitterSizes = settings->value("interface/tabgame_splittersizes").toByteArray(); knownMissingFeatures = settings->value("interface/knownmissingfeatures", "").toString(); useTearOffMenus = settings->value("interface/usetearoffmenus", true).toBool(); cardViewInitialRowsMax = settings->value("interface/cardViewInitialRowsMax", 14).toInt(); @@ -713,12 +709,6 @@ void SettingsCache::setAnnotateTokens(QT_STATE_CHANGED_T _annotateTokens) settings->setValue("interface/annotatetokens", annotateTokens); } -void SettingsCache::setTabGameSplitterSizes(const QByteArray &_tabGameSplitterSizes) -{ - tabGameSplitterSizes = _tabGameSplitterSizes; - settings->setValue("interface/tabgame_splittersizes", tabGameSplitterSizes); -} - void SettingsCache::setShowShortcuts(QT_STATE_CHANGED_T _showShortcuts) { showShortcuts = static_cast(_showShortcuts); @@ -1090,24 +1080,6 @@ void SettingsCache::setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignore settings->setValue("chat/ignore_unregistered_messages", ignoreUnregisteredUserMessages); } -void SettingsCache::setMainWindowGeometry(const QByteArray &_mainWindowGeometry) -{ - mainWindowGeometry = _mainWindowGeometry; - settings->setValue("interface/main_window_geometry", mainWindowGeometry); -} - -void SettingsCache::setTokenDialogGeometry(const QByteArray &_tokenDialogGeometry) -{ - tokenDialogGeometry = _tokenDialogGeometry; - settings->setValue("interface/token_dialog_geometry", tokenDialogGeometry); -} - -void SettingsCache::setSetsDialogGeometry(const QByteArray &_setsDialogGeometry) -{ - setsDialogGeometry = _setsDialogGeometry; - settings->setValue("interface/sets_dialog_geometry", setsDialogGeometry); -} - void SettingsCache::setPixmapCacheSize(const int _pixmapCacheSize) { pixmapCacheSize = _pixmapCacheSize; diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index 57adb394a..5c5054105 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -205,9 +205,6 @@ private: DebugSettings *debugSettings; CardCounterSettings *cardCounterSettings; - QByteArray mainWindowGeometry; - QByteArray tokenDialogGeometry; - QByteArray setsDialogGeometry; QString lang; QString deckPath, filtersPath, replaysPath, picsPath, redirectCachePath, customPicsPath, cardDatabasePath, customCardDatabasePath, themesPath, spoilerDatabasePath, tokenDatabasePath, themeName, homeTabBackgroundSource; @@ -238,7 +235,6 @@ private: bool doNotDeleteArrowsInSubPhases; int startingHandSize; bool annotateTokens; - QByteArray tabGameSplitterSizes; bool showShortcuts; bool showGameSelectorFilterToolbar; bool displayCardNames; @@ -345,18 +341,6 @@ public: QString getSettingsPath(); [[nodiscard]] QString getCachePath() const; [[nodiscard]] QString getNetworkCachePath() const; - [[nodiscard]] const QByteArray &getMainWindowGeometry() const - { - return mainWindowGeometry; - } - [[nodiscard]] const QByteArray &getTokenDialogGeometry() const - { - return tokenDialogGeometry; - } - [[nodiscard]] const QByteArray &getSetsDialogGeometry() const - { - return setsDialogGeometry; - } [[nodiscard]] QString getLang() const { return lang; @@ -555,10 +539,6 @@ public: { return annotateTokens; } - [[nodiscard]] QByteArray getTabGameSplitterSizes() const - { - return tabGameSplitterSizes; - } [[nodiscard]] bool getShowShortcuts() const { return showShortcuts; @@ -995,9 +975,6 @@ public: public slots: void setDownloadSpoilerStatus(bool _spoilerStatus); - void setMainWindowGeometry(const QByteArray &_mainWindowGeometry); - void setTokenDialogGeometry(const QByteArray &_tokenDialog); - void setSetsDialogGeometry(const QByteArray &_setsDialog); void setLang(const QString &_lang); void setShowTipsOnStartup(bool _showTipsOnStartup); void setSeenTips(const QList &_seenTips); @@ -1034,7 +1011,6 @@ public slots: void setDoNotDeleteArrowsInSubPhases(QT_STATE_CHANGED_T _doNotDeleteArrowsInSubPhases); void setStartingHandSize(int _startingHandSize); void setAnnotateTokens(QT_STATE_CHANGED_T _annotateTokens); - void setTabGameSplitterSizes(const QByteArray &_tabGameSplitterSizes); void setShowShortcuts(QT_STATE_CHANGED_T _showShortcuts); void setShowGameSelectorFilterToolbar(QT_STATE_CHANGED_T _showGameSelectorFilterToolbar); void setDisplayCardNames(QT_STATE_CHANGED_T _displayCardNames); diff --git a/cockatrice/src/game/dialogs/dlg_create_token.cpp b/cockatrice/src/game/dialogs/dlg_create_token.cpp index 836c0c16a..df264f065 100644 --- a/cockatrice/src/game/dialogs/dlg_create_token.cpp +++ b/cockatrice/src/game/dialogs/dlg_create_token.cpp @@ -146,13 +146,13 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa setWindowTitle(tr("Create token")); resize(600, 500); - restoreGeometry(SettingsCache::instance().getTokenDialogGeometry()); + restoreGeometry(SettingsCache::instance().layouts().getTokenDialogGeometry()); } void DlgCreateToken::closeEvent(QCloseEvent *event) { event->accept(); - SettingsCache::instance().setTokenDialogGeometry(saveGeometry()); + SettingsCache::instance().layouts().setTokenDialogGeometry(saveGeometry()); } void DlgCreateToken::faceDownCheckBoxToggled(bool checked) @@ -225,13 +225,13 @@ void DlgCreateToken::actChooseTokenFromDeck(bool checked) void DlgCreateToken::actOk() { - SettingsCache::instance().setTokenDialogGeometry(saveGeometry()); + SettingsCache::instance().layouts().setTokenDialogGeometry(saveGeometry()); accept(); } void DlgCreateToken::actReject() { - SettingsCache::instance().setTokenDialogGeometry(saveGeometry()); + SettingsCache::instance().layouts().setTokenDialogGeometry(saveGeometry()); reject(); } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp index 2cda51f2a..5e6a6a62d 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp @@ -189,11 +189,11 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) setWindowTitle(tr("Manage sets")); setMinimumSize(800, 500); - auto &geometry = SettingsCache::instance().getSetsDialogGeometry(); + auto geometry = SettingsCache::instance().layouts().getSetsDialogGeometry(); if (!geometry.isEmpty()) { restoreGeometry(geometry); } - auto &headerState = SettingsCache::instance().layouts().getSetsDialogHeaderState(); + auto headerState = SettingsCache::instance().layouts().getSetsDialogHeaderState(); if (!headerState.isEmpty()) { view->header()->restoreState(headerState); view->header()->setSortIndicator(SORT_RESET, Qt::DescendingOrder); @@ -209,7 +209,7 @@ WndSets::~WndSets() void WndSets::closeEvent(QCloseEvent * /*ev*/) { - SettingsCache::instance().setSetsDialogGeometry(saveGeometry()); + SettingsCache::instance().layouts().setSetsDialogGeometry(saveGeometry()); } void WndSets::saveHeaderState() diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 2e135d170..3724ff29d 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -879,7 +879,7 @@ MainWindow::MainWindow(QWidget *parent) retranslateUi(); - if (!restoreGeometry(SettingsCache::instance().getMainWindowGeometry())) { + if (!restoreGeometry(SettingsCache::instance().layouts().getMainWindowGeometry())) { setWindowState(Qt::WindowMaximized); } aFullScreen->setChecked(static_cast(windowState() & Qt::WindowFullScreen)); @@ -1098,7 +1098,7 @@ void MainWindow::closeEvent(QCloseEvent *event) tip->close(); event->accept(); - SettingsCache::instance().setMainWindowGeometry(saveGeometry()); + SettingsCache::instance().layouts().setMainWindowGeometry(saveGeometry()); tabSupervisor->deleteLater(); } diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp index f5e6da0c2..704778955 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp @@ -4,9 +4,11 @@ const static QString STATE_PROP = "state"; const static QString GEOMETRY_PROP = "geometry"; const static QString SIZE_PROP = "widgetSize"; +const static QString GROUP_MAIN_WINDOW = "mainWindow"; const static QString GROUP_DECK_EDITOR = "deckEditor"; const static QString GROUP_DECK_EDITOR_DB = "deckEditorDb"; const static QString GROUP_SETS_DIALOG = "setsDialog"; +const static QString GROUP_TOKEN_DIALOG = "tokenDialog"; const static QString GROUP_GAME_PLAY_AREA = "gamePlayArea"; const static QString GROUP_REPLAY_PLAY_AREA = "replayPlayArea"; @@ -15,6 +17,16 @@ LayoutsSettings::LayoutsSettings(const QString &settingPath, QObject *parent) { } +void LayoutsSettings::setMainWindowGeometry(const QByteArray &value) +{ + setValue(value, GEOMETRY_PROP, GROUP_MAIN_WINDOW); +} + +QByteArray LayoutsSettings::getMainWindowGeometry() +{ + return getValue(GEOMETRY_PROP, GROUP_MAIN_WINDOW).toByteArray(); +} + const QByteArray LayoutsSettings::getDeckEditorLayoutState() { return getValue(STATE_PROP, GROUP_DECK_EDITOR).toByteArray(); @@ -110,6 +122,26 @@ void LayoutsSettings::setSetsDialogHeaderState(const QByteArray &value) setValue(value, STATE_PROP, GROUP_SETS_DIALOG, "header"); } +void LayoutsSettings::setSetsDialogGeometry(const QByteArray &value) +{ + setValue(value, GEOMETRY_PROP, GROUP_SETS_DIALOG); +} + +QByteArray LayoutsSettings::getSetsDialogGeometry() +{ + return getValue(GEOMETRY_PROP, GROUP_SETS_DIALOG).toByteArray(); +} + +void LayoutsSettings::setTokenDialogGeometry(const QByteArray &value) +{ + setValue(value, GEOMETRY_PROP, GROUP_TOKEN_DIALOG); +} + +QByteArray LayoutsSettings::getTokenDialogGeometry() +{ + return getValue(GEOMETRY_PROP, GROUP_TOKEN_DIALOG).toByteArray(); +} + void LayoutsSettings::setGamePlayAreaGeometry(const QByteArray &value) { setValue(value, GEOMETRY_PROP, GROUP_GAME_PLAY_AREA); diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h index 9c4df77df..59b261a2d 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h @@ -17,6 +17,8 @@ class LayoutsSettings : public SettingsManager friend class SettingsCache; public: + void setMainWindowGeometry(const QByteArray &value); + void setDeckEditorLayoutState(const QByteArray &value); void setDeckEditorGeometry(const QByteArray &value); void setDeckEditorCardDatabaseSize(const QSize &value); @@ -26,6 +28,8 @@ public: void setDeckEditorFilterSize(const QSize &value); void setDeckEditorDbHeaderState(const QByteArray &value); void setSetsDialogHeaderState(const QByteArray &value); + void setSetsDialogGeometry(const QByteArray &value); + void setTokenDialogGeometry(const QByteArray &value); void setGamePlayAreaGeometry(const QByteArray &value); void setGamePlayAreaState(const QByteArray &value); @@ -40,6 +44,8 @@ public: void setReplayPlayerListSize(const QSize &value); void setReplayReplaySize(const QSize &value); + QByteArray getMainWindowGeometry(); + const QByteArray getDeckEditorLayoutState(); const QByteArray getDeckEditorGeometry(); QSize getDeckEditorCardDatabaseSize(); @@ -49,6 +55,8 @@ public: QSize getDeckEditorFilterSize(); const QByteArray getDeckEditorDbHeaderState(); const QByteArray getSetsDialogHeaderState(); + QByteArray getSetsDialogGeometry(); + QByteArray getTokenDialogGeometry(); const QByteArray getGamePlayAreaLayoutState(); const QByteArray getGamePlayAreaGeometry(); From edc86917318cd83284f2b6987c653c9916040706 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 7 Feb 2026 20:42:23 -0800 Subject: [PATCH 198/325] [LayoutSettings] Don't return by const value (#6592) * [LayoutSettings] Don't return by const value * fix compile failure --- .../widgets/tabs/tab_deck_editor.cpp | 2 +- .../tab_deck_editor_visual.cpp | 2 +- .../settings/layouts_settings.cpp | 30 +++++++++---------- .../libcockatrice/settings/layouts_settings.h | 30 +++++++++---------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index 1d667640d..1f977b429 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -136,7 +136,7 @@ void TabDeckEditor::loadLayout() { LayoutsSettings &layouts = SettingsCache::instance().layouts(); - auto &layoutState = layouts.getDeckEditorLayoutState(); + auto layoutState = layouts.getDeckEditorLayoutState(); if (layoutState.isNull()) restartLayout(); else { diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 74c1021fb..518fd86c4 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -273,7 +273,7 @@ void TabDeckEditorVisual::refreshShortcuts() void TabDeckEditorVisual::loadLayout() { LayoutsSettings &layouts = SettingsCache::instance().layouts(); - auto &layoutState = layouts.getDeckEditorLayoutState(); + auto layoutState = layouts.getDeckEditorLayoutState(); if (layoutState.isNull()) { restartLayout(); } else { diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp index 704778955..4165101d1 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp @@ -27,7 +27,7 @@ QByteArray LayoutsSettings::getMainWindowGeometry() return getValue(GEOMETRY_PROP, GROUP_MAIN_WINDOW).toByteArray(); } -const QByteArray LayoutsSettings::getDeckEditorLayoutState() +QByteArray LayoutsSettings::getDeckEditorLayoutState() { return getValue(STATE_PROP, GROUP_DECK_EDITOR).toByteArray(); } @@ -37,7 +37,7 @@ void LayoutsSettings::setDeckEditorLayoutState(const QByteArray &value) setValue(value, STATE_PROP, GROUP_DECK_EDITOR); } -const QByteArray LayoutsSettings::getDeckEditorGeometry() +QByteArray LayoutsSettings::getDeckEditorGeometry() { return getValue(GEOMETRY_PROP, GROUP_DECK_EDITOR).toByteArray(); } @@ -102,7 +102,7 @@ void LayoutsSettings::setDeckEditorFilterSize(const QSize &value) setValue(value, "filter", GROUP_DECK_EDITOR, SIZE_PROP); } -const QByteArray LayoutsSettings::getDeckEditorDbHeaderState() +QByteArray LayoutsSettings::getDeckEditorDbHeaderState() { return getValue(STATE_PROP, GROUP_DECK_EDITOR_DB, "header").toByteArray(); } @@ -112,7 +112,7 @@ void LayoutsSettings::setDeckEditorDbHeaderState(const QByteArray &value) setValue(value, STATE_PROP, GROUP_DECK_EDITOR_DB, "header"); } -const QByteArray LayoutsSettings::getSetsDialogHeaderState() +QByteArray LayoutsSettings::getSetsDialogHeaderState() { return getValue(STATE_PROP, GROUP_SETS_DIALOG, "header").toByteArray(); } @@ -152,17 +152,17 @@ void LayoutsSettings::setGamePlayAreaState(const QByteArray &value) setValue(value, STATE_PROP, GROUP_GAME_PLAY_AREA); } -const QByteArray LayoutsSettings::getGamePlayAreaLayoutState() +QByteArray LayoutsSettings::getGamePlayAreaLayoutState() { return getValue(STATE_PROP, GROUP_GAME_PLAY_AREA).toByteArray(); } -const QByteArray LayoutsSettings::getGamePlayAreaGeometry() +QByteArray LayoutsSettings::getGamePlayAreaGeometry() { return getValue(GEOMETRY_PROP, GROUP_GAME_PLAY_AREA).toByteArray(); } -const QSize LayoutsSettings::getGameCardInfoSize() +QSize LayoutsSettings::getGameCardInfoSize() { QVariant previous = getValue("cardInfo", GROUP_GAME_PLAY_AREA, SIZE_PROP); return previous == QVariant() ? QSize(250, 360) : previous.toSize(); @@ -173,7 +173,7 @@ void LayoutsSettings::setGameCardInfoSize(const QSize &value) setValue(value, "cardInfo", GROUP_GAME_PLAY_AREA, SIZE_PROP); } -const QSize LayoutsSettings::getGameMessageLayoutSize() +QSize LayoutsSettings::getGameMessageLayoutSize() { QVariant previous = getValue("messageLayout", GROUP_GAME_PLAY_AREA, SIZE_PROP); return previous == QVariant() ? QSize(250, 250) : previous.toSize(); @@ -184,7 +184,7 @@ void LayoutsSettings::setGameMessageLayoutSize(const QSize &value) setValue(value, "messageLayout", GROUP_GAME_PLAY_AREA, SIZE_PROP); } -const QSize LayoutsSettings::getGamePlayerListSize() +QSize LayoutsSettings::getGamePlayerListSize() { QVariant previous = getValue("playerList", GROUP_GAME_PLAY_AREA, SIZE_PROP); return previous == QVariant() ? QSize(250, 50) : previous.toSize(); @@ -205,17 +205,17 @@ void LayoutsSettings::setReplayPlayAreaState(const QByteArray &value) setValue(value, STATE_PROP, GROUP_REPLAY_PLAY_AREA); } -const QByteArray LayoutsSettings::getReplayPlayAreaLayoutState() +QByteArray LayoutsSettings::getReplayPlayAreaLayoutState() { return getValue(STATE_PROP, GROUP_REPLAY_PLAY_AREA).toByteArray(); } -const QByteArray LayoutsSettings::getReplayPlayAreaGeometry() +QByteArray LayoutsSettings::getReplayPlayAreaGeometry() { return getValue(GEOMETRY_PROP, GROUP_REPLAY_PLAY_AREA).toByteArray(); } -const QSize LayoutsSettings::getReplayCardInfoSize() +QSize LayoutsSettings::getReplayCardInfoSize() { QVariant previous = getValue("cardInfo", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); return previous == QVariant() ? QSize(250, 360) : previous.toSize(); @@ -226,7 +226,7 @@ void LayoutsSettings::setReplayCardInfoSize(const QSize &value) setValue(value, "cardInfo", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); } -const QSize LayoutsSettings::getReplayMessageLayoutSize() +QSize LayoutsSettings::getReplayMessageLayoutSize() { QVariant previous = getValue("messageLayout", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); return previous == QVariant() ? QSize(250, 200) : previous.toSize(); @@ -237,7 +237,7 @@ void LayoutsSettings::setReplayMessageLayoutSize(const QSize &value) setValue(value, "messageLayout", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); } -const QSize LayoutsSettings::getReplayPlayerListSize() +QSize LayoutsSettings::getReplayPlayerListSize() { QVariant previous = getValue("playerList", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); return previous == QVariant() ? QSize(250, 50) : previous.toSize(); @@ -248,7 +248,7 @@ void LayoutsSettings::setReplayPlayerListSize(const QSize &value) setValue(value, "playerList", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); } -const QSize LayoutsSettings::getReplayReplaySize() +QSize LayoutsSettings::getReplayReplaySize() { QVariant previous = getValue("replay", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); return previous == QVariant() ? QSize(900, 100) : previous.toSize(); diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h index 59b261a2d..bfa5e37a6 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h @@ -46,30 +46,30 @@ public: QByteArray getMainWindowGeometry(); - const QByteArray getDeckEditorLayoutState(); - const QByteArray getDeckEditorGeometry(); + QByteArray getDeckEditorLayoutState(); + QByteArray getDeckEditorGeometry(); QSize getDeckEditorCardDatabaseSize(); QSize getDeckEditorCardSize(); QSize getDeckEditorDeckSize(); QSize getDeckEditorPrintingSelectorSize(); QSize getDeckEditorFilterSize(); - const QByteArray getDeckEditorDbHeaderState(); - const QByteArray getSetsDialogHeaderState(); + QByteArray getDeckEditorDbHeaderState(); + QByteArray getSetsDialogHeaderState(); QByteArray getSetsDialogGeometry(); QByteArray getTokenDialogGeometry(); - const QByteArray getGamePlayAreaLayoutState(); - const QByteArray getGamePlayAreaGeometry(); - const QSize getGameCardInfoSize(); - const QSize getGameMessageLayoutSize(); - const QSize getGamePlayerListSize(); + QByteArray getGamePlayAreaLayoutState(); + QByteArray getGamePlayAreaGeometry(); + QSize getGameCardInfoSize(); + QSize getGameMessageLayoutSize(); + QSize getGamePlayerListSize(); - const QByteArray getReplayPlayAreaLayoutState(); - const QByteArray getReplayPlayAreaGeometry(); - const QSize getReplayCardInfoSize(); - const QSize getReplayMessageLayoutSize(); - const QSize getReplayPlayerListSize(); - const QSize getReplayReplaySize(); + QByteArray getReplayPlayAreaLayoutState(); + QByteArray getReplayPlayAreaGeometry(); + QSize getReplayCardInfoSize(); + QSize getReplayMessageLayoutSize(); + QSize getReplayPlayerListSize(); + QSize getReplayReplaySize(); signals: public slots: From 1eb6027443ec7a4cdfbeb446efbff40e1ed467d4 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 8 Feb 2026 05:07:40 -0800 Subject: [PATCH 199/325] clean up freeDocksSize in tabs with dockWidgets (#6593) --- .../interface/widgets/tabs/tab_deck_editor.cpp | 7 ++----- .../src/interface/widgets/tabs/tab_game.cpp | 15 +++------------ .../visual_deck_editor/tab_deck_editor_visual.cpp | 15 ++++----------- 3 files changed, 9 insertions(+), 28 deletions(-) diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index 1f977b429..bc5ef30a2 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -190,12 +190,9 @@ void TabDeckEditor::restartLayout() /** @brief Frees dock sizes to allow flexible resizing. */ void TabDeckEditor::freeDocksSize() { - const QSize minSize(100, 100); - const QSize maxSize(5000, 5000); - for (auto dockWidget : dockToActions.keys()) { - dockWidget->setMinimumSize(minSize); - dockWidget->setMaximumSize(maxSize); + dockWidget->setMinimumSize(100, 100); + dockWidget->setMaximumSize(5000, 5000); } } diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index 2df6e5ac5..7baab6f8b 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -1113,18 +1113,9 @@ void TabGame::loadLayout() void TabGame::freeDocksSize() { - cardInfoDock->setMinimumSize(100, 100); - cardInfoDock->setMaximumSize(5000, 5000); - - messageLayoutDock->setMinimumSize(100, 100); - messageLayoutDock->setMaximumSize(5000, 5000); - - playerListDock->setMinimumSize(100, 100); - playerListDock->setMaximumSize(5000, 5000); - - if (replayDock) { - replayDock->setMinimumSize(100, 100); - replayDock->setMaximumSize(5000, 5000); + for (auto dockWidget : dockToActions.keys()) { + dockWidget->setMinimumSize(100, 100); + dockWidget->setMaximumSize(5000, 5000); } } diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 518fd86c4..5017601dc 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -249,17 +249,10 @@ void TabDeckEditorVisual::showPrintingSelector() /** @brief Set size restrictions for free floating dock widgets. */ void TabDeckEditorVisual::freeDocksSize() { - deckDockWidget->setMinimumSize(100, 100); - deckDockWidget->setMaximumSize(5000, 5000); - - cardInfoDockWidget->setMinimumSize(100, 100); - cardInfoDockWidget->setMaximumSize(5000, 5000); - - filterDockWidget->setMinimumSize(100, 100); - filterDockWidget->setMaximumSize(5000, 5000); - - printingSelectorDockWidget->setMinimumSize(100, 100); - printingSelectorDockWidget->setMaximumSize(5000, 5000); + for (auto dockWidget : dockToActions.keys()) { + dockWidget->setMinimumSize(100, 100); + dockWidget->setMaximumSize(5000, 5000); + } } /** @brief Refreshes keyboard shortcuts for this tab from settings. */ From ac7ff3a0e9126949c4018651bd4c8ceb4cd5ac82 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 8 Feb 2026 05:07:53 -0800 Subject: [PATCH 200/325] [LayoutSettings] Refactor how widgetSize settings are managed (#6594) --- .../widgets/tabs/abstract_tab_deck_editor.cpp | 4 +- .../widgets/tabs/abstract_tab_deck_editor.h | 3 +- .../widgets/tabs/tab_deck_editor.cpp | 39 +++--- .../src/interface/widgets/tabs/tab_game.cpp | 58 ++++---- .../src/interface/widgets/tabs/tab_game.h | 3 +- .../tab_deck_editor_visual.cpp | 33 ++--- .../settings/layouts_settings.cpp | 131 +++--------------- .../libcockatrice/settings/layouts_settings.h | 32 ++--- 8 files changed, 92 insertions(+), 211 deletions(-) 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 44fa46bb1..afc834e10 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -75,7 +75,7 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta &AbstractTabDeckEditor::refreshShortcuts); } -void AbstractTabDeckEditor::registerDockWidget(QMenu *_viewMenu, QDockWidget *widget) +void AbstractTabDeckEditor::registerDockWidget(QMenu *_viewMenu, QDockWidget *widget, const QSize &defaultSize) { QMenu *menu = _viewMenu->addMenu(QString()); @@ -102,7 +102,7 @@ void AbstractTabDeckEditor::registerDockWidget(QMenu *_viewMenu, QDockWidget *wi connect(filter, &VisibilityChangeListener::visibilityChanged, aVisible, [aVisible](bool visible) { aVisible->setChecked(visible); }); - dockToActions.insert(widget, {menu, aVisible, aFloating}); + dockToActions.insert(widget, {menu, aVisible, aFloating, defaultSize}); } /** diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h index 9c62a5c93..3730a7cdd 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h @@ -274,13 +274,14 @@ protected: QMenu *menu; ///< The menu containing the actions QAction *aVisible; ///< The menu action that toggles visibility QAction *aFloating; ///< The menu action that toggles floating + QSize defaultSize; ///< The default size of the dock }; /** * @brief registers a QDockWidget as a managed dock widget. Creates the associated actions and menu, adds them to * the viewMenu, and connects those actions to the tab's slots. */ - void registerDockWidget(QMenu *_viewMenu, QDockWidget *widget); + void registerDockWidget(QMenu *_viewMenu, QDockWidget *widget, const QSize &defaultSize); /** @brief Confirms deck open action based on settings and modified state. * @param openInSameTabIfBlank Whether to reuse same tab if blank. diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index bc5ef30a2..cf258d454 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -54,11 +54,11 @@ void TabDeckEditor::createMenus() viewMenu = new QMenu(this); - registerDockWidget(viewMenu, cardDatabaseDockWidget); - registerDockWidget(viewMenu, cardInfoDockWidget); - registerDockWidget(viewMenu, deckDockWidget); - registerDockWidget(viewMenu, filterDockWidget); - registerDockWidget(viewMenu, printingSelectorDockWidget); + registerDockWidget(viewMenu, cardDatabaseDockWidget, {500, 500}); + registerDockWidget(viewMenu, cardInfoDockWidget, {250, 500}); + registerDockWidget(viewMenu, deckDockWidget, {250, 360}); + registerDockWidget(viewMenu, filterDockWidget, {250, 250}); + registerDockWidget(viewMenu, printingSelectorDockWidget, {525, 250}); connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this, [this](bool enabled) { dockToActions[printingSelectorDockWidget].menu->setEnabled(!enabled); }); @@ -144,20 +144,14 @@ void TabDeckEditor::loadLayout() restoreGeometry(layouts.getDeckEditorGeometry()); } - cardDatabaseDockWidget->setMinimumSize(layouts.getDeckEditorCardDatabaseSize()); - cardDatabaseDockWidget->setMaximumSize(layouts.getDeckEditorCardDatabaseSize()); + for (auto it = dockToActions.constKeyValueBegin(); it != dockToActions.constKeyValueEnd(); ++it) { + auto dockWidget = it->first; + auto actions = it->second; - cardInfoDockWidget->setMinimumSize(layouts.getDeckEditorCardSize()); - cardInfoDockWidget->setMaximumSize(layouts.getDeckEditorCardSize()); - - filterDockWidget->setMinimumSize(layouts.getDeckEditorFilterSize()); - filterDockWidget->setMaximumSize(layouts.getDeckEditorFilterSize()); - - deckDockWidget->setMinimumSize(layouts.getDeckEditorDeckSize()); - deckDockWidget->setMaximumSize(layouts.getDeckEditorDeckSize()); - - printingSelectorDockWidget->setMinimumSize(layouts.getDeckEditorPrintingSelectorSize()); - printingSelectorDockWidget->setMaximumSize(layouts.getDeckEditorPrintingSelectorSize()); + QSize size = layouts.getDeckEditorWidgetSize(dockWidget->objectName(), actions.defaultSize); + dockWidget->setMinimumSize(size); + dockWidget->setMaximumSize(size); + } QTimer::singleShot(100, this, &TabDeckEditor::freeDocksSize); } @@ -208,11 +202,10 @@ bool TabDeckEditor::eventFilter(QObject *o, QEvent *e) LayoutsSettings &layouts = SettingsCache::instance().layouts(); layouts.setDeckEditorLayoutState(saveState()); layouts.setDeckEditorGeometry(saveGeometry()); - layouts.setDeckEditorCardDatabaseSize(cardDatabaseDockWidget->size()); - layouts.setDeckEditorCardSize(cardInfoDockWidget->size()); - layouts.setDeckEditorFilterSize(filterDockWidget->size()); - layouts.setDeckEditorDeckSize(deckDockWidget->size()); - layouts.setDeckEditorPrintingSelectorSize(printingSelectorDockWidget->size()); + + for (auto dockWidget : dockToActions.keys()) { + layouts.setDeckEditorWidgetSize(dockWidget->objectName(), dockWidget->size()); + } } return false; } diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index 7baab6f8b..e4a11f979 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -1034,12 +1034,12 @@ void TabGame::createViewMenuItems() { viewMenu = new QMenu(this); - registerDockWidget(viewMenu, cardInfoDock); - registerDockWidget(viewMenu, messageLayoutDock); - registerDockWidget(viewMenu, playerListDock); + registerDockWidget(viewMenu, cardInfoDock, {250, 360}); + registerDockWidget(viewMenu, messageLayoutDock, {250, 200}); + registerDockWidget(viewMenu, playerListDock, {250, 50}); if (replayDock) { - registerDockWidget(viewMenu, replayDock); + registerDockWidget(viewMenu, replayDock, {900, 100}); } viewMenu->addSeparator(); @@ -1051,7 +1051,7 @@ void TabGame::createViewMenuItems() addTabMenu(viewMenu); } -void TabGame::registerDockWidget(QMenu *_viewMenu, QDockWidget *widget) +void TabGame::registerDockWidget(QMenu *_viewMenu, QDockWidget *widget, const QSize &defaultSize) { QMenu *menu = _viewMenu->addMenu(QString()); @@ -1078,7 +1078,7 @@ void TabGame::registerDockWidget(QMenu *_viewMenu, QDockWidget *widget) connect(filter, &VisibilityChangeListener::visibilityChanged, aVisible, [aVisible](bool visible) { aVisible->setChecked(visible); }); - dockToActions.insert(widget, {menu, aVisible, aFloating}); + dockToActions.insert(widget, {menu, aVisible, aFloating, defaultSize}); } void TabGame::loadLayout() @@ -1088,24 +1088,27 @@ void TabGame::loadLayout() restoreGeometry(layouts.getReplayPlayAreaGeometry()); restoreState(layouts.getReplayPlayAreaLayoutState()); - cardInfoDock->setMinimumSize(layouts.getReplayCardInfoSize()); - cardInfoDock->setMaximumSize(layouts.getReplayCardInfoSize()); - messageLayoutDock->setMinimumSize(layouts.getReplayMessageLayoutSize()); - messageLayoutDock->setMaximumSize(layouts.getReplayMessageLayoutSize()); - playerListDock->setMinimumSize(layouts.getReplayPlayerListSize()); - playerListDock->setMaximumSize(layouts.getReplayPlayerListSize()); - replayDock->setMinimumSize(layouts.getReplayReplaySize()); - replayDock->setMaximumSize(layouts.getReplayReplaySize()); + for (auto it = dockToActions.constKeyValueBegin(); it != dockToActions.constKeyValueEnd(); ++it) { + auto dockWidget = it->first; + auto actions = it->second; + + QSize size = layouts.getReplayPlayAreaWidgetSize(dockWidget->objectName(), actions.defaultSize); + dockWidget->setMinimumSize(size); + dockWidget->setMaximumSize(size); + } + } else { restoreGeometry(layouts.getGamePlayAreaGeometry()); restoreState(layouts.getGamePlayAreaLayoutState()); - cardInfoDock->setMinimumSize(layouts.getGameCardInfoSize()); - cardInfoDock->setMaximumSize(layouts.getGameCardInfoSize()); - messageLayoutDock->setMinimumSize(layouts.getGameMessageLayoutSize()); - messageLayoutDock->setMaximumSize(layouts.getGameMessageLayoutSize()); - playerListDock->setMinimumSize(layouts.getGamePlayerListSize()); - playerListDock->setMaximumSize(layouts.getGamePlayerListSize()); + for (auto it = dockToActions.constKeyValueBegin(); it != dockToActions.constKeyValueEnd(); ++it) { + auto dockWidget = it->first; + auto actions = it->second; + + QSize size = layouts.getGamePlayAreaWidgetSize(dockWidget->objectName(), actions.defaultSize); + dockWidget->setMinimumSize(size); + dockWidget->setMaximumSize(size); + } } QTimer::singleShot(100, this, &TabGame::freeDocksSize); @@ -1333,16 +1336,17 @@ void TabGame::hideEvent(QHideEvent *event) if (replayDock) { layouts.setReplayPlayAreaState(saveState()); layouts.setReplayPlayAreaGeometry(saveGeometry()); - layouts.setReplayCardInfoSize(cardInfoDock->size()); - layouts.setReplayMessageLayoutSize(messageLayoutDock->size()); - layouts.setReplayPlayerListSize(playerListDock->size()); - layouts.setReplayReplaySize(replayDock->size()); + + for (auto dockWidget : dockToActions.keys()) { + layouts.setReplayPlayAreaWidgetSize(dockWidget->objectName(), dockWidget->size()); + } } else { layouts.setGamePlayAreaState(saveState()); layouts.setGamePlayAreaGeometry(saveGeometry()); - layouts.setGameCardInfoSize(cardInfoDock->size()); - layouts.setGameMessageLayoutSize(messageLayoutDock->size()); - layouts.setGamePlayerListSize(playerListDock->size()); + + for (auto dockWidget : dockToActions.keys()) { + layouts.setGamePlayAreaWidgetSize(dockWidget->objectName(), dockWidget->size()); + } } Tab::hideEvent(event); diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.h b/cockatrice/src/interface/widgets/tabs/tab_game.h index 4f944bf87..c35557743 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.h +++ b/cockatrice/src/interface/widgets/tabs/tab_game.h @@ -94,6 +94,7 @@ private: QMenu *menu; QAction *aVisible; QAction *aFloating; + QSize defaultSize; }; QMap dockToActions; @@ -117,7 +118,7 @@ private: void createMenuItems(); void createReplayMenuItems(); void createViewMenuItems(); - void registerDockWidget(QMenu *_viewMenu, QDockWidget *widget); + void registerDockWidget(QMenu *_viewMenu, QDockWidget *widget, const QSize &defaultSize); void createCardInfoDock(bool bReplay = false); void createPlayerListDock(bool bReplay = false); void createMessageDock(bool bReplay = false); diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 5017601dc..9d1611ed5 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -97,10 +97,10 @@ void TabDeckEditorVisual::createMenus() viewMenu = new QMenu(this); - registerDockWidget(viewMenu, cardInfoDockWidget); - registerDockWidget(viewMenu, deckDockWidget); - registerDockWidget(viewMenu, filterDockWidget); - registerDockWidget(viewMenu, printingSelectorDockWidget); + registerDockWidget(viewMenu, cardInfoDockWidget, {250, 500}); + registerDockWidget(viewMenu, deckDockWidget, {250, 360}); + registerDockWidget(viewMenu, filterDockWidget, {250, 250}); + registerDockWidget(viewMenu, printingSelectorDockWidget, {525, 250}); viewMenu->addSeparator(); @@ -274,17 +274,14 @@ void TabDeckEditorVisual::loadLayout() restoreGeometry(layouts.getDeckEditorGeometry()); } - cardInfoDockWidget->setMinimumSize(layouts.getDeckEditorCardSize()); - cardInfoDockWidget->setMaximumSize(layouts.getDeckEditorCardSize()); + for (auto it = dockToActions.constKeyValueBegin(); it != dockToActions.constKeyValueEnd(); ++it) { + auto dockWidget = it->first; + auto actions = it->second; - filterDockWidget->setMinimumSize(layouts.getDeckEditorFilterSize()); - filterDockWidget->setMaximumSize(layouts.getDeckEditorFilterSize()); - - deckDockWidget->setMinimumSize(layouts.getDeckEditorDeckSize()); - deckDockWidget->setMaximumSize(layouts.getDeckEditorDeckSize()); - - printingSelectorDockWidget->setMinimumSize(layouts.getDeckEditorPrintingSelectorSize()); - printingSelectorDockWidget->setMaximumSize(layouts.getDeckEditorPrintingSelectorSize()); + QSize size = layouts.getDeckEditorWidgetSize(dockWidget->objectName(), actions.defaultSize); + dockWidget->setMinimumSize(size); + dockWidget->setMaximumSize(size); + } QTimer::singleShot(100, this, &TabDeckEditorVisual::freeDocksSize); } @@ -349,10 +346,10 @@ bool TabDeckEditorVisual::eventFilter(QObject *o, QEvent *e) LayoutsSettings &layouts = SettingsCache::instance().layouts(); layouts.setDeckEditorLayoutState(saveState()); layouts.setDeckEditorGeometry(saveGeometry()); - layouts.setDeckEditorCardSize(cardInfoDockWidget->size()); - layouts.setDeckEditorFilterSize(filterDockWidget->size()); - layouts.setDeckEditorDeckSize(deckDockWidget->size()); - layouts.setDeckEditorPrintingSelectorSize(printingSelectorDockWidget->size()); + + for (auto dockWidget : dockToActions.keys()) { + layouts.setDeckEditorWidgetSize(dockWidget->objectName(), dockWidget->size()); + } } return false; } diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp index 4165101d1..d58d84323 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp @@ -47,59 +47,15 @@ void LayoutsSettings::setDeckEditorGeometry(const QByteArray &value) setValue(value, GEOMETRY_PROP, GROUP_DECK_EDITOR); } -QSize LayoutsSettings::getDeckEditorCardDatabaseSize() +void LayoutsSettings::setDeckEditorWidgetSize(const QString &widgetName, const QSize &value) { - QVariant previous = getValue("cardDatabase", GROUP_DECK_EDITOR, SIZE_PROP); - return previous == QVariant() ? QSize(500, 500) : previous.toSize(); + setValue(value, widgetName, GROUP_DECK_EDITOR, SIZE_PROP); } -void LayoutsSettings::setDeckEditorCardDatabaseSize(const QSize &value) +QSize LayoutsSettings::getDeckEditorWidgetSize(const QString &widgetName, const QSize &defaultValue) { - setValue(value, "cardDatabase", GROUP_DECK_EDITOR, SIZE_PROP); -} - -QSize LayoutsSettings::getDeckEditorCardSize() -{ - QVariant previous = getValue("card", GROUP_DECK_EDITOR, SIZE_PROP); - return previous == QVariant() ? QSize(250, 500) : previous.toSize(); -} - -void LayoutsSettings::setDeckEditorCardSize(const QSize &value) -{ - setValue(value, "card", GROUP_DECK_EDITOR, SIZE_PROP); -} - -QSize LayoutsSettings::getDeckEditorDeckSize() -{ - QVariant previous = getValue("deck", GROUP_DECK_EDITOR, SIZE_PROP); - return previous == QVariant() ? QSize(250, 360) : previous.toSize(); -} - -void LayoutsSettings::setDeckEditorDeckSize(const QSize &value) -{ - setValue(value, "deck", GROUP_DECK_EDITOR, SIZE_PROP); -} - -QSize LayoutsSettings::getDeckEditorPrintingSelectorSize() -{ - QVariant previous = getValue("printingSelector", GROUP_DECK_EDITOR, SIZE_PROP); - return previous == QVariant() ? QSize(525, 250) : previous.toSize(); -} - -void LayoutsSettings::setDeckEditorPrintingSelectorSize(const QSize &value) -{ - setValue(value, "printingSelector", GROUP_DECK_EDITOR, SIZE_PROP); -} - -QSize LayoutsSettings::getDeckEditorFilterSize() -{ - QVariant previous = getValue("filter", GROUP_DECK_EDITOR, SIZE_PROP); - return previous == QVariant() ? QSize(250, 250) : previous.toSize(); -} - -void LayoutsSettings::setDeckEditorFilterSize(const QSize &value) -{ - setValue(value, "filter", GROUP_DECK_EDITOR, SIZE_PROP); + QVariant previous = getValue(widgetName, GROUP_DECK_EDITOR, SIZE_PROP); + return previous == QVariant() ? defaultValue : previous.toSize(); } QByteArray LayoutsSettings::getDeckEditorDbHeaderState() @@ -162,37 +118,15 @@ QByteArray LayoutsSettings::getGamePlayAreaGeometry() return getValue(GEOMETRY_PROP, GROUP_GAME_PLAY_AREA).toByteArray(); } -QSize LayoutsSettings::getGameCardInfoSize() +void LayoutsSettings::setGamePlayAreaWidgetSize(const QString &widgetName, const QSize &value) { - QVariant previous = getValue("cardInfo", GROUP_GAME_PLAY_AREA, SIZE_PROP); - return previous == QVariant() ? QSize(250, 360) : previous.toSize(); + setValue(value, widgetName, GROUP_GAME_PLAY_AREA, SIZE_PROP); } -void LayoutsSettings::setGameCardInfoSize(const QSize &value) +QSize LayoutsSettings::getGamePlayAreaWidgetSize(const QString &widgetName, const QSize &defaultValue) { - setValue(value, "cardInfo", GROUP_GAME_PLAY_AREA, SIZE_PROP); -} - -QSize LayoutsSettings::getGameMessageLayoutSize() -{ - QVariant previous = getValue("messageLayout", GROUP_GAME_PLAY_AREA, SIZE_PROP); - return previous == QVariant() ? QSize(250, 250) : previous.toSize(); -} - -void LayoutsSettings::setGameMessageLayoutSize(const QSize &value) -{ - setValue(value, "messageLayout", GROUP_GAME_PLAY_AREA, SIZE_PROP); -} - -QSize LayoutsSettings::getGamePlayerListSize() -{ - QVariant previous = getValue("playerList", GROUP_GAME_PLAY_AREA, SIZE_PROP); - return previous == QVariant() ? QSize(250, 50) : previous.toSize(); -} - -void LayoutsSettings::setGamePlayerListSize(const QSize &value) -{ - setValue(value, "playerList", GROUP_GAME_PLAY_AREA, SIZE_PROP); + QVariant previous = getValue(widgetName, GROUP_GAME_PLAY_AREA, SIZE_PROP); + return previous == QVariant() ? defaultValue : previous.toSize(); } void LayoutsSettings::setReplayPlayAreaGeometry(const QByteArray &value) @@ -215,46 +149,13 @@ QByteArray LayoutsSettings::getReplayPlayAreaGeometry() return getValue(GEOMETRY_PROP, GROUP_REPLAY_PLAY_AREA).toByteArray(); } -QSize LayoutsSettings::getReplayCardInfoSize() +void LayoutsSettings::setReplayPlayAreaWidgetSize(const QString &widgetName, const QSize &value) { - QVariant previous = getValue("cardInfo", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); - return previous == QVariant() ? QSize(250, 360) : previous.toSize(); + setValue(value, widgetName, GROUP_REPLAY_PLAY_AREA, SIZE_PROP); } -void LayoutsSettings::setReplayCardInfoSize(const QSize &value) +QSize LayoutsSettings::getReplayPlayAreaWidgetSize(const QString &widgetName, const QSize &defaultValue) { - setValue(value, "cardInfo", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); -} - -QSize LayoutsSettings::getReplayMessageLayoutSize() -{ - QVariant previous = getValue("messageLayout", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); - return previous == QVariant() ? QSize(250, 200) : previous.toSize(); -} - -void LayoutsSettings::setReplayMessageLayoutSize(const QSize &value) -{ - setValue(value, "messageLayout", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); -} - -QSize LayoutsSettings::getReplayPlayerListSize() -{ - QVariant previous = getValue("playerList", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); - return previous == QVariant() ? QSize(250, 50) : previous.toSize(); -} - -void LayoutsSettings::setReplayPlayerListSize(const QSize &value) -{ - setValue(value, "playerList", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); -} - -QSize LayoutsSettings::getReplayReplaySize() -{ - QVariant previous = getValue("replay", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); - return previous == QVariant() ? QSize(900, 100) : previous.toSize(); -} - -void LayoutsSettings::setReplayReplaySize(const QSize &value) -{ - setValue(value, "replay", GROUP_REPLAY_PLAY_AREA, SIZE_PROP); -} + QVariant previous = getValue(widgetName, GROUP_REPLAY_PLAY_AREA, SIZE_PROP); + return previous == QVariant() ? defaultValue : previous.toSize(); +} \ No newline at end of file diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h index bfa5e37a6..e3dd9eac7 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h @@ -21,11 +21,8 @@ public: void setDeckEditorLayoutState(const QByteArray &value); void setDeckEditorGeometry(const QByteArray &value); - void setDeckEditorCardDatabaseSize(const QSize &value); - void setDeckEditorCardSize(const QSize &value); - void setDeckEditorDeckSize(const QSize &value); - void setDeckEditorPrintingSelectorSize(const QSize &value); - void setDeckEditorFilterSize(const QSize &value); + void setDeckEditorWidgetSize(const QString &widgetName, const QSize &value); + void setDeckEditorDbHeaderState(const QByteArray &value); void setSetsDialogHeaderState(const QByteArray &value); void setSetsDialogGeometry(const QByteArray &value); @@ -33,26 +30,18 @@ public: void setGamePlayAreaGeometry(const QByteArray &value); void setGamePlayAreaState(const QByteArray &value); - void setGameCardInfoSize(const QSize &value); - void setGameMessageLayoutSize(const QSize &value); - void setGamePlayerListSize(const QSize &value); + void setGamePlayAreaWidgetSize(const QString &widgetName, const QSize &value); void setReplayPlayAreaGeometry(const QByteArray &value); void setReplayPlayAreaState(const QByteArray &value); - void setReplayCardInfoSize(const QSize &value); - void setReplayMessageLayoutSize(const QSize &value); - void setReplayPlayerListSize(const QSize &value); - void setReplayReplaySize(const QSize &value); + void setReplayPlayAreaWidgetSize(const QString &widgetName, const QSize &value); QByteArray getMainWindowGeometry(); QByteArray getDeckEditorLayoutState(); QByteArray getDeckEditorGeometry(); - QSize getDeckEditorCardDatabaseSize(); - QSize getDeckEditorCardSize(); - QSize getDeckEditorDeckSize(); - QSize getDeckEditorPrintingSelectorSize(); - QSize getDeckEditorFilterSize(); + QSize getDeckEditorWidgetSize(const QString &widgetName, const QSize &defaultValue = {}); + QByteArray getDeckEditorDbHeaderState(); QByteArray getSetsDialogHeaderState(); QByteArray getSetsDialogGeometry(); @@ -60,16 +49,11 @@ public: QByteArray getGamePlayAreaLayoutState(); QByteArray getGamePlayAreaGeometry(); - QSize getGameCardInfoSize(); - QSize getGameMessageLayoutSize(); - QSize getGamePlayerListSize(); + QSize getGamePlayAreaWidgetSize(const QString &widgetName, const QSize &defaultValue = {}); QByteArray getReplayPlayAreaLayoutState(); QByteArray getReplayPlayAreaGeometry(); - QSize getReplayCardInfoSize(); - QSize getReplayMessageLayoutSize(); - QSize getReplayPlayerListSize(); - QSize getReplayReplaySize(); + QSize getReplayPlayAreaWidgetSize(const QString &widgetName, const QSize &defaultValue = {}); signals: public slots: From bdb42bbbbd1a30dfc7080e0837fbdc8e76e9b3bb Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 8 Feb 2026 13:37:56 -0800 Subject: [PATCH 201/325] [VDE] Separate layout settings for visual deck editor (#6595) --- .../tab_deck_editor_visual.cpp | 12 +++---- .../settings/layouts_settings.cpp | 32 +++++++++++++++++++ .../libcockatrice/settings/layouts_settings.h | 8 +++++ 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 9d1611ed5..29f871823 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -266,19 +266,19 @@ void TabDeckEditorVisual::refreshShortcuts() void TabDeckEditorVisual::loadLayout() { LayoutsSettings &layouts = SettingsCache::instance().layouts(); - auto layoutState = layouts.getDeckEditorLayoutState(); + auto layoutState = layouts.getVisualDeckEditorLayoutState(); if (layoutState.isNull()) { restartLayout(); } else { restoreState(layoutState); - restoreGeometry(layouts.getDeckEditorGeometry()); + restoreGeometry(layouts.getVisualDeckEditorGeometry()); } for (auto it = dockToActions.constKeyValueBegin(); it != dockToActions.constKeyValueEnd(); ++it) { auto dockWidget = it->first; auto actions = it->second; - QSize size = layouts.getDeckEditorWidgetSize(dockWidget->objectName(), actions.defaultSize); + QSize size = layouts.getVisualDeckEditorWidgetSize(dockWidget->objectName(), actions.defaultSize); dockWidget->setMinimumSize(size); dockWidget->setMaximumSize(size); } @@ -344,11 +344,11 @@ bool TabDeckEditorVisual::eventFilter(QObject *o, QEvent *e) { if (o == this && e->type() == QEvent::Hide) { LayoutsSettings &layouts = SettingsCache::instance().layouts(); - layouts.setDeckEditorLayoutState(saveState()); - layouts.setDeckEditorGeometry(saveGeometry()); + layouts.setVisualDeckEditorLayoutState(saveState()); + layouts.setVisualDeckEditorGeometry(saveGeometry()); for (auto dockWidget : dockToActions.keys()) { - layouts.setDeckEditorWidgetSize(dockWidget->objectName(), dockWidget->size()); + layouts.setVisualDeckEditorWidgetSize(dockWidget->objectName(), dockWidget->size()); } } return false; diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp index d58d84323..0ec03ba07 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp @@ -6,6 +6,7 @@ const static QString SIZE_PROP = "widgetSize"; const static QString GROUP_MAIN_WINDOW = "mainWindow"; const static QString GROUP_DECK_EDITOR = "deckEditor"; +const static QString GROUP_VISUAL_DECK_EDITOR = "visualDeckEditor"; const static QString GROUP_DECK_EDITOR_DB = "deckEditorDb"; const static QString GROUP_SETS_DIALOG = "setsDialog"; const static QString GROUP_TOKEN_DIALOG = "tokenDialog"; @@ -58,6 +59,37 @@ QSize LayoutsSettings::getDeckEditorWidgetSize(const QString &widgetName, const return previous == QVariant() ? defaultValue : previous.toSize(); } +QByteArray LayoutsSettings::getVisualDeckEditorLayoutState() +{ + return getValue(STATE_PROP, GROUP_VISUAL_DECK_EDITOR).toByteArray(); +} + +void LayoutsSettings::setVisualDeckEditorLayoutState(const QByteArray &value) +{ + setValue(value, STATE_PROP, GROUP_VISUAL_DECK_EDITOR); +} + +QByteArray LayoutsSettings::getVisualDeckEditorGeometry() +{ + return getValue(GEOMETRY_PROP, GROUP_VISUAL_DECK_EDITOR).toByteArray(); +} + +void LayoutsSettings::setVisualDeckEditorGeometry(const QByteArray &value) +{ + setValue(value, GEOMETRY_PROP, GROUP_VISUAL_DECK_EDITOR); +} + +void LayoutsSettings::setVisualDeckEditorWidgetSize(const QString &widgetName, const QSize &value) +{ + setValue(value, widgetName, GROUP_VISUAL_DECK_EDITOR, SIZE_PROP); +} + +QSize LayoutsSettings::getVisualDeckEditorWidgetSize(const QString &widgetName, const QSize &defaultValue) +{ + QVariant previous = getValue(widgetName, GROUP_VISUAL_DECK_EDITOR, SIZE_PROP); + return previous == QVariant() ? defaultValue : previous.toSize(); +} + QByteArray LayoutsSettings::getDeckEditorDbHeaderState() { return getValue(STATE_PROP, GROUP_DECK_EDITOR_DB, "header").toByteArray(); diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h index e3dd9eac7..620753815 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h @@ -23,6 +23,10 @@ public: void setDeckEditorGeometry(const QByteArray &value); void setDeckEditorWidgetSize(const QString &widgetName, const QSize &value); + void setVisualDeckEditorLayoutState(const QByteArray &value); + void setVisualDeckEditorGeometry(const QByteArray &value); + void setVisualDeckEditorWidgetSize(const QString &widgetName, const QSize &value); + void setDeckEditorDbHeaderState(const QByteArray &value); void setSetsDialogHeaderState(const QByteArray &value); void setSetsDialogGeometry(const QByteArray &value); @@ -42,6 +46,10 @@ public: QByteArray getDeckEditorGeometry(); QSize getDeckEditorWidgetSize(const QString &widgetName, const QSize &defaultValue = {}); + QByteArray getVisualDeckEditorLayoutState(); + QByteArray getVisualDeckEditorGeometry(); + QSize getVisualDeckEditorWidgetSize(const QString &widgetName, const QSize &defaultValue = {}); + QByteArray getDeckEditorDbHeaderState(); QByteArray getSetsDialogHeaderState(); QByteArray getSetsDialogGeometry(); From 88d0ebb12d8a5451ccf4999b3451de4ac48e42d2 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:50:11 -0800 Subject: [PATCH 202/325] [TabDeckEditor] Fix printingSelector dock close button not working (#6604) --- .../deck_editor/deck_editor_printing_selector_dock_widget.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp index 7c2e1bec4..a91601f2e 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp @@ -57,4 +57,7 @@ void DeckEditorPrintingSelectorDockWidget::setVisibleWidget(bool overridePrintin setWidget(printingSelectorDockContents); printingSelector->updateDisplay(); } + + printingDisabledInfoWidget->setVisible(overridePrintings); + printingSelectorDockContents->setVisible(!overridePrintings); } From ef87b54b4336ac5481b2c81512fdf6ae2912ea0e Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Thu, 19 Feb 2026 08:32:38 +0100 Subject: [PATCH 203/325] [PictureLoader] Set accept header so we don't get CloudFlare'd (#6607) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 34 minutes Co-authored-by: Lukas Brübach --- .../interface/card_picture_loader/card_picture_loader_worker.cpp | 1 + 1 file changed, 1 insertion(+) 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 128b03c95..a246d74f2 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 @@ -86,6 +86,7 @@ QNetworkReply *CardPictureLoaderWorker::makeRequest(const QUrl &url, CardPicture QNetworkRequest req(url); req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); + req.setRawHeader("Accept", "image/avif,image/webp,image/apng,image/,/*;q=0.8"); if (!picDownload) { req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache); } From a7bb5254a390cacb306a86372321cea43be794cc Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 18 Feb 2026 23:32:56 -0800 Subject: [PATCH 204/325] [VDE] Fix Qt warnings in log (#6605) * [VDE] Fix Qt warnings in log * fix button size --- .../widgets/deck_editor/printing_disabled_info_widget.cpp | 3 +-- .../widgets/quick_settings/settings_button_widget.cpp | 2 +- .../visual_deck_editor_sample_hand_widget.cpp | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cockatrice/src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp index 7c48d1e81..23670b1b3 100644 --- a/cockatrice/src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp @@ -12,7 +12,6 @@ PrintingDisabledInfoWidget::PrintingDisabledInfoWidget(QWidget *parent) : QWidge { auto layout = new QVBoxLayout(this); layout->setObjectName("PrintingDisabledInfoWidgetFrame"); - setLayout(layout); QLabel *imageLabel = new QLabel(this); imageLabel->setAlignment(Qt::AlignCenter); @@ -26,7 +25,7 @@ PrintingDisabledInfoWidget::PrintingDisabledInfoWidget(QWidget *parent) : QWidge settingsButton = new QPushButton(this); connect(settingsButton, &QPushButton::clicked, this, &PrintingDisabledInfoWidget::disableOverridePrintings); - auto buttonLayout = new QHBoxLayout(this); + auto buttonLayout = new QHBoxLayout(); buttonLayout->addStretch(); buttonLayout->addWidget(settingsButton); buttonLayout->addStretch(); 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 57dfba800..81812104a 100644 --- a/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp +++ b/cockatrice/src/interface/widgets/quick_settings/settings_button_widget.cpp @@ -38,7 +38,7 @@ void SettingsButtonWidget::setButtonIcon(QPixmap iconMap) void SettingsButtonWidget::setButtonText(const QString &buttonText) { // 🔓 unlock size constraints - button->setMinimumSize(QSize()); + button->setMinimumSize(QSize(0, 0)); button->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp index 3a34e07a7..cc35372b0 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp @@ -19,7 +19,7 @@ VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *pare layout->setSpacing(0); setLayout(layout); - auto upperLayout = new QVBoxLayout(this); + auto upperLayout = new QVBoxLayout(); upperLayout->setContentsMargins(0, 0, 0, 0); upperLayout->setSpacing(0); From 6ab558dd58ecbf5eb52cc1b822ba8b00d3d8bba6 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Thu, 19 Feb 2026 08:36:35 +0100 Subject: [PATCH 205/325] reset style to the default instead of the first key (#6596) --- cockatrice/src/interface/theme_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index 30c3a0974..abacb2d68 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -346,7 +346,7 @@ void ThemeManager::themeChangedSlot() qApp->setStyle(QStyleFactory::create("Fusion")); qApp->setPalette(createDarkGreenFusionPalette()); } else { - qApp->setStyle(QStyleFactory::create(QStyleFactory::keys().first())); + qApp->setStyle(""); } if (dirPath.isEmpty()) { From 189f3a7bbc8be81c4a5a7badee2d8de9ee7b3f8a Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 21 Feb 2026 04:40:47 -0800 Subject: [PATCH 206/325] [ChatView] Fix game log first line incorrect background color (#6612) --- .../src/interface/widgets/server/chat_view/chat_view.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp index adbae45c5..ffd4986a6 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp @@ -75,8 +75,14 @@ void ChatView::adjustColorsToPalette() void ChatView::refreshBlockColors() { QTextDocument *doc = document(); - QTextCursor cursor(doc); + // Empty QTextDocuments still have 1 block, so we need handle this edge case + if (doc->isEmpty()) { + evenNumber = true; + return; + } + + QTextCursor cursor(doc); bool even = true; // start fresh for (QTextBlock block = doc->begin(); block.isValid(); block = block.next()) { From 5c3c3bfdbada07dfe8f79531f5bab7bac7ca71c0 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 21 Feb 2026 06:36:35 -0800 Subject: [PATCH 207/325] [ChatView] Fix extra blank line at beginning (#6613) --- .../widgets/server/chat_view/chat_view.cpp | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp index ffd4986a6..731f30942 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp @@ -89,9 +89,9 @@ void ChatView::refreshBlockColors() QTextBlockFormat fmt = block.blockFormat(); if (even) - fmt.setBackground(palette().window()); - else fmt.setBackground(palette().base()); + else + fmt.setBackground(palette().window()); fmt.setForeground(palette().text()); @@ -113,20 +113,28 @@ QTextCursor ChatView::prepareBlock(bool same) { lastSender.clear(); - QTextCursor cursor(document()); + QTextDocument *doc = document(); + QTextCursor cursor(doc); + cursor.movePosition(QTextCursor::End); if (same) { cursor.insertHtml("
    "); } else { QTextBlockFormat blockFormat; - if ((evenNumber = !evenNumber)) - blockFormat.setBackground(palette().window()); - else + if (evenNumber) blockFormat.setBackground(palette().base()); + else + blockFormat.setBackground(palette().window()); + + evenNumber = !evenNumber; blockFormat.setForeground(palette().text()); blockFormat.setBottomMargin(4); - cursor.insertBlock(blockFormat); + + // Empty QTextDocuments still have 1 block. Just write to that block instead of inserting a new one + if (!doc->isEmpty()) { + cursor.insertBlock(blockFormat); + } } return cursor; From 006abf79b1f9fed6496db5155f77b054f27a41e4 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sat, 21 Feb 2026 15:37:26 +0100 Subject: [PATCH 208/325] Add oracle to win pdb's (#6611) --- .github/workflows/desktop-build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 684b375cc..4bd68d238 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -511,13 +511,14 @@ jobs: path: ${{steps.build.outputs.path}} if-no-files-found: error - - name: Upload pdb database + - name: Upload PDBs (Program Databases) if: matrix.os == 'Windows' uses: actions/upload-artifact@v6 with: - name: Windows${{matrix.target}}-debug-pdbs + name: Windows${{matrix.target}}-PDBs path: | build/cockatrice/Release/*.pdb + build/oracle/Release/*.pdb build/servatrice/Release/*.pdb if-no-files-found: error From 99424e460b1f96808ba75ac2d21bd6240990d5ee Mon Sep 17 00:00:00 2001 From: tooomm Date: Sat, 21 Feb 2026 15:39:48 +0100 Subject: [PATCH 209/325] CI: Fix artifact digest sha for attestation (#6614) * Fix artifact digest sha for attestation * linux, too * Update desktop-build.yml --- .github/workflows/desktop-build.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 4bd68d238..5fab8f993 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -220,13 +220,13 @@ jobs: - name: Upload to release id: upload_release - if: matrix.package != 'skip' && needs.configure.outputs.tag != null + if: needs.configure.outputs.tag != null && matrix.package != 'skip' shell: bash env: GH_TOKEN: ${{github.token}} tag_name: ${{needs.configure.outputs.tag}} - asset_path: ${{steps.build.outputs.path}} asset_name: ${{steps.build.outputs.name}} + asset_path: ${{steps.build.outputs.path}} run: gh release upload "$tag_name" "$asset_path#$asset_name" - name: Attest binary provenance @@ -235,14 +235,15 @@ jobs: uses: actions/attest-build-provenance@v3 with: subject-name: ${{steps.build.outputs.name}} - subject-digest: sha256:${{ steps.upload_artifact.outputs.artifact-digest }} + subject-path: ${{steps.build.outputs.path}} + show-summary: false - name: Verify binary attestation if: steps.attestation.outcome == 'success' shell: bash env: GH_TOKEN: ${{github.token}} - run: gh attestation verify ${{steps.build.outputs.path}} -R Cockatrice/Cockatrice + run: gh attestation verify ${{steps.build.outputs.path}} --repo Cockatrice/Cockatrice build-vcpkg: strategy: @@ -529,8 +530,8 @@ jobs: env: GH_TOKEN: ${{github.token}} tag_name: ${{needs.configure.outputs.tag}} - asset_path: ${{steps.build.outputs.path}} asset_name: ${{steps.build.outputs.name}} + asset_path: ${{steps.build.outputs.path}} run: gh release upload "$tag_name" "$asset_path#$asset_name" - name: Attest binary provenance @@ -539,11 +540,12 @@ jobs: uses: actions/attest-build-provenance@v3 with: subject-name: ${{steps.build.outputs.name}} - subject-digest: sha256:${{ steps.upload_artifact.outputs.artifact-digest }} + subject-path: ${{steps.build.outputs.path}} + show-summary: false - name: Verify binary attestation if: steps.attestation.outcome == 'success' shell: bash env: GH_TOKEN: ${{github.token}} - run: gh attestation verify ${{steps.build.outputs.path}} -R Cockatrice/Cockatrice + run: gh attestation verify ${{steps.build.outputs.path}} --repo Cockatrice/Cockatrice From 485e4d56aa993193479500ee27d26ea2599079a6 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Sat, 21 Feb 2026 21:38:05 +0100 Subject: [PATCH 210/325] Update CONTRIBUTING.md (#6615) I keep forgetting this so I'm making the warning bigger --- .github/CONTRIBUTING.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index ffde66d7b..b293b3937 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -461,7 +461,11 @@ revoke the tag by doing the following: git push --delete upstream $TAG_NAME git tag -d $TAG_NAME ``` -You can also do this on GitHub, you'll also want to delete the false release. +You can also do this on GitHub. + +> [!NOTE] +> If you want to push a new release to replace it immediately with the same +> name you have to delete the automatically created release first! In the first lines of [CMakeLists.txt]( https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt) From f0da3cff405eb7ae05e7b514a48e8b67d9b3b102 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 22 Feb 2026 03:12:28 -0800 Subject: [PATCH 211/325] [PictureLoader] Add hash of new gatherer card back image to blacklist (#6621) --- .../card_picture_loader/card_picture_loader_worker_work.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp index 4c676c704..6bd57c3d5 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp @@ -12,7 +12,10 @@ #include // Card back returned by gatherer when card is not found -static const QStringList MD5_BLACKLIST = {"db0c48db407a907c16ade38de048a441"}; +static const QStringList MD5_BLACKLIST = { + "db0c48db407a907c16ade38de048a441", // Old card back hash. Keep around just in case + "fbc7d763c08771c260b39e2115414eeb" // Current card back hash +}; CardPictureLoaderWorkerWork::CardPictureLoaderWorkerWork(const CardPictureLoaderWorker *worker, const ExactCard &toLoad) : QObject(nullptr), cardToDownload(CardPictureToLoad(toLoad)), From 71cf3fabbffcffd6cf8061bfcad5f00f0d570e0f Mon Sep 17 00:00:00 2001 From: Bruno Alexandre Rosa <1791393+brunoalr@users.noreply.github.com> Date: Sun, 22 Feb 2026 14:52:07 -0300 Subject: [PATCH 212/325] build: use qt 6.10 on macOS and win10 (#6491) * build: use qt 6.8.* LTS in Windows 10 and macOS * bump msvc * bump to 6.10 * reset style to the default instead of the first key --------- Co-authored-by: ebbit1q --- .github/workflows/desktop-build.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 5fab8f993..35e0c0055 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -260,7 +260,7 @@ jobs: make_package: 1 package_suffix: "-macOS13_Intel" artifact_name: macOS13_Intel-package - qt_version: 6.6.* + qt_version: 6.10.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets cmake_generator: Ninja @@ -275,7 +275,7 @@ jobs: make_package: 1 package_suffix: "-macOS14" artifact_name: macOS14-package - qt_version: 6.6.* + qt_version: 6.10.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets cmake_generator: Ninja @@ -290,7 +290,7 @@ jobs: make_package: 1 package_suffix: "-macOS15" artifact_name: macOS15-package - qt_version: 6.6.* + qt_version: 6.10.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets cmake_generator: Ninja @@ -302,7 +302,7 @@ jobs: soc: Apple xcode: "16.4" type: Debug - qt_version: 6.6.* + qt_version: 6.10.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets cmake_generator: Ninja @@ -327,8 +327,8 @@ jobs: make_package: 1 package_suffix: "-Win10" artifact_name: Windows10-installer - qt_version: 6.6.* - qt_arch: win64_msvc2019_64 + qt_version: 6.10.* + qt_arch: win64_msvc2022_64 qt_modules: qtimageformats qtmultimedia qtwebsockets cmake_generator: "Visual Studio 17 2022" cmake_generator_platform: x64 From 0f2899b5c7da0521ac389fad06c3d0f9eb8d185e Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 22 Feb 2026 13:11:10 -0800 Subject: [PATCH 213/325] [DeckEditor] Alternate row colors in history list (#6626) --- .../widgets/deck_editor/deck_list_history_manager_widget.cpp | 1 + 1 file changed, 1 insertion(+) 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 d2d748753..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 @@ -36,6 +36,7 @@ DeckListHistoryManagerWidget::DeckListHistoryManagerWidget(DeckStateManager *_de historyLabel = new QLabel(this); historyList = new QListWidget(this); + historyList->setAlternatingRowColors(true); historyButton->addSettingsWidget(historyLabel); historyButton->addSettingsWidget(historyList); From c6dc7eee64f4f776005ba9c81428fa43caa08373 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 22 Feb 2026 22:11:58 +0100 Subject: [PATCH 214/325] CI: Remove Windows 7 build (#6625) * Update release_template.md * Remove Win7 --- .ci/release_template.md | 1 - .github/workflows/desktop-build.yml | 12 ------------ 2 files changed, 13 deletions(-) diff --git a/.ci/release_template.md b/.ci/release_template.md index 0207662b5..0e6c05165 100644 --- a/.ci/release_template.md +++ b/.ci/release_template.md @@ -10,7 +10,6 @@ Available pre-compiled binaries for installation: WindowsWindows 10+ - • Windows 7+ macOSmacOS 15+ Sequoia Apple M diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 35e0c0055..fbb0e2af3 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -308,18 +308,6 @@ jobs: cmake_generator: Ninja use_ccache: 1 - - os: Windows - target: 7 - runner: windows-2022 - type: Release - make_package: 1 - package_suffix: "-Win7" - artifact_name: Windows7-installer - qt_version: 5.15.* - qt_arch: win64_msvc2019_64 - cmake_generator: "Visual Studio 17 2022" - cmake_generator_platform: x64 - - os: Windows target: 10 runner: windows-2022 From a90997353bfa76435a91b78ede8e2e6189f84cf9 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 22 Feb 2026 16:33:08 -0800 Subject: [PATCH 215/325] [TabGame] Fix concede removing player without waiting for server (#6622) --- cockatrice/src/interface/widgets/tabs/tab_game.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index e4a11f979..fae9dffe5 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -488,14 +488,12 @@ void TabGame::actConcede() QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) return; emit game->getPlayerManager()->activeLocalPlayerConceded(); - player->setConceded(true); } else { if (QMessageBox::question(this, tr("Unconcede"), tr("You have already conceded. Do you want to return to this game?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) return; emit game->getPlayerManager()->activeLocalPlayerUnconceded(); - player->setConceded(false); } } From 9f00c6f9554c4dc789ecafb9d134064fb10155b2 Mon Sep 17 00:00:00 2001 From: tooomm Date: Mon, 23 Feb 2026 01:44:14 +0100 Subject: [PATCH 216/325] Update vcpkg submodule (#6627) * Update vcpkg * Target 2025.10.17 --- vcpkg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vcpkg b/vcpkg index 623240005..74e653621 160000 --- a/vcpkg +++ b/vcpkg @@ -1 +1 @@ -Subproject commit 62324000504cdd27282f8275c99135cfb2bd1dc0 +Subproject commit 74e6536215718009aae747d86d84b78376bf9e09 From 2cb16c9fd03b67c2010c87f7349425d74e84167d Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 22 Feb 2026 20:54:58 -0800 Subject: [PATCH 217/325] [CardDatabaseDisplay] Reduce width by using icons (#6603) * [CardDatabaseDisplay] Reduce width by using icons * use public domain filter svg icon --- cockatrice/cockatrice.qrc | 1 + cockatrice/resources/icons/filter.svg | 12 ++++ ...database_display_filter_toolbar_widget.cpp | 59 +++++++++++++------ ...l_database_display_filter_toolbar_widget.h | 2 + 4 files changed, 56 insertions(+), 18 deletions(-) create mode 100644 cockatrice/resources/icons/filter.svg diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc index f087bfe66..37fb145f0 100644 --- a/cockatrice/cockatrice.qrc +++ b/cockatrice/cockatrice.qrc @@ -24,6 +24,7 @@ resources/icons/dragon.svg resources/icons/dropdown_collapsed.svg resources/icons/dropdown_expanded.svg + resources/icons/filter.svg resources/icons/floppy_disk.svg resources/icons/forgot_password.svg resources/icons/gear.svg diff --git a/cockatrice/resources/icons/filter.svg b/cockatrice/resources/icons/filter.svg new file mode 100644 index 000000000..d53d9ba29 --- /dev/null +++ b/cockatrice/resources/icons/filter.svg @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file 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 324236ab7..3cc1bf23b 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 @@ -2,11 +2,14 @@ #include "visual_database_display_widget.h" +#include + VisualDatabaseDisplayFilterToolbarWidget::VisualDatabaseDisplayFilterToolbarWidget(VisualDatabaseDisplayWidget *_parent) : QWidget(_parent), visualDatabaseDisplay(_parent) { filterContainerLayout = new QHBoxLayout(this); filterContainerLayout->setContentsMargins(11, 0, 11, 0); + filterContainerLayout->setSpacing(2); setLayout(filterContainerLayout); filterContainerLayout->setAlignment(Qt::AlignLeft); @@ -15,9 +18,17 @@ VisualDatabaseDisplayFilterToolbarWidget::VisualDatabaseDisplayFilterToolbarWidg connect(this, &VisualDatabaseDisplayFilterToolbarWidget::searchModelChanged, visualDatabaseDisplay, &VisualDatabaseDisplayWidget::onSearchModelChanged); - filterByLabel = new QLabel(this); + sortGroupBox = new QGroupBox(this); + filterGroupBox = new QGroupBox(this); + + auto scalePixmap = [](const QString &fileName) { return QIcon(QPixmap(fileName)).pixmap({20, 20}); }; sortByLabel = new QLabel(this); + sortByLabel->setPixmap(scalePixmap("theme:icons/sort_arrow_down")); + + filterByLabel = new QLabel(this); + filterByLabel->setPixmap(scalePixmap("theme:icons/filter")); + sortColumnCombo = new QComboBox(this); sortColumnCombo->setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy::AdjustToContents); sortOrderCombo = new QComboBox(this); @@ -76,14 +87,20 @@ VisualDatabaseDisplayFilterToolbarWidget::VisualDatabaseDisplayFilterToolbarWidg void VisualDatabaseDisplayFilterToolbarWidget::initialize() { - sortByLabel->setVisible(true); - filterByLabel->setVisible(true); + // create groupbox layouts + auto sortLayout = new QHBoxLayout(this); + sortLayout->setContentsMargins(0, 0, 0, 0); + sortLayout->setSpacing(0); + sortGroupBox->setLayout(sortLayout); + sortLayout->setAlignment(Qt::AlignLeft); - quickFilterSaveLoadWidget->setVisible(true); - quickFilterNameWidget->setVisible(true); - quickFilterSubTypeWidget->setVisible(true); - quickFilterSetWidget->setVisible(true); + auto filterLayout = new QHBoxLayout(this); + filterLayout->setContentsMargins(0, 0, 0, 0); + filterLayout->setSpacing(2); + filterGroupBox->setLayout(filterLayout); + filterLayout->setAlignment(Qt::AlignLeft); + // create settings widgets auto filterModel = visualDatabaseDisplay->filterModel; saveLoadWidget = new VisualDatabaseDisplayFilterSaveLoadWidget(this, filterModel); @@ -101,23 +118,29 @@ void VisualDatabaseDisplayFilterToolbarWidget::initialize() quickFilterSetWidget->addSettingsWidget(setFilterWidget); quickFilterFormatLegalityWidget->addSettingsWidget(formatLegalityWidget); - filterContainerLayout->addWidget(sortByLabel); - filterContainerLayout->addWidget(sortColumnCombo); - filterContainerLayout->addWidget(sortOrderCombo); - filterContainerLayout->addWidget(filterByLabel); - filterContainerLayout->addWidget(quickFilterNameWidget); - filterContainerLayout->addWidget(quickFilterMainTypeWidget); - filterContainerLayout->addWidget(quickFilterSubTypeWidget); - filterContainerLayout->addWidget(quickFilterSetWidget); - filterContainerLayout->addWidget(quickFilterFormatLegalityWidget); + // fill groupbox layouts + sortLayout->addWidget(sortByLabel); + sortLayout->addWidget(sortColumnCombo); + sortLayout->addWidget(sortOrderCombo); + + filterLayout->addWidget(filterByLabel); + filterLayout->addWidget(quickFilterNameWidget); + filterLayout->addWidget(quickFilterMainTypeWidget); + filterLayout->addWidget(quickFilterSubTypeWidget); + filterLayout->addWidget(quickFilterSetWidget); + filterLayout->addWidget(quickFilterFormatLegalityWidget); + + // put everything into main layout + filterContainerLayout->addWidget(sortGroupBox); + filterContainerLayout->addWidget(filterGroupBox); filterContainerLayout->addStretch(); filterContainerLayout->addWidget(quickFilterSaveLoadWidget); } void VisualDatabaseDisplayFilterToolbarWidget::retranslateUi() { - sortByLabel->setText(tr("Sort by:")); - filterByLabel->setText(tr("Filter by:")); + sortByLabel->setToolTip(tr("Sort by")); + filterByLabel->setToolTip(tr("Filter by")); quickFilterSaveLoadWidget->setToolTip(tr("Save and load filters")); quickFilterNameWidget->setToolTip(tr("Filter by exact card name")); diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h index a6c614656..5cca5187a 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h @@ -25,9 +25,11 @@ public: private: VisualDatabaseDisplayWidget *visualDatabaseDisplay; + QGroupBox *sortGroupBox; QLabel *sortByLabel; QComboBox *sortColumnCombo, *sortOrderCombo; + QGroupBox *filterGroupBox; QLabel *filterByLabel; QHBoxLayout *filterContainerLayout; From 12c667afd7f823f4532907543ebcf1b25e87aeed Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:34:23 -0800 Subject: [PATCH 218/325] [Search] Fix OR usage in examples (#6628) --- cockatrice/resources/help/deck_search.md | 2 +- cockatrice/resources/help/search.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cockatrice/resources/help/deck_search.md b/cockatrice/resources/help/deck_search.md index 3263daf04..163137335 100644 --- a/cockatrice/resources/help/deck_search.md +++ b/cockatrice/resources/help/deck_search.md @@ -47,6 +47,6 @@ searches are case insensitive.
    [t:aggro OR o:control](#t:aggro OR o:control) (Any deck filename that contains either aggro or control)
    Grouping:
    -
    red -([[]]:100 or aggro) (Any deck that has red in its filename but is not 100 cards or has aggro in its filename)
    +
    red -([[]]:100 OR aggro) (Any deck that has red in its filename but is not 100 cards or has aggro in its filename)
    \ No newline at end of file diff --git a/cockatrice/resources/help/search.md b/cockatrice/resources/help/search.md index 63e6e74c3..0c8bdb450 100644 --- a/cockatrice/resources/help/search.md +++ b/cockatrice/resources/help/search.md @@ -51,16 +51,16 @@ In this list of examples below, each entry has an explanation and can be clicked
    Edition:
    [set:lea](#set:lea) (Cards that appear in Alpha, which has the set code LEA)
    -
    [e:lea or e:leb](#e:lea or e:leb) (Cards that appear in Alpha or Beta)
    +
    [e:lea OR e:leb](#e:lea OR e:leb) (Cards that appear in Alpha or Beta)
    Negate:
    [c:wu -c:m](#c:wu -c:m) (Any card that is white or blue, but not multicolored)
    Branching:
    -
    [t:sliver or o:changeling](#t:sliver or o:changeling) (Any card that is either a sliver or has changeling)
    +
    [t:sliver OR o:changeling](#t:sliver OR o:changeling) (Any card that is either a sliver or has changeling)
    Grouping:
    -
    t:angel -(angel or c:w) (Any angel that doesn't have angel in its name and isn't white)
    +
    t:angel -(angel OR c:w) (Any angel that doesn't have angel in its name and isn't white)
    Regular Expression:
    [/^fell/](#/^fell/) (Any card name that begins with "fell")
    From 7ad2481e3d2f595631f9fa6091c66d4cee12c928 Mon Sep 17 00:00:00 2001 From: Rob Blanckaert Date: Wed, 25 Feb 2026 23:55:34 -0800 Subject: [PATCH 219/325] Update UnescapedStringListPart to include parentheses (#6631) * Update UnescapedStringListPart to include parentheses * also update deck_filter_string * add unit test --------- Co-authored-by: RickyRister --- cockatrice/src/filters/deck_filter_string.cpp | 2 +- libcockatrice_filters/libcockatrice/filters/filter_string.cpp | 2 +- tests/carddatabase/filter_string_test.cpp | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/filters/deck_filter_string.cpp b/cockatrice/src/filters/deck_filter_string.cpp index 42a77fe5a..6b671831d 100644 --- a/cockatrice/src/filters/deck_filter_string.cpp +++ b/cockatrice/src/filters/deck_filter_string.cpp @@ -31,7 +31,7 @@ GenericQuery <- String NonDoubleQuoteUnlessEscaped <- '\\\"'. / !["]. NonSingleQuoteUnlessEscaped <- "\\\'". / ![']. -UnescapedStringListPart <- !['":<>=! ]. +UnescapedStringListPart <- !['":<>()=! ]. SingleApostropheString <- (UnescapedStringListPart+ ws*)* ['] (UnescapedStringListPart+ ws*)* String <- SingleApostropheString / UnescapedStringListPart+ / ["] ["] / ['] ['] diff --git a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp index 0d29f7897..704e8fadb 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp @@ -46,7 +46,7 @@ FieldQuery <- String [:] MatcherString / String ws? NumericExpression NonDoubleQuoteUnlessEscaped <- '\\\"'. / !["]. NonSingleQuoteUnlessEscaped <- "\\\'". / ![']. -UnescapedStringListPart <- !['":<>=! ]. +UnescapedStringListPart <- !['":<>()=! ]. SingleApostropheString <- (UnescapedStringListPart+ ws*)* ['] (UnescapedStringListPart+ ws*)* String <- SingleApostropheString / UnescapedStringListPart+ / ["] ["] / ['] ['] StringValue <- String / [(] StringList [)] diff --git a/tests/carddatabase/filter_string_test.cpp b/tests/carddatabase/filter_string_test.cpp index c3e8c6fb7..c6d68be1f 100644 --- a/tests/carddatabase/filter_string_test.cpp +++ b/tests/carddatabase/filter_string_test.cpp @@ -71,6 +71,8 @@ QUERY(Color2, cat, "c:gw", true) QUERY(Color3, cat, "c!g", true) QUERY(Color4, cat, "c!gw", false) +QUERY(BracketNextToUnquotedString, cat, "(o:woof OR o:meow)", true) + } // namespace int main(int argc, char **argv) From 6f8f9f016a3b4f81ee269c403cc718dca51c575b Mon Sep 17 00:00:00 2001 From: tooomm Date: Thu, 26 Feb 2026 22:31:18 +0100 Subject: [PATCH 220/325] CI: `windows-2025` runner (#6632) * `windows-2025` runner * Install NSIS * fix naming --- .github/workflows/desktop-build.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index fbb0e2af3..a0878046e 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -310,7 +310,7 @@ jobs: - os: Windows target: 10 - runner: windows-2022 + runner: windows-2025 type: Release make_package: 1 package_suffix: "-Win10" @@ -410,6 +410,11 @@ jobs: modules: ${{matrix.qt_modules}} cache: true + - name: Install NSIS + if: matrix.os == 'Windows' + shell: bash + run: choco install nsis + - name: Setup vcpkg cache id: vcpkg-cache uses: TAServers/vcpkg-cache@v3 From 208ccc3a1a67ff94d71b520dc3aaaa1ca305e195 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Fri, 27 Feb 2026 00:53:39 -0500 Subject: [PATCH 221/325] fix(docs): correct typos in README (#6640) Fix 'projet' -> 'project' and 'invovled' -> 'involved'. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c08aee0e7..8ad9a092c 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Latest beta version: # Community Resources [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA) -Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other projet contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out. +Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other project contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out. - [Official Website](https://cockatrice.github.io) - [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki) - [Official Discord](https://discord.gg/3Z9yzmA) @@ -109,7 +109,7 @@ Cockatrice tries to use the [Google Developer Documentation Style Guide](https:/ Cockatrice uses Transifex to manage translations. You can help us bring Cockatrice, Oracle and Webatrice to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/).
    -Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting invovled, and join a group of hundreds of others!
    +Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting involved, and join a group of hundreds of others!
    # Build [![CI Desktop](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [![CI Docker](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush) [![CI Web](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml?query=branch%3Amaster+event%3Apush) From 59ce70afc516d428706d09748287a3a13db2e308 Mon Sep 17 00:00:00 2001 From: tooomm Date: Mon, 2 Mar 2026 01:59:18 +0100 Subject: [PATCH 222/325] Add Code Docs link to issue template (#6649) --- .github/ISSUE_TEMPLATE/config.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index c5354332b..f9abf643d 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,9 +1,12 @@ blank_issues_enabled: false contact_links: - name: 💬 Discord Community (Get help with server issues, e.g. Login) - url: https://discord.gg/3Z9yzmA + url: https://discord.com/invite/3Z9yzmA about: Need help with using the client? Want to find some games? Try the Discord server! - name: 🌐 Translations (Help improve the localization of the app) url: https://explore.transifex.com/cockatrice/cockatrice/ # it is not possible to add a link to the wiki to this description about: For more information and guidance check our Translation FAQ on our wiki! + - name: 📖 Code Documentation + url: https://cockatrice.github.io/docs/ + about: From f978407a198c2127866e78635a68657da9997582 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 23:04:15 +0100 Subject: [PATCH 223/325] Bump actions/upload-artifact from 6 to 7 (#6652) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/desktop-build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index a0878046e..e48cc7d3f 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -212,7 +212,7 @@ jobs: - name: Upload artifact id: upload_artifact if: matrix.package != 'skip' - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: ${{matrix.distro}}${{matrix.version}}-package path: ${{steps.build.outputs.path}} @@ -499,7 +499,7 @@ jobs: - name: Upload artifact id: upload_artifact if: matrix.make_package - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: ${{matrix.artifact_name}} path: ${{steps.build.outputs.path}} @@ -507,7 +507,7 @@ jobs: - name: Upload PDBs (Program Databases) if: matrix.os == 'Windows' - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: Windows${{matrix.target}}-PDBs path: | From e57ee8e9c9745a1ec20dfdc2439223fe3a0d5847 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Mon, 2 Mar 2026 23:06:05 +0100 Subject: [PATCH 224/325] fix set sorting (#6630) --- .../widgets/dialogs/dlg_manage_sets.cpp | 32 +++++++++---------- .../widgets/dialogs/dlg_manage_sets.h | 8 +++-- .../database/card_set/card_sets_model.cpp | 2 +- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp index 5e6a6a62d..a4f54564f 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp @@ -20,8 +20,6 @@ #include #include -#define SORT_RESET -1 - WndSets::WndSets(QWidget *parent) : QMainWindow(parent) { setOrderIsSorted = false; @@ -97,7 +95,6 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) view->setDropIndicatorShown(true); view->setDragDropMode(QAbstractItemView::InternalMove); - view->sortByColumn(SetsModel::SortKeyCol, Qt::AscendingOrder); view->setColumnHidden(SetsModel::SortKeyCol, true); view->setColumnHidden(SetsModel::IsKnownCol, true); view->setColumnHidden(SetsModel::PriorityCol, true); @@ -196,10 +193,10 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) auto headerState = SettingsCache::instance().layouts().getSetsDialogHeaderState(); if (!headerState.isEmpty()) { view->header()->restoreState(headerState); - view->header()->setSortIndicator(SORT_RESET, Qt::DescendingOrder); } else { view->header()->resizeSections(QHeaderView::ResizeToContents); } + resetSort(); connect(view->header(), &QHeaderView::geometriesChanged, this, &WndSets::saveHeaderState); } @@ -239,6 +236,13 @@ void WndSets::rebuildMainLayout(int actionToTake) } } +void WndSets::resetSort() +{ + view->sortByColumn(SetsModel::SortKeyCol, Qt::AscendingOrder); + sortIndex = -1; + sortWarning->setVisible(false); +} + void WndSets::includeRebalancedCardsChanged(bool _includeRebalancedCards) { includeRebalancedCards = _includeRebalancedCards; @@ -260,9 +264,9 @@ void WndSets::actRestore() void WndSets::actRestoreOriginalOrder() { - view->header()->setSortIndicator(SORT_RESET, Qt::DescendingOrder); model->restoreOriginalOrder(); - sortWarning->setVisible(false); + view->selectionModel()->reset(); + resetSort(); } void WndSets::actDisableResetButton(const QString &filterString) @@ -288,11 +292,12 @@ void WndSets::actSort(int index) sortIndex = index; sortWarning->setVisible(true); } else { - view->header()->setSortIndicator(SORT_RESET, Qt::DescendingOrder); - sortIndex = -1; - sortWarning->setVisible(false); + resetSort(); } } + if (!view->selectionModel()->selection().empty()) { + view->scrollTo(view->selectionModel()->selectedRows().first()); + } } void WndSets::actIgnoreWarning() @@ -301,23 +306,18 @@ void WndSets::actIgnoreWarning() return; } model->sort(sortIndex, sortOrder); - view->header()->setSortIndicator(SORT_RESET, Qt::DescendingOrder); - sortIndex = -1; - sortWarning->setVisible(false); + resetSort(); } void WndSets::actDisableSortButtons(int index) { - if (index != SORT_RESET) { + if (index != SetsModel::SortKeyCol) { view->setDragEnabled(false); setOrderIsSorted = true; } else { setOrderIsSorted = false; view->setDragEnabled(true); } - if (!view->selectionModel()->selection().empty()) { - view->scrollTo(view->selectionModel()->selectedRows().first()); - } actToggleButtons(view->selectionModel()->selection(), QItemSelection()); } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h index d7341caff..d9b77a76e 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h @@ -45,9 +45,6 @@ private: QHBoxLayout *filterBox; int sortIndex; Qt::SortOrder sortOrder; - void closeEvent(QCloseEvent *ev) override; - void saveHeaderState(); - void rebuildMainLayout(int actionToTake); bool setOrderIsSorted; bool includeRebalancedCards; enum @@ -56,6 +53,11 @@ private: SOME_SETS_SELECTED }; + void closeEvent(QCloseEvent *ev) override; + void saveHeaderState(); + void rebuildMainLayout(int actionToTake); + void resetSort(); + public: explicit WndSets(QWidget *parent = nullptr); ~WndSets() override; diff --git a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp index b678e8276..f6dc4f9cf 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp +++ b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp @@ -45,7 +45,7 @@ QVariant SetsModel::data(const QModelIndex &index, int role) const switch (index.column()) { case SortKeyCol: - return QString("%1").arg(set->getSortKey(), 8, 10, QChar('0')); + return QString("%1").arg(index.row(), 8, 10, QChar('0')); case IsKnownCol: return set->getIsKnown(); case SetTypeCol: From 9794893b63ad1865d98913d601323e9bd58f3506 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 23:06:35 +0100 Subject: [PATCH 225/325] Bump actions/attest-build-provenance from 3 to 4 (#6653) Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 3 to 4. - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/desktop-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index e48cc7d3f..e95898097 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -232,7 +232,7 @@ jobs: - name: Attest binary provenance id: attestation if: steps.upload_release.outcome == 'success' - uses: actions/attest-build-provenance@v3 + uses: actions/attest-build-provenance@v4 with: subject-name: ${{steps.build.outputs.name}} subject-path: ${{steps.build.outputs.path}} @@ -530,7 +530,7 @@ jobs: - name: Attest binary provenance id: attestation if: steps.upload_release.outcome == 'success' - uses: actions/attest-build-provenance@v3 + uses: actions/attest-build-provenance@v4 with: subject-name: ${{steps.build.outputs.name}} subject-path: ${{steps.build.outputs.path}} From 2828854d3203b0b85159309596c997820b34cb10 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:17:35 -0800 Subject: [PATCH 226/325] [Server] Support face-down cards in all public zones (#6539) * [Server] Support face-down cards in all public zones * add null check * Check using zone names instead * add comment --- .../remote/game/server_abstract_player.cpp | 34 +++++++++++++++++-- .../protocol/pb/command_move_card.proto | 4 ++- 2 files changed, 34 insertions(+), 4 deletions(-) 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 b08495bad..00561bef2 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 @@ -198,6 +198,35 @@ shouldDestroyOnMove(const Server_Card *card, const Server_CardZone *startZone, c return true; } +/** + * @brief Determines whether the moved card should be face-down + */ +static bool +shouldBeFaceDown(const MoveCardStruct &cardStruct, const Server_CardZone *startZone, const Server_CardZone *targetZone) +{ + if (!targetZone) { + return false; + } + + // being face-down only makes sense for public zones + if (targetZone->getType() != ServerInfo_Zone::PublicZone) { + return false; + } + + // face-down property in proto takes precedence + if (cardStruct.cardToMove->has_face_down()) { + return cardStruct.cardToMove->face_down(); + } + + // Default to keep face-down the same if zone didn't change. + // Compare using zone names because face-down is maintained when changing controllers. + if (startZone && startZone->getName() == targetZone->getName()) { + return cardStruct.card->getFaceDown(); + } + + return false; +} + Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, Server_CardZone *startzone, const QList &_cards, @@ -257,10 +286,7 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, for (auto cardStruct : cardsToMove) { Server_Card *card = cardStruct.card; - const CardToMove *thisCardProperties = cardStruct.cardToMove; int originalPosition = cardStruct.position; - bool faceDown = targetzone->hasCoords() && - (thisCardProperties->has_face_down() ? thisCardProperties->face_down() : card->getFaceDown()); bool sourceBeingLookedAt; int position = startzone->removeCard(card, sourceBeingLookedAt); @@ -315,6 +341,8 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, ++xIndex; int newX = isReversed ? targetzone->getCards().size() - xCoord + xIndex : xCoord + xIndex; + bool faceDown = shouldBeFaceDown(cardStruct, startzone, targetzone); + if (targetzone->hasCoords()) { newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown); } else { diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/command_move_card.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/command_move_card.proto index 6e9301d27..a5b96da2e 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/command_move_card.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/command_move_card.proto @@ -6,7 +6,9 @@ message CardToMove { // Id of the card in its current zone optional sint32 card_id = 1 [default = -1]; - // Places the card face down, hiding its name + // If true, places the card face down, hiding its name. + // If false, forcibly turns the card face up. + // If not set, defers the resulting face down state to the server. optional bool face_down = 2; // When moving add this value to the power/toughness field of the card From 2fba5dcd202c9a98381fbfbe6022199dcad0f230 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:18:21 -0800 Subject: [PATCH 227/325] [Game] Refactor CardDragItem faceDown logic (#6552) * Rename properties and only pass forceFaceDown * [Game] Refactor CardDragItem faceDown logic * revert refactor * leave face_down unset unless forced --- cockatrice/src/game/board/card_drag_item.cpp | 5 +++-- cockatrice/src/game/board/card_drag_item.h | 8 ++++---- cockatrice/src/game/board/card_item.cpp | 9 ++++----- cockatrice/src/game/board/card_item.h | 2 +- cockatrice/src/game/zones/pile_zone.cpp | 4 ++-- cockatrice/src/game/zones/table_zone.cpp | 6 ++++-- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/cockatrice/src/game/board/card_drag_item.cpp b/cockatrice/src/game/board/card_drag_item.cpp index 9eadadeda..5ae56ccba 100644 --- a/cockatrice/src/game/board/card_drag_item.cpp +++ b/cockatrice/src/game/board/card_drag_item.cpp @@ -13,9 +13,10 @@ CardDragItem::CardDragItem(CardItem *_item, int _id, const QPointF &_hotSpot, - bool _faceDown, + bool _forceFaceDown, AbstractCardDragItem *parentDrag) - : AbstractCardDragItem(_item, _hotSpot, parentDrag), id(_id), faceDown(_faceDown), occupied(false), currentZone(0) + : AbstractCardDragItem(_item, _hotSpot, parentDrag), id(_id), forceFaceDown(_forceFaceDown), occupied(false), + currentZone(0) { } diff --git a/cockatrice/src/game/board/card_drag_item.h b/cockatrice/src/game/board/card_drag_item.h index 257fde150..930c6be6f 100644 --- a/cockatrice/src/game/board/card_drag_item.h +++ b/cockatrice/src/game/board/card_drag_item.h @@ -16,7 +16,7 @@ class CardDragItem : public AbstractCardDragItem Q_OBJECT private: int id; - bool faceDown; + bool forceFaceDown; bool occupied; CardZone *currentZone; @@ -24,15 +24,15 @@ public: CardDragItem(CardItem *_item, int _id, const QPointF &_hotSpot, - bool _faceDown, + bool _forceFaceDown, AbstractCardDragItem *parentDrag = 0); int getId() const { return id; } - bool getFaceDown() const + bool isForceFaceDown() const { - return faceDown; + return forceFaceDown; } void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; void updatePosition(const QPointF &cursorScenePos) override; diff --git a/cockatrice/src/game/board/card_item.cpp b/cockatrice/src/game/board/card_item.cpp index 34c2f9a98..baf04e993 100644 --- a/cockatrice/src/game/board/card_item.cpp +++ b/cockatrice/src/game/board/card_item.cpp @@ -251,10 +251,10 @@ void CardItem::processCardInfo(const ServerInfo_Card &_info) setDoesntUntap(_info.doesnt_untap()); } -CardDragItem *CardItem::createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool faceDown) +CardDragItem *CardItem::createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool forceFaceDown) { deleteDragItem(); - dragItem = new CardDragItem(this, _id, _pos, faceDown); + dragItem = new CardDragItem(this, _id, _pos, forceFaceDown); dragItem->setVisible(false); scene()->addItem(dragItem); dragItem->updatePosition(_scenePos); @@ -352,7 +352,7 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) // Use the buttonDownPos to align the hot spot with the position when // the user originally clicked - createDragItem(id, event->buttonDownPos(Qt::LeftButton), event->scenePos(), facedown || forceFaceDown); + createDragItem(id, event->buttonDownPos(Qt::LeftButton), event->scenePos(), forceFaceDown); dragItem->grabMouse(); int childIndex = 0; @@ -366,8 +366,7 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) childPos = card->pos() - pos(); else childPos = QPointF(childIndex * CARD_WIDTH / 2, 0); - CardDragItem *drag = - new CardDragItem(card, card->getId(), childPos, card->getFaceDown() || forceFaceDown, dragItem); + CardDragItem *drag = new CardDragItem(card, card->getId(), childPos, forceFaceDown, dragItem); drag->setPos(dragItem->pos() + childPos); scene()->addItem(drag); } diff --git a/cockatrice/src/game/board/card_item.h b/cockatrice/src/game/board/card_item.h index 86c4fe594..875c3f4d0 100644 --- a/cockatrice/src/game/board/card_item.h +++ b/cockatrice/src/game/board/card_item.h @@ -142,7 +142,7 @@ public: void processCardInfo(const ServerInfo_Card &_info); bool animationEvent(); - CardDragItem *createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool faceDown); + CardDragItem *createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool forceFaceDown); void deleteDragItem(); void drawArrow(const QColor &arrowColor); void drawAttachArrow(); diff --git a/cockatrice/src/game/zones/pile_zone.cpp b/cockatrice/src/game/zones/pile_zone.cpp index 2d1390826..521d13b99 100644 --- a/cockatrice/src/game/zones/pile_zone.cpp +++ b/cockatrice/src/game/zones/pile_zone.cpp @@ -101,12 +101,12 @@ void PileZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event) if (getLogic()->getCards().isEmpty()) return; - bool faceDown = event->modifiers().testFlag(Qt::ShiftModifier); + bool forceFaceDown = event->modifiers().testFlag(Qt::ShiftModifier); bool bottomCard = event->modifiers().testFlag(Qt::ControlModifier); CardItem *card = bottomCard ? getLogic()->getCards().last() : getLogic()->getCards().first(); const int cardid = getLogic()->contentsKnown() ? card->getId() : (bottomCard ? getLogic()->getCards().size() - 1 : 0); - CardDragItem *drag = card->createDragItem(cardid, event->pos(), event->scenePos(), faceDown); + CardDragItem *drag = card->createDragItem(cardid, event->pos(), event->scenePos(), forceFaceDown); drag->grabMouse(); setCursor(Qt::OpenHandCursor); } diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index c76514350..ca1052d20 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -134,8 +134,10 @@ void TableZone::handleDropEventByGrid(const QList &dragItems, for (const auto &item : dragItems) { CardToMove *ctm = cmd.mutable_cards_to_move()->add_card(); ctm->set_card_id(item->getId()); - ctm->set_face_down(item->getFaceDown()); - if (startZone->getName() != getLogic()->getName() && !item->getFaceDown()) { + if (item->isForceFaceDown()) { + ctm->set_face_down(true); + } + if (startZone->getName() != getLogic()->getName() && !item->isForceFaceDown()) { const auto &card = item->getItem()->getCard(); if (card) { ctm->set_pt(card.getInfo().getPowTough().toStdString()); From 846ecb7e8daa078022434acaf25f368259f3f748 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:19:26 -0800 Subject: [PATCH 228/325] [Client] Support face-down cards in all public zones (#6602) * [Server] Support face-down cards in all public zones * add null check * Check using zone names instead * add comment * Rename properties and only pass forceFaceDown * [Game] Refactor CardDragItem faceDown logic * revert refactor * leave face_down unset unless forced * [Client] Support face-down cards in all public zones * leave face_down unset unless forced * log face down * update remaining logs --- cockatrice/src/game/board/card_item.cpp | 4 +++- .../src/game/log/message_log_widget.cpp | 24 +++++++++++++++---- .../src/game/zones/logic/card_zone_logic.cpp | 6 +++-- cockatrice/src/game/zones/pile_zone.cpp | 9 +++++-- cockatrice/src/game/zones/stack_zone.cpp | 6 ++++- cockatrice/src/game/zones/view_zone.cpp | 21 ++++++++++++---- 6 files changed, 55 insertions(+), 15 deletions(-) diff --git a/cockatrice/src/game/board/card_item.cpp b/cockatrice/src/game/board/card_item.cpp index baf04e993..8b9549f07 100644 --- a/cockatrice/src/game/board/card_item.cpp +++ b/cockatrice/src/game/board/card_item.cpp @@ -212,10 +212,12 @@ void CardItem::setAttachedTo(CardItem *_attachedTo) } } +/** + * @brief Resets the fields that should be reset after a zone transition + */ void CardItem::resetState(bool keepAnnotations) { attacking = false; - facedown = false; counters.clear(); pt.clear(); if (!keepAnnotations) { diff --git a/cockatrice/src/game/log/message_log_widget.cpp b/cockatrice/src/game/log/message_log_widget.cpp index 1ff3e32ff..645974994 100644 --- a/cockatrice/src/game/log/message_log_widget.cpp +++ b/cockatrice/src/game/log/message_log_widget.cpp @@ -314,9 +314,17 @@ void MessageLogWidget::logMoveCard(Player *player, finalStr = tr("%1 puts %2 into play%3."); } } else if (targetZoneName == GRAVE_ZONE_NAME) { - finalStr = tr("%1 puts %2%3 into their graveyard."); + if (card->getFaceDown()) { + finalStr = tr("%1 puts %2%3 into their graveyard face down."); + } else { + finalStr = tr("%1 puts %2%3 into their graveyard."); + } } else if (targetZoneName == EXILE_ZONE_NAME) { - finalStr = tr("%1 exiles %2%3."); + if (card->getFaceDown()) { + finalStr = tr("%1 exiles %2%3 face down."); + } else { + finalStr = tr("%1 exiles %2%3."); + } } else if (targetZoneName == HAND_ZONE_NAME) { finalStr = tr("%1 moves %2%3 to their hand."); } else if (targetZoneName == DECK_ZONE_NAME) { @@ -335,10 +343,18 @@ void MessageLogWidget::logMoveCard(Player *player, finalStr = tr("%1 moves %2%3 to sideboard."); } else if (targetZoneName == STACK_ZONE_NAME) { soundEngine->playSound("play_card"); - finalStr = tr("%1 plays %2%3."); + if (card->getFaceDown()) { + finalStr = tr("%1 plays %2%3 face down."); + } else { + finalStr = tr("%1 plays %2%3."); + } } else { fourthArg = targetZoneName; - finalStr = tr("%1 moves %2%3 to custom zone '%4'."); + if (card->getFaceDown()) { + finalStr = tr("%1 moves %2%3 to custom zone '%4' face down."); + } else { + finalStr = tr("%1 moves %2%3 to custom zone '%4'."); + } } QString message = finalStr.arg(sanitizeHtml(player->getPlayerInfo()->getName()), cardStr, nameFrom.second); diff --git a/cockatrice/src/game/zones/logic/card_zone_logic.cpp b/cockatrice/src/game/zones/logic/card_zone_logic.cpp index bd32eab3e..1872ba95e 100644 --- a/cockatrice/src/game/zones/logic/card_zone_logic.cpp +++ b/cockatrice/src/game/zones/logic/card_zone_logic.cpp @@ -43,8 +43,10 @@ void CardZoneLogic::addCard(CardItem *card, const bool reorganize, const int x, for (auto *view : views) { if (qobject_cast(view->getLogic())->prepareAddCard(x)) { - view->getLogic()->addCard(new CardItem(player, nullptr, card->getCardRef(), card->getId()), reorganize, x, - y); + auto copy = new CardItem(player, nullptr, card->getCardRef(), card->getId()); + copy->setFaceDown(card->getFaceDown()); + + view->getLogic()->addCard(copy, reorganize, x, y); } } diff --git a/cockatrice/src/game/zones/pile_zone.cpp b/cockatrice/src/game/zones/pile_zone.cpp index 521d13b99..87a60fc03 100644 --- a/cockatrice/src/game/zones/pile_zone.cpp +++ b/cockatrice/src/game/zones/pile_zone.cpp @@ -68,8 +68,13 @@ void PileZone::handleDropEvent(const QList &dragItems, CardZoneL cmd.set_x(0); cmd.set_y(0); - for (int i = 0; i < dragItems.size(); ++i) - cmd.mutable_cards_to_move()->add_card()->set_card_id(dragItems[i]->getId()); + for (int i = 0; i < dragItems.size(); ++i) { + auto cardToMove = cmd.mutable_cards_to_move()->add_card(); + cardToMove->set_card_id(dragItems[i]->getId()); + if (dragItems[i]->isForceFaceDown()) { + cardToMove->set_face_down(true); + } + } getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(cmd); } diff --git a/cockatrice/src/game/zones/stack_zone.cpp b/cockatrice/src/game/zones/stack_zone.cpp index 820008b27..a9d649a4a 100644 --- a/cockatrice/src/game/zones/stack_zone.cpp +++ b/cockatrice/src/game/zones/stack_zone.cpp @@ -78,7 +78,11 @@ void StackZone::handleDropEvent(const QList &dragItems, for (CardDragItem *item : dragItems) { if (item) { - cmd.mutable_cards_to_move()->add_card()->set_card_id(item->getId()); + auto cardToMove = cmd.mutable_cards_to_move()->add_card(); + cardToMove->set_card_id(item->getId()); + if (item->isForceFaceDown()) { + cardToMove->set_face_down(true); + } } } diff --git a/cockatrice/src/game/zones/view_zone.cpp b/cockatrice/src/game/zones/view_zone.cpp index 4ae25989e..348ed6e87 100644 --- a/cockatrice/src/game/zones/view_zone.cpp +++ b/cockatrice/src/game/zones/view_zone.cpp @@ -73,7 +73,10 @@ void ZoneViewZone::initializeCards(const QList &cardLis for (int i = 0; i < cardList.size(); ++i) { auto card = cardList[i]; CardRef cardRef = {QString::fromStdString(card->name()), QString::fromStdString(card->provider_id())}; - getLogic()->addCard(new CardItem(getLogic()->getPlayer(), this, cardRef, card->id()), false, i); + auto copy = new CardItem(getLogic()->getPlayer(), this, cardRef, card->id()); + copy->setFaceDown(card->face_down()); + + getLogic()->addCard(copy, false, i); } reorganizeCards(); } else if (!qobject_cast(getLogic())->getOriginalZone()->contentsKnown()) { @@ -91,8 +94,10 @@ void ZoneViewZone::initializeCards(const QList &cardLis int number = numberCards == -1 ? c.size() : (numberCards < c.size() ? numberCards : c.size()); for (int i = 0; i < number; i++) { CardItem *card = c.at(i); - getLogic()->addCard(new CardItem(getLogic()->getPlayer(), this, card->getCardRef(), card->getId()), false, - i); + auto copy = new CardItem(getLogic()->getPlayer(), this, card->getCardRef(), card->getId()); + copy->setFaceDown(card->getFaceDown()); + + getLogic()->addCard(copy, false, i); } reorganizeCards(); } @@ -107,6 +112,7 @@ void ZoneViewZone::zoneDumpReceived(const Response &r) auto cardName = QString::fromStdString(cardInfo.name()); auto cardProviderId = QString::fromStdString(cardInfo.provider_id()); auto card = new CardItem(getLogic()->getPlayer(), this, {cardName, cardProviderId}, cardInfo.id(), getLogic()); + card->setFaceDown(cardInfo.face_down()); getLogic()->rawInsertCard(card, i); } @@ -279,8 +285,13 @@ void ZoneViewZone::handleDropEvent(const QList &dragItems, cmd.set_y(0); cmd.set_is_reversed(qobject_cast(getLogic())->getIsReversed()); - for (int i = 0; i < dragItems.size(); ++i) - cmd.mutable_cards_to_move()->add_card()->set_card_id(dragItems[i]->getId()); + for (int i = 0; i < dragItems.size(); ++i) { + auto cardToMove = cmd.mutable_cards_to_move()->add_card(); + cardToMove->set_card_id(dragItems[i]->getId()); + if (dragItems[i]->isForceFaceDown()) { + cardToMove->set_face_down(true); + } + } getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(cmd); } From 43acac5f5db0a8ddf5221ececf35f57af6d6ea9e Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Wed, 4 Mar 2026 00:16:19 +0100 Subject: [PATCH 229/325] empty card info when switching back to theme background (#6657) --- cockatrice/src/interface/widgets/general/home_widget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index 269326257..5176dde83 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -63,6 +63,7 @@ void HomeWidget::initializeBackgroundFromSource() switch (backgroundSourceType) { case BackgroundSources::Theme: background = QPixmap("theme:backgrounds/home"); + backgroundSourceCard->setCard(ExactCard()); updateButtonsToBackgroundColor(); update(); break; From 566c876bdc7f716ee56cf11f08c1aa6626a5c4a4 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Wed, 4 Mar 2026 00:16:45 +0100 Subject: [PATCH 230/325] fix scaling for background images on home tab (#6656) --- .../card_info_picture_art_crop_widget.cpp | 12 ++++-------- .../cards/card_info_picture_art_crop_widget.h | 4 ++-- .../interface/widgets/general/home_widget.cpp | 19 +++++++++++-------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.cpp index 451104d17..e5b0c4872 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.cpp @@ -8,7 +8,7 @@ CardInfoPictureArtCropWidget::CardInfoPictureArtCropWidget(QWidget *parent) hide(); } -QPixmap CardInfoPictureArtCropWidget::getProcessedBackground(const QSize &targetSize) +QPixmap CardInfoPictureArtCropWidget::getBackground() { // Load the full-resolution card image, not a pre-scaled one QPixmap fullResPixmap; @@ -17,10 +17,8 @@ QPixmap CardInfoPictureArtCropWidget::getProcessedBackground(const QSize &target } else { CardPictureLoader::getCardBackPixmap(fullResPixmap, QSize(745, 1040)); } - - // Fail-safe if loading failed if (fullResPixmap.isNull()) { - return QPixmap(targetSize); + return QPixmap(); // return null qpixmap } const QSize sz = fullResPixmap.size(); @@ -33,9 +31,7 @@ QPixmap CardInfoPictureArtCropWidget::getProcessedBackground(const QSize &target foilRect = foilRect.intersected(fullResPixmap.rect()); // always clamp to source bounds - // Crop first, then scale for best quality + // return full resolution image crop QPixmap cropped = fullResPixmap.copy(foilRect); - QPixmap scaled = cropped.scaled(targetSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); - - return scaled; + return cropped; } diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.h b/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.h index aed951cc6..0185f758c 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.h @@ -16,8 +16,8 @@ class CardInfoPictureArtCropWidget : public CardInfoPictureWidget public: explicit CardInfoPictureArtCropWidget(QWidget *parent = nullptr); - // Returns a processed (cropped & scaled) version of the pixmap - QPixmap getProcessedBackground(const QSize &targetSize); + // Returns a cropped version of the pixmap + QPixmap getBackground(); }; #endif // CARD_INFO_PICTURE_ART_CROP_WIDGET_H diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index 5176dde83..7d0bad813 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -125,7 +125,7 @@ void HomeWidget::updateRandomCard() connect(newCard.getCardPtr().data(), &CardInfo::pixmapUpdated, this, &HomeWidget::updateBackgroundProperties); backgroundSourceCard->setCard(newCard); - background = backgroundSourceCard->getProcessedBackground(size()); + background = backgroundSourceCard->getBackground(); if (SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() <= 0) { cardChangeTimer->stop(); @@ -143,7 +143,7 @@ void HomeWidget::onBackgroundShuffleFrequencyChanged() void HomeWidget::updateBackgroundProperties() { - background = backgroundSourceCard->getProcessedBackground(size()); + background = backgroundSourceCard->getBackground(); updateButtonsToBackgroundColor(); update(); // Triggers repaint } @@ -301,14 +301,17 @@ void HomeWidget::paintEvent(QPaintEvent *event) QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); - background = background.scaled(size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + if (!background.isNull()) { + QSize widgetSize = size() * devicePixelRatio(); + QPixmap toDraw = background.scaled(widgetSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); - // Draw already-scaled background centered - QSize widgetSize = size(); - QSize bgSize = background.size(); - QPoint topLeft((widgetSize.width() - bgSize.width()) / 2, (widgetSize.height() - bgSize.height()) / 2); + // Draw scaled background centered + QSize bgSize = toDraw.size(); + QPoint topLeft((widgetSize.width() - bgSize.width()) / (devicePixelRatio() * 2), // undo scaling for painter + (widgetSize.height() - bgSize.height()) / (devicePixelRatio() * 2)); - painter.drawPixmap(topLeft, background); + painter.drawPixmap(topLeft, toDraw); + } // Draw translucent black overlay with rounded corners QRectF overlayRect(5, 5, width() - 10, height() - 10); From b36ab66583ec60f1dbb9366f3246d274daa11595 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Wed, 4 Mar 2026 00:49:50 +0100 Subject: [PATCH 231/325] nullcheck printing's set in home tab background art crop (#6646) * nullcheck printing's set in home tab background art crop * set warning and properly set timer * fix merge --- .../interface/widgets/general/home_widget.cpp | 46 ++++++++++++------- .../interface/widgets/general/home_widget.h | 1 + 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index 7d0bad813..750ddd000 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -58,21 +58,24 @@ void HomeWidget::initializeBackgroundFromSource() auto backgroundSourceType = BackgroundSources::fromId(SettingsCache::instance().getHomeTabBackgroundSource()); - cardChangeTimer->stop(); - switch (backgroundSourceType) { case BackgroundSources::Theme: + cardChangeTimer->stop(); background = QPixmap("theme:backgrounds/home"); + backgroundSourceDeck = DeckList(); backgroundSourceCard->setCard(ExactCard()); updateButtonsToBackgroundColor(); update(); break; case BackgroundSources::RandomCardArt: - cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000); + backgroundSourceDeck = DeckList(); + updateRandomCard(); + onBackgroundShuffleFrequencyChanged(); break; case BackgroundSources::DeckFileArt: loadBackgroundSourceDeck(); - cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000); + updateRandomCard(); + onBackgroundShuffleFrequencyChanged(); break; } } @@ -84,6 +87,20 @@ void HomeWidget::loadBackgroundSourceDeck() backgroundSourceDeck = deckOpt.has_value() ? deckOpt.value().deckList : DeckList(); } +void HomeWidget::setRandomCard(ExactCard &newCard) +{ + static constexpr int ATTEMPTS = 10; + for (int i = 0; i < ATTEMPTS; ++i) { + ExactCard tmpCard = CardDatabaseManager::query()->getRandomCard(); + if (tmpCard != backgroundSourceCard->getCard() && tmpCard.getCardPtr()->getProperty("layout") == "normal" && + tmpCard.getPrinting().getSet() != nullptr) { + newCard = tmpCard; + return; + } + } + qWarning() << "failed to set random card image after" << ATTEMPTS << "attempts"; +} + void HomeWidget::updateRandomCard() { auto backgroundSourceType = BackgroundSources::fromId(SettingsCache::instance().getHomeTabBackgroundSource()); @@ -94,10 +111,7 @@ void HomeWidget::updateRandomCard() case BackgroundSources::Theme: break; case BackgroundSources::RandomCardArt: - do { - newCard = CardDatabaseManager::query()->getRandomCard(); - } while (newCard == backgroundSourceCard->getCard() && - newCard.getCardPtr()->getProperty("layout") != "normal"); + setRandomCard(newCard); break; case BackgroundSources::DeckFileArt: QList cardRefs = backgroundSourceDeck.getCardRefList(); @@ -126,19 +140,14 @@ void HomeWidget::updateRandomCard() connect(newCard.getCardPtr().data(), &CardInfo::pixmapUpdated, this, &HomeWidget::updateBackgroundProperties); backgroundSourceCard->setCard(newCard); background = backgroundSourceCard->getBackground(); - - if (SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() <= 0) { - cardChangeTimer->stop(); - } } void HomeWidget::onBackgroundShuffleFrequencyChanged() { cardChangeTimer->stop(); - if (SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() <= 0) { - return; + if (SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() > 0) { + cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000); } - cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000); } void HomeWidget::updateBackgroundProperties() @@ -325,8 +334,11 @@ void HomeWidget::paintEvent(QPaintEvent *event) QString cardName; ExactCard card = backgroundSourceCard->getCard(); if (card) { - cardName = card.getCardPtr()->getName() + " (" + card.getPrinting().getSet()->getCorrectedShortName() + ") " + - card.getPrinting().getProperty("num"); + cardName = card.getCardPtr()->getName(); + if (card.getPrinting().getSet() != nullptr) { + cardName += " (" + card.getPrinting().getSet()->getCorrectedShortName() + ") " + + card.getPrinting().getProperty("num"); + } } if (!cardName.isEmpty() && SettingsCache::instance().getHomeTabDisplayCardName()) { diff --git a/cockatrice/src/interface/widgets/general/home_widget.h b/cockatrice/src/interface/widgets/general/home_widget.h index 233fda543..b30bb5407 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.h +++ b/cockatrice/src/interface/widgets/general/home_widget.h @@ -45,6 +45,7 @@ private: QPair gradientColors; HomeStyledButton *connectButton; + void setRandomCard(ExactCard &newCard); void loadBackgroundSourceDeck(); }; From e7a3ad86eb8bc294da4b8d027392c4fb48a3bbb9 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 4 Mar 2026 06:00:18 -0800 Subject: [PATCH 232/325] [Game] Refactor move cards from library actions (#6658) * refactor move top/bottom cards actions * minor cleanup * translate zone display names --- cockatrice/src/game/player/player_actions.cpp | 84 +++++-------------- cockatrice/src/game/player/player_actions.h | 3 + 2 files changed, 25 insertions(+), 62 deletions(-) diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index ed77808b0..1a09836e4 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -424,37 +424,15 @@ void PlayerActions::actMoveTopCardToExile() void PlayerActions::actMoveTopCardsToGrave() { - const int maxCards = player->getDeckZone()->getCards().size(); - if (maxCards == 0) { - return; - } - - bool ok; - int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move top cards to grave"), - tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberTopCards, 1, - maxCards, 1, &ok); - if (!ok) { - return; - } else if (number > maxCards) { - number = maxCards; - } - defaultNumberTopCards = number; - - Command_MoveCard cmd; - cmd.set_start_zone("deck"); - cmd.set_target_player_id(player->getPlayerInfo()->getId()); - cmd.set_target_zone("grave"); - cmd.set_x(0); - cmd.set_y(0); - - for (int i = number - 1; i >= 0; --i) { - cmd.mutable_cards_to_move()->add_card()->set_card_id(i); - } - - sendGameCommand(cmd); + moveTopCardsTo("grave", tr("grave")); } void PlayerActions::actMoveTopCardsToExile() +{ + moveTopCardsTo("rfg", tr("exile")); +} + +void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName) { const int maxCards = player->getDeckZone()->getCards().size(); if (maxCards == 0) { @@ -462,12 +440,14 @@ void PlayerActions::actMoveTopCardsToExile() } bool ok; - int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move top cards to exile"), + int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move top cards to %1").arg(zoneDisplayName), tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberTopCards, 1, maxCards, 1, &ok); if (!ok) { return; - } else if (number > maxCards) { + } + + if (number > maxCards) { number = maxCards; } defaultNumberTopCards = number; @@ -475,7 +455,7 @@ void PlayerActions::actMoveTopCardsToExile() Command_MoveCard cmd; cmd.set_start_zone("deck"); cmd.set_target_player_id(player->getPlayerInfo()->getId()); - cmd.set_target_zone("rfg"); + cmd.set_target_zone(targetZone.toStdString()); cmd.set_x(0); cmd.set_y(0); @@ -628,37 +608,15 @@ void PlayerActions::actMoveBottomCardToExile() void PlayerActions::actMoveBottomCardsToGrave() { - const int maxCards = player->getDeckZone()->getCards().size(); - if (maxCards == 0) { - return; - } - - bool ok; - int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move bottom cards to grave"), - tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberBottomCards, 1, - maxCards, 1, &ok); - if (!ok) { - return; - } else if (number > maxCards) { - number = maxCards; - } - defaultNumberBottomCards = number; - - Command_MoveCard cmd; - cmd.set_start_zone("deck"); - cmd.set_target_player_id(player->getPlayerInfo()->getId()); - cmd.set_target_zone("grave"); - cmd.set_x(0); - cmd.set_y(0); - - for (int i = maxCards - number; i < maxCards; ++i) { - cmd.mutable_cards_to_move()->add_card()->set_card_id(i); - } - - sendGameCommand(cmd); + moveBottomCardsTo("grave", tr("grave")); } void PlayerActions::actMoveBottomCardsToExile() +{ + moveBottomCardsTo("rfg", tr("exile")); +} + +void PlayerActions::moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName) { const int maxCards = player->getDeckZone()->getCards().size(); if (maxCards == 0) { @@ -666,12 +624,14 @@ void PlayerActions::actMoveBottomCardsToExile() } bool ok; - int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move bottom cards to exile"), + int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move bottom cards to %1").arg(zoneDisplayName), tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberBottomCards, 1, maxCards, 1, &ok); if (!ok) { return; - } else if (number > maxCards) { + } + + if (number > maxCards) { number = maxCards; } defaultNumberBottomCards = number; @@ -679,7 +639,7 @@ void PlayerActions::actMoveBottomCardsToExile() Command_MoveCard cmd; cmd.set_start_zone("deck"); cmd.set_target_player_id(player->getPlayerInfo()->getId()); - cmd.set_target_zone("rfg"); + cmd.set_target_zone(targetZone.toStdString()); cmd.set_x(0); cmd.set_y(0); diff --git a/cockatrice/src/game/player/player_actions.h b/cockatrice/src/game/player/player_actions.h index b294b5946..beed2c241 100644 --- a/cockatrice/src/game/player/player_actions.h +++ b/cockatrice/src/game/player/player_actions.h @@ -180,6 +180,9 @@ private: FilterString movingCardsUntilFilter; int movingCardsUntilCounter = 0; + void moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName); + void moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName); + void createCard(const CardItem *sourceCard, const QString &dbCardName, CardRelationType attach = CardRelationType::DoesNotAttach, From 1bcea27a445c912b5d51462e5a35ab732942ded4 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 4 Mar 2026 18:19:41 -0800 Subject: [PATCH 233/325] [Game] Add face down versions of move cards from library actions (#6661) * implement actions * add new actions to menu * update shortcuts --- .../src/client/settings/shortcuts_settings.h | 16 +++++++ .../src/game/player/menu/library_menu.cpp | 28 ++++++++++++ .../src/game/player/menu/library_menu.h | 4 ++ cockatrice/src/game/player/player_actions.cpp | 44 +++++++++++++++---- cockatrice/src/game/player/player_actions.h | 8 +++- 5 files changed, 90 insertions(+), 10 deletions(-) diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h index 156ee82ea..47332b8c4 100644 --- a/cockatrice/src/client/settings/shortcuts_settings.h +++ b/cockatrice/src/client/settings/shortcuts_settings.h @@ -598,11 +598,19 @@ private: {"Player/aMoveTopCardsToGraveyard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard (Multiple)"), parseSequenceString("Alt+M"), ShortcutGroup::Move_top)}, + {"Player/aMoveTopCardsToGraveyardFaceDown", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard (Multiple), Face Down"), + parseSequenceString(""), + ShortcutGroup::Move_top)}, {"Player/aMoveTopCardToExile", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile"), parseSequenceString(""), ShortcutGroup::Move_top)}, {"Player/aMoveTopCardsToExile", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile (Multiple)"), parseSequenceString(""), ShortcutGroup::Move_top)}, + {"Player/aMoveTopCardsToExileFaceDown", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile (Multiple), Face Down"), + parseSequenceString(""), + ShortcutGroup::Move_top)}, {"Player/aMoveTopCardsUntil", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Stack Until Found"), parseSequenceString("Ctrl+Shift+Y"), ShortcutGroup::Move_top)}, @@ -620,11 +628,19 @@ private: {"Player/aMoveBottomCardsToGrave", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard (Multiple)"), parseSequenceString(""), ShortcutGroup::Move_bottom)}, + {"Player/aMoveBottomCardsToGraveFaceDown", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard (Multiple), Face Down"), + parseSequenceString(""), + ShortcutGroup::Move_bottom)}, {"Player/aMoveBottomCardToExile", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile"), parseSequenceString(""), ShortcutGroup::Move_bottom)}, {"Player/aMoveBottomCardsToExile", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile (Multiple)"), parseSequenceString(""), ShortcutGroup::Move_bottom)}, + {"Player/aMoveBottomCardsToExileFaceDown", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile (Multiple), Face Down"), + parseSequenceString(""), + ShortcutGroup::Move_bottom)}, {"Player/aMoveBottomCardToTop", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Top of Library"), parseSequenceString(""), ShortcutGroup::Move_bottom)}, diff --git a/cockatrice/src/game/player/menu/library_menu.cpp b/cockatrice/src/game/player/menu/library_menu.cpp index 749439558..1bb647d06 100644 --- a/cockatrice/src/game/player/menu/library_menu.cpp +++ b/cockatrice/src/game/player/menu/library_menu.cpp @@ -51,8 +51,10 @@ LibraryMenu::LibraryMenu(Player *_player, QWidget *parent) : TearOffMenu(parent) topLibraryMenu->addSeparator(); topLibraryMenu->addAction(aMoveTopCardToGraveyard); topLibraryMenu->addAction(aMoveTopCardsToGraveyard); + topLibraryMenu->addAction(aMoveTopCardsToGraveyardFaceDown); topLibraryMenu->addAction(aMoveTopCardToExile); topLibraryMenu->addAction(aMoveTopCardsToExile); + topLibraryMenu->addAction(aMoveTopCardsToExileFaceDown); topLibraryMenu->addAction(aMoveTopCardsUntil); topLibraryMenu->addSeparator(); topLibraryMenu->addAction(aShuffleTopCards); @@ -66,8 +68,10 @@ LibraryMenu::LibraryMenu(Player *_player, QWidget *parent) : TearOffMenu(parent) bottomLibraryMenu->addSeparator(); bottomLibraryMenu->addAction(aMoveBottomCardToGraveyard); bottomLibraryMenu->addAction(aMoveBottomCardsToGraveyard); + bottomLibraryMenu->addAction(aMoveBottomCardsToGraveyardFaceDown); bottomLibraryMenu->addAction(aMoveBottomCardToExile); bottomLibraryMenu->addAction(aMoveBottomCardsToExile); + bottomLibraryMenu->addAction(aMoveBottomCardsToExileFaceDown); bottomLibraryMenu->addSeparator(); bottomLibraryMenu->addAction(aShuffleBottomCards); @@ -136,8 +140,14 @@ void LibraryMenu::createMoveActions() connect(aMoveTopCardToExile, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardToExile); aMoveTopCardsToGraveyard = new QAction(this); connect(aMoveTopCardsToGraveyard, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardsToGrave); + aMoveTopCardsToGraveyardFaceDown = new QAction(this); + connect(aMoveTopCardsToGraveyardFaceDown, &QAction::triggered, playerActions, + &PlayerActions::actMoveTopCardsToGraveFaceDown); aMoveTopCardsToExile = new QAction(this); connect(aMoveTopCardsToExile, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardsToExile); + aMoveTopCardsToExileFaceDown = new QAction(this); + connect(aMoveTopCardsToExileFaceDown, &QAction::triggered, playerActions, + &PlayerActions::actMoveTopCardsToExileFaceDown); aMoveTopCardsUntil = new QAction(this); connect(aMoveTopCardsUntil, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardsUntil); aMoveTopCardToBottom = new QAction(this); @@ -156,8 +166,14 @@ void LibraryMenu::createMoveActions() aMoveBottomCardsToGraveyard = new QAction(this); connect(aMoveBottomCardsToGraveyard, &QAction::triggered, playerActions, &PlayerActions::actMoveBottomCardsToGrave); + aMoveBottomCardsToGraveyardFaceDown = new QAction(this); + connect(aMoveBottomCardsToGraveyardFaceDown, &QAction::triggered, playerActions, + &PlayerActions::actMoveBottomCardsToGraveFaceDown); aMoveBottomCardsToExile = new QAction(this); connect(aMoveBottomCardsToExile, &QAction::triggered, playerActions, &PlayerActions::actMoveBottomCardsToExile); + aMoveBottomCardsToExileFaceDown = new QAction(this); + connect(aMoveBottomCardsToExileFaceDown, &QAction::triggered, playerActions, + &PlayerActions::actMoveBottomCardsToExileFaceDown); aMoveBottomCardToTop = new QAction(this); connect(aMoveBottomCardToTop, &QAction::triggered, playerActions, &PlayerActions::actMoveBottomCardToTop); } @@ -216,7 +232,9 @@ void LibraryMenu::retranslateUi() aMoveTopCardToGraveyard->setText(tr("Move top card to grave&yard")); aMoveTopCardToExile->setText(tr("Move top card to e&xile")); aMoveTopCardsToGraveyard->setText(tr("Move top cards to &graveyard...")); + aMoveTopCardsToGraveyardFaceDown->setText(tr("Move top cards to graveyard face down...")); aMoveTopCardsToExile->setText(tr("Move top cards to &exile...")); + aMoveTopCardsToExileFaceDown->setText(tr("Move top cards to exile face down...")); aMoveTopCardsUntil->setText(tr("Put top cards on stack &until...")); aShuffleTopCards->setText(tr("Shuffle top cards...")); @@ -227,7 +245,9 @@ void LibraryMenu::retranslateUi() aMoveBottomCardToGraveyard->setText(tr("Move bottom card to grave&yard")); aMoveBottomCardToExile->setText(tr("Move bottom card to e&xile")); aMoveBottomCardsToGraveyard->setText(tr("Move bottom cards to &graveyard...")); + aMoveBottomCardsToGraveyardFaceDown->setText(tr("Move bottom cards to graveyard face down...")); aMoveBottomCardsToExile->setText(tr("Move bottom cards to &exile...")); + aMoveBottomCardsToExileFaceDown->setText(tr("Move bottom cards to exile face down...")); aMoveBottomCardToTop->setText(tr("Put bottom card on &top")); aShuffleBottomCards->setText(tr("Shuffle bottom cards...")); } @@ -335,8 +355,10 @@ void LibraryMenu::setShortcutsActive() aMoveTopToPlayFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveTopToPlayFaceDown")); aMoveTopCardToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardToGraveyard")); aMoveTopCardsToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsToGraveyard")); + aMoveTopCardsToGraveyardFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsToGraveyardFaceDown")); aMoveTopCardToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardToExile")); aMoveTopCardsToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsToExile")); + aMoveTopCardsToExileFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsToExileFaceDown")); aMoveTopCardsUntil->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsUntil")); aMoveTopCardToBottom->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardToBottom")); aDrawBottomCard->setShortcuts(shortcuts.getShortcut("Player/aDrawBottomCard")); @@ -345,8 +367,10 @@ void LibraryMenu::setShortcutsActive() aMoveBottomToPlayFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomToPlayFaceDown")); aMoveBottomCardToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardToGrave")); aMoveBottomCardsToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardsToGrave")); + aMoveBottomCardsToGraveyardFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardsToGraveFaceDown")); aMoveBottomCardToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardToExile")); aMoveBottomCardsToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardsToExile")); + aMoveBottomCardsToExileFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardsToExileFaceDown")); aMoveBottomCardToTop->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardToTop")); } @@ -367,8 +391,10 @@ void LibraryMenu::setShortcutsInactive() aMoveTopToPlayFaceDown->setShortcut(QKeySequence()); aMoveTopCardToGraveyard->setShortcut(QKeySequence()); aMoveTopCardsToGraveyard->setShortcut(QKeySequence()); + aMoveTopCardsToGraveyardFaceDown->setShortcut(QKeySequence()); aMoveTopCardToExile->setShortcut(QKeySequence()); aMoveTopCardsToExile->setShortcut(QKeySequence()); + aMoveTopCardsToExileFaceDown->setShortcut(QKeySequence()); aMoveTopCardsUntil->setShortcut(QKeySequence()); aDrawBottomCard->setShortcut(QKeySequence()); aDrawBottomCards->setShortcut(QKeySequence()); @@ -376,6 +402,8 @@ void LibraryMenu::setShortcutsInactive() aMoveBottomToPlayFaceDown->setShortcut(QKeySequence()); aMoveBottomCardToGraveyard->setShortcut(QKeySequence()); aMoveBottomCardsToGraveyard->setShortcut(QKeySequence()); + aMoveBottomCardsToGraveyardFaceDown->setShortcut(QKeySequence()); aMoveBottomCardToExile->setShortcut(QKeySequence()); aMoveBottomCardsToExile->setShortcut(QKeySequence()); + aMoveBottomCardsToExileFaceDown->setShortcut(QKeySequence()); } diff --git a/cockatrice/src/game/player/menu/library_menu.h b/cockatrice/src/game/player/menu/library_menu.h index 96d4b82b1..c0883107c 100644 --- a/cockatrice/src/game/player/menu/library_menu.h +++ b/cockatrice/src/game/player/menu/library_menu.h @@ -88,7 +88,9 @@ public: QAction *aMoveTopCardToGraveyard = nullptr; QAction *aMoveTopCardToExile = nullptr; QAction *aMoveTopCardsToGraveyard = nullptr; + QAction *aMoveTopCardsToGraveyardFaceDown = nullptr; QAction *aMoveTopCardsToExile = nullptr; + QAction *aMoveTopCardsToExileFaceDown = nullptr; QAction *aMoveTopCardsUntil = nullptr; QAction *aShuffleTopCards = nullptr; @@ -100,7 +102,9 @@ public: QAction *aMoveBottomCardToGraveyard = nullptr; QAction *aMoveBottomCardToExile = nullptr; QAction *aMoveBottomCardsToGraveyard = nullptr; + QAction *aMoveBottomCardsToGraveyardFaceDown = nullptr; QAction *aMoveBottomCardsToExile = nullptr; + QAction *aMoveBottomCardsToExileFaceDown = nullptr; QAction *aShuffleBottomCards = nullptr; int defaultNumberTopCards = 1; diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index 1a09836e4..514ac2e67 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -424,15 +424,25 @@ void PlayerActions::actMoveTopCardToExile() void PlayerActions::actMoveTopCardsToGrave() { - moveTopCardsTo("grave", tr("grave")); + moveTopCardsTo("grave", tr("grave"), false); +} + +void PlayerActions::actMoveTopCardsToGraveFaceDown() +{ + moveTopCardsTo("grave", tr("grave"), true); } void PlayerActions::actMoveTopCardsToExile() { - moveTopCardsTo("rfg", tr("exile")); + moveTopCardsTo("rfg", tr("exile"), false); } -void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName) +void PlayerActions::actMoveTopCardsToExileFaceDown() +{ + moveTopCardsTo("rfg", tr("exile"), true); +} + +void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown) { const int maxCards = player->getDeckZone()->getCards().size(); if (maxCards == 0) { @@ -460,7 +470,11 @@ void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zon cmd.set_y(0); for (int i = number - 1; i >= 0; --i) { - cmd.mutable_cards_to_move()->add_card()->set_card_id(i); + auto card = cmd.mutable_cards_to_move()->add_card(); + card->set_card_id(i); + if (faceDown) { + card->set_face_down(true); + } } sendGameCommand(cmd); @@ -608,15 +622,25 @@ void PlayerActions::actMoveBottomCardToExile() void PlayerActions::actMoveBottomCardsToGrave() { - moveBottomCardsTo("grave", tr("grave")); + moveBottomCardsTo("grave", tr("grave"), false); +} + +void PlayerActions::actMoveBottomCardsToGraveFaceDown() +{ + moveBottomCardsTo("grave", tr("grave"), true); } void PlayerActions::actMoveBottomCardsToExile() { - moveBottomCardsTo("rfg", tr("exile")); + moveBottomCardsTo("rfg", tr("exile"), false); } -void PlayerActions::moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName) +void PlayerActions::actMoveBottomCardsToExileFaceDown() +{ + moveBottomCardsTo("rfg", tr("exile"), true); +} + +void PlayerActions::moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown) { const int maxCards = player->getDeckZone()->getCards().size(); if (maxCards == 0) { @@ -644,7 +668,11 @@ void PlayerActions::moveBottomCardsTo(const QString &targetZone, const QString & cmd.set_y(0); for (int i = maxCards - number; i < maxCards; ++i) { - cmd.mutable_cards_to_move()->add_card()->set_card_id(i); + auto card = cmd.mutable_cards_to_move()->add_card(); + card->set_card_id(i); + if (faceDown) { + card->set_face_down(true); + } } sendGameCommand(cmd); diff --git a/cockatrice/src/game/player/player_actions.h b/cockatrice/src/game/player/player_actions.h index beed2c241..1d695578d 100644 --- a/cockatrice/src/game/player/player_actions.h +++ b/cockatrice/src/game/player/player_actions.h @@ -98,7 +98,9 @@ public slots: void actMoveTopCardToGrave(); void actMoveTopCardToExile(); void actMoveTopCardsToGrave(); + void actMoveTopCardsToGraveFaceDown(); void actMoveTopCardsToExile(); + void actMoveTopCardsToExileFaceDown(); void actMoveTopCardsUntil(); void actMoveTopCardToBottom(); void actDrawBottomCard(); @@ -108,7 +110,9 @@ public slots: void actMoveBottomCardToGrave(); void actMoveBottomCardToExile(); void actMoveBottomCardsToGrave(); + void actMoveBottomCardsToGraveFaceDown(); void actMoveBottomCardsToExile(); + void actMoveBottomCardsToExileFaceDown(); void actMoveBottomCardToTop(); void actSelectAll(); @@ -180,8 +184,8 @@ private: FilterString movingCardsUntilFilter; int movingCardsUntilCounter = 0; - void moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName); - void moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName); + void moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown); + void moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown); void createCard(const CardItem *sourceCard, const QString &dbCardName, From 04f06206b7ae3ff3b4f21d50af3310965adac350 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Thu, 5 Mar 2026 14:47:14 -0500 Subject: [PATCH 234/325] Refactor/z value constants (#6651) * feat(z-values): add centralized Z-value constants infrastructure Magic numbers scattered across the codebase make Z-value layering hard to understand and maintain. Centralizing them provides: - Self-documenting layer hierarchy - Validation utilities for development - Single source of truth for Z-value ranges Two-tier header design: - z_value_layer_manager.h: Foundation with constants and validation - z_values.h: User-facing namespace with semantic constants * refactor(z-values): replace magic Z-value numbers with ZValues constants Magic numbers like 2000000007 are impossible to understand without context. Named constants (ZValues::DRAG_ITEM) are self-documenting. This intentionally renumbers overlay Z-values to use a cleaner offset sequence. The relative stacking order is preserved, which is what matters for correct rendering. Each consumer now includes z_values.h and uses semantic constants instead of magic numbers. * refactor(z-values): removed redundant inline with contexpr, Updated doc, magic numbers removed from TableZone. --- .../game/board/abstract_card_drag_item.cpp | 5 +- .../src/game/board/abstract_card_item.cpp | 3 +- cockatrice/src/game/board/arrow_item.cpp | 3 +- cockatrice/src/game/z_value_layer_manager.h | 136 ++++++++++++++++++ cockatrice/src/game/z_values.h | 83 +++++++++++ cockatrice/src/game/zones/table_zone.cpp | 5 +- .../src/game/zones/view_zone_widget.cpp | 9 +- 7 files changed, 234 insertions(+), 10 deletions(-) create mode 100644 cockatrice/src/game/z_value_layer_manager.h create mode 100644 cockatrice/src/game/z_values.h diff --git a/cockatrice/src/game/board/abstract_card_drag_item.cpp b/cockatrice/src/game/board/abstract_card_drag_item.cpp index c961cbcb6..c9a964048 100644 --- a/cockatrice/src/game/board/abstract_card_drag_item.cpp +++ b/cockatrice/src/game/board/abstract_card_drag_item.cpp @@ -1,6 +1,7 @@ #include "abstract_card_drag_item.h" #include "../../client/settings/cache_settings.h" +#include "../z_values.h" #include #include @@ -18,13 +19,13 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item, { if (parentDrag) { parentDrag->addChildDrag(this); - setZValue(2000000007 + hotSpot.x() * 1000000 + hotSpot.y() * 1000 + 1000); + setZValue(ZValues::childDragZValue(hotSpot.x(), hotSpot.y())); connect(parentDrag, &QObject::destroyed, this, &AbstractCardDragItem::deleteLater); } else { hotSpot = QPointF{qBound(0.0, hotSpot.x(), static_cast(CARD_WIDTH - 1)), qBound(0.0, hotSpot.y(), static_cast(CARD_HEIGHT - 1))}; setCursor(Qt::ClosedHandCursor); - setZValue(2000000007); + setZValue(ZValues::DRAG_ITEM); } if (item->getTapped()) setTransform(QTransform() diff --git a/cockatrice/src/game/board/abstract_card_item.cpp b/cockatrice/src/game/board/abstract_card_item.cpp index d5538011d..f8365c3d6 100644 --- a/cockatrice/src/game/board/abstract_card_item.cpp +++ b/cockatrice/src/game/board/abstract_card_item.cpp @@ -3,6 +3,7 @@ #include "../../client/settings/cache_settings.h" #include "../../interface/card_picture_loader/card_picture_loader.h" #include "../game_scene.h" +#include "../z_values.h" #include #include @@ -215,7 +216,7 @@ void AbstractCardItem::setHovered(bool _hovered) if (_hovered) processHoverEvent(); isHovered = _hovered; - setZValue(_hovered ? 2000000004 : realZValue); + setZValue(_hovered ? ZValues::HOVERED_CARD : realZValue); setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1); setTransformOriginPoint(_hovered ? CARD_WIDTH / 2 : 0, _hovered ? CARD_HEIGHT / 2 : 0); update(); diff --git a/cockatrice/src/game/board/arrow_item.cpp b/cockatrice/src/game/board/arrow_item.cpp index 1352b3a05..257d96f8a 100644 --- a/cockatrice/src/game/board/arrow_item.cpp +++ b/cockatrice/src/game/board/arrow_item.cpp @@ -5,6 +5,7 @@ #include "../player/player.h" #include "../player/player_actions.h" #include "../player/player_target.h" +#include "../z_values.h" #include "../zones/card_zone.h" #include "card_item.h" @@ -23,7 +24,7 @@ ArrowItem::ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTar : QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), targetLocked(false), color(_color), fullColor(true) { - setZValue(2000000005); + setZValue(ZValues::ARROWS); if (startItem) startItem->addArrowFrom(this); diff --git a/cockatrice/src/game/z_value_layer_manager.h b/cockatrice/src/game/z_value_layer_manager.h new file mode 100644 index 000000000..303edb8a5 --- /dev/null +++ b/cockatrice/src/game/z_value_layer_manager.h @@ -0,0 +1,136 @@ +/** + * @file z_value_layer_manager.h + * @ingroup GameGraphics + * @brief Semantic Z-value layer management for game scene rendering. + * + * This file provides a structured approach to Z-value allocation in the game scene. + * Z-values in Qt determine stacking order - higher values render on top of lower values. + * + * ## Layer Architecture + * + * The game scene is organized into three conceptual layers: + * + * 1. **Zone Layer (0-999)**: Zone backgrounds, containers, and static elements + * - Zone backgrounds (0.5-1.0) + * - Cards within zones (1.0 base + index) + * + * 2. **Card Layer (1-40,000,000)**: Dynamic card rendering on the table zone + * - Cards use formula: (actualY + CARD_HEIGHT) * 100000 + (actualX + 1) * 100 + * - Maximum card Z-value: ~40,000,000 (with 3 rows, actualY <= ~289) + * + * 3. **Overlay Layer (2,000,000,000+)**: UI elements that must appear above all cards + * - Hovered cards (+1) + * - Arrows (+3) + * - Zone views (+4) + * - Drag items (+5, +6) + * - Top UI elements (+7) + * + * ## Design Rationale + * + * The large gap between card Z-values (max ~40M) and overlay base (2B) provides + * safety margin for future table zone expansions while ensuring overlays always + * render above cards regardless of table position. + * + * ## Usage + * + * Prefer using the semantic constants from ZValues namespace: + * @code + * card->setZValue(ZValues::HOVERED_CARD); + * arrow->setZValue(ZValues::ARROWS); + * @endcode + * + * Use validation functions to verify card Z-values during development: + * @code + * Q_ASSERT(ZValueLayerManager::isValidCardZValue(cardZ)); + * @endcode + */ + +#ifndef Z_VALUE_LAYER_MANAGER_H +#define Z_VALUE_LAYER_MANAGER_H + +#include + +/** + * @namespace ZValueLayerManager + * @brief Utilities for Z-value validation and layer management. + */ +namespace ZValueLayerManager +{ + +/** + * @enum Layer + * @brief Semantic layer identifiers for Z-value allocation. + * + * These represent conceptual rendering layers, not actual Z-values. + * Use the corresponding ZValues constants for actual rendering. + */ +enum class Layer +{ + /// Zone-level elements like backgrounds and containers + Zone, + /// Cards rendered in zones (uses sequential Z-values) + Card, + /// Temporary UI elements like hovered cards and drag items + Overlay +}; + +/** + * @brief Maximum Z-value a card can have on the table zone. + * + * Based on table zone formula: (actualY + CARD_HEIGHT) * 100000 + (actualX + 1) * 100 + * With maximum 3 rows and CARD_HEIGHT ~96, actualY <= ~289. + * Maximum: (289 + 96) * 100000 + 100 * 100 = 38,510,000 + * + * We use 40,000,000 as a safe upper bound with margin. + */ +constexpr qreal CARD_Z_VALUE_MAX = 40000000.0; + +/** + * @brief Base Z-value for overlay elements. + * + * Must exceed CARD_Z_VALUE_MAX to ensure overlays render above all cards. + * The 50x margin (2B vs 40M) provides safety for future expansion. + */ +constexpr qreal OVERLAY_BASE = 2000000000.0; + +/** + * @brief Validates that a Z-value is within the valid card range. + * + * Cards should have Z-values between CARD_BASE (1.0) and CARD_Z_VALUE_MAX. + * Values outside this range may interfere with overlay rendering. + * + * @param zValue The Z-value to validate + * @return true if the Z-value is valid for a card + */ +[[nodiscard]] constexpr bool isValidCardZValue(qreal zValue) +{ + return zValue >= 1.0 && zValue <= CARD_Z_VALUE_MAX; +} + +/** + * @brief Validates that a Z-value is in the overlay layer. + * + * Overlay elements should have Z-values at or above OVERLAY_BASE. + * + * @param zValue The Z-value to validate + * @return true if the Z-value is valid for an overlay element + */ +[[nodiscard]] constexpr bool isOverlayZValue(qreal zValue) +{ + return zValue >= OVERLAY_BASE; +} + +/** + * @brief Returns the Z-value for a specific overlay element. + * + * @param offset Offset from OVERLAY_BASE (0-7 for current elements) + * @return The absolute Z-value for the overlay element + */ +[[nodiscard]] constexpr qreal overlayZValue(qreal offset) +{ + return OVERLAY_BASE + offset; +} + +} // namespace ZValueLayerManager + +#endif // Z_VALUE_LAYER_MANAGER_H diff --git a/cockatrice/src/game/z_values.h b/cockatrice/src/game/z_values.h new file mode 100644 index 000000000..2c7fe9066 --- /dev/null +++ b/cockatrice/src/game/z_values.h @@ -0,0 +1,83 @@ +#ifndef Z_VALUES_H +#define Z_VALUES_H + +#include "z_value_layer_manager.h" + +/** + * @file z_values.h + * @ingroup GameGraphics + * @brief Centralized Z-value constants for rendering layer order. + * + * Z-values in Qt determine stacking order. Higher values render on top. + * These constants define the visual layering hierarchy for the game scene. + * + * ## Layer Architecture + * + * See z_value_layer_manager.h for detailed documentation on the three-layer + * architecture (Zone, Card, Overlay) and the rationale for Z-value choices. + * + * ## Quick Reference + * + * | Layer | Z-Value Range | Purpose | + * |----------|------------------|-----------------------------------| + * | Zone | 0.5 - 1.0 | Zone backgrounds, containers | + * | Card | 1.0 - 40,000,000 | Cards on table (position-based) | + * | Overlay | 2,000,000,000+ | UI elements above all cards | + */ + +namespace ZValues +{ + +// Expose base for callers that need it +constexpr qreal OVERLAY_BASE = ZValueLayerManager::OVERLAY_BASE; + +// Overlay layer Z-values for items that should appear above normal cards +constexpr qreal HOVERED_CARD = ZValueLayerManager::overlayZValue(1.0); +constexpr qreal ARROWS = ZValueLayerManager::overlayZValue(3.0); +constexpr qreal ZONE_VIEW_WIDGET = ZValueLayerManager::overlayZValue(4.0); +constexpr qreal DRAG_ITEM = ZValueLayerManager::overlayZValue(5.0); +constexpr qreal DRAG_ITEM_CHILD = ZValueLayerManager::overlayZValue(6.0); +constexpr qreal TOP_UI = ZValueLayerManager::overlayZValue(7.0); + +/** + * @brief Compute Z-value for child drag items based on hotspot position. + * + * When dragging multiple cards together, each child card needs a unique Z-value + * to prevent Z-fighting (flickering/flashing). The Z-values are derived from + * their position when grabbed to conserve original stacking. The formula encodes + * 2D coordinates into a single value where X has higher weight, ensuring + * deterministic visual stacking. + * + * @param hotSpotX The X coordinate of the grab position + * @param hotSpotY The Y coordinate of the grab position + * @return Unique Z-value for the child drag item + */ +[[nodiscard]] constexpr qreal childDragZValue(qreal hotSpotX, qreal hotSpotY) +{ + return DRAG_ITEM_CHILD + hotSpotX * 1000000 + hotSpotY * 1000 + 1000; +} + +/** + * @brief Compute Z-value for cards on the table zone based on position. + * + * Cards lower on the table (higher Y) render above cards higher up, + * and cards to the right (higher X) render above cards to the left. + * This creates natural visual stacking for overlapping cards. + * + * @param x The X coordinate of the card position + * @param y The Y coordinate of the card position + * @return Z-value for the card's table position + */ +[[nodiscard]] constexpr qreal tableCardZValue(qreal x, qreal y) +{ + constexpr qreal CARD_HEIGHT_FOR_Z = 102.0; + return (y + CARD_HEIGHT_FOR_Z) * 100000.0 + (x + 1) * 100.0; +} + +// Card layering (general architecture, not command-zone specific) +constexpr qreal CARD_BASE = 1.0; +constexpr qreal CARD_MAX = ZValueLayerManager::CARD_Z_VALUE_MAX; + +} // namespace ZValues + +#endif // Z_VALUES_H diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index ca1052d20..33c867c43 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -7,6 +7,7 @@ #include "../board/card_item.h" #include "../player/player.h" #include "../player/player_actions.h" +#include "../z_values.h" #include "logic/table_zone_logic.h" #include @@ -169,7 +170,7 @@ void TableZone::reorganizeCards() actualY += 15; getLogic()->getCards()[i]->setPos(actualX, actualY); - getLogic()->getCards()[i]->setRealZValue((actualY + CARD_HEIGHT) * 100000 + (actualX + 1) * 100); + getLogic()->getCards()[i]->setRealZValue(ZValues::tableCardZValue(actualX, actualY)); QListIterator attachedCardIterator(getLogic()->getCards()[i]->getAttachedCards()); int j = 0; @@ -179,7 +180,7 @@ void TableZone::reorganizeCards() qreal childX = actualX - j * STACKED_CARD_OFFSET_X; qreal childY = y + 5; attachedCard->setPos(childX, childY); - attachedCard->setRealZValue((childY + CARD_HEIGHT) * 100000 + (childX + 1) * 100); + attachedCard->setRealZValue(ZValues::tableCardZValue(childX, childY)); } } diff --git a/cockatrice/src/game/zones/view_zone_widget.cpp b/cockatrice/src/game/zones/view_zone_widget.cpp index 3ea4eb119..0ad3b10f1 100644 --- a/cockatrice/src/game/zones/view_zone_widget.cpp +++ b/cockatrice/src/game/zones/view_zone_widget.cpp @@ -7,6 +7,7 @@ #include "../game_scene.h" #include "../player/player.h" #include "../player/player_actions.h" +#include "../z_values.h" #include "view_zone.h" #include @@ -47,7 +48,7 @@ ZoneViewWidget::ZoneViewWidget(Player *_player, { setAcceptHoverEvents(true); setAttribute(Qt::WA_DeleteOnClose); - setZValue(2000000006); + setZValue(ZValues::ZONE_VIEW_WIDGET); setFlag(ItemIgnoresTransformations); QGraphicsLinearLayout *vbox = new QGraphicsLinearLayout(Qt::Vertical); @@ -71,7 +72,7 @@ ZoneViewWidget::ZoneViewWidget(Player *_player, QGraphicsProxyWidget *searchEditProxy = new QGraphicsProxyWidget; searchEditProxy->setWidget(&searchEdit); - searchEditProxy->setZValue(2000000007); + searchEditProxy->setZValue(ZValues::DRAG_ITEM); vbox->addItem(searchEditProxy); // top row @@ -80,13 +81,13 @@ ZoneViewWidget::ZoneViewWidget(Player *_player, // groupBy options QGraphicsProxyWidget *groupBySelectorProxy = new QGraphicsProxyWidget; groupBySelectorProxy->setWidget(&groupBySelector); - groupBySelectorProxy->setZValue(2000000008); + groupBySelectorProxy->setZValue(ZValues::TOP_UI); hTopRow->addItem(groupBySelectorProxy); // sortBy options QGraphicsProxyWidget *sortBySelectorProxy = new QGraphicsProxyWidget; sortBySelectorProxy->setWidget(&sortBySelector); - sortBySelectorProxy->setZValue(2000000007); + sortBySelectorProxy->setZValue(ZValues::DRAG_ITEM); hTopRow->addItem(sortBySelectorProxy); vbox->addItem(hTopRow); From e39bbd2b316753f254be8ae8b60c5c8808160b6b Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Fri, 6 Mar 2026 00:19:10 +0100 Subject: [PATCH 235/325] take default theme name from startup instead of using empty (#6663) --- cockatrice/src/interface/theme_manager.cpp | 4 +++- cockatrice/src/interface/theme_manager.h | 1 + cockatrice/src/interface/widgets/general/home_widget.cpp | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index abacb2d68..1dc61fdb7 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -89,6 +90,7 @@ struct PaletteColorInfo ThemeManager::ThemeManager(QObject *parent) : QObject(parent) { + defaultStyleName = qApp->style()->objectName(); ensureThemeDirectoryExists(); #if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, &ThemeManager::themeChangedSlot); @@ -346,7 +348,7 @@ void ThemeManager::themeChangedSlot() qApp->setStyle(QStyleFactory::create("Fusion")); qApp->setPalette(createDarkGreenFusionPalette()); } else { - qApp->setStyle(""); + qApp->setStyle(defaultStyleName); // setting the style also sets the palette } if (dirPath.isEmpty()) { diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index 594ede2bf..d942f0fef 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -40,6 +40,7 @@ public: }; private: + QString defaultStyleName; std::array brushes; QStringMap availableThemes; /* diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index 750ddd000..ea20ef6a0 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -46,6 +46,8 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor) &HomeWidget::onBackgroundShuffleFrequencyChanged); // Lambda is cleaner to read than overloading this connect(&SettingsCache::instance(), &SettingsCache::homeTabDisplayCardNameChanged, this, [this] { repaint(); }); + connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this, + &HomeWidget::updateButtonsToBackgroundColor); } void HomeWidget::initializeBackgroundFromSource() From 14f1925edc8a47045e771e71746e07290fef3679 Mon Sep 17 00:00:00 2001 From: tooomm Date: Fri, 6 Mar 2026 01:50:15 +0100 Subject: [PATCH 236/325] Add icon to exe (#6655) --- cmake/NSIS.template.in | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/NSIS.template.in b/cmake/NSIS.template.in index a0d51fa2b..2fdc61fb9 100644 --- a/cmake/NSIS.template.in +++ b/cmake/NSIS.template.in @@ -26,6 +26,7 @@ Var PortableMode !define MUI_WELCOMEPAGE_TEXT "This wizard will guide you through the installation of Cockatrice.$\r$\n$\r$\nClick Next to continue." !define MUI_FINISHPAGE_RUN "$INSTDIR/cockatrice.exe" !define MUI_FINISHPAGE_RUN_TEXT "Run 'Cockatrice' now" +!define MUI_ICON "${NSIS_SOURCE_PATH}\cockatrice\resources\appicon.ico" !insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_LICENSE "${NSIS_SOURCE_PATH}\LICENSE" From bd5cbb89d47c2cdfac9eb59e550e737d51321775 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Thu, 5 Mar 2026 22:13:58 -0500 Subject: [PATCH 237/325] refactor: extract CARD_HEIGHT to shared CardDimensions header (#6668) * refactor: extract CARD_HEIGHT to shared CardDimensions header Move duplicated CARD_WIDTH/CARD_HEIGHT constants to card_dimensions.h. Fixed documentation in z_value_layer_manager.h. * WIDTH_F used directly instead of casting * Improved consistency and added missing newlines at end of files --- .../game/board/abstract_card_drag_item.cpp | 12 ++++---- .../src/game/board/abstract_card_drag_item.h | 2 +- .../src/game/board/abstract_card_item.cpp | 10 +++---- .../src/game/board/abstract_card_item.h | 4 +-- cockatrice/src/game/board/card_item.cpp | 9 +++--- cockatrice/src/game/board/card_item.h | 2 -- cockatrice/src/game/card_dimensions.h | 29 +++++++++++++++++++ cockatrice/src/game/deckview/deck_view.cpp | 22 +++++++------- .../src/game/player/player_graphics_item.cpp | 22 +++++++------- cockatrice/src/game/z_value_layer_manager.h | 8 ++--- cockatrice/src/game/z_values.h | 4 +-- cockatrice/src/game/zones/hand_zone.cpp | 2 +- cockatrice/src/game/zones/pile_zone.cpp | 12 ++++---- cockatrice/src/game/zones/table_zone.cpp | 22 +++++++------- cockatrice/src/game/zones/table_zone.h | 4 +-- cockatrice/src/game/zones/view_zone.cpp | 16 +++++----- .../src/game/zones/view_zone_widget.cpp | 4 +-- .../cards/card_info_display_widget.cpp | 2 +- 18 files changed, 108 insertions(+), 78 deletions(-) create mode 100644 cockatrice/src/game/card_dimensions.h diff --git a/cockatrice/src/game/board/abstract_card_drag_item.cpp b/cockatrice/src/game/board/abstract_card_drag_item.cpp index c9a964048..8e3def4ca 100644 --- a/cockatrice/src/game/board/abstract_card_drag_item.cpp +++ b/cockatrice/src/game/board/abstract_card_drag_item.cpp @@ -8,8 +8,6 @@ #include #include -static const float CARD_WIDTH_HALF = CARD_WIDTH / 2; -static const float CARD_HEIGHT_HALF = CARD_HEIGHT / 2; const QColor GHOST_MASK = QColor(255, 255, 255, 50); AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item, @@ -22,16 +20,16 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item, setZValue(ZValues::childDragZValue(hotSpot.x(), hotSpot.y())); connect(parentDrag, &QObject::destroyed, this, &AbstractCardDragItem::deleteLater); } else { - hotSpot = QPointF{qBound(0.0, hotSpot.x(), static_cast(CARD_WIDTH - 1)), - qBound(0.0, hotSpot.y(), static_cast(CARD_HEIGHT - 1))}; + hotSpot = QPointF{qBound(0.0, hotSpot.x(), CardDimensions::WIDTH_F - 1), + qBound(0.0, hotSpot.y(), CardDimensions::HEIGHT_F - 1)}; setCursor(Qt::ClosedHandCursor); setZValue(ZValues::DRAG_ITEM); } if (item->getTapped()) setTransform(QTransform() - .translate(CARD_WIDTH_HALF, CARD_HEIGHT_HALF) + .translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F) .rotate(90) - .translate(-CARD_WIDTH_HALF, -CARD_HEIGHT_HALF)); + .translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F)); setCacheMode(DeviceCoordinateCache); @@ -48,7 +46,7 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item, QPainterPath AbstractCardDragItem::shape() const { QPainterPath shape; - qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0; + qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0; shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius); return shape; } diff --git a/cockatrice/src/game/board/abstract_card_drag_item.h b/cockatrice/src/game/board/abstract_card_drag_item.h index 1d741eded..fe3b87983 100644 --- a/cockatrice/src/game/board/abstract_card_drag_item.h +++ b/cockatrice/src/game/board/abstract_card_drag_item.h @@ -34,7 +34,7 @@ public: AbstractCardDragItem(AbstractCardItem *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag = 0); [[nodiscard]] QRectF boundingRect() const override { - return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT); + return QRectF(0, 0, CardDimensions::WIDTH_F, CardDimensions::HEIGHT_F); } [[nodiscard]] QPainterPath shape() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; diff --git a/cockatrice/src/game/board/abstract_card_item.cpp b/cockatrice/src/game/board/abstract_card_item.cpp index f8365c3d6..9ec6ada9a 100644 --- a/cockatrice/src/game/board/abstract_card_item.cpp +++ b/cockatrice/src/game/board/abstract_card_item.cpp @@ -39,13 +39,13 @@ AbstractCardItem::~AbstractCardItem() QRectF AbstractCardItem::boundingRect() const { - return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT); + return QRectF(0, 0, CardDimensions::WIDTH_F, CardDimensions::HEIGHT_F); } QPainterPath AbstractCardItem::shape() const { QPainterPath shape; - qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0; + qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0; shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius); return shape; } @@ -218,7 +218,7 @@ void AbstractCardItem::setHovered(bool _hovered) isHovered = _hovered; setZValue(_hovered ? ZValues::HOVERED_CARD : realZValue); setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1); - setTransformOriginPoint(_hovered ? CARD_WIDTH / 2 : 0, _hovered ? CARD_HEIGHT / 2 : 0); + setTransformOriginPoint(_hovered ? CardDimensions::WIDTH_HALF_F : 0, _hovered ? CardDimensions::HEIGHT_HALF_F : 0); update(); } @@ -274,9 +274,9 @@ void AbstractCardItem::setTapped(bool _tapped, bool canAnimate) else { tapAngle = tapped ? 90 : 0; setTransform(QTransform() - .translate((float)CARD_WIDTH / 2, (float)CARD_HEIGHT / 2) + .translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F) .rotate(tapAngle) - .translate((float)-CARD_WIDTH / 2, (float)-CARD_HEIGHT / 2)); + .translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F)); update(); } } diff --git a/cockatrice/src/game/board/abstract_card_item.h b/cockatrice/src/game/board/abstract_card_item.h index dad73dc14..7d2c29cae 100644 --- a/cockatrice/src/game/board/abstract_card_item.h +++ b/cockatrice/src/game/board/abstract_card_item.h @@ -8,6 +8,7 @@ #define ABSTRACTCARDITEM_H #include "../../game_graphics/board/graphics_item_type.h" +#include "../card_dimensions.h" #include "arrow_target.h" #include @@ -15,9 +16,6 @@ class Player; -const int CARD_WIDTH = 72; -const int CARD_HEIGHT = 102; - class AbstractCardItem : public ArrowTarget { Q_OBJECT diff --git a/cockatrice/src/game/board/card_item.cpp b/cockatrice/src/game/board/card_item.cpp index 8b9549f07..62de4a02e 100644 --- a/cockatrice/src/game/board/card_item.cpp +++ b/cockatrice/src/game/board/card_item.cpp @@ -367,8 +367,9 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) if (zone->getHasCardAttr()) childPos = card->pos() - pos(); else - childPos = QPointF(childIndex * CARD_WIDTH / 2, 0); - CardDragItem *drag = new CardDragItem(card, card->getId(), childPos, forceFaceDown, dragItem); + childPos = QPointF(childIndex * CardDimensions::WIDTH_HALF_F, 0); + CardDragItem *drag = + new CardDragItem(card, card->getId(), childPos, card->getFaceDown() || forceFaceDown, dragItem); drag->setPos(dragItem->pos() + childPos); scene()->addItem(drag); } @@ -475,9 +476,9 @@ bool CardItem::animationEvent() } setTransform(QTransform() - .translate(CARD_WIDTH_HALF, CARD_HEIGHT_HALF) + .translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F) .rotate(tapAngle) - .translate(-CARD_WIDTH_HALF, -CARD_HEIGHT_HALF)); + .translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F)); setHovered(false); update(); diff --git a/cockatrice/src/game/board/card_item.h b/cockatrice/src/game/board/card_item.h index 875c3f4d0..da2097a2c 100644 --- a/cockatrice/src/game/board/card_item.h +++ b/cockatrice/src/game/board/card_item.h @@ -21,8 +21,6 @@ class QAction; class QColor; const int MAX_COUNTERS_ON_CARD = 999; -const float CARD_WIDTH_HALF = CARD_WIDTH / 2; -const float CARD_HEIGHT_HALF = CARD_HEIGHT / 2; const int ROTATION_DEGREES_PER_FRAME = 10; class CardItem : public AbstractCardItem diff --git a/cockatrice/src/game/card_dimensions.h b/cockatrice/src/game/card_dimensions.h new file mode 100644 index 000000000..255d0bc04 --- /dev/null +++ b/cockatrice/src/game/card_dimensions.h @@ -0,0 +1,29 @@ +#ifndef CARD_DIMENSIONS_H +#define CARD_DIMENSIONS_H + +#include + +/** + * @file card_dimensions.h + * @brief Canonical card dimension constants for layout and Z-value calculations. + * + * These values represent the logical pixel dimensions of a standard card graphic. + * They are used throughout the game scene for layout, rendering, and Z-value computation. + */ +namespace CardDimensions +{ +/// Card width in pixels +constexpr int WIDTH = 72; +/// Card height in pixels +constexpr int HEIGHT = 102; + +/// Pre-converted for floating-point contexts (Z-value calculations) +constexpr qreal WIDTH_F = static_cast(WIDTH); +constexpr qreal HEIGHT_F = static_cast(HEIGHT); + +/// Half-dimensions for centering and rotation transforms +constexpr qreal WIDTH_HALF_F = WIDTH_F / 2; +constexpr qreal HEIGHT_HALF_F = HEIGHT_F / 2; +} // namespace CardDimensions + +#endif // CARD_DIMENSIONS_H diff --git a/cockatrice/src/game/deckview/deck_view.cpp b/cockatrice/src/game/deckview/deck_view.cpp index 383545cf3..620dfaa5f 100644 --- a/cockatrice/src/game/deckview/deck_view.cpp +++ b/cockatrice/src/game/deckview/deck_view.cpp @@ -95,8 +95,9 @@ void DeckViewCard::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti pen.setJoinStyle(Qt::MiterJoin); pen.setColor(originZone == DECK_ZONE_MAIN ? Qt::green : Qt::red); painter->setPen(pen); - qreal cardRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * (CARD_WIDTH - 3) : 0.0; - painter->drawRoundedRect(QRectF(1.5, 1.5, CARD_WIDTH - 3., CARD_HEIGHT - 3.), cardRadius, cardRadius); + qreal cardRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * (CardDimensions::WIDTH_F - 3) : 0.0; + painter->drawRoundedRect(QRectF(1.5, 1.5, CardDimensions::WIDTH_F - 3, CardDimensions::HEIGHT_F - 3), cardRadius, + cardRadius); painter->restore(); } @@ -122,7 +123,7 @@ void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event) if (c == this) continue; ++j; - auto childPos = QPointF(j * CARD_WIDTH / 2, 0); + auto childPos = QPointF(j * CardDimensions::WIDTH_HALF_F, 0); auto *drag = new DeckViewCardDragItem(c, childPos, dragItem); drag->setPos(dragItem->pos() + childPos); scene()->addItem(drag); @@ -204,7 +205,7 @@ void DeckViewCardContainer::paint(QPainter *painter, const QStyleOptionGraphicsI painter->setPen(QColor(255, 255, 255, 100)); painter->drawLine(QPointF(0, yUntilNow - paddingY / 2), QPointF(width, yUntilNow - paddingY / 2)); } - qreal thisRowHeight = CARD_HEIGHT * currentRowsAndCols[i].first; + qreal thisRowHeight = CardDimensions::HEIGHT_F * currentRowsAndCols[i].first; QRectF textRect(0, yUntilNow, totalTextWidth, thisRowHeight); yUntilNow += thisRowHeight + paddingY; @@ -260,9 +261,9 @@ QSizeF DeckViewCardContainer::calculateBoundingRect(const QList> // Calculate space needed for cards for (int i = 0; i < rowsAndCols.size(); ++i) { - totalHeight += CARD_HEIGHT * rowsAndCols[i].first + paddingY; - if (CARD_WIDTH * rowsAndCols[i].second > totalWidth) - totalWidth = CARD_WIDTH * rowsAndCols[i].second; + totalHeight += CardDimensions::HEIGHT_F * rowsAndCols[i].first + paddingY; + if (CardDimensions::WIDTH_F * rowsAndCols[i].second > totalWidth) + totalWidth = CardDimensions::WIDTH_F * rowsAndCols[i].second; } return QSizeF(getCardTypeTextWidth() + totalWidth, totalHeight + separatorY + paddingY); @@ -289,9 +290,10 @@ void DeckViewCardContainer::rearrangeItems(const QList> &rowsAnd std::sort(row.begin(), row.end(), DeckViewCardContainer::sortCardsByName); for (int j = 0; j < row.size(); ++j) { DeckViewCard *card = row[j]; - card->setPos(x + (j % tempCols) * CARD_WIDTH, yUntilNow + (j / tempCols) * CARD_HEIGHT); + card->setPos(x + (j % tempCols) * CardDimensions::WIDTH_F, + yUntilNow + (j / tempCols) * CardDimensions::HEIGHT_F); } - yUntilNow += tempRows * CARD_HEIGHT + paddingY; + yUntilNow += tempRows * CardDimensions::HEIGHT_F + paddingY; } prepareGeometryChange(); @@ -392,7 +394,7 @@ void DeckViewScene::applySideboardPlan(const QList &plan) void DeckViewScene::rearrangeItems() { - const int spacing = CARD_HEIGHT / 3; + const int spacing = CardDimensions::HEIGHT / 3; QList contList = cardContainers.values(); // Initialize space requirements diff --git a/cockatrice/src/game/player/player_graphics_item.cpp b/cockatrice/src/game/player/player_graphics_item.cpp index 30648c926..bcc4b7f72 100644 --- a/cockatrice/src/game/player/player_graphics_item.cpp +++ b/cockatrice/src/game/player/player_graphics_item.cpp @@ -19,7 +19,8 @@ PlayerGraphicsItem::PlayerGraphicsItem(Player *_player) : player(_player) playerArea = new PlayerArea(this); playerTarget = new PlayerTarget(player, playerArea); - qreal avatarMargin = (counterAreaWidth + CARD_HEIGHT + 15 - playerTarget->boundingRect().width()) / 2.0; + qreal avatarMargin = + (counterAreaWidth + CardDimensions::HEIGHT_F + 15 - playerTarget->boundingRect().width()) / 2.0; playerTarget->setPos(QPointF(avatarMargin, avatarMargin)); initializeZones(); @@ -55,8 +56,9 @@ void PlayerGraphicsItem::onPlayerActiveChanged(bool _active) void PlayerGraphicsItem::initializeZones() { deckZoneGraphicsItem = new PileZone(player->getDeckZone(), this); - auto base = QPointF(counterAreaWidth + (CARD_HEIGHT - CARD_WIDTH + 15) / 2.0, - 10 + playerTarget->boundingRect().height() + 5 - (CARD_HEIGHT - CARD_WIDTH) / 2.0); + auto base = QPointF(counterAreaWidth + (CardDimensions::HEIGHT_F - CardDimensions::WIDTH_F + 15) / 2.0, + 10 + playerTarget->boundingRect().height() + 5 - + (CardDimensions::HEIGHT_F - CardDimensions::WIDTH_F) / 2.0); deckZoneGraphicsItem->setPos(base); qreal h = deckZoneGraphicsItem->boundingRect().width() + 5; @@ -95,7 +97,7 @@ QRectF PlayerGraphicsItem::boundingRect() const qreal PlayerGraphicsItem::getMinimumWidth() const { - qreal result = tableZoneGraphicsItem->getMinimumWidth() + CARD_HEIGHT + 15 + counterAreaWidth + + qreal result = tableZoneGraphicsItem->getMinimumWidth() + CardDimensions::HEIGHT_F + 15 + counterAreaWidth + stackZoneGraphicsItem->boundingRect().width(); if (!SettingsCache::instance().getHorizontalHand()) { result += handZoneGraphicsItem->boundingRect().width(); @@ -112,8 +114,8 @@ void PlayerGraphicsItem::paint(QPainter * /*painter*/, void PlayerGraphicsItem::processSceneSizeChange(int newPlayerWidth) { // Extend table (and hand, if horizontal) to accommodate the new player width. - qreal tableWidth = - newPlayerWidth - CARD_HEIGHT - 15 - counterAreaWidth - stackZoneGraphicsItem->boundingRect().width(); + qreal tableWidth = newPlayerWidth - CardDimensions::HEIGHT_F - 15 - counterAreaWidth - + stackZoneGraphicsItem->boundingRect().width(); if (!SettingsCache::instance().getHorizontalHand()) { tableWidth -= handZoneGraphicsItem->boundingRect().width(); } @@ -152,7 +154,7 @@ void PlayerGraphicsItem::rearrangeCounters() void PlayerGraphicsItem::rearrangeZones() { - auto base = QPointF(CARD_HEIGHT + counterAreaWidth + 15, 0); + auto base = QPointF(CardDimensions::HEIGHT_F + counterAreaWidth + 15, 0); if (SettingsCache::instance().getHorizontalHand()) { if (mirrored) { if (player->getHandZone()->contentsKnown()) { @@ -203,7 +205,7 @@ void PlayerGraphicsItem::rearrangeZones() void PlayerGraphicsItem::updateBoundingRect() { prepareGeometryChange(); - qreal width = CARD_HEIGHT + 15 + counterAreaWidth + stackZoneGraphicsItem->boundingRect().width(); + qreal width = CardDimensions::HEIGHT_F + 15 + counterAreaWidth + stackZoneGraphicsItem->boundingRect().width(); if (SettingsCache::instance().getHorizontalHand()) { qreal handHeight = player->getPlayerInfo()->getHandVisible() ? handZoneGraphicsItem->boundingRect().height() : 0; @@ -214,7 +216,7 @@ void PlayerGraphicsItem::updateBoundingRect() 0, 0, width + handZoneGraphicsItem->boundingRect().width() + tableZoneGraphicsItem->boundingRect().width(), tableZoneGraphicsItem->boundingRect().height()); } - playerArea->setSize(CARD_HEIGHT + counterAreaWidth + 15, bRect.height()); + playerArea->setSize(CardDimensions::HEIGHT_F + counterAreaWidth + 15, bRect.height()); emit sizeChanged(); -} \ No newline at end of file +} diff --git a/cockatrice/src/game/z_value_layer_manager.h b/cockatrice/src/game/z_value_layer_manager.h index 303edb8a5..4eb864486 100644 --- a/cockatrice/src/game/z_value_layer_manager.h +++ b/cockatrice/src/game/z_value_layer_manager.h @@ -15,7 +15,7 @@ * - Cards within zones (1.0 base + index) * * 2. **Card Layer (1-40,000,000)**: Dynamic card rendering on the table zone - * - Cards use formula: (actualY + CARD_HEIGHT) * 100000 + (actualX + 1) * 100 + * - Cards use formula: (actualY + CardDimensions::HEIGHT) * 100000 + (actualX + 1) * 100 * - Maximum card Z-value: ~40,000,000 (with 3 rows, actualY <= ~289) * * 3. **Overlay Layer (2,000,000,000+)**: UI elements that must appear above all cards @@ -77,9 +77,9 @@ enum class Layer /** * @brief Maximum Z-value a card can have on the table zone. * - * Based on table zone formula: (actualY + CARD_HEIGHT) * 100000 + (actualX + 1) * 100 - * With maximum 3 rows and CARD_HEIGHT ~96, actualY <= ~289. - * Maximum: (289 + 96) * 100000 + 100 * 100 = 38,510,000 + * Based on table zone formula: (actualY + CardDimensions::HEIGHT) * 100000 + (actualX + 1) * 100 + * With maximum 3 rows and CardDimensions::HEIGHT = 102, actualY <= ~289. + * Maximum: (289 + 102) * 100000 + 100 * 100 = 39,110,000 * * We use 40,000,000 as a safe upper bound with margin. */ diff --git a/cockatrice/src/game/z_values.h b/cockatrice/src/game/z_values.h index 2c7fe9066..c6e7f2c8a 100644 --- a/cockatrice/src/game/z_values.h +++ b/cockatrice/src/game/z_values.h @@ -1,6 +1,7 @@ #ifndef Z_VALUES_H #define Z_VALUES_H +#include "card_dimensions.h" #include "z_value_layer_manager.h" /** @@ -70,8 +71,7 @@ constexpr qreal TOP_UI = ZValueLayerManager::overlayZValue(7.0); */ [[nodiscard]] constexpr qreal tableCardZValue(qreal x, qreal y) { - constexpr qreal CARD_HEIGHT_FOR_Z = 102.0; - return (y + CARD_HEIGHT_FOR_Z) * 100000.0 + (x + 1) * 100.0; + return (y + CardDimensions::HEIGHT_F) * 100000.0 + (x + 1) * 100.0; } // Card layering (general architecture, not command-zone specific) diff --git a/cockatrice/src/game/zones/hand_zone.cpp b/cockatrice/src/game/zones/hand_zone.cpp index cc3b44910..7badfcca4 100644 --- a/cockatrice/src/game/zones/hand_zone.cpp +++ b/cockatrice/src/game/zones/hand_zone.cpp @@ -56,7 +56,7 @@ void HandZone::handleDropEvent(const QList &dragItems, QRectF HandZone::boundingRect() const { if (SettingsCache::instance().getHorizontalHand()) - return QRectF(0, 0, width, CARD_HEIGHT + 10); + return QRectF(0, 0, width, CardDimensions::HEIGHT_F + 10); else return QRectF(0, 0, 100, zoneHeight); } diff --git a/cockatrice/src/game/zones/pile_zone.cpp b/cockatrice/src/game/zones/pile_zone.cpp index 87a60fc03..d85b5f4e2 100644 --- a/cockatrice/src/game/zones/pile_zone.cpp +++ b/cockatrice/src/game/zones/pile_zone.cpp @@ -19,9 +19,9 @@ PileZone::PileZone(PileZoneLogic *_logic, QGraphicsItem *parent) : CardZone(_log setCursor(Qt::OpenHandCursor); setTransform(QTransform() - .translate((float)CARD_WIDTH / 2, (float)CARD_HEIGHT / 2) + .translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F) .rotate(90) - .translate((float)-CARD_WIDTH / 2, (float)-CARD_HEIGHT / 2)); + .translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F)); connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) { Q_UNUSED(_roundCardCorners); @@ -33,13 +33,13 @@ PileZone::PileZone(PileZoneLogic *_logic, QGraphicsItem *parent) : CardZone(_log QRectF PileZone::boundingRect() const { - return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT); + return QRectF(0, 0, CardDimensions::WIDTH_F, CardDimensions::HEIGHT_F); } QPainterPath PileZone::shape() const { QPainterPath shape; - qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0; + qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0; shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius); return shape; } @@ -52,9 +52,9 @@ void PileZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*optio getLogic()->getCards().at(0)->paintPicture(painter, getLogic()->getCards().at(0)->getTranslatedSize(painter), 90); - painter->translate((float)CARD_WIDTH / 2, (float)CARD_HEIGHT / 2); + painter->translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F); painter->rotate(-90); - painter->translate((float)-CARD_WIDTH / 2, (float)-CARD_HEIGHT / 2); + painter->translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F); paintNumberEllipse(getLogic()->getCards().size(), 28, Qt::white, -1, -1, painter); } diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index 33c867c43..a020e4255 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -31,7 +31,7 @@ TableZone::TableZone(TableZoneLogic *_logic, QGraphicsItem *parent) : SelectZone updateBg(); - height = MARGIN_TOP + MARGIN_BOTTOM + TABLEROWS * CARD_HEIGHT + (TABLEROWS - 1) * PADDING_Y; + height = MARGIN_TOP + MARGIN_BOTTOM + TABLEROWS * CardDimensions::HEIGHT + (TABLEROWS - 1) * PADDING_Y; width = MIN_WIDTH; currentMinimumWidth = width; @@ -106,7 +106,7 @@ void TableZone::paintLandDivider(QPainter *painter) { // Place the line 2 grid heights down then back it off just enough to allow // some space between a 3-card stack and the land area. - qreal separatorY = MARGIN_TOP + 2 * (CARD_HEIGHT + PADDING_Y) - STACKED_CARD_OFFSET_Y / 2; + qreal separatorY = MARGIN_TOP + 2 * (CardDimensions::HEIGHT + PADDING_Y) - STACKED_CARD_OFFSET_Y / 2; if (isInverted()) separatorY = height - separatorY; painter->setPen(QColor(255, 255, 255, 40)); @@ -232,7 +232,7 @@ void TableZone::resizeToContents() // Minimum width is the rightmost card position plus enough room for // another card with padding, then margin. - currentMinimumWidth = xMax + (2 * CARD_WIDTH) + PADDING_X + MARGIN_RIGHT; + currentMinimumWidth = xMax + (2 * CardDimensions::WIDTH) + PADDING_X + MARGIN_RIGHT; if (currentMinimumWidth < MIN_WIDTH) currentMinimumWidth = MIN_WIDTH; @@ -282,10 +282,10 @@ void TableZone::computeCardStackWidths() const int key = getCardStackMapKey(gridPoint.x() / 3, gridPoint.y()); const int stackCount = cardStackCount.value(key, 0); if (stackCount == 1) - cardStackWidth.insert(key, CARD_WIDTH + getLogic()->getCards()[i]->getAttachedCards().size() * - STACKED_CARD_OFFSET_X); + cardStackWidth.insert(key, CardDimensions::WIDTH + getLogic()->getCards()[i]->getAttachedCards().size() * + STACKED_CARD_OFFSET_X); else - cardStackWidth.insert(key, CARD_WIDTH + (stackCount - 1) * STACKED_CARD_OFFSET_X); + cardStackWidth.insert(key, CardDimensions::WIDTH + (stackCount - 1) * STACKED_CARD_OFFSET_X); } } @@ -299,7 +299,7 @@ QPointF TableZone::mapFromGrid(QPoint gridPoint) const // Add in width of card stack plus padding for each column for (int i = 0; i < gridPoint.x() / 3; ++i) { const int key = getCardStackMapKey(i, gridPoint.y()); - x += cardStackWidth.value(key, CARD_WIDTH) + PADDING_X; + x += cardStackWidth.value(key, CardDimensions::WIDTH) + PADDING_X; } if (isInverted()) @@ -310,7 +310,7 @@ QPointF TableZone::mapFromGrid(QPoint gridPoint) const // Add in card size and padding for each row for (int i = 0; i < gridPoint.y(); ++i) - y += CARD_HEIGHT + PADDING_Y; + y += CardDimensions::HEIGHT + PADDING_Y; return QPointF(x, y); } @@ -324,7 +324,7 @@ QPoint TableZone::mapToGrid(const QPointF &mapPoint) const int y = mapPoint.y() - MARGIN_TOP; // Below calculation effectively rounds to the nearest grid point. - const int gridPointHeight = CARD_HEIGHT + PADDING_Y; + const int gridPointHeight = CardDimensions::HEIGHT + PADDING_Y; int gridPointY = (y + PADDING_Y / 2) / gridPointHeight; gridPointY = clampValidTableRow(gridPointY); @@ -340,7 +340,7 @@ QPoint TableZone::mapToGrid(const QPointF &mapPoint) const // Maximum value is a card width from the right margin, referenced to the // grid area. - const int xMax = width - MARGIN_LEFT - MARGIN_RIGHT - CARD_WIDTH; + const int xMax = width - MARGIN_LEFT - MARGIN_RIGHT - CardDimensions::WIDTH; int xStack = 0; int xNextStack = 0; @@ -348,7 +348,7 @@ QPoint TableZone::mapToGrid(const QPointF &mapPoint) const while ((xNextStack <= x) && (xNextStack <= xMax)) { xStack = xNextStack; const int key = getCardStackMapKey(nextStackCol, gridPointY); - xNextStack += cardStackWidth.value(key, CARD_WIDTH) + PADDING_X; + xNextStack += cardStackWidth.value(key, CardDimensions::WIDTH) + PADDING_X; nextStackCol++; } int stackCol = qMax(nextStackCol - 1, 0); diff --git a/cockatrice/src/game/zones/table_zone.h b/cockatrice/src/game/zones/table_zone.h index 3b353e76c..61eb48d7b 100644 --- a/cockatrice/src/game/zones/table_zone.h +++ b/cockatrice/src/game/zones/table_zone.h @@ -41,12 +41,12 @@ private: /* Minimum width of the table zone including margins. */ - static const int MIN_WIDTH = MARGIN_LEFT + (5 * CARD_WIDTH) + MARGIN_RIGHT; + static const int MIN_WIDTH = MARGIN_LEFT + (5 * CardDimensions::WIDTH) + MARGIN_RIGHT; /* Offset sizes when cards are stacked on each other in the grid */ - static const int STACKED_CARD_OFFSET_X = CARD_WIDTH / 3; + static const int STACKED_CARD_OFFSET_X = CardDimensions::WIDTH / 3; static const int STACKED_CARD_OFFSET_Y = PADDING_Y / 3; /* diff --git a/cockatrice/src/game/zones/view_zone.cpp b/cockatrice/src/game/zones/view_zone.cpp index 348ed6e87..d2fd1e971 100644 --- a/cockatrice/src/game/zones/view_zone.cpp +++ b/cockatrice/src/game/zones/view_zone.cpp @@ -118,7 +118,9 @@ void ZoneViewZone::zoneDumpReceived(const Response &r) qobject_cast(getLogic())->updateCardIds(ZoneViewZoneLogic::INITIALIZE); reorganizeCards(); - emit getLogic()->cardCountChanged(); + // clang-format off + emit getLogic()->cardCountChanged(); // emit keyword causes spurious spacing around -> + // clang-format on } // Because of boundingRect(), this function must not be called before the zone was added to a scene. @@ -166,8 +168,8 @@ void ZoneViewZone::reorganizeCards() // determine bounding rect qreal aleft = 0; qreal atop = 0; - qreal awidth = gridSize.cols * CARD_WIDTH + (CARD_WIDTH / 2) + HORIZONTAL_PADDING; - qreal aheight = (gridSize.rows * CARD_HEIGHT) / 3 + CARD_HEIGHT * 1.3; + qreal awidth = gridSize.cols * CardDimensions::WIDTH_F + CardDimensions::WIDTH_HALF_F + HORIZONTAL_PADDING; + qreal aheight = (gridSize.rows * CardDimensions::HEIGHT_F) / 3 + CardDimensions::HEIGHT_F * 1.3; optimumRect = QRectF(aleft, atop, awidth, aheight); updateGeometry(); @@ -210,8 +212,8 @@ ZoneViewZone::GridSize ZoneViewZone::positionCardsForDisplay(CardList &cards, Ca } lastColumnProp = columnProp; - qreal x = col * CARD_WIDTH; - qreal y = row * CARD_HEIGHT / 3; + qreal x = col * CardDimensions::WIDTH_F; + qreal y = row * CardDimensions::HEIGHT_F / 3; c->setPos(HORIZONTAL_PADDING + x, VERTICAL_PADDING + y); c->setRealZValue(i); longestRow = qMax(row, longestRow); @@ -238,8 +240,8 @@ ZoneViewZone::GridSize ZoneViewZone::positionCardsForDisplay(CardList &cards, Ca for (int i = 0; i < cardCount; i++) { CardItem *c = cards.at(i); - qreal x = (i / rows) * CARD_WIDTH; - qreal y = (i % rows) * CARD_HEIGHT / 3; + qreal x = (i / rows) * CardDimensions::WIDTH_F; + qreal y = (i % rows) * CardDimensions::HEIGHT_F / 3; c->setPos(HORIZONTAL_PADDING + x, VERTICAL_PADDING + y); c->setRealZValue(i); } diff --git a/cockatrice/src/game/zones/view_zone_widget.cpp b/cockatrice/src/game/zones/view_zone_widget.cpp index 0ad3b10f1..23d7d6a19 100644 --- a/cockatrice/src/game/zones/view_zone_widget.cpp +++ b/cockatrice/src/game/zones/view_zone_widget.cpp @@ -458,7 +458,7 @@ void ZoneViewWidget::resizeScrollbar(const qreal newZoneHeight) */ static qreal rowsToHeight(int rows) { - const qreal cardsHeight = (rows + 1) * (CARD_HEIGHT / 3); + const qreal cardsHeight = (rows + 1) * (CardDimensions::HEIGHT_F / 3); return cardsHeight + 5; // +5 padding to make the cutoff look nicer } @@ -574,4 +574,4 @@ void ZoneViewWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) if (event->pos().y() <= 0) { expandWindow(); } -} \ No newline at end of file +} diff --git a/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp index f8b581e33..4930fbbfd 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp @@ -10,7 +10,7 @@ #include CardInfoDisplayWidget::CardInfoDisplayWidget(const CardRef &cardRef, QWidget *parent, Qt::WindowFlags flags) - : QFrame(parent, flags), aspectRatio((qreal)CARD_HEIGHT / (qreal)CARD_WIDTH) + : QFrame(parent, flags), aspectRatio(CardDimensions::HEIGHT_F / CardDimensions::WIDTH_F) { setContentsMargins(3, 3, 3, 3); pic = new CardInfoPictureWidget(); From dead99363909f1b010ce4d468dc311d0a84218a1 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 6 Mar 2026 10:39:04 -0800 Subject: [PATCH 238/325] [DeckList] Refactor load from plaintext to take normalizer as param (#6664) * [DeckList] Refactor load from plaintext to take normalizer as param * update usages * weaken unit test * weaken unit test more * revert unit test * move CardNameNormalizer to libcockatrice_card * update unit test * formatting --- .../parsers/interface_json_deck_parser.h | 7 +-- .../src/interface/deck_loader/deck_loader.cpp | 5 ++- .../dialogs/dlg_load_deck_from_clipboard.cpp | 3 +- .../dialogs/dlg_load_deck_from_website.cpp | 3 +- .../dialogs/dlg_load_deck_from_website.h | 1 + ...idekt_api_response_deck_display_widget.cpp | 5 +-- .../average_deck/edhrec_deck_api_response.cpp | 5 +-- libcockatrice_card/CMakeLists.txt | 2 + .../card/import/card_name_normalizer.cpp | 45 +++++++++++++++++++ .../card/import/card_name_normalizer.h | 15 +++++++ .../libcockatrice/deck_list/deck_list.cpp | 43 ++++-------------- .../libcockatrice/deck_list/deck_list.h | 6 ++- tests/loading_from_clipboard/CMakeLists.txt | 3 +- .../clipboard_testing.cpp | 3 +- 14 files changed, 94 insertions(+), 52 deletions(-) create mode 100644 libcockatrice_card/libcockatrice/card/import/card_name_normalizer.cpp create mode 100644 libcockatrice_card/libcockatrice/card/import/card_name_normalizer.h 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 e19e4f4e4..1818aa35c 100644 --- a/cockatrice/src/client/network/parsers/interface_json_deck_parser.h +++ b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h @@ -8,10 +8,11 @@ #define INTERFACE_JSON_DECK_PARSER_H #include "../../../interface/deck_loader/card_node_function.h" -#include "../../../interface/deck_loader/deck_loader.h" #include #include +#include +#include class IJsonDeckParser { @@ -49,7 +50,7 @@ public: outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n'; } - deckList.loadFromStream_Plain(outStream, false); + deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer()); deckList.forEachCard(CardNodeFunction::ResolveProviderId()); return deckList; @@ -96,7 +97,7 @@ public: outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n'; } - deckList.loadFromStream_Plain(outStream, false); + deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer()); deckList.forEachCard(CardNodeFunction::ResolveProviderId()); QJsonObject commandersObj = obj.value("commanders").toObject(); diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index 3481e245b..d71dca24a 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -42,7 +43,7 @@ DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bo DeckList deckList; switch (fmt) { case DeckFileFormat::PlainText: - result = deckList.loadFromFile_Plain(&file); + result = deckList.loadFromFile_Plain(&file, CardNameNormalizer()); break; case DeckFileFormat::Cockatrice: { result = deckList.loadFromFile_Native(&file); @@ -50,7 +51,7 @@ DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bo qCInfo(DeckLoaderLog) << "Failed to load " << fileName << "as cockatrice format; retrying as plain format"; file.seek(0); - result = deckList.loadFromFile_Plain(&file); + result = deckList.loadFromFile_Plain(&file, CardNameNormalizer()); fmt = DeckFileFormat::PlainText; } break; 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 b72130c81..9ceb35d78 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 @@ -14,6 +14,7 @@ #include #include #include +#include /** * Creates the main layout and connects the signals that are common to all versions of this window @@ -81,7 +82,7 @@ bool AbstractDlgDeckTextEdit::loadIntoDeck(DeckList &deckList) const QTextStream stream(&buffer); - if (deckList.loadFromStream_Plain(stream, true)) { + if (deckList.loadFromStream_Plain(stream, true, CardNameNormalizer())) { if (loadSetNameAndNumberCheckBox->isChecked()) { deckList.forEachCard(CardNodeFunction::ResolveProviderId()); } else { 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 da4135246..ba00873d9 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 @@ -8,6 +8,7 @@ #include #include #include +#include #include DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent) @@ -99,7 +100,7 @@ void DlgLoadDeckFromWebsite::accept() // Parse the plain text deck here DeckList deckList; QTextStream stream(&deckText); - deckList.loadFromStream_Plain(stream, false); + deckList.loadFromStream_Plain(stream, false, CardNameNormalizer()); deckList.forEachCard(CardNodeFunction::ResolveProviderId()); deck = deckList; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h index fe6f0a7e9..1ac98d206 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include 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 f40b9094f..8b17cd49e 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 @@ -2,7 +2,6 @@ #include "../../../../../deck_loader/card_node_function.h" #include "../../../../../deck_loader/deck_loader.h" -#include "../../../../cards/card_info_picture_with_text_overlay_widget.h" #include "../../../../cards/card_size_widget.h" #include "../../../../cards/deck_card_zone_display_widget.h" #include "../../../../visual_deck_editor/visual_deck_display_options_widget.h" @@ -10,7 +9,7 @@ #include "../api_response/deck/archidekt_api_response_deck.h" #include -#include +#include ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWidget *parent, ArchidektApiResponseDeck _response, @@ -80,7 +79,7 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset); auto decklist = QSharedPointer(new DeckList); - decklist->loadFromStream_Plain(deckStream, false); + decklist->loadFromStream_Plain(deckStream, false, CardNameNormalizer()); model->setDeckList(decklist); model->forEachCard(CardNodeFunction::ResolveProviderId()); diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp index a744a9b14..1cb070c30 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp @@ -1,11 +1,10 @@ #include "edhrec_deck_api_response.h" -#include "../../../../../../deck_loader/deck_loader.h" - #include #include #include #include +#include void EdhrecDeckApiResponse::fromJson(const QJsonArray &json) { @@ -15,7 +14,7 @@ void EdhrecDeckApiResponse::fromJson(const QJsonArray &json) } QTextStream stream(&deckList); - deck.loadFromStream_Plain(stream, true); + deck.loadFromStream_Plain(stream, true, CardNameNormalizer()); } void EdhrecDeckApiResponse::debugPrint() const diff --git a/libcockatrice_card/CMakeLists.txt b/libcockatrice_card/CMakeLists.txt index f516cde00..dd3799e33 100644 --- a/libcockatrice_card/CMakeLists.txt +++ b/libcockatrice_card/CMakeLists.txt @@ -12,6 +12,7 @@ set(HEADERS libcockatrice/card/database/parser/card_database_parser.h libcockatrice/card/database/parser/cockatrice_xml_3.h libcockatrice/card/database/parser/cockatrice_xml_4.h + libcockatrice/card/import/card_name_normalizer.h libcockatrice/card/printing/exact_card.h libcockatrice/card/printing/printing_info.h libcockatrice/card/set/card_set.h @@ -36,6 +37,7 @@ add_library( libcockatrice/card/database/parser/card_database_parser.cpp libcockatrice/card/database/parser/cockatrice_xml_3.cpp libcockatrice/card/database/parser/cockatrice_xml_4.cpp + libcockatrice/card/import/card_name_normalizer.cpp libcockatrice/card/printing/exact_card.cpp libcockatrice/card/printing/printing_info.cpp libcockatrice/card/relation/card_relation.cpp diff --git a/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.cpp b/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.cpp new file mode 100644 index 000000000..2349dc3e8 --- /dev/null +++ b/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.cpp @@ -0,0 +1,45 @@ +#include "card_name_normalizer.h" + +#include + +QString CardNameNormalizer::operator()(const QString &cardNameString) const +{ + QString cardName = cardNameString; + + // Regex for advanced card parsing + static const QRegularExpression reSplitCard(R"( ?\/\/ ?)"); + static const QRegularExpression reBrace(R"( ?[\[\{][^\]\}]*[\]\}] ?)"); // not nested + static const QRegularExpression reRoundBrace(R"(^\([^\)]*\) ?)"); // () are only matched at start of string + static const QRegularExpression reDigitBrace(R"( ?\(\d*\) ?)"); // () are matched if containing digits + static const QRegularExpression reBraceDigit( + R"( ?\([\dA-Z]+\) *\d+$)"); // () are matched if containing setcode then a number + static const QRegularExpression reDoubleFacedMarker(R"( ?\(Transform\) ?)"); + + static const QHash differences{{QRegularExpression("’"), "'"}, + {QRegularExpression("Æ"), "Ae"}, + {QRegularExpression("æ"), "ae"}, + {QRegularExpression(" ?[|/]+ ?"), " // "}}; + + // Handle advanced card types + if (cardName.contains(reSplitCard)) { + cardName = cardName.split(reSplitCard).join(" // "); + } + + if (cardName.contains(reDoubleFacedMarker)) { + QStringList faces = cardName.split(reDoubleFacedMarker); + cardName = faces.first().trimmed(); + } + + // Remove unnecessary characters + cardName.remove(reBrace); + cardName.remove(reRoundBrace); // I'll be entirely honest here, these are split to accommodate just three cards + cardName.remove(reDigitBrace); // from un-sets that have a word in between round braces at the end + cardName.remove(reBraceDigit); // very specific format with the set code in () and collectors number after + + // Normalize characters + for (auto diff = differences.constBegin(); diff != differences.constEnd(); ++diff) { + cardName.replace(diff.key(), diff.value()); + } + + return cardName; +} \ No newline at end of file diff --git a/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.h b/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.h new file mode 100644 index 000000000..716e7da80 --- /dev/null +++ b/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.h @@ -0,0 +1,15 @@ +#ifndef COCKATRICE_CARD_NAME_NORMALIZER_H +#define COCKATRICE_CARD_NAME_NORMALIZER_H + +#include + +/** + * Functor that normalizes the raw card name parsed during a plaintext deck import into the card name that Cockatrice + * uses. + */ +struct CardNameNormalizer +{ + QString operator()(const QString &cardNameString) const; +}; + +#endif // COCKATRICE_CARD_NAME_NORMALIZER_H diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index 3d1070f15..e3e7b41c0 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -199,9 +199,12 @@ bool DeckList::saveToFile_Native(QIODevice *device) const * * @param in The text to load * @param preserveMetadata If true, don't clear the existing metadata + * @param cardNameNormalizer Function that takes the parsed card name string in the text and * @return False if the input was empty, true otherwise. */ -bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata) +bool DeckList::loadFromStream_Plain(QTextStream &in, + bool preserveMetadata, + const std::function &cardNameNormalizer) { const QRegularExpression reCardLine(R"(^\s*[\w\[\(\{].*$)", QRegularExpression::UseUnicodePropertiesOption); const QRegularExpression reEmpty("^\\s*$"); @@ -213,23 +216,11 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata) // Regex for advanced card parsing const QRegularExpression reMultiplier(R"(^[xX\(\[]*(\d+)[xX\*\)\]]* ?(.+))"); - const QRegularExpression reSplitCard(R"( ?\/\/ ?)"); - const QRegularExpression reBrace(R"( ?[\[\{][^\]\}]*[\]\}] ?)"); // not nested - const QRegularExpression reRoundBrace(R"(^\([^\)]*\) ?)"); // () are only matched at start of string - const QRegularExpression reDigitBrace(R"( ?\(\d*\) ?)"); // () are matched if containing digits - const QRegularExpression reBraceDigit( - R"( ?\([\dA-Z]+\) *\d+$)"); // () are matched if containing setcode then a number - const QRegularExpression reDoubleFacedMarker(R"( ?\(Transform\) ?)"); // 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]*))"); - const QHash differences{{QRegularExpression("’"), QString("'")}, - {QRegularExpression("Æ"), QString("Ae")}, - {QRegularExpression("æ"), QString("ae")}, - {QRegularExpression(" ?[|/]+ ?"), QString(" // ")}}; - cleanList(preserveMetadata); auto inputs = in.readAll().trimmed().split('\n'); @@ -355,26 +346,8 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata) cardName = match.captured(2); } - // Handle advanced card types - if (cardName.contains(reSplitCard)) { - cardName = cardName.split(reSplitCard).join(" // "); - } - - if (cardName.contains(reDoubleFacedMarker)) { - QStringList faces = cardName.split(reDoubleFacedMarker); - cardName = faces.first().trimmed(); - } - - // Remove unnecessary characters - cardName.remove(reBrace); - cardName.remove(reRoundBrace); // I'll be entirely honest here, these are split to accommodate just three cards - cardName.remove(reDigitBrace); // from un-sets that have a word in between round braces at the end - cardName.remove(reBraceDigit); // very specific format with the set code in () and collectors number after - - // Normalize names - for (auto diff = differences.constBegin(); diff != differences.constEnd(); ++diff) { - cardName.replace(diff.key(), diff.value()); - } + // Normalize the card name + cardName = cardNameNormalizer(cardName); // Determine the zone (mainboard/sideboard) QString zoneName = sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN; @@ -387,10 +360,10 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata) return true; } -bool DeckList::loadFromFile_Plain(QIODevice *device) +bool DeckList::loadFromFile_Plain(QIODevice *device, const std::function &cardNameNormalizer) { QTextStream in(device); - return loadFromStream_Plain(in, false); + return loadFromStream_Plain(in, false, cardNameNormalizer); } bool DeckList::saveToStream_Plain(QTextStream &stream, bool prefixSideboardCards, bool slashTappedOutSplitCards) const diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index 91e56fc9f..a96adeb38 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -206,8 +206,10 @@ public: /// @name Serialization (Plain text) ///@{ - bool loadFromStream_Plain(QTextStream &stream, bool preserveMetadata); - bool loadFromFile_Plain(QIODevice *device); + bool loadFromStream_Plain(QTextStream &stream, + bool preserveMetadata, + const std::function &cardNameNormalizer); + bool loadFromFile_Plain(QIODevice *device, const std::function &cardNameNormalizer); bool saveToStream_Plain(QTextStream &stream, bool prefixSideboardCards, bool slashTappedOutSplitCards) const; bool saveToFile_Plain(QIODevice *device, bool prefixSideboardCards = true, bool slashTappedOutSplitCards = false) const; diff --git a/tests/loading_from_clipboard/CMakeLists.txt b/tests/loading_from_clipboard/CMakeLists.txt index 40e85c66e..eecaafcbb 100644 --- a/tests/loading_from_clipboard/CMakeLists.txt +++ b/tests/loading_from_clipboard/CMakeLists.txt @@ -10,6 +10,7 @@ set(TEST_QT_MODULES ${COCKATRICE_QT_VERSION_NAME}::Concurrent ${COCKATRICE_QT_VE ) target_link_libraries( - loading_from_clipboard_test libcockatrice_deck_list Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} + loading_from_clipboard_test libcockatrice_deck_list libcockatrice_card Threads::Threads ${GTEST_BOTH_LIBRARIES} + ${TEST_QT_MODULES} ) add_test(NAME loading_from_clipboard_test COMMAND loading_from_clipboard_test) diff --git a/tests/loading_from_clipboard/clipboard_testing.cpp b/tests/loading_from_clipboard/clipboard_testing.cpp index 29535c1dc..a9977b800 100644 --- a/tests/loading_from_clipboard/clipboard_testing.cpp +++ b/tests/loading_from_clipboard/clipboard_testing.cpp @@ -1,6 +1,7 @@ #include "clipboard_testing.h" #include +#include #include DeckList getDeckList(const QString &clipboard) @@ -8,7 +9,7 @@ DeckList getDeckList(const QString &clipboard) DeckList deckList; QString cp(clipboard); QTextStream stream(&cp); // text stream requires local copy - deckList.loadFromStream_Plain(stream, false); + deckList.loadFromStream_Plain(stream, false, CardNameNormalizer()); return deckList; } From 2f10634ca2478521f02a85dc4bc4cfe010940077 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 6 Mar 2026 11:48:17 -0800 Subject: [PATCH 239/325] [DeckList] Fix double-faced cards not importing correctly (#6665) * [DeckList] Fix double-faced cards not importing correctly * make tests compile --- .../card/import/card_name_normalizer.cpp | 21 +++++++++++++++++++ tests/loading_from_clipboard/CMakeLists.txt | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.cpp b/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.cpp index 2349dc3e8..91ebd6647 100644 --- a/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.cpp +++ b/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.cpp @@ -1,7 +1,25 @@ #include "card_name_normalizer.h" +#include "../database/card_database_manager.h" +#include "../printing/exact_card.h" + #include +/** + * @brief Resolves the complete display name of a card. + * @param cardName Base name. + * @return Full display name, or the cardName unchanged if a display name is not found. + */ +static QString getCompleteCardName(const QString &cardName) +{ + ExactCard temp = CardDatabaseManager::query()->guessCard({cardName}); + if (temp) { + return temp.getName(); + } + + return cardName; +} + QString CardNameNormalizer::operator()(const QString &cardNameString) const { QString cardName = cardNameString; @@ -41,5 +59,8 @@ QString CardNameNormalizer::operator()(const QString &cardNameString) const cardName.replace(diff.key(), diff.value()); } + // Resolve complete card name + cardName = getCompleteCardName(cardName); + return cardName; } \ No newline at end of file diff --git a/tests/loading_from_clipboard/CMakeLists.txt b/tests/loading_from_clipboard/CMakeLists.txt index eecaafcbb..85133a8e8 100644 --- a/tests/loading_from_clipboard/CMakeLists.txt +++ b/tests/loading_from_clipboard/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DCARDDB_DATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/data/\"") -add_executable(loading_from_clipboard_test clipboard_testing.cpp loading_from_clipboard_test.cpp) +add_executable(loading_from_clipboard_test ${VERSION_STRING_CPP} clipboard_testing.cpp loading_from_clipboard_test.cpp) if(NOT GTEST_FOUND) add_dependencies(loading_from_clipboard_test gtest) From f15b70e4aef3994ab2fa9e371ee4cbbcb9b41a15 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 8 Mar 2026 17:53:16 +0100 Subject: [PATCH 240/325] Use new attest action (#6671) --- .github/workflows/desktop-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index e95898097..2cd25fc32 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -232,7 +232,7 @@ jobs: - name: Attest binary provenance id: attestation if: steps.upload_release.outcome == 'success' - uses: actions/attest-build-provenance@v4 + uses: actions/attest@v4 with: subject-name: ${{steps.build.outputs.name}} subject-path: ${{steps.build.outputs.path}} @@ -530,7 +530,7 @@ jobs: - name: Attest binary provenance id: attestation if: steps.upload_release.outcome == 'success' - uses: actions/attest-build-provenance@v4 + uses: actions/attest@v4 with: subject-name: ${{steps.build.outputs.name}} subject-path: ${{steps.build.outputs.path}} From c375cdbb1ae84faedc142eadf11ca5a63332bdfe Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 8 Mar 2026 18:03:01 +0100 Subject: [PATCH 241/325] CI: Upload artifact files directly (#6654) * Upload artifact files directly * match pdbs name * Update name variable --- .ci/name_build.sh | 10 +++++----- .github/workflows/desktop-build.yml | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.ci/name_build.sh b/.ci/name_build.sh index 060f8a68e..7b964e21c 100755 --- a/.ci/name_build.sh +++ b/.ci/name_build.sh @@ -35,15 +35,15 @@ if ! cd "$path"; then fi # set filename -name="${file%.*}" # remove all after last . -new_name="$name$SUFFIX." +name="${file%.*}" # remove file extension +new_name="$name$SUFFIX" if [[ $MAKE_ZIP ]]; then - filename="${new_name}zip" + filename="${new_name}.zip" echo "creating zip '$filename' from '$file'" zip "$filename" "$file" else extension="${file##*.}" # remove all before last . - filename="$new_name$extension" + filename="$new_name.$extension" echo "renaming '$file' to '$filename'" mv "$file" "$filename" fi @@ -52,4 +52,4 @@ cd "$oldpwd" relative_path="$path/$filename" ls -l "$relative_path" echo "path=$relative_path" >>"$GITHUB_OUTPUT" -echo "name=$filename" >>"$GITHUB_OUTPUT" +echo "name=$new_name" >>"$GITHUB_OUTPUT" diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 2cd25fc32..bc49db2da 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -214,8 +214,8 @@ jobs: if: matrix.package != 'skip' uses: actions/upload-artifact@v7 with: - name: ${{matrix.distro}}${{matrix.version}}-package path: ${{steps.build.outputs.path}} + archive: false if-no-files-found: error - name: Upload to release @@ -501,15 +501,15 @@ jobs: if: matrix.make_package uses: actions/upload-artifact@v7 with: - name: ${{matrix.artifact_name}} path: ${{steps.build.outputs.path}} + archive: false if-no-files-found: error - name: Upload PDBs (Program Databases) if: matrix.os == 'Windows' uses: actions/upload-artifact@v7 with: - name: Windows${{matrix.target}}-PDBs + name: ${{steps.build.outputs.name}}-PDBs path: | build/cockatrice/Release/*.pdb build/oracle/Release/*.pdb From 4606fcdbd59ed762b8cedb8472cfda771b90642b Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 8 Mar 2026 23:27:15 +0100 Subject: [PATCH 242/325] Add description for code docs (#6679) --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index f9abf643d..02bdc148a 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -9,4 +9,4 @@ contact_links: about: For more information and guidance check our Translation FAQ on our wiki! - name: 📖 Code Documentation url: https://cockatrice.github.io/docs/ - about: + about: Helpful source focusing on developers, but there are also references for users! From 852429f24861ae6381dd4b18d6576e74621b980d Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 8 Mar 2026 23:48:30 +0100 Subject: [PATCH 243/325] remove unused zip command (#6676) --- .ci/name_build.sh | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/.ci/name_build.sh b/.ci/name_build.sh index 7b964e21c..b111aded6 100755 --- a/.ci/name_build.sh +++ b/.ci/name_build.sh @@ -2,7 +2,6 @@ # used by the ci to rename build artifacts # renames the file to [original name][SUFFIX].[original extension] # where SUFFIX is either available in the environment or as the first arg -# if MAKE_ZIP is set instead a zip is made # expected to be run in the build directory unless BUILD_DIR is set # adds output to GITHUB_OUTPUT builddir="${BUILD_DIR:=.}" @@ -22,8 +21,8 @@ set -e # find file found="$(find "$builddir" -maxdepth 1 -type f -name "$findrx" -print -quit)" -path="${found%/*}" # remove all after last / -file="${found##*/}" # remove all before last / +path="${found%/*}" # remove all including first "/" from right side +file="${found##*/}" # remove all including last "/" from left side if [[ ! $file ]]; then echo "::error file=$0::could not find package" exit 1 @@ -35,18 +34,12 @@ if ! cd "$path"; then fi # set filename -name="${file%.*}" # remove file extension +name="${file%.*}" # remove all including first "." from right side new_name="$name$SUFFIX" -if [[ $MAKE_ZIP ]]; then - filename="${new_name}.zip" - echo "creating zip '$filename' from '$file'" - zip "$filename" "$file" -else - extension="${file##*.}" # remove all before last . - filename="$new_name.$extension" - echo "renaming '$file' to '$filename'" - mv "$file" "$filename" -fi +extension="${file##*.}" # remove all including last "." from left side +filename="$new_name.$extension" +echo "renaming '$file' to '$filename'" +mv "$file" "$filename" cd "$oldpwd" relative_path="$path/$filename" From fd293444c5a1700638fbbdb3b70d9f9677fd1821 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 8 Mar 2026 23:50:33 +0100 Subject: [PATCH 244/325] tweaks (#6674) --- .github/workflows/docker-release.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 1846766bf..2fe646374 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -62,5 +62,6 @@ jobs: push: ${{ github.ref_type == 'tag' }} tags: ${{ steps.metadata.outputs.tags }} labels: ${{ steps.metadata.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + annotations: ${{ steps.metadata.outputs.annotations }} + cache-from: type=gha,scope=servatrice + cache-to: type=gha,mode=max,scope=servatrice From 15a1d5440bc8aa794a2ae157fbd0e9b624ca3de4 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 8 Mar 2026 15:50:54 -0700 Subject: [PATCH 245/325] [DeckEditor] Fix undo/redo resetting deck sorting (#6673) --- .../src/interface/widgets/deck_editor/deck_state_manager.cpp | 2 ++ 1 file changed, 2 insertions(+) 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 8afbfaaa2..8da27b63c 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp @@ -320,6 +320,7 @@ void DeckStateManager::undo(int steps) deckListModel->rebuildTree(); emit deckListModel->layoutChanged(); + emit deckReplaced(); } void DeckStateManager::redo(int steps) @@ -338,6 +339,7 @@ void DeckStateManager::redo(int steps) deckListModel->rebuildTree(); emit deckListModel->layoutChanged(); + emit deckReplaced(); } void DeckStateManager::requestHistorySave(const QString &reason) From e79bbc67b9be76d31f149b04fb7cadeb2ef6c224 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 8 Mar 2026 15:57:39 -0700 Subject: [PATCH 246/325] [DeckEditor] Fix undo/redo clearing legality (#6675) --- .../libcockatrice/models/deck_list/deck_list_model.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 8cbf23be4..e43c612f0 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -85,6 +85,8 @@ void DeckListModel::rebuildTree() } endResetModel(); + + refreshCardFormatLegalities(); } int DeckListModel::rowCount(const QModelIndex &parent) const @@ -649,7 +651,6 @@ void DeckListModel::setDeckList(const QSharedPointer &_deck) deckList = _deck; } rebuildTree(); - refreshCardFormatLegalities(); emit deckReplaced(); } From 413b4b637b8949e2b52c5109d02daed0f8deeab5 Mon Sep 17 00:00:00 2001 From: tooomm Date: Mon, 9 Mar 2026 21:20:38 +0100 Subject: [PATCH 247/325] sign+notarize only releases (#6678) --- .github/workflows/desktop-build.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index bc49db2da..d627906fd 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -220,7 +220,7 @@ jobs: - name: Upload to release id: upload_release - if: needs.configure.outputs.tag != null && matrix.package != 'skip' + if: matrix.package != 'skip' && needs.configure.outputs.tag != null shell: bash env: GH_TOKEN: ${{github.token}} @@ -453,7 +453,8 @@ jobs: key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}} - name: Sign app bundle - if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null) + if: matrix.os == 'macOS' && matrix.make_package && needs.configure.outputs.tag != null + id: sign_macos env: MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }} MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }} @@ -465,7 +466,7 @@ jobs: fi - name: Notarize app bundle - if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null) + if: steps.sign_macos.outcome == 'success' env: MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }} MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }} @@ -497,8 +498,8 @@ jobs: fi - name: Upload artifact - id: upload_artifact if: matrix.make_package + id: upload_artifact uses: actions/upload-artifact@v7 with: path: ${{steps.build.outputs.path}} @@ -506,7 +507,7 @@ jobs: if-no-files-found: error - name: Upload PDBs (Program Databases) - if: matrix.os == 'Windows' + if: matrix.os == 'Windows' && github.ref_type != 'tag' uses: actions/upload-artifact@v7 with: name: ${{steps.build.outputs.name}}-PDBs @@ -517,8 +518,8 @@ jobs: if-no-files-found: error - name: Upload to release + if: needs.configure.outputs.tag != null && matrix.make_package == '1' id: upload_release - if: needs.configure.outputs.tag != null shell: bash env: GH_TOKEN: ${{github.token}} @@ -528,8 +529,8 @@ jobs: run: gh release upload "$tag_name" "$asset_path#$asset_name" - name: Attest binary provenance - id: attestation if: steps.upload_release.outcome == 'success' + id: attestation uses: actions/attest@v4 with: subject-name: ${{steps.build.outputs.name}} From 8268311fab199a86e000e7b753027afac75996dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 21:22:26 +0100 Subject: [PATCH 248/325] Bump docker/setup-qemu-action from 3 to 4 (#6683) Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 2fe646374..02a06e592 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -41,7 +41,7 @@ jobs: org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker buildx uses: docker/setup-buildx-action@v3 From 95d7e027fc8f3f4ed44b3d76b8144a07a1421058 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 21:30:47 +0100 Subject: [PATCH 249/325] Bump docker/setup-buildx-action from 3 to 4 (#6682) Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 02a06e592..65ccd1322 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -44,7 +44,7 @@ jobs: uses: docker/setup-qemu-action@v4 - name: Set up Docker buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Login to GitHub Container Registry if: github.ref_type == 'tag' From 42cec1045725a29b72b1278ace7d037c5662f9d4 Mon Sep 17 00:00:00 2001 From: tooomm Date: Mon, 9 Mar 2026 21:32:47 +0100 Subject: [PATCH 250/325] CI: Cleanup (#6677) * Cleanup vcpkg matrix * Add filename with extension as output * fix name -> fullname, cleanup --- .ci/name_build.sh | 1 + .github/workflows/desktop-build.yml | 10 ++-------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.ci/name_build.sh b/.ci/name_build.sh index b111aded6..85818bbd9 100755 --- a/.ci/name_build.sh +++ b/.ci/name_build.sh @@ -46,3 +46,4 @@ relative_path="$path/$filename" ls -l "$relative_path" echo "path=$relative_path" >>"$GITHUB_OUTPUT" echo "name=$new_name" >>"$GITHUB_OUTPUT" +echo "fullname=$filename" >>"$GITHUB_OUTPUT" diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index d627906fd..233d9e488 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -225,7 +225,7 @@ jobs: env: GH_TOKEN: ${{github.token}} tag_name: ${{needs.configure.outputs.tag}} - asset_name: ${{steps.build.outputs.name}} + asset_name: ${{steps.build.outputs.fullname}} asset_path: ${{steps.build.outputs.path}} run: gh release upload "$tag_name" "$asset_path#$asset_name" @@ -234,7 +234,6 @@ jobs: if: steps.upload_release.outcome == 'success' uses: actions/attest@v4 with: - subject-name: ${{steps.build.outputs.name}} subject-path: ${{steps.build.outputs.path}} show-summary: false @@ -259,7 +258,6 @@ jobs: override_target: 13 make_package: 1 package_suffix: "-macOS13_Intel" - artifact_name: macOS13_Intel-package qt_version: 6.10.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets @@ -274,7 +272,6 @@ jobs: type: Release make_package: 1 package_suffix: "-macOS14" - artifact_name: macOS14-package qt_version: 6.10.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets @@ -289,7 +286,6 @@ jobs: type: Release make_package: 1 package_suffix: "-macOS15" - artifact_name: macOS15-package qt_version: 6.10.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets @@ -314,7 +310,6 @@ jobs: type: Release make_package: 1 package_suffix: "-Win10" - artifact_name: Windows10-installer qt_version: 6.10.* qt_arch: win64_msvc2022_64 qt_modules: qtimageformats qtmultimedia qtwebsockets @@ -524,7 +519,7 @@ jobs: env: GH_TOKEN: ${{github.token}} tag_name: ${{needs.configure.outputs.tag}} - asset_name: ${{steps.build.outputs.name}} + asset_name: ${{steps.build.outputs.fullname}} asset_path: ${{steps.build.outputs.path}} run: gh release upload "$tag_name" "$asset_path#$asset_name" @@ -533,7 +528,6 @@ jobs: id: attestation uses: actions/attest@v4 with: - subject-name: ${{steps.build.outputs.name}} subject-path: ${{steps.build.outputs.path}} show-summary: false From 9e2276a59fa0df05b16bb4e9dbd0a2a377dd572a Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Wed, 11 Mar 2026 19:34:05 -0400 Subject: [PATCH 251/325] Refactor zone names (#6686) * Add ZoneNames constants for protocol zone identifiers. Introduce a centralized ZoneNames namespace providing constexpr constants for zone identifiers used in the client-server protocol. This establishes a single source of truth for zone names like TABLE, GRAVE, EXILE, HAND, DECK, SIDEBOARD, and STACK. The protocol values remain unchanged (e.g., EXILE maps to rfg for backwards compatibility) while providing meaningful constant names. * refactor(server): use ZoneNames constants in server game logic Replace hardcoded zone name strings with ZoneNames:: constants in: - server_player.cpp: zone setup, draw, shuffle, mulligan operations - server_abstract_player.cpp: card movement and token destruction - server_game.cpp: returning cards when players leave No functional changes - purely mechanical string literal replacement. * refactor(client): use ZoneNames constants in core player/zone logic Update the foundational player and zone classes to use ZoneNames:: constants instead of string literals. Changes include: - player.h/cpp: zone initialization and builtinZones set - card_zone_logic.cpp: zone name translation for UI display - table_zone.cpp: table zone operations No functional changes - purely mechanical string literal replacement. * refactor(client): use ZoneNames constants in player actions and events Replace zone name strings with ZoneNames:: constants in the player action and event handling code. player_actions.cpp contains the most extensive changes (~90+ replacements) covering all card movement commands. No functional changes - purely mechanical string literal replacement. * refactor(client): use ZoneNames constants in zone menu handlers Update all zone-specific menu files to use ZoneNames:: constants for QAction data values and zone targeting. This covers context menus for cards, graveyard, hand, and exile (RFG) zones. No functional changes - purely mechanical string literal replacement. * refactor(client): use ZoneNames constants in game scene components Update remaining game scene components to use ZoneNames:: constants: - arrow_item.cpp: arrow drawing between cards - game_scene.cpp: zone view positioning - message_log_widget.cpp: removes duplicate local static constants that were previously defining zone names redundantly - phases_toolbar.cpp: phase actions (untap all) Notable: message_log_widget.cpp previously had its own local constants (TABLE_ZONE_NAME, GRAVE_ZONE_NAME, etc.) which are now removed in favor of the centralized ZoneNames:: constants. * formatting fix --- cockatrice/src/game/board/arrow_item.cpp | 19 +-- cockatrice/src/game/game_scene.cpp | 5 +- .../src/game/log/message_log_widget.cpp | 43 +++--- cockatrice/src/game/phases_toolbar.cpp | 3 +- cockatrice/src/game/player/menu/card_menu.cpp | 14 +- .../src/game/player/menu/grave_menu.cpp | 9 +- cockatrice/src/game/player/menu/hand_menu.cpp | 9 +- cockatrice/src/game/player/menu/rfg_menu.cpp | 10 +- cockatrice/src/game/player/player.cpp | 22 +-- cockatrice/src/game/player/player.h | 15 +- cockatrice/src/game/player/player_actions.cpp | 137 +++++++++--------- .../src/game/player/player_event_handler.cpp | 5 +- .../src/game/zones/logic/card_zone_logic.cpp | 11 +- cockatrice/src/game/zones/table_zone.cpp | 3 +- .../remote/game/server_abstract_player.cpp | 9 +- .../server/remote/game/server_game.cpp | 5 +- .../server/remote/game/server_player.cpp | 33 +++-- libcockatrice_utility/CMakeLists.txt | 9 +- .../libcockatrice/utility/zone_names.h | 19 +++ 19 files changed, 207 insertions(+), 173 deletions(-) create mode 100644 libcockatrice_utility/libcockatrice/utility/zone_names.h diff --git a/cockatrice/src/game/board/arrow_item.cpp b/cockatrice/src/game/board/arrow_item.cpp index 257d96f8a..60585a774 100644 --- a/cockatrice/src/game/board/arrow_item.cpp +++ b/cockatrice/src/game/board/arrow_item.cpp @@ -19,6 +19,7 @@ #include #include #include +#include ArrowItem::ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &_color) : QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), targetLocked(false), @@ -239,16 +240,16 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) } // if the card is in hand then we will move the card to stack or table as part of drawing the arrow - if (startZone->getName() == "hand") { + if (startZone->getName() == ZoneNames::HAND) { startCard->playCard(false); CardInfoPtr ci = startCard->getCard().getCardPtr(); bool playToStack = SettingsCache::instance().getPlayToStack(); - if (ci && - ((!playToStack && ci->getUiAttributes().tableRow == 3) || - (playToStack && ci->getUiAttributes().tableRow != 0 && startCard->getZone()->getName() != "stack"))) - cmd.set_start_zone("stack"); + if (ci && ((!playToStack && ci->getUiAttributes().tableRow == 3) || + (playToStack && ci->getUiAttributes().tableRow != 0 && + startCard->getZone()->getName() != ZoneNames::STACK))) + cmd.set_start_zone(ZoneNames::STACK); else - cmd.set_start_zone(playToStack ? "stack" : "table"); + cmd.set_start_zone(playToStack ? ZoneNames::STACK : ZoneNames::TABLE); } if (deleteInPhase != 0) { @@ -318,7 +319,7 @@ void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCard) { // do nothing if target is already attached to another card or is not in play - if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != "table") { + if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != ZoneNames::TABLE) { return; } @@ -326,12 +327,12 @@ void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCar CardZoneLogic *targetZone = targetCard->getZone(); // move card onto table first if attaching from some other zone - if (startZone->getName() != "table") { + if (startZone->getName() != ZoneNames::TABLE) { player->getPlayerActions()->playCardToTable(startCard, false); } Command_AttachCard cmd; - cmd.set_start_zone("table"); + cmd.set_start_zone(ZoneNames::TABLE); cmd.set_card_id(startCard->getId()); cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId()); cmd.set_target_zone(targetZone->getName().toStdString()); diff --git a/cockatrice/src/game/game_scene.cpp b/cockatrice/src/game/game_scene.cpp index 77037cd6e..5dc3b48f7 100644 --- a/cockatrice/src/game/game_scene.cpp +++ b/cockatrice/src/game/game_scene.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include /** @@ -410,9 +411,9 @@ void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numb connect(item, &ZoneViewWidget::closePressed, this, &GameScene::removeZoneView); addItem(item); - if (zoneName == "grave") + if (zoneName == ZoneNames::GRAVE) item->setPos(360, 100); - else if (zoneName == "rfg") + else if (zoneName == ZoneNames::EXILE) item->setPos(380, 120); else item->setPos(340, 80); diff --git a/cockatrice/src/game/log/message_log_widget.cpp b/cockatrice/src/game/log/message_log_widget.cpp index 645974994..c38e433eb 100644 --- a/cockatrice/src/game/log/message_log_widget.cpp +++ b/cockatrice/src/game/log/message_log_widget.cpp @@ -10,16 +10,9 @@ #include <../../client/settings/card_counter_settings.h> #include #include +#include #include -static const QString TABLE_ZONE_NAME = "table"; -static const QString GRAVE_ZONE_NAME = "grave"; -static const QString EXILE_ZONE_NAME = "rfg"; -static const QString HAND_ZONE_NAME = "hand"; -static const QString DECK_ZONE_NAME = "deck"; -static const QString SIDEBOARD_ZONE_NAME = "sb"; -static const QString STACK_ZONE_NAME = "stack"; - static QString sanitizeHtml(QString dirty) { return dirty.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """); @@ -37,15 +30,15 @@ MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position QString fromStr; QString zoneName = zone->getName(); - if (zoneName == TABLE_ZONE_NAME) { + if (zoneName == ZoneNames::TABLE) { fromStr = tr(" from play"); - } else if (zoneName == GRAVE_ZONE_NAME) { + } else if (zoneName == ZoneNames::GRAVE) { fromStr = tr(" from their graveyard"); - } else if (zoneName == EXILE_ZONE_NAME) { + } else if (zoneName == ZoneNames::EXILE) { fromStr = tr(" from exile"); - } else if (zoneName == HAND_ZONE_NAME) { + } else if (zoneName == ZoneNames::HAND) { fromStr = tr(" from their hand"); - } else if (zoneName == DECK_ZONE_NAME) { + } else if (zoneName == ZoneNames::DECK) { if (position == 0) { if (cardName.isEmpty()) { if (ownerChange) { @@ -83,9 +76,9 @@ MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position fromStr = tr(" from their library"); } } - } else if (zoneName == SIDEBOARD_ZONE_NAME) { + } else if (zoneName == ZoneNames::SIDEBOARD) { fromStr = tr(" from sideboard"); - } else if (zoneName == STACK_ZONE_NAME) { + } else if (zoneName == ZoneNames::STACK) { fromStr = tr(" from the stack"); } else { fromStr = tr(" from custom zone '%1'").arg(zoneName); @@ -275,9 +268,9 @@ void MessageLogWidget::logMoveCard(Player *player, bool ownerChanged = startZone->getPlayer() != targetZone->getPlayer(); // do not log if moved within the same zone - if ((startZoneName == TABLE_ZONE_NAME && targetZoneName == TABLE_ZONE_NAME && !ownerChanged) || - (startZoneName == HAND_ZONE_NAME && targetZoneName == HAND_ZONE_NAME) || - (startZoneName == EXILE_ZONE_NAME && targetZoneName == EXILE_ZONE_NAME)) { + if ((startZoneName == ZoneNames::TABLE && targetZoneName == ZoneNames::TABLE && !ownerChanged) || + (startZoneName == ZoneNames::HAND && targetZoneName == ZoneNames::HAND) || + (startZoneName == ZoneNames::EXILE && targetZoneName == ZoneNames::EXILE)) { return; } @@ -306,28 +299,28 @@ void MessageLogWidget::logMoveCard(Player *player, QString finalStr; std::optional fourthArg; - if (targetZoneName == TABLE_ZONE_NAME) { + if (targetZoneName == ZoneNames::TABLE) { soundEngine->playSound("play_card"); if (card->getFaceDown()) { finalStr = tr("%1 puts %2 into play%3 face down."); } else { finalStr = tr("%1 puts %2 into play%3."); } - } else if (targetZoneName == GRAVE_ZONE_NAME) { + } else if (targetZoneName == ZoneNames::GRAVE) { if (card->getFaceDown()) { finalStr = tr("%1 puts %2%3 into their graveyard face down."); } else { finalStr = tr("%1 puts %2%3 into their graveyard."); } - } else if (targetZoneName == EXILE_ZONE_NAME) { + } else if (targetZoneName == ZoneNames::EXILE) { if (card->getFaceDown()) { finalStr = tr("%1 exiles %2%3 face down."); } else { finalStr = tr("%1 exiles %2%3."); } - } else if (targetZoneName == HAND_ZONE_NAME) { + } else if (targetZoneName == ZoneNames::HAND) { finalStr = tr("%1 moves %2%3 to their hand."); - } else if (targetZoneName == DECK_ZONE_NAME) { + } else if (targetZoneName == ZoneNames::DECK) { if (newX == -1) { finalStr = tr("%1 puts %2%3 into their library."); } else if (newX >= targetZone->getCards().size()) { @@ -339,9 +332,9 @@ void MessageLogWidget::logMoveCard(Player *player, fourthArg = QString::number(newX); finalStr = tr("%1 puts %2%3 into their library %4 cards from the top."); } - } else if (targetZoneName == SIDEBOARD_ZONE_NAME) { + } else if (targetZoneName == ZoneNames::SIDEBOARD) { finalStr = tr("%1 moves %2%3 to sideboard."); - } else if (targetZoneName == STACK_ZONE_NAME) { + } else if (targetZoneName == ZoneNames::STACK) { soundEngine->playSound("play_card"); if (card->getFaceDown()) { finalStr = tr("%1 plays %2%3 face down."); diff --git a/cockatrice/src/game/phases_toolbar.cpp b/cockatrice/src/game/phases_toolbar.cpp index 5106e40de..2341a1d7f 100644 --- a/cockatrice/src/game/phases_toolbar.cpp +++ b/cockatrice/src/game/phases_toolbar.cpp @@ -11,6 +11,7 @@ #include #include #include +#include PhaseButton::PhaseButton(const QString &_name, QGraphicsItem *parent, QAction *_doubleClickAction, bool _highlightable) : QObject(), QGraphicsItem(parent), name(_name), active(false), highlightable(_highlightable), @@ -259,7 +260,7 @@ void PhasesToolbar::actNextTurn() void PhasesToolbar::actUntapAll() { Command_SetCardAttr cmd; - cmd.set_zone("table"); + cmd.set_zone(ZoneNames::TABLE); cmd.set_attribute(AttrTapped); cmd.set_attr_value("0"); diff --git a/cockatrice/src/game/player/menu/card_menu.cpp b/cockatrice/src/game/player/menu/card_menu.cpp index ddf0a55f3..ef95f052e 100644 --- a/cockatrice/src/game/player/menu/card_menu.cpp +++ b/cockatrice/src/game/player/menu/card_menu.cpp @@ -12,6 +12,7 @@ #include #include +#include CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive) : player(_player), card(_card), shortcutsActive(_shortcutsActive) @@ -115,11 +116,12 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive } else if (writeableCard) { if (card->getZone()) { - if (card->getZone()->getName() == "table") { + if (card->getZone()->getName() == ZoneNames::TABLE) { createTableMenu(); - } else if (card->getZone()->getName() == "stack") { + } else if (card->getZone()->getName() == ZoneNames::STACK) { createStackMenu(); - } else if (card->getZone()->getName() == "rfg" || card->getZone()->getName() == "grave") { + } else if (card->getZone()->getName() == ZoneNames::EXILE || + card->getZone()->getName() == ZoneNames::GRAVE) { createGraveyardOrExileMenu(); } else { createHandOrCustomZoneMenu(); @@ -128,7 +130,7 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive addMenu(new MoveMenu(player)); } } else { - if (card->getZone() && card->getZone()->getName() != "hand") { + if (card->getZone() && card->getZone()->getName() != ZoneNames::HAND) { addAction(aDrawArrow); addSeparator(); addRelatedCardView(); @@ -285,7 +287,7 @@ void CardMenu::createHandOrCustomZoneMenu() addMenu(new MoveMenu(player)); // actions that are really wonky when done from deck or sideboard - if (card->getZone()->getName() == "hand") { + if (card->getZone()->getName() == ZoneNames::HAND) { addSeparator(); addAction(aAttach); addAction(aDrawArrow); @@ -298,7 +300,7 @@ void CardMenu::createHandOrCustomZoneMenu() } addRelatedCardView(); - if (card->getZone()->getName() == "hand") { + if (card->getZone()->getName() == ZoneNames::HAND) { addRelatedCardActions(); } } diff --git a/cockatrice/src/game/player/menu/grave_menu.cpp b/cockatrice/src/game/player/menu/grave_menu.cpp index d4faca42b..2af62c08a 100644 --- a/cockatrice/src/game/player/menu/grave_menu.cpp +++ b/cockatrice/src/game/player/menu/grave_menu.cpp @@ -6,6 +6,7 @@ #include #include +#include GraveyardMenu::GraveyardMenu(Player *_player, QWidget *parent) : TearOffMenu(parent), player(_player) { @@ -39,16 +40,16 @@ void GraveyardMenu::createMoveActions() if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { aMoveGraveToTopLibrary = new QAction(this); - aMoveGraveToTopLibrary->setData(QList() << "deck" << 0); + aMoveGraveToTopLibrary->setData(QList() << ZoneNames::DECK << 0); aMoveGraveToBottomLibrary = new QAction(this); - aMoveGraveToBottomLibrary->setData(QList() << "deck" << -1); + aMoveGraveToBottomLibrary->setData(QList() << ZoneNames::DECK << -1); aMoveGraveToHand = new QAction(this); - aMoveGraveToHand->setData(QList() << "hand" << 0); + aMoveGraveToHand->setData(QList() << ZoneNames::HAND << 0); aMoveGraveToRfg = new QAction(this); - aMoveGraveToRfg->setData(QList() << "rfg" << 0); + aMoveGraveToRfg->setData(QList() << ZoneNames::EXILE << 0); connect(aMoveGraveToTopLibrary, &QAction::triggered, grave, &PileZoneLogic::moveAllToZone); connect(aMoveGraveToBottomLibrary, &QAction::triggered, grave, &PileZoneLogic::moveAllToZone); diff --git a/cockatrice/src/game/player/menu/hand_menu.cpp b/cockatrice/src/game/player/menu/hand_menu.cpp index 019ab925c..d65c136bf 100644 --- a/cockatrice/src/game/player/menu/hand_menu.cpp +++ b/cockatrice/src/game/player/menu/hand_menu.cpp @@ -9,6 +9,7 @@ #include #include +#include HandMenu::HandMenu(Player *_player, PlayerActions *actions, QWidget *parent) : TearOffMenu(parent), player(_player) { @@ -76,13 +77,13 @@ HandMenu::HandMenu(Player *_player, PlayerActions *actions, QWidget *parent) : T if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) { aMoveHandToTopLibrary = new QAction(this); - aMoveHandToTopLibrary->setData(QList() << "deck" << 0); + aMoveHandToTopLibrary->setData(QList() << ZoneNames::DECK << 0); aMoveHandToBottomLibrary = new QAction(this); - aMoveHandToBottomLibrary->setData(QList() << "deck" << -1); + aMoveHandToBottomLibrary->setData(QList() << ZoneNames::DECK << -1); aMoveHandToGrave = new QAction(this); - aMoveHandToGrave->setData(QList() << "grave" << 0); + aMoveHandToGrave->setData(QList() << ZoneNames::GRAVE << 0); aMoveHandToRfg = new QAction(this); - aMoveHandToRfg->setData(QList() << "rfg" << 0); + aMoveHandToRfg->setData(QList() << ZoneNames::EXILE << 0); auto hand = player->getHandZone(); diff --git a/cockatrice/src/game/player/menu/rfg_menu.cpp b/cockatrice/src/game/player/menu/rfg_menu.cpp index 25e162581..8a101c04c 100644 --- a/cockatrice/src/game/player/menu/rfg_menu.cpp +++ b/cockatrice/src/game/player/menu/rfg_menu.cpp @@ -3,6 +3,8 @@ #include "../player.h" #include "../player_actions.h" +#include + RfgMenu::RfgMenu(Player *_player, QWidget *parent) : TearOffMenu(parent), player(_player) { createMoveActions(); @@ -30,13 +32,13 @@ void RfgMenu::createMoveActions() auto rfg = player->getRfgZone(); aMoveRfgToTopLibrary = new QAction(this); - aMoveRfgToTopLibrary->setData(QList() << "deck" << 0); + aMoveRfgToTopLibrary->setData(QList() << ZoneNames::DECK << 0); aMoveRfgToBottomLibrary = new QAction(this); - aMoveRfgToBottomLibrary->setData(QList() << "deck" << -1); + aMoveRfgToBottomLibrary->setData(QList() << ZoneNames::DECK << -1); aMoveRfgToHand = new QAction(this); - aMoveRfgToHand->setData(QList() << "hand" << 0); + aMoveRfgToHand->setData(QList() << ZoneNames::HAND << 0); aMoveRfgToGrave = new QAction(this); - aMoveRfgToGrave->setData(QList() << "grave" << 0); + aMoveRfgToGrave->setData(QList() << ZoneNames::GRAVE << 0); connect(aMoveRfgToTopLibrary, &QAction::triggered, rfg, &PileZoneLogic::moveAllToZone); connect(aMoveRfgToBottomLibrary, &QAction::triggered, rfg, &PileZoneLogic::moveAllToZone); diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index 0723ae6bc..ac4149f0e 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -61,15 +61,15 @@ void Player::forwardActionSignalsToEventHandler() void Player::initializeZones() { - addZone(new PileZoneLogic(this, "deck", false, true, false, this)); - addZone(new PileZoneLogic(this, "grave", false, false, true, this)); - addZone(new PileZoneLogic(this, "rfg", false, false, true, this)); - addZone(new PileZoneLogic(this, "sb", false, false, false, this)); - addZone(new TableZoneLogic(this, "table", true, false, true, this)); - addZone(new StackZoneLogic(this, "stack", true, false, true, this)); + addZone(new PileZoneLogic(this, ZoneNames::DECK, false, true, false, this)); + addZone(new PileZoneLogic(this, ZoneNames::GRAVE, false, false, true, this)); + addZone(new PileZoneLogic(this, ZoneNames::EXILE, false, false, true, this)); + addZone(new PileZoneLogic(this, ZoneNames::SIDEBOARD, false, false, false, this)); + addZone(new TableZoneLogic(this, ZoneNames::TABLE, true, false, true, this)); + addZone(new StackZoneLogic(this, ZoneNames::STACK, true, false, true, this)); bool visibleHand = playerInfo->getLocalOrJudge() || (game->getPlayerManager()->isSpectator() && game->getGameMetaInfo()->spectatorsOmniscient()); - addZone(new HandZoneLogic(this, "hand", false, false, visibleHand, this)); + addZone(new HandZoneLogic(this, ZoneNames::HAND, false, false, visibleHand, this)); } Player::~Player() @@ -119,13 +119,13 @@ void Player::setZoneId(int _zoneId) void Player::processPlayerInfo(const ServerInfo_Player &info) { static QSet builtinZones{/* PileZones */ - "deck", "grave", "rfg", "sb", + ZoneNames::DECK, ZoneNames::GRAVE, ZoneNames::EXILE, ZoneNames::SIDEBOARD, /* TableZone */ - "table", + ZoneNames::TABLE, /* StackZone */ - "stack", + ZoneNames::STACK, /* HandZone */ - "hand"}; + ZoneNames::HAND}; clearCounters(); clearArrows(); diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h index 0a03b3abe..e9c008821 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player.h @@ -27,6 +27,7 @@ #include #include #include +#include inline Q_LOGGING_CATEGORY(PlayerLog, "player"); @@ -155,37 +156,37 @@ public: PileZoneLogic *getDeckZone() { - return qobject_cast(zones.value("deck")); + return qobject_cast(zones.value(ZoneNames::DECK)); } PileZoneLogic *getGraveZone() { - return qobject_cast(zones.value("grave")); + return qobject_cast(zones.value(ZoneNames::GRAVE)); } PileZoneLogic *getRfgZone() { - return qobject_cast(zones.value("rfg")); + return qobject_cast(zones.value(ZoneNames::EXILE)); } PileZoneLogic *getSideboardZone() { - return qobject_cast(zones.value("sb")); + return qobject_cast(zones.value(ZoneNames::SIDEBOARD)); } TableZoneLogic *getTableZone() { - return qobject_cast(zones.value("table")); + return qobject_cast(zones.value(ZoneNames::TABLE)); } StackZoneLogic *getStackZone() { - return qobject_cast(zones.value("stack")); + return qobject_cast(zones.value(ZoneNames::STACK)); } HandZoneLogic *getHandZone() { - return qobject_cast(zones.value("hand")); + return qobject_cast(zones.value(ZoneNames::HAND)); } AbstractCounter *addCounter(const ServerInfo_Counter &counter); diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index 514ac2e67..20e526727 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -28,6 +28,7 @@ #include #include #include +#include // milliseconds in between triggers of the move top cards until action static constexpr int MOVE_TOP_CARD_UNTIL_INTERVAL = 100; @@ -63,13 +64,13 @@ void PlayerActions::playCard(CardItem *card, bool faceDown) int tableRow = info.getUiAttributes().tableRow; bool playToStack = SettingsCache::instance().getPlayToStack(); QString currentZone = card->getZone()->getName(); - if (currentZone == "stack" && tableRow == 3) { - cmd.set_target_zone("grave"); + if (currentZone == ZoneNames::STACK && tableRow == 3) { + cmd.set_target_zone(ZoneNames::GRAVE); cmd.set_x(0); cmd.set_y(0); - } else if (!faceDown && - ((!playToStack && tableRow == 3) || ((playToStack && tableRow != 0) && currentZone != "stack"))) { - cmd.set_target_zone("stack"); + } else if (!faceDown && ((!playToStack && tableRow == 3) || + ((playToStack && tableRow != 0) && currentZone != ZoneNames::STACK))) { + cmd.set_target_zone(ZoneNames::STACK); cmd.set_x(-1); cmd.set_y(0); } else { @@ -81,7 +82,7 @@ void PlayerActions::playCard(CardItem *card, bool faceDown) } cardToMove->set_tapped(!faceDown && info.getUiAttributes().cipt); if (tableRow != 3) - cmd.set_target_zone("table"); + cmd.set_target_zone(ZoneNames::TABLE); cmd.set_x(gridPoint.x()); cmd.set_y(gridPoint.y()); } @@ -124,7 +125,7 @@ void PlayerActions::playCardToTable(const CardItem *card, bool faceDown) cardToMove->set_pt(info.getPowTough().toStdString()); } cardToMove->set_tapped(!faceDown && info.getUiAttributes().cipt); - cmd.set_target_zone("table"); + cmd.set_target_zone(ZoneNames::TABLE); cmd.set_x(gridPoint.x()); cmd.set_y(gridPoint.y()); sendGameCommand(cmd); @@ -132,12 +133,12 @@ void PlayerActions::playCardToTable(const CardItem *card, bool faceDown) void PlayerActions::actViewLibrary() { - player->getGameScene()->toggleZoneView(player, "deck", -1); + player->getGameScene()->toggleZoneView(player, ZoneNames::DECK, -1); } void PlayerActions::actViewHand() { - player->getGameScene()->toggleZoneView(player, "hand", -1); + player->getGameScene()->toggleZoneView(player, ZoneNames::HAND, -1); } /** @@ -181,7 +182,7 @@ void PlayerActions::actViewTopCards() deckSize, 1, &ok); if (ok) { defaultNumberTopCards = number; - player->getGameScene()->toggleZoneView(player, "deck", number); + player->getGameScene()->toggleZoneView(player, ZoneNames::DECK, number); } } @@ -194,14 +195,14 @@ void PlayerActions::actViewBottomCards() deckSize, 1, &ok); if (ok) { defaultNumberBottomCards = number; - player->getGameScene()->toggleZoneView(player, "deck", number, true); + player->getGameScene()->toggleZoneView(player, ZoneNames::DECK, number, true); } } void PlayerActions::actAlwaysRevealTopCard() { Command_ChangeZoneProperties cmd; - cmd.set_zone_name("deck"); + cmd.set_zone_name(ZoneNames::DECK); cmd.set_always_reveal_top_card(player->getPlayerMenu()->getLibraryMenu()->isAlwaysRevealTopCardChecked()); sendGameCommand(cmd); @@ -210,7 +211,7 @@ void PlayerActions::actAlwaysRevealTopCard() void PlayerActions::actAlwaysLookAtTopCard() { Command_ChangeZoneProperties cmd; - cmd.set_zone_name("deck"); + cmd.set_zone_name(ZoneNames::DECK); cmd.set_always_look_at_top_card(player->getPlayerMenu()->getLibraryMenu()->isAlwaysLookAtTopCardChecked()); sendGameCommand(cmd); @@ -223,17 +224,17 @@ void PlayerActions::actOpenDeckInDeckEditor() void PlayerActions::actViewGraveyard() { - player->getGameScene()->toggleZoneView(player, "grave", -1); + player->getGameScene()->toggleZoneView(player, ZoneNames::GRAVE, -1); } void PlayerActions::actViewRfg() { - player->getGameScene()->toggleZoneView(player, "rfg", -1); + player->getGameScene()->toggleZoneView(player, ZoneNames::EXILE, -1); } void PlayerActions::actViewSideboard() { - player->getGameScene()->toggleZoneView(player, "sb", -1); + player->getGameScene()->toggleZoneView(player, ZoneNames::SIDEBOARD, -1); } void PlayerActions::actShuffle() @@ -263,7 +264,7 @@ void PlayerActions::actShuffleTop() defaultNumberTopCards = number; Command_Shuffle cmd; - cmd.set_zone_name("deck"); + cmd.set_zone_name(ZoneNames::DECK); cmd.set_start(0); cmd.set_end(number - 1); // inclusive, the indexed card at end will be shuffled @@ -292,7 +293,7 @@ void PlayerActions::actShuffleBottom() defaultNumberBottomCards = number; Command_Shuffle cmd; - cmd.set_zone_name("deck"); + cmd.set_zone_name(ZoneNames::DECK); cmd.set_start(-number); cmd.set_end(-1); @@ -376,7 +377,7 @@ void PlayerActions::actUndoDraw() void PlayerActions::cmdSetTopCard(Command_MoveCard &cmd) { - cmd.set_start_zone("deck"); + cmd.set_start_zone(ZoneNames::DECK); auto *cardToMove = cmd.mutable_cards_to_move()->add_card(); cardToMove->set_card_id(0); cmd.set_target_player_id(player->getPlayerInfo()->getId()); @@ -386,7 +387,7 @@ void PlayerActions::cmdSetBottomCard(Command_MoveCard &cmd) { CardZoneLogic *zone = player->getDeckZone(); int lastCard = zone->getCards().size() - 1; - cmd.set_start_zone("deck"); + cmd.set_start_zone(ZoneNames::DECK); auto *cardToMove = cmd.mutable_cards_to_move()->add_card(); cardToMove->set_card_id(lastCard); cmd.set_target_player_id(player->getPlayerInfo()->getId()); @@ -400,7 +401,7 @@ void PlayerActions::actMoveTopCardToGrave() Command_MoveCard cmd; cmdSetTopCard(cmd); - cmd.set_target_zone("grave"); + cmd.set_target_zone(ZoneNames::GRAVE); cmd.set_x(0); cmd.set_y(0); @@ -415,7 +416,7 @@ void PlayerActions::actMoveTopCardToExile() Command_MoveCard cmd; cmdSetTopCard(cmd); - cmd.set_target_zone("rfg"); + cmd.set_target_zone(ZoneNames::EXILE); cmd.set_x(0); cmd.set_y(0); @@ -424,22 +425,22 @@ void PlayerActions::actMoveTopCardToExile() void PlayerActions::actMoveTopCardsToGrave() { - moveTopCardsTo("grave", tr("grave"), false); + moveTopCardsTo(ZoneNames::GRAVE, tr("grave"), false); } void PlayerActions::actMoveTopCardsToGraveFaceDown() { - moveTopCardsTo("grave", tr("grave"), true); + moveTopCardsTo(ZoneNames::GRAVE, tr("grave"), true); } void PlayerActions::actMoveTopCardsToExile() { - moveTopCardsTo("rfg", tr("exile"), false); + moveTopCardsTo(ZoneNames::EXILE, tr("exile"), false); } void PlayerActions::actMoveTopCardsToExileFaceDown() { - moveTopCardsTo("rfg", tr("exile"), true); + moveTopCardsTo(ZoneNames::EXILE, tr("exile"), true); } void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown) @@ -463,7 +464,7 @@ void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zon defaultNumberTopCards = number; Command_MoveCard cmd; - cmd.set_start_zone("deck"); + cmd.set_start_zone(ZoneNames::DECK); cmd.set_target_player_id(player->getPlayerInfo()->getId()); cmd.set_target_zone(targetZone.toStdString()); cmd.set_x(0); @@ -549,7 +550,7 @@ void PlayerActions::actMoveTopCardToBottom() Command_MoveCard cmd; cmdSetTopCard(cmd); - cmd.set_target_zone("deck"); + cmd.set_target_zone(ZoneNames::DECK); cmd.set_x(-1); // bottom of deck cmd.set_y(0); @@ -564,7 +565,7 @@ void PlayerActions::actMoveTopCardToPlay() Command_MoveCard cmd; cmdSetTopCard(cmd); - cmd.set_target_zone("stack"); + cmd.set_target_zone(ZoneNames::STACK); cmd.set_x(-1); cmd.set_y(0); @@ -578,12 +579,12 @@ void PlayerActions::actMoveTopCardToPlayFaceDown() } Command_MoveCard cmd; - cmd.set_start_zone("deck"); + cmd.set_start_zone(ZoneNames::DECK); CardToMove *cardToMove = cmd.mutable_cards_to_move()->add_card(); cardToMove->set_card_id(0); cardToMove->set_face_down(true); cmd.set_target_player_id(player->getPlayerInfo()->getId()); - cmd.set_target_zone("table"); + cmd.set_target_zone(ZoneNames::TABLE); cmd.set_x(-1); cmd.set_y(0); @@ -598,7 +599,7 @@ void PlayerActions::actMoveBottomCardToGrave() Command_MoveCard cmd; cmdSetBottomCard(cmd); - cmd.set_target_zone("grave"); + cmd.set_target_zone(ZoneNames::GRAVE); cmd.set_x(0); cmd.set_y(0); @@ -613,7 +614,7 @@ void PlayerActions::actMoveBottomCardToExile() Command_MoveCard cmd; cmdSetBottomCard(cmd); - cmd.set_target_zone("rfg"); + cmd.set_target_zone(ZoneNames::EXILE); cmd.set_x(0); cmd.set_y(0); @@ -622,22 +623,22 @@ void PlayerActions::actMoveBottomCardToExile() void PlayerActions::actMoveBottomCardsToGrave() { - moveBottomCardsTo("grave", tr("grave"), false); + moveBottomCardsTo(ZoneNames::GRAVE, tr("grave"), false); } void PlayerActions::actMoveBottomCardsToGraveFaceDown() { - moveBottomCardsTo("grave", tr("grave"), true); + moveBottomCardsTo(ZoneNames::GRAVE, tr("grave"), true); } void PlayerActions::actMoveBottomCardsToExile() { - moveBottomCardsTo("rfg", tr("exile"), false); + moveBottomCardsTo(ZoneNames::EXILE, tr("exile"), false); } void PlayerActions::actMoveBottomCardsToExileFaceDown() { - moveBottomCardsTo("rfg", tr("exile"), true); + moveBottomCardsTo(ZoneNames::EXILE, tr("exile"), true); } void PlayerActions::moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown) @@ -661,7 +662,7 @@ void PlayerActions::moveBottomCardsTo(const QString &targetZone, const QString & defaultNumberBottomCards = number; Command_MoveCard cmd; - cmd.set_start_zone("deck"); + cmd.set_start_zone(ZoneNames::DECK); cmd.set_target_player_id(player->getPlayerInfo()->getId()); cmd.set_target_zone(targetZone.toStdString()); cmd.set_x(0); @@ -686,7 +687,7 @@ void PlayerActions::actMoveBottomCardToTop() Command_MoveCard cmd; cmdSetBottomCard(cmd); - cmd.set_target_zone("deck"); + cmd.set_target_zone(ZoneNames::DECK); cmd.set_x(0); // top of deck cmd.set_y(0); @@ -756,7 +757,7 @@ void PlayerActions::actDrawBottomCard() Command_MoveCard cmd; cmdSetBottomCard(cmd); - cmd.set_target_zone("hand"); + cmd.set_target_zone(ZoneNames::HAND); cmd.set_x(0); cmd.set_y(0); @@ -782,9 +783,9 @@ void PlayerActions::actDrawBottomCards() defaultNumberBottomCards = number; Command_MoveCard cmd; - cmd.set_start_zone("deck"); + cmd.set_start_zone(ZoneNames::DECK); cmd.set_target_player_id(player->getPlayerInfo()->getId()); - cmd.set_target_zone("hand"); + cmd.set_target_zone(ZoneNames::HAND); cmd.set_x(0); cmd.set_y(0); @@ -803,7 +804,7 @@ void PlayerActions::actMoveBottomCardToPlay() Command_MoveCard cmd; cmdSetBottomCard(cmd); - cmd.set_target_zone("stack"); + cmd.set_target_zone(ZoneNames::STACK); cmd.set_x(-1); cmd.set_y(0); @@ -820,13 +821,13 @@ void PlayerActions::actMoveBottomCardToPlayFaceDown() int lastCard = zone->getCards().size() - 1; Command_MoveCard cmd; - cmd.set_start_zone("deck"); + cmd.set_start_zone(ZoneNames::DECK); auto *cardToMove = cmd.mutable_cards_to_move()->add_card(); cardToMove->set_card_id(lastCard); cardToMove->set_face_down(true); cmd.set_target_player_id(player->getPlayerInfo()->getId()); - cmd.set_target_zone("table"); + cmd.set_target_zone(ZoneNames::TABLE); cmd.set_x(-1); cmd.set_y(0); @@ -836,7 +837,7 @@ void PlayerActions::actMoveBottomCardToPlayFaceDown() void PlayerActions::actUntapAll() { Command_SetCardAttr cmd; - cmd.set_zone("table"); + cmd.set_zone(ZoneNames::TABLE); cmd.set_attribute(AttrTapped); cmd.set_attr_value("0"); @@ -886,7 +887,7 @@ void PlayerActions::actCreateAnotherToken() } Command_CreateToken cmd; - cmd.set_zone("table"); + cmd.set_zone(ZoneNames::TABLE); cmd.set_card_name(lastTokenInfo.name.toStdString()); cmd.set_card_provider_id(lastTokenInfo.providerId.toStdString()); cmd.set_color(lastTokenInfo.color.toStdString()); @@ -1067,7 +1068,7 @@ bool PlayerActions::createRelatedFromRelation(const CardItem *sourceCard, const // move card onto table first if attaching from some other zone // we only do this for AttachTo because cross-zone TransformInto is already handled server-side - if (attachType == CardRelationType::AttachTo && sourceCard->getZone()->getName() != "table") { + if (attachType == CardRelationType::AttachTo && sourceCard->getZone()->getName() != ZoneNames::TABLE) { playCardToTable(sourceCard, false); } @@ -1093,7 +1094,7 @@ void PlayerActions::createCard(const CardItem *sourceCard, // create the token for the related card Command_CreateToken cmd; - cmd.set_zone("table"); + cmd.set_zone(ZoneNames::TABLE); cmd.set_card_name(cardInfo->getName().toStdString()); switch (cardInfo->getColors().size()) { case 0: @@ -1122,12 +1123,12 @@ void PlayerActions::createCard(const CardItem *sourceCard, switch (attachType) { case CardRelationType::DoesNotAttach: - cmd.set_target_zone("table"); + cmd.set_target_zone(ZoneNames::TABLE); cmd.set_card_provider_id(relatedCard.getPrinting().getUuid().toStdString()); break; case CardRelationType::AttachTo: - cmd.set_target_zone("table"); // We currently only support creating tokens on the table + cmd.set_target_zone(ZoneNames::TABLE); // We currently only support creating tokens on the table cmd.set_card_provider_id(relatedCard.getPrinting().getUuid().toStdString()); cmd.set_target_card_id(sourceCard->getId()); cmd.set_target_mode(Command_CreateToken::ATTACH_TO); @@ -1135,7 +1136,7 @@ void PlayerActions::createCard(const CardItem *sourceCard, case CardRelationType::TransformInto: // allow cards to directly transform on stack - cmd.set_zone(sourceCard->getZone()->getName() == "stack" ? "stack" : "table"); + cmd.set_zone(sourceCard->getZone()->getName() == ZoneNames::STACK ? ZoneNames::STACK : ZoneNames::TABLE); // Transform card zone changes are handled server-side cmd.set_target_zone(sourceCard->getZone()->getName().toStdString()); cmd.set_target_card_id(sourceCard->getId()); @@ -1250,7 +1251,7 @@ void PlayerActions::actMoveCardXCardsFromTop() cmd->set_start_zone(startZone.toStdString()); cmd->mutable_cards_to_move()->CopyFrom(idList); cmd->set_target_player_id(player->getPlayerInfo()->getId()); - cmd->set_target_zone("deck"); + cmd->set_target_zone(ZoneNames::DECK); cmd->set_x(number); cmd->set_y(0); commandList.append(cmd); @@ -1639,7 +1640,7 @@ void PlayerActions::playSelectedCards(const bool faceDown) [](const auto &card1, const auto &card2) { return card1->getId() > card2->getId(); }); for (auto &card : selectedCards) { - if (card && !isUnwritableRevealZone(card->getZone()) && card->getZone()->getName() != "table") { + if (card && !isUnwritableRevealZone(card->getZone()) && card->getZone()->getName() != ZoneNames::TABLE) { playCard(card, faceDown); } } @@ -1692,7 +1693,7 @@ void PlayerActions::actRevealHand(int revealToPlayerId) if (revealToPlayerId != -1) { cmd.set_player_id(revealToPlayerId); } - cmd.set_zone_name("hand"); + cmd.set_zone_name(ZoneNames::HAND); sendGameCommand(cmd); } @@ -1703,7 +1704,7 @@ void PlayerActions::actRevealRandomHandCard(int revealToPlayerId) if (revealToPlayerId != -1) { cmd.set_player_id(revealToPlayerId); } - cmd.set_zone_name("hand"); + cmd.set_zone_name(ZoneNames::HAND); cmd.add_card_id(RANDOM_CARD_FROM_ZONE); sendGameCommand(cmd); @@ -1715,7 +1716,7 @@ void PlayerActions::actRevealLibrary(int revealToPlayerId) if (revealToPlayerId != -1) { cmd.set_player_id(revealToPlayerId); } - cmd.set_zone_name("deck"); + cmd.set_zone_name(ZoneNames::DECK); sendGameCommand(cmd); } @@ -1726,7 +1727,7 @@ void PlayerActions::actLendLibrary(int lendToPlayerId) if (lendToPlayerId != -1) { cmd.set_player_id(lendToPlayerId); } - cmd.set_zone_name("deck"); + cmd.set_zone_name(ZoneNames::DECK); cmd.set_grant_write_access(true); sendGameCommand(cmd); @@ -1739,7 +1740,7 @@ void PlayerActions::actRevealTopCards(int revealToPlayerId, int amount) cmd.set_player_id(revealToPlayerId); } - cmd.set_zone_name("deck"); + cmd.set_zone_name(ZoneNames::DECK); cmd.set_top_cards(amount); // backward compatibility: servers before #1051 only permits to reveal the first card cmd.add_card_id(0); @@ -1753,7 +1754,7 @@ void PlayerActions::actRevealRandomGraveyardCard(int revealToPlayerId) if (revealToPlayerId != -1) { cmd.set_player_id(revealToPlayerId); } - cmd.set_zone_name("grave"); + cmd.set_zone_name(ZoneNames::GRAVE); cmd.add_card_id(RANDOM_CARD_FROM_ZONE); sendGameCommand(cmd); } @@ -1816,7 +1817,7 @@ void PlayerActions::cardMenuAction() } case cmClone: { auto *cmd = new Command_CreateToken; - cmd->set_zone("table"); + cmd->set_zone(ZoneNames::TABLE); cmd->set_card_name(card->getName().toStdString()); cmd->set_card_provider_id(card->getProviderId().toStdString()); cmd->set_color(card->getColor().toStdString()); @@ -1858,13 +1859,13 @@ void PlayerActions::cardMenuAction() cmd->set_start_zone(startZone.toStdString()); cmd->mutable_cards_to_move()->CopyFrom(idList); cmd->set_target_player_id(player->getPlayerInfo()->getId()); - cmd->set_target_zone("deck"); + cmd->set_target_zone(ZoneNames::DECK); cmd->set_x(0); cmd->set_y(0); if (idList.card_size() > 1) { auto *scmd = new Command_Shuffle; - scmd->set_zone_name("deck"); + scmd->set_zone_name(ZoneNames::DECK); scmd->set_start(0); scmd->set_end(idList.card_size() - 1); // inclusive, the indexed card at end will be shuffled // Server process events backwards, so... @@ -1880,13 +1881,13 @@ void PlayerActions::cardMenuAction() cmd->set_start_zone(startZone.toStdString()); cmd->mutable_cards_to_move()->CopyFrom(idList); cmd->set_target_player_id(player->getPlayerInfo()->getId()); - cmd->set_target_zone("deck"); + cmd->set_target_zone(ZoneNames::DECK); cmd->set_x(-1); cmd->set_y(0); if (idList.card_size() > 1) { auto *scmd = new Command_Shuffle; - scmd->set_zone_name("deck"); + scmd->set_zone_name(ZoneNames::DECK); scmd->set_start(-idList.card_size()); scmd->set_end(-1); // Server process events backwards, so... @@ -1902,7 +1903,7 @@ void PlayerActions::cardMenuAction() cmd->set_start_zone(startZone.toStdString()); cmd->mutable_cards_to_move()->CopyFrom(idList); cmd->set_target_player_id(player->getPlayerInfo()->getId()); - cmd->set_target_zone("hand"); + cmd->set_target_zone(ZoneNames::HAND); cmd->set_x(0); cmd->set_y(0); commandList.append(cmd); @@ -1914,7 +1915,7 @@ void PlayerActions::cardMenuAction() cmd->set_start_zone(startZone.toStdString()); cmd->mutable_cards_to_move()->CopyFrom(idList); cmd->set_target_player_id(player->getPlayerInfo()->getId()); - cmd->set_target_zone("grave"); + cmd->set_target_zone(ZoneNames::GRAVE); cmd->set_x(0); cmd->set_y(0); commandList.append(cmd); @@ -1926,7 +1927,7 @@ void PlayerActions::cardMenuAction() cmd->set_start_zone(startZone.toStdString()); cmd->mutable_cards_to_move()->CopyFrom(idList); cmd->set_target_player_id(player->getPlayerInfo()->getId()); - cmd->set_target_zone("rfg"); + cmd->set_target_zone(ZoneNames::EXILE); cmd->set_x(0); cmd->set_y(0); commandList.append(cmd); diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp index 331605918..f4c3840e0 100644 --- a/cockatrice/src/game/player/player_event_handler.cpp +++ b/cockatrice/src/game/player/player_event_handler.cpp @@ -30,6 +30,7 @@ #include #include #include +#include PlayerEventHandler::PlayerEventHandler(Player *_player) : player(_player) { @@ -321,8 +322,8 @@ void PlayerEventHandler::eventMoveCard(const Event_MoveCard &event, const GameEv } player->getPlayerMenu()->updateCardMenu(card); - if (player->getPlayerActions()->isMovingCardsUntil() && startZoneString == "deck" && - targetZone->getName() == "stack") { + if (player->getPlayerActions()->isMovingCardsUntil() && startZoneString == ZoneNames::DECK && + targetZone->getName() == ZoneNames::STACK) { player->getPlayerActions()->moveOneCardUntil(card); } } diff --git a/cockatrice/src/game/zones/logic/card_zone_logic.cpp b/cockatrice/src/game/zones/logic/card_zone_logic.cpp index 1872ba95e..e917e4ad7 100644 --- a/cockatrice/src/game/zones/logic/card_zone_logic.cpp +++ b/cockatrice/src/game/zones/logic/card_zone_logic.cpp @@ -10,6 +10,7 @@ #include #include #include +#include /** * @param _player the player that the zone belongs to @@ -174,9 +175,9 @@ void CardZoneLogic::clearContents() QString CardZoneLogic::getTranslatedName(bool theirOwn, GrammaticalCase gc) const { QString ownerName = player->getPlayerInfo()->getName(); - if (name == "hand") + if (name == ZoneNames::HAND) return (theirOwn ? tr("their hand", "nominative") : tr("%1's hand", "nominative").arg(ownerName)); - else if (name == "deck") + else if (name == ZoneNames::DECK) switch (gc) { case CaseLookAtZone: return (theirOwn ? tr("their library", "look at zone") @@ -192,11 +193,11 @@ QString CardZoneLogic::getTranslatedName(bool theirOwn, GrammaticalCase gc) cons default: return (theirOwn ? tr("their library", "nominative") : tr("%1's library", "nominative").arg(ownerName)); } - else if (name == "grave") + else if (name == ZoneNames::GRAVE) return (theirOwn ? tr("their graveyard", "nominative") : tr("%1's graveyard", "nominative").arg(ownerName)); - else if (name == "rfg") + else if (name == ZoneNames::EXILE) return (theirOwn ? tr("their exile", "nominative") : tr("%1's exile", "nominative").arg(ownerName)); - else if (name == "sb") + else if (name == ZoneNames::SIDEBOARD) switch (gc) { case CaseLookAtZone: return (theirOwn ? tr("their sideboard", "look at zone") diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index a020e4255..b6ac2150b 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -15,6 +15,7 @@ #include #include #include +#include const QColor TableZone::BACKGROUND_COLOR = QColor(100, 100, 100); const QColor TableZone::FADE_MASK = QColor(0, 0, 0, 80); @@ -195,7 +196,7 @@ void TableZone::toggleTapped() auto isCardOnTable = [](const QGraphicsItem *item) { if (auto card = qgraphicsitem_cast(item)) { - return card->getZone()->getName() == "table"; + return card->getZone()->getName() == ZoneNames::TABLE; } return false; }; 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 00561bef2..f04bcc849 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 @@ -48,6 +48,7 @@ #include #include #include +#include Server_AbstractPlayer::Server_AbstractPlayer(Server_Game *_game, int _playerId, @@ -190,8 +191,8 @@ shouldDestroyOnMove(const Server_Card *card, const Server_CardZone *startZone, c } // Allow tokens on the stack - if ((startZone->getName() == "table" || startZone->getName() == "stack") && - (targetZone->getName() == "table" || targetZone->getName() == "stack")) { + if ((startZone->getName() == ZoneNames::TABLE || startZone->getName() == ZoneNames::STACK) && + (targetZone->getName() == ZoneNames::TABLE || targetZone->getName() == ZoneNames::STACK)) { return false; } @@ -264,7 +265,7 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, } // do not allow attached cards to move around on the table - if (card->getParentCard() && targetzone->getName() == "table") { + if (card->getParentCard() && targetzone->getName() == ZoneNames::TABLE) { continue; } @@ -347,7 +348,7 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown); } else { yCoord = 0; - card->resetState(targetzone->getName() == "stack"); + card->resetState(targetzone->getName() == ZoneNames::STACK); } targetzone->insertCard(card, newX, yCoord); 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 eae23dacf..2224ddb13 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -51,6 +51,7 @@ #include #include #include +#include Server_Game::Server_Game(const ServerInfo_User &_creatorInfo, int _gameId, @@ -832,7 +833,7 @@ void Server_Game::returnCardsFromPlayer(GameEventStorage &ges, Server_AbstractPl QMutexLocker locker(&gameMutex); // Return cards to their rightful owners before conceding the game static const QRegularExpression ownerRegex{"Owner: ?([^\n]+)"}; - const auto &playerTable = player->getZones().value("table"); + const auto &playerTable = player->getZones().value(ZoneNames::TABLE); for (const auto &card : playerTable->getCards()) { if (card == nullptr) { continue; @@ -858,7 +859,7 @@ void Server_Game::returnCardsFromPlayer(GameEventStorage &ges, Server_AbstractPl continue; } - const auto &targetZone = otherPlayer->getZones().value("table"); + const auto &targetZone = otherPlayer->getZones().value(ZoneNames::TABLE); if (playerTable == nullptr || targetZone == nullptr) { continue; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp index e62f861a9..1175e4b57 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp @@ -47,6 +47,7 @@ #include #include #include +#include Server_Player::Server_Player(Server_Game *_game, int _playerId, @@ -80,15 +81,15 @@ void Server_Player::setupZones() // ------------------------------------------------------------------ // Create zones - auto *deckZone = new Server_CardZone(this, "deck", false, ServerInfo_Zone::HiddenZone); + auto *deckZone = new Server_CardZone(this, ZoneNames::DECK, false, ServerInfo_Zone::HiddenZone); addZone(deckZone); - auto *sbZone = new Server_CardZone(this, "sb", false, ServerInfo_Zone::HiddenZone); + auto *sbZone = new Server_CardZone(this, ZoneNames::SIDEBOARD, false, ServerInfo_Zone::HiddenZone); addZone(sbZone); - addZone(new Server_CardZone(this, "table", true, ServerInfo_Zone::PublicZone)); - addZone(new Server_CardZone(this, "hand", false, ServerInfo_Zone::PrivateZone)); - addZone(new Server_CardZone(this, "stack", false, ServerInfo_Zone::PublicZone)); - addZone(new Server_CardZone(this, "grave", false, ServerInfo_Zone::PublicZone)); - addZone(new Server_CardZone(this, "rfg", false, ServerInfo_Zone::PublicZone)); + addZone(new Server_CardZone(this, ZoneNames::TABLE, true, ServerInfo_Zone::PublicZone)); + addZone(new Server_CardZone(this, ZoneNames::HAND, false, ServerInfo_Zone::PrivateZone)); + addZone(new Server_CardZone(this, ZoneNames::STACK, false, ServerInfo_Zone::PublicZone)); + addZone(new Server_CardZone(this, ZoneNames::GRAVE, false, ServerInfo_Zone::PublicZone)); + addZone(new Server_CardZone(this, ZoneNames::EXILE, false, ServerInfo_Zone::PublicZone)); addCounter(new Server_Counter(0, "life", makeColor(255, 255, 255), 25, game->getStartingLifeTotal())); addCounter(new Server_Counter(1, "w", makeColor(255, 255, 150), 20, 0)); @@ -164,8 +165,8 @@ void Server_Player::addCounter(Server_Counter *counter) Response::ResponseCode Server_Player::drawCards(GameEventStorage &ges, int number) { - Server_CardZone *deckZone = zones.value("deck"); - Server_CardZone *handZone = zones.value("hand"); + Server_CardZone *deckZone = zones.value(ZoneNames::DECK); + Server_CardZone *handZone = zones.value(ZoneNames::HAND); if (deckZone->getCards().size() < number) { number = deckZone->getCards().size(); } @@ -210,7 +211,7 @@ void Server_Player::onCardBeingMoved(GameEventStorage &ges, // "Undo draw" should only remain valid if the just-drawn card stays within the user's hand (e.g., they only // reorder their hand). If a just-drawn card leaves the hand then remove cards before it from the list // (Ignore the case where the card is currently being un-drawn.) - if (startzone->getName() == "hand" && targetzone->getName() != "hand" && !undoingDraw) { + if (startzone->getName() == ZoneNames::HAND && targetzone->getName() != ZoneNames::HAND && !undoingDraw) { int index = lastDrawList.lastIndexOf(card->getId()); if (index != -1) { lastDrawList.erase(lastDrawList.begin(), lastDrawList.begin() + index); @@ -326,11 +327,11 @@ Server_Player::cmdShuffle(const Command_Shuffle &cmd, ResponseContainer & /*rc*/ return Response::RespContextError; } - if (cmd.has_zone_name() && cmd.zone_name() != "deck") { + if (cmd.has_zone_name() && cmd.zone_name() != ZoneNames::DECK) { return Response::RespFunctionNotAllowed; } - Server_CardZone *zone = zones.value("deck"); + Server_CardZone *zone = zones.value(ZoneNames::DECK); if (!zone) { return Response::RespNameNotFound; } @@ -357,8 +358,8 @@ Server_Player::cmdMulligan(const Command_Mulligan &cmd, ResponseContainer & /*rc return Response::RespContextError; } - Server_CardZone *hand = zones.value("hand"); - Server_CardZone *_deck = zones.value("deck"); + Server_CardZone *hand = zones.value(ZoneNames::HAND); + Server_CardZone *_deck = zones.value(ZoneNames::DECK); int number = cmd.number(); if (!hand->getCards().isEmpty()) { @@ -414,8 +415,8 @@ Server_Player::cmdUndoDraw(const Command_UndoDraw & /*cmd*/, ResponseContainer & Response::ResponseCode retVal; auto *cardToMove = new CardToMove; cardToMove->set_card_id(lastDrawList.takeLast()); - retVal = moveCard(ges, zones.value("hand"), QList() << cardToMove, zones.value("deck"), 0, 0, - false, true); + retVal = moveCard(ges, zones.value(ZoneNames::HAND), QList() << cardToMove, + zones.value(ZoneNames::DECK), 0, 0, false, true); delete cardToMove; return retVal; diff --git a/libcockatrice_utility/CMakeLists.txt b/libcockatrice_utility/CMakeLists.txt index 0575c260f..c0c7d8cc9 100644 --- a/libcockatrice_utility/CMakeLists.txt +++ b/libcockatrice_utility/CMakeLists.txt @@ -10,8 +10,13 @@ set(UTILITY_SOURCES libcockatrice/utility/expression.cpp libcockatrice/utility/l ) set(UTILITY_HEADERS - libcockatrice/utility/color.h libcockatrice/utility/expression.h libcockatrice/utility/levenshtein.h - libcockatrice/utility/macros.h libcockatrice/utility/passwordhasher.h libcockatrice/utility/trice_limits.h + libcockatrice/utility/color.h + libcockatrice/utility/expression.h + libcockatrice/utility/levenshtein.h + libcockatrice/utility/macros.h + libcockatrice/utility/passwordhasher.h + libcockatrice/utility/trice_limits.h + libcockatrice/utility/zone_names.h ) add_library(libcockatrice_utility STATIC ${UTILITY_SOURCES} ${UTILITY_HEADERS}) diff --git a/libcockatrice_utility/libcockatrice/utility/zone_names.h b/libcockatrice_utility/libcockatrice/utility/zone_names.h new file mode 100644 index 000000000..d1463de6a --- /dev/null +++ b/libcockatrice_utility/libcockatrice/utility/zone_names.h @@ -0,0 +1,19 @@ +#ifndef ZONE_NAMES_H +#define ZONE_NAMES_H + +namespace ZoneNames +{ +// Protocol-level zone identifiers shared between client and server. +// These must match exactly across all components. + +constexpr const char *TABLE = "table"; +constexpr const char *GRAVE = "grave"; +constexpr const char *EXILE = "rfg"; // "removed from game" +constexpr const char *HAND = "hand"; +constexpr const char *DECK = "deck"; +constexpr const char *SIDEBOARD = "sb"; +constexpr const char *STACK = "stack"; + +} // namespace ZoneNames + +#endif // ZONE_NAMES_H From 20ad9af989ffd2caace45170fc01af46fd43d818 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 12 Mar 2026 11:00:32 -0700 Subject: [PATCH 252/325] [CacheSettings] Refactor country list creation (#6687) --- .../src/client/settings/cache_settings.cpp | 266 +----------------- 1 file changed, 15 insertions(+), 251 deletions(-) diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 30093fd52..e7a5d114e 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -1115,257 +1115,21 @@ void SettingsCache::setClientVersion(const QString &_clientVersion) QStringList SettingsCache::getCountries() const { - static QStringList countries = QStringList() << "ad" - << "ae" - << "af" - << "ag" - << "ai" - << "al" - << "am" - << "ao" - << "aq" - << "ar" - << "as" - << "at" - << "au" - << "aw" - << "ax" - << "az" - << "ba" - << "bb" - << "bd" - << "be" - << "bf" - << "bg" - << "bh" - << "bi" - << "bj" - << "bl" - << "bm" - << "bn" - << "bo" - << "bq" - << "br" - << "bs" - << "bt" - << "bv" - << "bw" - << "by" - << "bz" - << "ca" - << "cc" - << "cd" - << "cf" - << "cg" - << "ch" - << "ci" - << "ck" - << "cl" - << "cm" - << "cn" - << "co" - << "cr" - << "cu" - << "cv" - << "cw" - << "cx" - << "cy" - << "cz" - << "de" - << "dj" - << "dk" - << "dm" - << "do" - << "dz" - << "ec" - << "ee" - << "eg" - << "eh" - << "er" - << "es" - << "et" - << "eu" - << "fi" - << "fj" - << "fk" - << "fm" - << "fo" - << "fr" - << "ga" - << "gb" - << "gd" - << "ge" - << "gf" - << "gg" - << "gh" - << "gi" - << "gl" - << "gm" - << "gn" - << "gp" - << "gq" - << "gr" - << "gs" - << "gt" - << "gu" - << "gw" - << "gy" - << "hk" - << "hm" - << "hn" - << "hr" - << "ht" - << "hu" - << "id" - << "ie" - << "il" - << "im" - << "in" - << "io" - << "iq" - << "ir" - << "is" - << "it" - << "je" - << "jm" - << "jo" - << "jp" - << "ke" - << "kg" - << "kh" - << "ki" - << "km" - << "kn" - << "kp" - << "kr" - << "kw" - << "ky" - << "kz" - << "la" - << "lb" - << "lc" - << "li" - << "lk" - << "lr" - << "ls" - << "lt" - << "lu" - << "lv" - << "ly" - << "ma" - << "mc" - << "md" - << "me" - << "mf" - << "mg" - << "mh" - << "mk" - << "ml" - << "mm" - << "mn" - << "mo" - << "mp" - << "mq" - << "mr" - << "ms" - << "mt" - << "mu" - << "mv" - << "mw" - << "mx" - << "my" - << "mz" - << "na" - << "nc" - << "ne" - << "nf" - << "ng" - << "ni" - << "nl" - << "no" - << "np" - << "nr" - << "nu" - << "nz" - << "om" - << "pa" - << "pe" - << "pf" - << "pg" - << "ph" - << "pk" - << "pl" - << "pm" - << "pn" - << "pr" - << "ps" - << "pt" - << "pw" - << "py" - << "qa" - << "re" - << "ro" - << "rs" - << "ru" - << "rw" - << "sa" - << "sb" - << "sc" - << "sd" - << "se" - << "sg" - << "sh" - << "si" - << "sj" - << "sk" - << "sl" - << "sm" - << "sn" - << "so" - << "sr" - << "ss" - << "st" - << "sv" - << "sx" - << "sy" - << "sz" - << "tc" - << "td" - << "tf" - << "tg" - << "th" - << "tj" - << "tk" - << "tl" - << "tm" - << "tn" - << "to" - << "tr" - << "tt" - << "tv" - << "tw" - << "tz" - << "ua" - << "ug" - << "um" - << "us" - << "uy" - << "uz" - << "va" - << "vc" - << "ve" - << "vg" - << "vi" - << "vn" - << "vu" - << "wf" - << "ws" - << "xk" - << "ye" - << "yt" - << "za" - << "zm" - << "zw"; + static const QStringList countries = { + "ad", "ae", "af", "ag", "ai", "al", "am", "ao", "aq", "ar", "as", "at", "au", "aw", "ax", "az", "ba", "bb", + "bd", "be", "bf", "bg", "bh", "bi", "bj", "bl", "bm", "bn", "bo", "bq", "br", "bs", "bt", "bv", "bw", "by", + "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr", "cu", "cv", "cw", "cx", + "cy", "cz", "de", "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er", "es", "et", "eu", "fi", "fj", + "fk", "fm", "fo", "fr", "ga", "gb", "gd", "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq", "gr", + "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr", "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", + "ir", "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki", "km", "kn", "kp", "kr", "kw", "ky", "kz", + "la", "lb", "lc", "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc", "md", "me", "mf", "mg", "mh", + "mk", "ml", "mm", "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na", "nc", + "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu", "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", + "pm", "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "rs", "ru", "rw", "sa", "sb", "sc", "sd", "se", + "sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", "so", "sr", "ss", "st", "sv", "sx", "sy", "sz", "tc", "td", + "tf", "tg", "th", "tj", "tk", "tl", "tm", "tn", "to", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "um", "us", + "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu", "wf", "ws", "xk", "ye", "yt", "za", "zm", "zw"}; return countries; } From aa4592dc9e26c2ea0d08ae68ab7d3b09b77797a8 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Thu, 12 Mar 2026 17:30:01 -0400 Subject: [PATCH 253/325] Add local game options (#6669) * Add local game options dialog. Introduces LocalGameOptions struct and DlgLocalGameOptions dialog to replace the previous QInputDialog for starting local games. Encapsulates game configuration with a simple interface that prevents parameter explosion as options are added. The dialog provides UI with settings persistence via SettingsCache * integrate local game options into main window. Replaces QInputDialog with DlgLocalGameOptions in actSinglePlayer(). The startLocalGame() function now accepts LocalGameOptions, enabling configuration of starting life total and spectator visibility in addition to player count. Also adds user documentation for the local game options flow. * Removed superfluous documentation file * removed spectator option and moved structure definition * Now remember settings separately and & shortcuts removed * re-run checks --- cockatrice/CMakeLists.txt | 1 + .../src/client/settings/cache_settings.cpp | 25 ++++++ .../src/client/settings/cache_settings.h | 21 +++++ .../dialogs/dlg_local_game_options.cpp | 83 +++++++++++++++++++ .../widgets/dialogs/dlg_local_game_options.h | 53 ++++++++++++ cockatrice/src/interface/window_main.cpp | 22 ++--- cockatrice/src/interface/window_main.h | 4 +- .../extra-pages/user_documentation/index.md | 2 +- 8 files changed, 199 insertions(+), 12 deletions(-) create mode 100644 cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp create mode 100644 cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index d675b809d..1ca3c77c2 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -39,6 +39,7 @@ set(cockatrice_SOURCES src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp src/interface/widgets/dialogs/dlg_load_remote_deck.cpp + src/interface/widgets/dialogs/dlg_local_game_options.cpp src/interface/widgets/dialogs/dlg_manage_sets.cpp src/interface/widgets/dialogs/dlg_register.cpp src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index e7a5d114e..1d8121b19 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -385,6 +385,13 @@ SettingsCache::SettingsCache() defaultStartingLifeTotal = settings->value("game/defaultstartinglifetotal", 20).toInt(); shareDecklistsOnLoad = settings->value("game/sharedecklistsonload", false).toBool(); rememberGameSettings = settings->value("game/remembergamesettings", true).toBool(); + + // Local game settings use "localgameoptions/" prefix to keep them separate + // from server game settings which use "game/" prefix + localGameRememberSettings = settings->value("localgameoptions/remembersettings", false).toBool(); + localGameMaxPlayers = settings->value("localgameoptions/maxplayers", 1).toInt(); + localGameStartingLifeTotal = settings->value("localgameoptions/startinglifetotal", 20).toInt(); + clientID = settings->value("personal/clientid", CLIENT_INFO_NOT_SET).toString(); clientVersion = settings->value("personal/clientversion", CLIENT_INFO_NOT_SET).toString(); } @@ -1242,6 +1249,24 @@ void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings) settings->setValue("game/remembergamesettings", rememberGameSettings); } +void SettingsCache::setLocalGameRememberSettings(bool value) +{ + localGameRememberSettings = value; + settings->setValue("localgameoptions/remembersettings", value); +} + +void SettingsCache::setLocalGameMaxPlayers(int value) +{ + localGameMaxPlayers = value; + settings->setValue("localgameoptions/maxplayers", value); +} + +void SettingsCache::setLocalGameStartingLifeTotal(int value) +{ + localGameStartingLifeTotal = value; + settings->setValue("localgameoptions/startinglifetotal", value); +} + void SettingsCache::setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate) { notifyAboutUpdates = static_cast(_notifyaboutupdate); diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index 5c5054105..2bbf85352 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -330,6 +330,12 @@ private: [[nodiscard]] QString getSafeConfigFilePath(QString configEntry, QString defaultPath) const; void loadPaths(); bool rememberGameSettings; + + // Local game settings (separate from server game settings in game/*) + bool localGameRememberSettings; + int localGameMaxPlayers; + int localGameStartingLifeTotal; + QList releaseChannels; bool isPortableBuild; bool roundCardCorners; @@ -862,6 +868,18 @@ public: { return rememberGameSettings; } + [[nodiscard]] bool getLocalGameRememberSettings() const + { + return localGameRememberSettings; + } + [[nodiscard]] int getLocalGameMaxPlayers() const + { + return localGameMaxPlayers; + } + [[nodiscard]] int getLocalGameStartingLifeTotal() const + { + return localGameStartingLifeTotal; + } [[nodiscard]] int getKeepAlive() const override { return keepalive; @@ -1089,6 +1107,9 @@ public slots: void setDefaultStartingLifeTotal(const int _defaultStartingLifeTotal); void setShareDecklistsOnLoad(const bool _shareDecklistsOnLoad); void setRememberGameSettings(const bool _rememberGameSettings); + void setLocalGameRememberSettings(bool value); + void setLocalGameMaxPlayers(int value); + void setLocalGameStartingLifeTotal(int value); void setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value); void setStartupCardUpdateCheckPromptForUpdate(bool value); void setStartupCardUpdateCheckAlwaysUpdate(bool value); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp new file mode 100644 index 000000000..52466ff10 --- /dev/null +++ b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp @@ -0,0 +1,83 @@ +#include "dlg_local_game_options.h" + +#include "../../../client/settings/cache_settings.h" + +#include +#include +#include +#include +#include +#include +#include + +DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent) +{ + numberPlayersLabel = new QLabel(tr("Players:"), this); + numberPlayersEdit = new QSpinBox(this); + numberPlayersEdit->setMinimum(1); + numberPlayersEdit->setMaximum(8); + numberPlayersEdit->setValue(1); + numberPlayersLabel->setBuddy(numberPlayersEdit); + + auto *generalGrid = new QGridLayout; + generalGrid->addWidget(numberPlayersLabel, 0, 0); + generalGrid->addWidget(numberPlayersEdit, 0, 1); + generalGroupBox = new QGroupBox(tr("General"), this); + generalGroupBox->setLayout(generalGrid); + + startingLifeTotalLabel = new QLabel(tr("Starting life total:"), this); + startingLifeTotalEdit = new QSpinBox(this); + startingLifeTotalEdit->setMinimum(1); + startingLifeTotalEdit->setMaximum(99999); + startingLifeTotalEdit->setValue(20); + startingLifeTotalLabel->setBuddy(startingLifeTotalEdit); + + auto *gameSetupGrid = new QGridLayout; + gameSetupGrid->addWidget(startingLifeTotalLabel, 0, 0); + gameSetupGrid->addWidget(startingLifeTotalEdit, 0, 1); + gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options"), this); + gameSetupOptionsGroupBox->setLayout(gameSetupGrid); + + rememberSettingsCheckBox = new QCheckBox(tr("Remember settings"), this); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgLocalGameOptions::actOK); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgLocalGameOptions::reject); + + auto *mainLayout = new QVBoxLayout; + mainLayout->addWidget(generalGroupBox); + mainLayout->addWidget(gameSetupOptionsGroupBox); + mainLayout->addWidget(rememberSettingsCheckBox); + mainLayout->addWidget(buttonBox); + setLayout(mainLayout); + + rememberSettingsCheckBox->setChecked(SettingsCache::instance().getLocalGameRememberSettings()); + if (rememberSettingsCheckBox->isChecked()) { + numberPlayersEdit->setValue(SettingsCache::instance().getLocalGameMaxPlayers()); + startingLifeTotalEdit->setValue(SettingsCache::instance().getLocalGameStartingLifeTotal()); + } + + setWindowTitle(tr("Local game options")); + setFixedHeight(sizeHint().height()); + + numberPlayersEdit->setFocus(); +} + +void DlgLocalGameOptions::actOK() +{ + SettingsCache::instance().setLocalGameRememberSettings(rememberSettingsCheckBox->isChecked()); + if (rememberSettingsCheckBox->isChecked()) { + SettingsCache::instance().setLocalGameMaxPlayers(numberPlayersEdit->value()); + SettingsCache::instance().setLocalGameStartingLifeTotal(startingLifeTotalEdit->value()); + } + + accept(); +} + +LocalGameOptions DlgLocalGameOptions::getOptions() const +{ + return LocalGameOptions{ + .numberPlayers = numberPlayersEdit->value(), + .startingLifeTotal = startingLifeTotalEdit->value(), + }; +} diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.h b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.h new file mode 100644 index 000000000..4307581a4 --- /dev/null +++ b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.h @@ -0,0 +1,53 @@ +/** + * @file dlg_local_game_options.h + * @ingroup RoomDialogs + * @brief Dialog for configuring local game options. + * + * Provides a user interface for setting up local games with configurable + * number of players and starting life total. + */ + +#ifndef DLG_LOCAL_GAME_OPTIONS_H +#define DLG_LOCAL_GAME_OPTIONS_H + +#include + +struct LocalGameOptions +{ + int numberPlayers = 1; + int startingLifeTotal = 20; +}; + +class QCheckBox; +class QDialogButtonBox; +class QGroupBox; +class QLabel; +class QSpinBox; + +class DlgLocalGameOptions : public QDialog +{ + Q_OBJECT +public: + explicit DlgLocalGameOptions(QWidget *parent = nullptr); + + [[nodiscard]] LocalGameOptions getOptions() const; + +private slots: + void actOK(); + +private: + QGroupBox *generalGroupBox; + QGroupBox *gameSetupOptionsGroupBox; + + QLabel *numberPlayersLabel; + QSpinBox *numberPlayersEdit; + + QLabel *startingLifeTotalLabel; + QSpinBox *startingLifeTotalEdit; + + QCheckBox *rememberSettingsCheckBox; + + QDialogButtonBox *buttonBox; +}; + +#endif // DLG_LOCAL_GAME_OPTIONS_H diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 3724ff29d..fbba6c3f5 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -27,6 +27,7 @@ #include "../interface/widgets/dialogs/dlg_forgot_password_challenge.h" #include "../interface/widgets/dialogs/dlg_forgot_password_request.h" #include "../interface/widgets/dialogs/dlg_forgot_password_reset.h" +#include "../interface/widgets/dialogs/dlg_local_game_options.h" #include "../interface/widgets/dialogs/dlg_manage_sets.h" #include "../interface/widgets/dialogs/dlg_register.h" #include "../interface/widgets/dialogs/dlg_settings.h" @@ -49,7 +50,6 @@ #include #include #include -#include #include #include #include @@ -225,16 +225,15 @@ void MainWindow::actDisconnect() void MainWindow::actSinglePlayer() { - bool ok; - int numberPlayers = - QInputDialog::getInt(this, tr("Number of players"), tr("Please enter the number of players."), 1, 1, 8, 1, &ok); - if (!ok) + DlgLocalGameOptions dlg(this); + if (dlg.exec() != QDialog::Accepted) { return; + } - startLocalGame(numberPlayers); + startLocalGame(dlg.getOptions()); } -void MainWindow::startLocalGame(int numberPlayers) +void MainWindow::startLocalGame(const LocalGameOptions &options) { aConnect->setEnabled(false); aRegister->setEnabled(false); @@ -248,7 +247,7 @@ void MainWindow::startLocalGame(int numberPlayers) QList localClients; localClients.append(mainClient); - for (int i = 0; i < numberPlayers - 1; ++i) { + for (int i = 0; i < options.numberPlayers - 1; ++i) { LocalServerInterface *slaveLsi = localServer->newConnection(); LocalClient *slaveClient = new LocalClient(slaveLsi, tr("Player %1").arg(i + 2), SettingsCache::instance().getClientID(), this); @@ -257,7 +256,8 @@ void MainWindow::startLocalGame(int numberPlayers) tabSupervisor->startLocal(localClients); Command_CreateGame createCommand; - createCommand.set_max_players(static_cast(numberPlayers)); + createCommand.set_max_players(static_cast(options.numberPlayers)); + createCommand.set_starting_life_total(options.startingLifeTotal); mainClient->sendCommand(LocalClient::prepareRoomCommand(createCommand, 0)); } @@ -913,7 +913,9 @@ MainWindow::MainWindow(QWidget *parent) void MainWindow::startupConfigCheck() { if (SettingsCache::instance().debug().getLocalGameOnStartup()) { - startLocalGame(SettingsCache::instance().debug().getLocalGamePlayerCount()); + LocalGameOptions options; + options.numberPlayers = SettingsCache::instance().debug().getLocalGamePlayerCount(); + startLocalGame(options); } if (SettingsCache::instance().getCheckUpdatesOnStartup()) { diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h index 75b0f5062..ed6de5b0d 100644 --- a/cockatrice/src/interface/window_main.h +++ b/cockatrice/src/interface/window_main.h @@ -25,6 +25,8 @@ #ifndef WINDOW_H #define WINDOW_H +#include "widgets/dialogs/dlg_local_game_options.h" + #include #include #include @@ -137,7 +139,7 @@ private: void createCardUpdateProcess(bool background = false); void exitCardDatabaseUpdate(); - void startLocalGame(int numberPlayers); + void startLocalGame(const LocalGameOptions &options); QList tabMenus; QMenu *cockatriceMenu, *dbMenu, *tabsMenu, *helpMenu, *trayIconMenu; diff --git a/doc/doxygen/extra-pages/user_documentation/index.md b/doc/doxygen/extra-pages/user_documentation/index.md index 3f10a0ac6..d7e9d529d 100644 --- a/doc/doxygen/extra-pages/user_documentation/index.md +++ b/doc/doxygen/extra-pages/user_documentation/index.md @@ -1,5 +1,5 @@ @page user_reference User Reference - + ## Deck Management - @subpage creating_decks From 2b2a6db0810937ffaf935170d1001cbbe3305cac Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Fri, 13 Mar 2026 21:22:48 +0100 Subject: [PATCH 254/325] Translate oracle/oracle_en@source.ts in it (#6692) 100% translated source file: 'oracle/oracle_en@source.ts' on 'it'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- oracle/translations/oracle_it.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/oracle/translations/oracle_it.ts b/oracle/translations/oracle_it.ts index 1765bbbca..bea5af275 100644 --- a/oracle/translations/oracle_it.ts +++ b/oracle/translations/oracle_it.ts @@ -173,7 +173,7 @@ e pedine che verranno usate da Cockatrice. spoiler - + spoiler @@ -193,7 +193,7 @@ e pedine che verranno usate da Cockatrice. Local file: - + File nel pc: @@ -203,7 +203,7 @@ e pedine che verranno usate da Cockatrice. Choose file... - + Scegli file... @@ -231,7 +231,7 @@ e pedine che verranno usate da Cockatrice. tokens - + pedine @@ -251,7 +251,7 @@ e pedine che verranno usate da Cockatrice. Local file: - + File nel pc: @@ -261,7 +261,7 @@ e pedine che verranno usate da Cockatrice. Choose file... - + Scegli file... @@ -392,12 +392,12 @@ e pedine che verranno usate da Cockatrice. Load %1 file - + Carica %1 file %1 file (%1) - + %1 file (%1) @@ -421,12 +421,12 @@ e pedine che verranno usate da Cockatrice. Please choose a file. - + Seleziona un file. Cannot open file '%1'. - + Impossibile aprire il file '%1'. From 33d5721490f04c2697c8153a560c0300edf6ea06 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 14 Mar 2026 03:41:38 -0700 Subject: [PATCH 255/325] [Game] Fix not using zone-specific card menu for opponent's cards (#6695) --- cockatrice/src/game/player/menu/card_menu.cpp | 57 ++++++++++--------- cockatrice/src/game/player/menu/card_menu.h | 9 +-- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/cockatrice/src/game/player/menu/card_menu.cpp b/cockatrice/src/game/player/menu/card_menu.cpp index ef95f052e..cd77c2968 100644 --- a/cockatrice/src/game/player/menu/card_menu.cpp +++ b/cockatrice/src/game/player/menu/card_menu.cpp @@ -113,32 +113,20 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive addAction(aSelectAll); addAction(aSelectColumn); addRelatedCardView(); - } else if (writeableCard) { - + } else { if (card->getZone()) { if (card->getZone()->getName() == ZoneNames::TABLE) { - createTableMenu(); + createTableMenu(writeableCard); } else if (card->getZone()->getName() == ZoneNames::STACK) { - createStackMenu(); + createStackMenu(writeableCard); } else if (card->getZone()->getName() == ZoneNames::EXILE || card->getZone()->getName() == ZoneNames::GRAVE) { - createGraveyardOrExileMenu(); + createGraveyardOrExileMenu(writeableCard); } else { - createHandOrCustomZoneMenu(); + createHandOrCustomZoneMenu(writeableCard); } } else { - addMenu(new MoveMenu(player)); - } - } else { - if (card->getZone() && card->getZone()->getName() != ZoneNames::HAND) { - addAction(aDrawArrow); - addSeparator(); - addRelatedCardView(); - addRelatedCardActions(); - addSeparator(); - addAction(aClone); - addSeparator(); - addAction(aSelectAll); + createZonelessMenu(writeableCard); } } } @@ -154,11 +142,9 @@ void CardMenu::removePlayer(Player *playerToRemove) } } -void CardMenu::createTableMenu() +void CardMenu::createTableMenu(bool canModifyCard) { // Card is on the battlefield - bool canModifyCard = player->getPlayerInfo()->judge || card->getOwner() == player; - if (!canModifyCard) { addRelatedCardView(); addRelatedCardActions(); @@ -213,10 +199,8 @@ void CardMenu::createTableMenu() addMenu(mCardCounters); } -void CardMenu::createStackMenu() +void CardMenu::createStackMenu(bool canModifyCard) { - bool canModifyCard = player->getPlayerInfo()->judge || card->getOwner() == player; - // Card is on the stack if (canModifyCard) { addAction(aAttach); @@ -238,10 +222,8 @@ void CardMenu::createStackMenu() addRelatedCardActions(); } -void CardMenu::createGraveyardOrExileMenu() +void CardMenu::createGraveyardOrExileMenu(bool canModifyCard) { - bool canModifyCard = player->getPlayerInfo()->judge || card->getOwner() == player; - // Card is in the graveyard or exile if (canModifyCard) { addAction(aPlay); @@ -270,8 +252,20 @@ void CardMenu::createGraveyardOrExileMenu() addRelatedCardActions(); } -void CardMenu::createHandOrCustomZoneMenu() +void CardMenu::createHandOrCustomZoneMenu(bool canModifyCard) { + if (!canModifyCard) { + addAction(aDrawArrow); + addSeparator(); + addRelatedCardView(); + addRelatedCardActions(); + addSeparator(); + addAction(aClone); + addSeparator(); + addAction(aSelectAll); + return; + } + // Card is in hand or a custom zone specified by server addAction(aPlay); addAction(aPlayFacedown); @@ -305,6 +299,13 @@ void CardMenu::createHandOrCustomZoneMenu() } } +void CardMenu::createZonelessMenu(bool canModifyCard) +{ + if (canModifyCard) { + addMenu(new MoveMenu(player)); + } +} + /** * @brief Populates the menu with an action for each active player. * diff --git a/cockatrice/src/game/player/menu/card_menu.h b/cockatrice/src/game/player/menu/card_menu.h index afe24da62..b7f2f8241 100644 --- a/cockatrice/src/game/player/menu/card_menu.h +++ b/cockatrice/src/game/player/menu/card_menu.h @@ -18,10 +18,11 @@ class CardMenu : public QMenu public: explicit CardMenu(Player *player, const CardItem *card, bool shortcutsActive); void removePlayer(Player *playerToRemove); - void createTableMenu(); - void createStackMenu(); - void createGraveyardOrExileMenu(); - void createHandOrCustomZoneMenu(); + void createTableMenu(bool canModifyCard); + void createStackMenu(bool canModifyCard); + void createGraveyardOrExileMenu(bool canModifyCard); + void createHandOrCustomZoneMenu(bool canModifyCard); + void createZonelessMenu(bool canModifyCard); QMenu *mCardCounters; From 8180d2e3b07d45f8c9d65d9c9602bc26d9fe9396 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sat, 14 Mar 2026 15:12:10 +0100 Subject: [PATCH 256/325] CI: Update compiler cache on hit (#6691) * update ccache * Update desktop-build.yml * Update desktop-build.yml --- .github/workflows/desktop-build.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 233d9e488..f99c91abb 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -202,6 +202,13 @@ jobs: --ccache "$CCACHE_SIZE" $NO_CLIENT .ci/name_build.sh + # Delete used cache to emulate a cache update. See https://github.com/actions/cache/issues/342. + - name: Delete old compiler cache (ccache) + if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit && steps.build.outcome == 'success' + env: + GH_TOKEN: ${{ github.token }} + run: gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }} + - name: Save compiler cache (ccache) if: github.ref == 'refs/heads/master' uses: actions/cache/save@v5 @@ -438,6 +445,13 @@ jobs: TARGET_MACOS_VERSION: ${{ matrix.override_target }} run: .ci/compile.sh --server --test --vcpkg + # Delete used cache to emulate a cache update. See https://github.com/actions/cache/issues/342. + - name: Delete old compiler cache (ccache) + if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 && steps.ccache_restore.outputs.cache-hit && steps.build.outcome == 'success' + env: + GH_TOKEN: ${{ github.token }} + run: gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }} + - name: Save compiler cache (ccache) if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 uses: actions/cache/save@v5 From 9bb399606ced58ed15cbb85dfb9737c130854fcb Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Sun, 15 Mar 2026 03:39:44 -0400 Subject: [PATCH 257/325] refactor: extract shared card insertion algorithm from hand/stack zones (#6701) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hand and stack zones had near-identical addCardImpl() implementations, differing only in whether resetState() preserves annotations. Extract the shared pattern into a template function (CardZoneAlgorithms::addCardToList) to eliminate duplication and enable isolated testing without Qt dependencies. Pile, table, and zone-view logic are intentionally excluded — their post-add behavior (signals, coordinate placement, hidden cards) is materially different. --- .../game/zones/logic/card_zone_algorithms.h | 45 +++++ .../src/game/zones/logic/hand_zone_logic.cpp | 16 +- .../src/game/zones/logic/stack_zone_logic.cpp | 16 +- tests/CMakeLists.txt | 1 + tests/card_zone_algorithms/CMakeLists.txt | 15 ++ .../card_zone_algorithms_test.cpp | 159 ++++++++++++++++++ 6 files changed, 226 insertions(+), 26 deletions(-) create mode 100644 cockatrice/src/game/zones/logic/card_zone_algorithms.h create mode 100644 tests/card_zone_algorithms/CMakeLists.txt create mode 100644 tests/card_zone_algorithms/card_zone_algorithms_test.cpp diff --git a/cockatrice/src/game/zones/logic/card_zone_algorithms.h b/cockatrice/src/game/zones/logic/card_zone_algorithms.h new file mode 100644 index 000000000..118a7d29d --- /dev/null +++ b/cockatrice/src/game/zones/logic/card_zone_algorithms.h @@ -0,0 +1,45 @@ +#ifndef COCKATRICE_CARD_ZONE_ALGORITHMS_H +#define COCKATRICE_CARD_ZONE_ALGORITHMS_H + +namespace CardZoneAlgorithms +{ + +/** + * Shared insertion logic for zones where cards become visible on add and follow + * the standard pattern: clamp index, insert, clear identity if contents unknown, + * reset state, show card. + * + * Zones with different post-add behavior (signal connections, positional resets, + * hidden cards, or coordinate-based placement) should NOT use this — implement + * addCardImpl directly instead. + * + * Template parameters allow testing with lightweight mocks that avoid Qt graphics + * dependencies. + * + * @tparam CardList Must provide: size() -> int, insert(int, CardType*), + * getContentsKnown() -> bool + * @tparam CardType Must provide: setId(int), setCardRef(CardRefType), + * resetState(bool), setVisible(bool) + * @param keepAnnotations Forwarded to card->resetState(). Stack-like zones preserve + * annotations across zone transitions; hand-like zones clear them. + */ +template +void addCardToList(CardList &cards, CardType *card, int x, bool keepAnnotations) +{ + if (x < 0 || x >= cards.size()) { + x = static_cast(cards.size()); + } + cards.insert(x, card); + + if (!cards.getContentsKnown()) { + card->setId(-1); + card->setCardRef({}); + } + + card->resetState(keepAnnotations); + card->setVisible(true); +} + +} // namespace CardZoneAlgorithms + +#endif // COCKATRICE_CARD_ZONE_ALGORITHMS_H diff --git a/cockatrice/src/game/zones/logic/hand_zone_logic.cpp b/cockatrice/src/game/zones/logic/hand_zone_logic.cpp index 292b9f7e3..efa85d649 100644 --- a/cockatrice/src/game/zones/logic/hand_zone_logic.cpp +++ b/cockatrice/src/game/zones/logic/hand_zone_logic.cpp @@ -1,6 +1,7 @@ #include "hand_zone_logic.h" #include "../../board/card_item.h" +#include "card_zone_algorithms.h" HandZoneLogic::HandZoneLogic(Player *_player, const QString &_name, @@ -14,16 +15,5 @@ HandZoneLogic::HandZoneLogic(Player *_player, void HandZoneLogic::addCardImpl(CardItem *card, int x, int /*y*/) { - // if x is negative set it to add at end - if (x < 0 || x >= cards.size()) { - x = cards.size(); - } - cards.insert(x, card); - - if (!cards.getContentsKnown()) { - card->setId(-1); - card->setCardRef({}); - } - card->resetState(); - card->setVisible(true); -} \ No newline at end of file + CardZoneAlgorithms::addCardToList(cards, card, x, false); +} diff --git a/cockatrice/src/game/zones/logic/stack_zone_logic.cpp b/cockatrice/src/game/zones/logic/stack_zone_logic.cpp index a477a8025..6f3cb6e99 100644 --- a/cockatrice/src/game/zones/logic/stack_zone_logic.cpp +++ b/cockatrice/src/game/zones/logic/stack_zone_logic.cpp @@ -1,6 +1,7 @@ #include "stack_zone_logic.h" #include "../../board/card_item.h" +#include "card_zone_algorithms.h" StackZoneLogic::StackZoneLogic(Player *_player, const QString &_name, @@ -14,16 +15,5 @@ StackZoneLogic::StackZoneLogic(Player *_player, void StackZoneLogic::addCardImpl(CardItem *card, int x, int /*y*/) { - // if x is negative set it to add at end - if (x < 0 || x >= cards.size()) { - x = static_cast(cards.size()); - } - cards.insert(x, card); - - if (!cards.getContentsKnown()) { - card->setId(-1); - card->setCardRef({}); - } - card->resetState(true); - card->setVisible(true); -} \ No newline at end of file + CardZoneAlgorithms::addCardToList(cards, card, x, true); +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d80ccce4f..3a9eb55cf 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -60,6 +60,7 @@ target_link_libraries( ${TEST_QT_MODULES} ) +add_subdirectory(card_zone_algorithms) add_subdirectory(carddatabase) add_subdirectory(loading_from_clipboard) add_subdirectory(oracle) diff --git a/tests/card_zone_algorithms/CMakeLists.txt b/tests/card_zone_algorithms/CMakeLists.txt new file mode 100644 index 000000000..889e92eaa --- /dev/null +++ b/tests/card_zone_algorithms/CMakeLists.txt @@ -0,0 +1,15 @@ +add_executable(card_zone_algorithms_test card_zone_algorithms_test.cpp) + +target_include_directories(card_zone_algorithms_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src/game/zones/logic) + +target_link_libraries( + card_zone_algorithms_test + PRIVATE Threads::Threads + PRIVATE ${GTEST_BOTH_LIBRARIES} +) + +add_test(NAME card_zone_algorithms_test COMMAND card_zone_algorithms_test) + +if(NOT GTEST_FOUND) + add_dependencies(card_zone_algorithms_test gtest) +endif() diff --git a/tests/card_zone_algorithms/card_zone_algorithms_test.cpp b/tests/card_zone_algorithms/card_zone_algorithms_test.cpp new file mode 100644 index 000000000..cc098cae9 --- /dev/null +++ b/tests/card_zone_algorithms/card_zone_algorithms_test.cpp @@ -0,0 +1,159 @@ +#include "card_zone_algorithms.h" + +#include +#include + +struct MockCardRef +{ +}; + +struct MockCard +{ + int idSet = 0; + bool idWasCalled = false; + MockCardRef cardRefSet{}; + bool cardRefWasCalled = false; + bool resetStateCalled = false; + bool resetStateKeepAnnotations = false; + bool visibleSet = false; + + void setId(int id) + { + idSet = id; + idWasCalled = true; + } + + void setCardRef(MockCardRef ref) + { + cardRefSet = ref; + cardRefWasCalled = true; + } + + void resetState(bool keepAnnotations) + { + resetStateCalled = true; + resetStateKeepAnnotations = keepAnnotations; + } + + void setVisible(bool visible) + { + visibleSet = visible; + } +}; + +class MockCardList +{ + std::vector cards; + bool contentsKnown; + +public: + explicit MockCardList(bool _contentsKnown) : contentsKnown(_contentsKnown) + { + } + + int size() const + { + return static_cast(cards.size()); + } + + void insert(int index, MockCard *card) + { + cards.insert(cards.begin() + index, card); + } + + bool getContentsKnown() const + { + return contentsKnown; + } + + MockCard *at(int index) const + { + return cards.at(index); + } +}; + +class AddCardAlgorithmTest : public ::testing::Test +{ +protected: + MockCardList knownList{true}; + MockCardList unknownList{false}; +}; + +TEST_F(AddCardAlgorithmTest, NegativeIndexClampsToEnd) +{ + MockCard a, b; + CardZoneAlgorithms::addCardToList(knownList, &a, 0, false); + CardZoneAlgorithms::addCardToList(knownList, &b, -1, false); + + EXPECT_EQ(knownList.at(0), &a); + EXPECT_EQ(knownList.at(1), &b); + EXPECT_EQ(knownList.size(), 2); +} + +TEST_F(AddCardAlgorithmTest, IndexBeyondSizeClampsToEnd) +{ + MockCard a, b; + CardZoneAlgorithms::addCardToList(knownList, &a, 0, false); + CardZoneAlgorithms::addCardToList(knownList, &b, 999, false); + + EXPECT_EQ(knownList.at(0), &a); + EXPECT_EQ(knownList.at(1), &b); + EXPECT_EQ(knownList.size(), 2); +} + +TEST_F(AddCardAlgorithmTest, ContentsKnownPreservesIdentity) +{ + MockCard card; + CardZoneAlgorithms::addCardToList(knownList, &card, 0, false); + + EXPECT_FALSE(card.idWasCalled); + EXPECT_FALSE(card.cardRefWasCalled); + EXPECT_TRUE(card.visibleSet); +} + +TEST_F(AddCardAlgorithmTest, ContentsUnknownClearsIdentity) +{ + MockCard card; + CardZoneAlgorithms::addCardToList(unknownList, &card, 0, false); + + EXPECT_TRUE(card.idWasCalled); + EXPECT_EQ(card.idSet, -1); + EXPECT_TRUE(card.cardRefWasCalled); +} + +TEST_F(AddCardAlgorithmTest, MidListInsertionPreservesOrder) +{ + MockCard a, b, c; + CardZoneAlgorithms::addCardToList(knownList, &a, 0, 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), &c); + EXPECT_EQ(knownList.at(2), &b); +} + +TEST_F(AddCardAlgorithmTest, KeepAnnotationsFalsePassedThrough) +{ + MockCard card; + CardZoneAlgorithms::addCardToList(knownList, &card, 0, false); + + EXPECT_TRUE(card.resetStateCalled); + EXPECT_FALSE(card.resetStateKeepAnnotations); +} + +TEST_F(AddCardAlgorithmTest, KeepAnnotationsTruePassedThrough) +{ + MockCard card; + CardZoneAlgorithms::addCardToList(knownList, &card, 0, true); + + EXPECT_TRUE(card.resetStateCalled); + EXPECT_TRUE(card.resetStateKeepAnnotations); +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From ce652de272f816d88fdd475fd00025965bd1b5d5 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Sun, 15 Mar 2026 12:24:50 -0400 Subject: [PATCH 258/325] Fix/cmake test only build (#6709) * fix(cmake): guard filter_string_test behind WITH_ORACLE or WITH_CLIENT. filter_string_test links against libcockatrice_filters, which is only built when WITH_ORACLE or WITH_CLIENT is enabled. Without this guard, test-only builds fail at configure time because the target doesn't exist. The guard condition mirrors the one in the root CMakeLists.txt that controls whether libcockatrice_filters is built. * fix(cmake): centralize TEST_QT_MODULES in FindQtRuntime.cmake Each test CMakeLists.txt was independently defining TEST_QT_MODULES with its own subset of Qt modules. This duplicated knowledge that already lives in FindQtRuntime.cmake (which handles module discovery for all other targets: SERVATRICE, COCKATRICE, ORACLE). Consolidate into a single definition using the union of all test requirements (Concurrent Network Svg Widgets), matching the existing pattern for application-target modules. This ensures test-only builds (-DTEST=ON without application targets) discover all necessary Qt components. * fix(cmake): guard libcockatrice_network behind application targets. libcockatrice_network is only needed by the client, server, and oracle targets. Other application-specific libraries (settings, models, filters) already have similar guards. This was an oversight that caused test-only builds to fail when network dependencies weren't available. --- CMakeLists.txt | 11 +++++-- cmake/FindQtRuntime.cmake | 4 ++- tests/CMakeLists.txt | 2 ++ tests/carddatabase/CMakeLists.txt | 33 ++++++++++----------- tests/loading_from_clipboard/CMakeLists.txt | 4 --- tests/oracle/CMakeLists.txt | 2 -- 6 files changed, 30 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb4f3bd2d..fe808a652 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -254,7 +254,9 @@ endif() set(CPACK_PACKAGE_CONTACT "Zach Halpern ") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_NAME}") set(CPACK_PACKAGE_VENDOR "Cockatrice Development Team") -set(CPACK_PACKAGE_DESCRIPTION "Cockatrice is an open-source, multiplatform application for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline.") +set(CPACK_PACKAGE_DESCRIPTION + "Cockatrice is an open-source, multiplatform application for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline." +) set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}") @@ -328,7 +330,12 @@ include(CPack) add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_interfaces ${CMAKE_BINARY_DIR}/libcockatrice_interfaces) add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_protocol ${CMAKE_BINARY_DIR}/libcockatrice_protocol) -add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_network ${CMAKE_BINARY_DIR}/libcockatrice_network) +if(WITH_CLIENT + OR WITH_SERVER + OR WITH_ORACLE +) + add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_network ${CMAKE_BINARY_DIR}/libcockatrice_network) +endif() add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_deck_list ${CMAKE_BINARY_DIR}/libcockatrice_deck_list) add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_rng ${CMAKE_BINARY_DIR}/libcockatrice_rng) add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_card ${CMAKE_BINARY_DIR}/libcockatrice_card) diff --git a/cmake/FindQtRuntime.cmake b/cmake/FindQtRuntime.cmake index c205ebdcf..42060c835 100644 --- a/cmake/FindQtRuntime.cmake +++ b/cmake/FindQtRuntime.cmake @@ -29,7 +29,9 @@ if(WITH_ORACLE) set(_ORACLE_NEEDED Concurrent Network Svg Widgets) endif() if(TEST) - set(_TEST_NEEDED Widgets) + # Union of Qt modules required across all test targets (independent of application targets). + # When adding a new test that needs additional Qt modules, add them here rather than in the test's CMakeLists.txt. + set(_TEST_NEEDED Concurrent Network Svg Widgets) endif() set(REQUIRED_QT_COMPONENTS ${REQUIRED_QT_COMPONENTS} ${_SERVATRICE_NEEDED} ${_COCKATRICE_NEEDED} ${_ORACLE_NEEDED} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3a9eb55cf..c5346e59f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,3 +1,5 @@ +# NOTE: Qt modules for tests are defined centrally in cmake/FindQtRuntime.cmake (the _TEST_NEEDED variable). +# If a new test needs additional Qt modules, add them there — not in individual test CMakeLists.txt files. enable_testing() add_test(NAME dummy_test COMMAND dummy_test) diff --git a/tests/carddatabase/CMakeLists.txt b/tests/carddatabase/CMakeLists.txt index 4eeffc363..987e23cd5 100644 --- a/tests/carddatabase/CMakeLists.txt +++ b/tests/carddatabase/CMakeLists.txt @@ -6,13 +6,6 @@ project(CardDatabaseTests VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MI # ------------------------ add_definitions("-DCARDDB_DATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/data/\"") -# ------------------------ -# Qt modules -# ------------------------ -set(TEST_QT_MODULES ${COCKATRICE_QT_VERSION_NAME}::Concurrent ${COCKATRICE_QT_VERSION_NAME}::Network - ${COCKATRICE_QT_VERSION_NAME}::Widgets ${COCKATRICE_QT_VERSION_NAME}::Svg -) - # ------------------------ # Card Database Test # ------------------------ @@ -30,23 +23,29 @@ add_test(NAME carddatabase_test COMMAND carddatabase_test) # ------------------------ # Filter String Test +# (guard must match the condition for libcockatrice_filters in the root CMakeLists.txt) # ------------------------ -add_executable(filter_string_test ${MOCKS_SOURCES} ${VERSION_STRING_CPP} filter_string_test.cpp mocks.cpp) +if(WITH_ORACLE OR WITH_CLIENT) + add_executable(filter_string_test ${MOCKS_SOURCES} ${VERSION_STRING_CPP} filter_string_test.cpp mocks.cpp) -target_link_libraries( - filter_string_test - PRIVATE libcockatrice_filters - PRIVATE Threads::Threads - PRIVATE ${GTEST_BOTH_LIBRARIES} - PRIVATE ${TEST_QT_MODULES} -) + target_link_libraries( + filter_string_test + PRIVATE libcockatrice_filters + PRIVATE Threads::Threads + PRIVATE ${GTEST_BOTH_LIBRARIES} + PRIVATE ${TEST_QT_MODULES} + ) -add_test(NAME filter_string_test COMMAND filter_string_test) + add_test(NAME filter_string_test COMMAND filter_string_test) + + if(NOT GTEST_FOUND) + add_dependencies(filter_string_test gtest) + endif() +endif() # ------------------------ # Dependencies on gtest # ------------------------ if(NOT GTEST_FOUND) add_dependencies(carddatabase_test gtest) - add_dependencies(filter_string_test gtest) endif() diff --git a/tests/loading_from_clipboard/CMakeLists.txt b/tests/loading_from_clipboard/CMakeLists.txt index 85133a8e8..719d62f45 100644 --- a/tests/loading_from_clipboard/CMakeLists.txt +++ b/tests/loading_from_clipboard/CMakeLists.txt @@ -5,10 +5,6 @@ if(NOT GTEST_FOUND) add_dependencies(loading_from_clipboard_test gtest) endif() -set(TEST_QT_MODULES ${COCKATRICE_QT_VERSION_NAME}::Concurrent ${COCKATRICE_QT_VERSION_NAME}::Network - ${COCKATRICE_QT_VERSION_NAME}::Widgets -) - target_link_libraries( loading_from_clipboard_test libcockatrice_deck_list libcockatrice_card Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} diff --git a/tests/oracle/CMakeLists.txt b/tests/oracle/CMakeLists.txt index b89663d6f..c5c1e9097 100644 --- a/tests/oracle/CMakeLists.txt +++ b/tests/oracle/CMakeLists.txt @@ -4,8 +4,6 @@ if(NOT GTEST_FOUND) add_dependencies(parse_cipt_test gtest) endif() -set(TEST_QT_MODULES ${COCKATRICE_QT_VERSION_NAME}::Widgets) - target_link_libraries(parse_cipt_test Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}) add_test(NAME parse_cipt_test COMMAND parse_cipt_test) From cf01dc770b349abaca1b88f573fd0bfee1686416 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 15 Mar 2026 17:41:17 +0100 Subject: [PATCH 259/325] CI: Adjustments to ccache usage (#6703) * Increase ccache size * Fix Arch ccache usage * more cleanup * harmonize names --- .github/workflows/desktop-build.yml | 30 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index f99c91abb..021bf9e81 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -153,10 +153,10 @@ jobs: continue-on-error: ${{matrix.allow-failure == 'yes'}} env: NAME: ${{matrix.distro}}${{matrix.version}} - CACHE: ${{github.workspace}}/.cache/${{matrix.distro}}${{matrix.version}} # directory for caching docker image and ccache + CACHE_DIR: ${{github.workspace}}/.cache/${{matrix.distro}}${{matrix.version}} # directory for caching docker image and ccache # Cache size over the entire repo is 10Gi: # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy - CCACHE_SIZE: 500M + CCACHE_SIZE: 550M CMAKE_GENERATOR: 'Ninja' steps: @@ -169,7 +169,7 @@ jobs: env: BRANCH_NAME: ${{ github.head_ref || github.ref_name }} with: - path: ${{env.CACHE}} + path: ${{env.CACHE_DIR}} key: ccache-${{matrix.distro}}${{matrix.version}}-${{env.BRANCH_NAME}} restore-keys: ccache-${{matrix.distro}}${{matrix.version}}- @@ -202,18 +202,18 @@ jobs: --ccache "$CCACHE_SIZE" $NO_CLIENT .ci/name_build.sh - # Delete used cache to emulate a cache update. See https://github.com/actions/cache/issues/342. - - name: Delete old compiler cache (ccache) - if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit && steps.build.outcome == 'success' + # Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342. + - name: Delete remote compiler cache (ccache) + if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit env: GH_TOKEN: ${{ github.token }} run: gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }} - - name: Save compiler cache (ccache) + - name: Save updated compiler cache (ccache) if: github.ref == 'refs/heads/master' uses: actions/cache/save@v5 with: - path: ${{env.CACHE}} + path: ${{env.CACHE_DIR}} key: ${{ steps.ccache_restore.outputs.cache-primary-key }} - name: Upload artifact @@ -330,7 +330,7 @@ jobs: CCACHE_DIR: ${{github.workspace}}/.cache/ # Cache size over the entire repo is 10Gi: # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy - CCACHE_SIZE: 500M + CCACHE_SIZE: 550M steps: - name: Checkout @@ -445,21 +445,19 @@ jobs: TARGET_MACOS_VERSION: ${{ matrix.override_target }} run: .ci/compile.sh --server --test --vcpkg - # Delete used cache to emulate a cache update. See https://github.com/actions/cache/issues/342. - - name: Delete old compiler cache (ccache) - if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 && steps.ccache_restore.outputs.cache-hit && steps.build.outcome == 'success' + # Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342. + - name: Delete remote compiler cache (ccache) + if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 && steps.ccache_restore.outputs.cache-hit env: GH_TOKEN: ${{ github.token }} run: gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }} - - name: Save compiler cache (ccache) + - name: Save updated compiler cache (ccache) if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 uses: actions/cache/save@v5 - env: - BRANCH_NAME: ${{ github.head_ref || github.ref_name }} with: path: ${{env.CCACHE_DIR}} - key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}} + key: ${{ steps.ccache_restore.outputs.cache-primary-key }} - name: Sign app bundle if: matrix.os == 'macOS' && matrix.make_package && needs.configure.outputs.tag != null From 96f436b65e1f1bd63a7012af6ce692e42a04e340 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 15 Mar 2026 17:47:48 +0100 Subject: [PATCH 260/325] CI: Resolve to latest Qt version in range for Windows as well (#6700) * CI: Resolve to latest Qt version in range for Windows as well * install aqt * Check dedicated win version --- .ci/resolve_latest_aqt_qt_version.sh | 11 ++++++++++- .github/workflows/desktop-build.yml | 9 +++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.ci/resolve_latest_aqt_qt_version.sh b/.ci/resolve_latest_aqt_qt_version.sh index 154c15321..066140ac9 100755 --- a/.ci/resolve_latest_aqt_qt_version.sh +++ b/.ci/resolve_latest_aqt_qt_version.sh @@ -27,7 +27,16 @@ if ! hash aqt; then fi # Resolve latest patch -if ! qt_resolved=$(aqt list-qt mac desktop --spec "$qt_spec" --latest-version); then +if [[ $RUNNER_OS == macOS ]]; then + if ! qt_resolved=$(aqt list-qt mac desktop --spec "$qt_spec" --latest-version); then + exit 1 + fi +elif [[ $RUNNER_OS == Windows ]]; then + if ! qt_resolved=$(aqt list-qt windows desktop --spec "$qt_spec" --latest-version); then + exit 1 + fi +else + echo "aqt command for $RUNNER_OS not defined." exit 1 fi diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 021bf9e81..e176ae527 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -361,15 +361,12 @@ jobs: restore-keys: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}- - name: Install aqtinstall - if: matrix.os == 'macOS' run: pipx install aqtinstall - # Checking if there's a newer, uncached version of Qt available to install via aqtinstall + # Resolve given wildcard versions (e.g. Qt 6.6.*) to latest version via aqtinstall to avoid stale caches on new releases - name: Resolve latest Qt patch version - if: matrix.os == 'macOS' id: resolve_qt_version shell: bash - # Ouputs the version of Qt to install via aqtinstall run: .ci/resolve_latest_aqt_qt_version.sh "${{matrix.qt_version}}" - name: Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries (${{ matrix.soc }} macOS) @@ -386,10 +383,10 @@ jobs: if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' uses: jurplel/install-qt-action@v4 with: - cache: false version: ${{ steps.resolve_qt_version.outputs.version }} arch: ${{matrix.qt_arch}} modules: ${{matrix.qt_modules}} + cache: false dir: ${{github.workspace}} - name: Thin Qt libraries (${{ matrix.soc }} macOS) @@ -407,7 +404,7 @@ jobs: if: matrix.os == 'Windows' uses: jurplel/install-qt-action@v4 with: - version: ${{matrix.qt_version}} + version: ${{ steps.resolve_qt_version.outputs.version }} arch: ${{matrix.qt_arch}} modules: ${{matrix.qt_modules}} cache: true From 4a3d79d00b16041f1ac91da81641716d5d165178 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 21:10:22 +0100 Subject: [PATCH 261/325] Bump docker/build-push-action from 6 to 7 (#6714) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v6...v7) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 65ccd1322..e1f2795b1 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -55,7 +55,7 @@ jobs: password: ${{ github.token }} - name: Build and push Docker image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . platforms: linux/amd64,linux/arm64 From b4a5c863d75f5e4b3f6ca72b8fbcefcb0afa124f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 21:11:32 +0100 Subject: [PATCH 262/325] Bump docker/metadata-action from 5 to 6 (#6713) Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6. - [Release notes](https://github.com/docker/metadata-action/releases) - [Commits](https://github.com/docker/metadata-action/compare/v5...v6) --- updated-dependencies: - dependency-name: docker/metadata-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index e1f2795b1..2a4aab1ea 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -27,7 +27,7 @@ jobs: - name: Docker metadata id: metadata - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: | ghcr.io/cockatrice/servatrice From dd8164611b7899e545f43dfd8aa17212afb1b4b9 Mon Sep 17 00:00:00 2001 From: tooomm Date: Mon, 16 Mar 2026 23:38:04 +0100 Subject: [PATCH 263/325] revert env name (#6711) --- .github/workflows/desktop-build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index e176ae527..02c3f7aec 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -153,7 +153,7 @@ jobs: continue-on-error: ${{matrix.allow-failure == 'yes'}} env: NAME: ${{matrix.distro}}${{matrix.version}} - CACHE_DIR: ${{github.workspace}}/.cache/${{matrix.distro}}${{matrix.version}} # directory for caching docker image and ccache + CACHE: ${{github.workspace}}/.cache/${{matrix.distro}}${{matrix.version}} # directory for caching docker image and ccache # Cache size over the entire 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 @@ -169,7 +169,7 @@ jobs: env: BRANCH_NAME: ${{ github.head_ref || github.ref_name }} with: - path: ${{env.CACHE_DIR}} + path: ${{env.CACHE}} key: ccache-${{matrix.distro}}${{matrix.version}}-${{env.BRANCH_NAME}} restore-keys: ccache-${{matrix.distro}}${{matrix.version}}- @@ -213,7 +213,7 @@ jobs: if: github.ref == 'refs/heads/master' uses: actions/cache/save@v5 with: - path: ${{env.CACHE_DIR}} + path: ${{env.CACHE}} key: ${{ steps.ccache_restore.outputs.cache-primary-key }} - name: Upload artifact From 69c046cca426339caec5023ea2de18df85d0d697 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Mon, 16 Mar 2026 23:38:47 +0100 Subject: [PATCH 264/325] remove all separate storing of widget sizes, rely on geometry (#6712) --- .../widgets/tabs/abstract_tab_deck_editor.h | 1 - .../widgets/tabs/tab_deck_editor.cpp | 26 -------- .../interface/widgets/tabs/tab_deck_editor.h | 3 - .../src/interface/widgets/tabs/tab_game.cpp | 60 +++---------------- .../src/interface/widgets/tabs/tab_game.h | 1 - .../tab_deck_editor_visual.cpp | 24 -------- .../tab_deck_editor_visual.h | 7 +-- .../settings/layouts_settings.cpp | 44 -------------- .../libcockatrice/settings/layouts_settings.h | 8 --- 9 files changed, 8 insertions(+), 166 deletions(-) diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h index 3730a7cdd..477c3f973 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h @@ -237,7 +237,6 @@ protected slots: // UI Layout Management virtual void loadLayout() = 0; virtual void restartLayout() = 0; - virtual void freeDocksSize() = 0; virtual void refreshShortcuts() = 0; /** @brief Handles dock close events. */ diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp index cf258d454..59ee6bdeb 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.cpp @@ -143,17 +143,6 @@ void TabDeckEditor::loadLayout() restoreState(layoutState); restoreGeometry(layouts.getDeckEditorGeometry()); } - - for (auto it = dockToActions.constKeyValueBegin(); it != dockToActions.constKeyValueEnd(); ++it) { - auto dockWidget = it->first; - auto actions = it->second; - - QSize size = layouts.getDeckEditorWidgetSize(dockWidget->objectName(), actions.defaultSize); - dockWidget->setMinimumSize(size); - dockWidget->setMaximumSize(size); - } - - QTimer::singleShot(100, this, &TabDeckEditor::freeDocksSize); } /** @@ -177,17 +166,6 @@ void TabDeckEditor::restartLayout() splitDockWidget(printingSelectorDockWidget, deckDockWidget, Qt::Horizontal); splitDockWidget(cardInfoDockWidget, printingSelectorDockWidget, Qt::Horizontal); splitDockWidget(cardInfoDockWidget, filterDockWidget, Qt::Vertical); - - QTimer::singleShot(100, this, &TabDeckEditor::freeDocksSize); -} - -/** @brief Frees dock sizes to allow flexible resizing. */ -void TabDeckEditor::freeDocksSize() -{ - for (auto dockWidget : dockToActions.keys()) { - dockWidget->setMinimumSize(100, 100); - dockWidget->setMaximumSize(5000, 5000); - } } /** @@ -202,10 +180,6 @@ bool TabDeckEditor::eventFilter(QObject *o, QEvent *e) LayoutsSettings &layouts = SettingsCache::instance().layouts(); layouts.setDeckEditorLayoutState(saveState()); layouts.setDeckEditorGeometry(saveGeometry()); - - for (auto dockWidget : dockToActions.keys()) { - layouts.setDeckEditorWidgetSize(dockWidget->objectName(), dockWidget->size()); - } } return false; } diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h index 285d241d2..ab7a0bfc5 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_editor.h @@ -62,9 +62,6 @@ protected slots: /** @brief Resets the layout to default positions and dock states. */ void restartLayout() override; - /** @brief Frees the dock sizes for resizing flexibility. */ - void freeDocksSize() override; - /** @brief Refreshes shortcuts for this tab from settings. */ void refreshShortcuts() override; diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index fae9dffe5..cf8269069 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -1085,38 +1085,9 @@ void TabGame::loadLayout() if (replayDock) { restoreGeometry(layouts.getReplayPlayAreaGeometry()); restoreState(layouts.getReplayPlayAreaLayoutState()); - - for (auto it = dockToActions.constKeyValueBegin(); it != dockToActions.constKeyValueEnd(); ++it) { - auto dockWidget = it->first; - auto actions = it->second; - - QSize size = layouts.getReplayPlayAreaWidgetSize(dockWidget->objectName(), actions.defaultSize); - dockWidget->setMinimumSize(size); - dockWidget->setMaximumSize(size); - } - } else { restoreGeometry(layouts.getGamePlayAreaGeometry()); restoreState(layouts.getGamePlayAreaLayoutState()); - - for (auto it = dockToActions.constKeyValueBegin(); it != dockToActions.constKeyValueEnd(); ++it) { - auto dockWidget = it->first; - auto actions = it->second; - - QSize size = layouts.getGamePlayAreaWidgetSize(dockWidget->objectName(), actions.defaultSize); - dockWidget->setMinimumSize(size); - dockWidget->setMaximumSize(size); - } - } - - QTimer::singleShot(100, this, &TabGame::freeDocksSize); -} - -void TabGame::freeDocksSize() -{ - for (auto dockWidget : dockToActions.keys()) { - dockWidget->setMinimumSize(100, 100); - dockWidget->setMaximumSize(5000, 5000); } } @@ -1139,24 +1110,15 @@ void TabGame::actResetLayout() replayDock->setFloating(false); addDockWidget(Qt::BottomDockWidgetArea, replayDock); - cardInfoDock->setMinimumSize(250, 360); - cardInfoDock->setMaximumSize(250, 360); - messageLayoutDock->setMinimumSize(250, 200); - messageLayoutDock->setMaximumSize(250, 200); - playerListDock->setMinimumSize(250, 50); - playerListDock->setMaximumSize(250, 50); - replayDock->setMinimumSize(900, 100); - replayDock->setMaximumSize(900, 100); + cardInfoDock->resize(250, 360); + messageLayoutDock->resize(250, 200); + playerListDock->resize(250, 50); + replayDock->resize(900, 100); } else { - cardInfoDock->setMinimumSize(250, 360); - cardInfoDock->setMaximumSize(250, 360); - messageLayoutDock->setMinimumSize(250, 250); - messageLayoutDock->setMaximumSize(250, 250); - playerListDock->setMinimumSize(250, 50); - playerListDock->setMaximumSize(250, 50); + cardInfoDock->resize(250, 360); + messageLayoutDock->resize(250, 250); + playerListDock->resize(250, 50); } - - QTimer::singleShot(100, this, &TabGame::freeDocksSize); } void TabGame::createPlayAreaWidget(bool bReplay) @@ -1334,17 +1296,9 @@ void TabGame::hideEvent(QHideEvent *event) if (replayDock) { layouts.setReplayPlayAreaState(saveState()); layouts.setReplayPlayAreaGeometry(saveGeometry()); - - for (auto dockWidget : dockToActions.keys()) { - layouts.setReplayPlayAreaWidgetSize(dockWidget->objectName(), dockWidget->size()); - } } else { layouts.setGamePlayAreaState(saveState()); layouts.setGamePlayAreaGeometry(saveGeometry()); - - for (auto dockWidget : dockToActions.keys()) { - layouts.setGamePlayAreaWidgetSize(dockWidget->objectName(), dockWidget->size()); - } } Tab::hideEvent(event); diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.h b/cockatrice/src/interface/widgets/tabs/tab_game.h index c35557743..d8746ccc9 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.h +++ b/cockatrice/src/interface/widgets/tabs/tab_game.h @@ -165,7 +165,6 @@ private slots: void notifyPlayerKicked(); void processPlayerLeave(Player *leavingPlayer); void actResetLayout(); - void freeDocksSize(); void hideEvent(QHideEvent *event) override; diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 29f871823..d03ac483b 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -246,15 +246,6 @@ void TabDeckEditorVisual::showPrintingSelector() printingSelectorDockWidget->setVisible(true); } -/** @brief Set size restrictions for free floating dock widgets. */ -void TabDeckEditorVisual::freeDocksSize() -{ - for (auto dockWidget : dockToActions.keys()) { - dockWidget->setMinimumSize(100, 100); - dockWidget->setMaximumSize(5000, 5000); - } -} - /** @brief Refreshes keyboard shortcuts for this tab from settings. */ void TabDeckEditorVisual::refreshShortcuts() { @@ -273,17 +264,6 @@ void TabDeckEditorVisual::loadLayout() restoreState(layoutState); restoreGeometry(layouts.getVisualDeckEditorGeometry()); } - - for (auto it = dockToActions.constKeyValueBegin(); it != dockToActions.constKeyValueEnd(); ++it) { - auto dockWidget = it->first; - auto actions = it->second; - - QSize size = layouts.getVisualDeckEditorWidgetSize(dockWidget->objectName(), actions.defaultSize); - dockWidget->setMinimumSize(size); - dockWidget->setMaximumSize(size); - } - - QTimer::singleShot(100, this, &TabDeckEditorVisual::freeDocksSize); } /** @brief Resets the layout to default positions and dock states. */ @@ -346,10 +326,6 @@ bool TabDeckEditorVisual::eventFilter(QObject *o, QEvent *e) LayoutsSettings &layouts = SettingsCache::instance().layouts(); layouts.setVisualDeckEditorLayoutState(saveState()); layouts.setVisualDeckEditorGeometry(saveGeometry()); - - for (auto dockWidget : dockToActions.keys()) { - layouts.setVisualDeckEditorWidgetSize(dockWidget->objectName(), dockWidget->size()); - } } return false; } diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h index 371699c4d..8a0677c9d 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h @@ -66,11 +66,6 @@ protected slots: */ void restartLayout() override; - /** - * @brief Set size restrictions for free floating dock widgets. - */ - void freeDocksSize() override; - /** * @brief Refresh keyboard shortcuts for this tab. */ @@ -178,4 +173,4 @@ public slots: bool actSaveDeckAs() override; }; -#endif \ No newline at end of file +#endif diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp index 0ec03ba07..f7704cbc7 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp @@ -48,17 +48,6 @@ void LayoutsSettings::setDeckEditorGeometry(const QByteArray &value) setValue(value, GEOMETRY_PROP, GROUP_DECK_EDITOR); } -void LayoutsSettings::setDeckEditorWidgetSize(const QString &widgetName, const QSize &value) -{ - setValue(value, widgetName, GROUP_DECK_EDITOR, SIZE_PROP); -} - -QSize LayoutsSettings::getDeckEditorWidgetSize(const QString &widgetName, const QSize &defaultValue) -{ - QVariant previous = getValue(widgetName, GROUP_DECK_EDITOR, SIZE_PROP); - return previous == QVariant() ? defaultValue : previous.toSize(); -} - QByteArray LayoutsSettings::getVisualDeckEditorLayoutState() { return getValue(STATE_PROP, GROUP_VISUAL_DECK_EDITOR).toByteArray(); @@ -79,17 +68,6 @@ void LayoutsSettings::setVisualDeckEditorGeometry(const QByteArray &value) setValue(value, GEOMETRY_PROP, GROUP_VISUAL_DECK_EDITOR); } -void LayoutsSettings::setVisualDeckEditorWidgetSize(const QString &widgetName, const QSize &value) -{ - setValue(value, widgetName, GROUP_VISUAL_DECK_EDITOR, SIZE_PROP); -} - -QSize LayoutsSettings::getVisualDeckEditorWidgetSize(const QString &widgetName, const QSize &defaultValue) -{ - QVariant previous = getValue(widgetName, GROUP_VISUAL_DECK_EDITOR, SIZE_PROP); - return previous == QVariant() ? defaultValue : previous.toSize(); -} - QByteArray LayoutsSettings::getDeckEditorDbHeaderState() { return getValue(STATE_PROP, GROUP_DECK_EDITOR_DB, "header").toByteArray(); @@ -150,17 +128,6 @@ QByteArray LayoutsSettings::getGamePlayAreaGeometry() return getValue(GEOMETRY_PROP, GROUP_GAME_PLAY_AREA).toByteArray(); } -void LayoutsSettings::setGamePlayAreaWidgetSize(const QString &widgetName, const QSize &value) -{ - setValue(value, widgetName, GROUP_GAME_PLAY_AREA, SIZE_PROP); -} - -QSize LayoutsSettings::getGamePlayAreaWidgetSize(const QString &widgetName, const QSize &defaultValue) -{ - QVariant previous = getValue(widgetName, GROUP_GAME_PLAY_AREA, SIZE_PROP); - return previous == QVariant() ? defaultValue : previous.toSize(); -} - void LayoutsSettings::setReplayPlayAreaGeometry(const QByteArray &value) { setValue(value, GEOMETRY_PROP, GROUP_REPLAY_PLAY_AREA); @@ -180,14 +147,3 @@ QByteArray LayoutsSettings::getReplayPlayAreaGeometry() { return getValue(GEOMETRY_PROP, GROUP_REPLAY_PLAY_AREA).toByteArray(); } - -void LayoutsSettings::setReplayPlayAreaWidgetSize(const QString &widgetName, const QSize &value) -{ - setValue(value, widgetName, GROUP_REPLAY_PLAY_AREA, SIZE_PROP); -} - -QSize LayoutsSettings::getReplayPlayAreaWidgetSize(const QString &widgetName, const QSize &defaultValue) -{ - QVariant previous = getValue(widgetName, GROUP_REPLAY_PLAY_AREA, SIZE_PROP); - return previous == QVariant() ? defaultValue : previous.toSize(); -} \ No newline at end of file diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h index 620753815..cab9a456e 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h @@ -21,11 +21,9 @@ public: void setDeckEditorLayoutState(const QByteArray &value); void setDeckEditorGeometry(const QByteArray &value); - void setDeckEditorWidgetSize(const QString &widgetName, const QSize &value); void setVisualDeckEditorLayoutState(const QByteArray &value); void setVisualDeckEditorGeometry(const QByteArray &value); - void setVisualDeckEditorWidgetSize(const QString &widgetName, const QSize &value); void setDeckEditorDbHeaderState(const QByteArray &value); void setSetsDialogHeaderState(const QByteArray &value); @@ -34,21 +32,17 @@ public: void setGamePlayAreaGeometry(const QByteArray &value); void setGamePlayAreaState(const QByteArray &value); - void setGamePlayAreaWidgetSize(const QString &widgetName, const QSize &value); void setReplayPlayAreaGeometry(const QByteArray &value); void setReplayPlayAreaState(const QByteArray &value); - void setReplayPlayAreaWidgetSize(const QString &widgetName, const QSize &value); QByteArray getMainWindowGeometry(); QByteArray getDeckEditorLayoutState(); QByteArray getDeckEditorGeometry(); - QSize getDeckEditorWidgetSize(const QString &widgetName, const QSize &defaultValue = {}); QByteArray getVisualDeckEditorLayoutState(); QByteArray getVisualDeckEditorGeometry(); - QSize getVisualDeckEditorWidgetSize(const QString &widgetName, const QSize &defaultValue = {}); QByteArray getDeckEditorDbHeaderState(); QByteArray getSetsDialogHeaderState(); @@ -57,11 +51,9 @@ public: QByteArray getGamePlayAreaLayoutState(); QByteArray getGamePlayAreaGeometry(); - QSize getGamePlayAreaWidgetSize(const QString &widgetName, const QSize &defaultValue = {}); QByteArray getReplayPlayAreaLayoutState(); QByteArray getReplayPlayAreaGeometry(); - QSize getReplayPlayAreaWidgetSize(const QString &widgetName, const QSize &defaultValue = {}); signals: public slots: From fc453c68a70d374632977882f35d188775f64359 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Mon, 16 Mar 2026 18:44:29 -0400 Subject: [PATCH 265/325] Add card selection counter (#6685) * feat(game): add drag selection counter overlay Display count of selected cards inside the lasso during drag selection. Count appears near cursor, repositioning to stay within selection bounds. Includes SelectionRubberBand subclass to allow label to appear above band. QRubberBand calls raise() in showEvent/changeEvent to stay on top - this subclass suppresses that behavior so dragCountLabel can be visible. Adds user setting to enable/disable the drag count overlay. * feat(game): add persistent selection counter overlay. Display total count of selected cards in bottom-right corner when multiple cards are selected. Updates on selection changes and window resize. The counter connects to QGraphicsScene::selectionChanged to stay up-to-date without requiring manual refresh. Adds user setting to enable/disable the total count overlay. --------- Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com> --- .../src/client/settings/cache_settings.cpp | 15 +++ .../src/client/settings/cache_settings.h | 12 ++ cockatrice/src/game/game_scene.cpp | 4 +- cockatrice/src/game/game_scene.h | 4 +- cockatrice/src/game/game_view.cpp | 121 +++++++++++++++++- cockatrice/src/game/game_view.h | 6 +- cockatrice/src/game/zones/select_zone.cpp | 3 +- .../widgets/dialogs/dlg_settings.cpp | 14 +- .../interface/widgets/dialogs/dlg_settings.h | 2 + 9 files changed, 170 insertions(+), 11 deletions(-) diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 1d8121b19..a66897b4a 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -284,6 +284,9 @@ SettingsCache::SettingsCache() closeEmptyCardView = settings->value("interface/closeEmptyCardView", true).toBool(); focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool(); + showDragSelectionCount = settings->value("interface/showlassoselectioncount", true).toBool(); + showTotalSelectionCount = settings->value("interface/showpersistentselectioncount", true).toBool(); + showShortcuts = settings->value("menu/showshortcuts", true).toBool(); showGameSelectorFilterToolbar = settings->value("menu/showgameselectorfiltertoolbar", true).toBool(); displayCardNames = settings->value("cards/displaycardnames", true).toBool(); @@ -1308,6 +1311,18 @@ void SettingsCache::setRoundCardCorners(bool _roundCardCorners) emit roundCardCornersChanged(roundCardCorners); } +void SettingsCache::setShowDragSelectionCount(QT_STATE_CHANGED_T _showDragSelectionCount) +{ + showDragSelectionCount = static_cast(_showDragSelectionCount); + settings->setValue("interface/showlassoselectioncount", showDragSelectionCount); +} + +void SettingsCache::setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSelectionCount) +{ + showTotalSelectionCount = static_cast(_showTotalSelectionCount); + settings->setValue("interface/showpersistentselectioncount", showTotalSelectionCount); +} + void SettingsCache::loadPaths() { QString dataPath = getDataPath(); diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index 2bbf85352..ece61487f 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -340,6 +340,8 @@ private: bool isPortableBuild; bool roundCardCorners; bool showStatusBar; + bool showDragSelectionCount; + bool showTotalSelectionCount; public: SettingsCache(); @@ -455,6 +457,14 @@ public: { return showStatusBar; } + [[nodiscard]] bool getShowDragSelectionCount() const + { + return showDragSelectionCount; + } + [[nodiscard]] bool getShowTotalSelectionCount() const + { + return showTotalSelectionCount; + } [[nodiscard]] bool getNotificationsEnabled() const { return notificationsEnabled; @@ -1120,5 +1130,7 @@ public slots: void setUpdateReleaseChannelIndex(int value); void setMaxFontSize(int _max); void setRoundCardCorners(bool _roundCardCorners); + void setShowDragSelectionCount(QT_STATE_CHANGED_T _showDragSelectionCount); + void setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSelectionCount); }; #endif diff --git a/cockatrice/src/game/game_scene.cpp b/cockatrice/src/game/game_scene.cpp index 5dc3b48f7..034ff6947 100644 --- a/cockatrice/src/game/game_scene.cpp +++ b/cockatrice/src/game/game_scene.cpp @@ -521,9 +521,9 @@ void GameScene::startRubberBand(const QPointF &selectionOrigin) emit sigStartRubberBand(selectionOrigin); } -void GameScene::resizeRubberBand(const QPointF &cursorPoint) +void GameScene::resizeRubberBand(const QPointF &cursorPoint, int selectedCount) { - emit sigResizeRubberBand(cursorPoint); + emit sigResizeRubberBand(cursorPoint, selectedCount); } void GameScene::stopRubberBand() diff --git a/cockatrice/src/game/game_scene.h b/cockatrice/src/game/game_scene.h index 86fa0795a..f08e83aa4 100644 --- a/cockatrice/src/game/game_scene.h +++ b/cockatrice/src/game/game_scene.h @@ -163,7 +163,7 @@ public: /** Unregisters a card from animation updates. */ void unregisterAnimationItem(AbstractCardItem *card); void startRubberBand(const QPointF &selectionOrigin); - void resizeRubberBand(const QPointF &cursorPoint); + void resizeRubberBand(const QPointF &cursorPoint, int selectedCount); void stopRubberBand(); public slots: @@ -196,7 +196,7 @@ protected: signals: void sigStartRubberBand(const QPointF &selectionOrigin); - void sigResizeRubberBand(const QPointF &cursorPoint); + void sigResizeRubberBand(const QPointF &cursorPoint, int selectedCount); void sigStopRubberBand(); }; diff --git a/cockatrice/src/game/game_view.cpp b/cockatrice/src/game/game_view.cpp index dd5cc70c1..ce53828a7 100644 --- a/cockatrice/src/game/game_view.cpp +++ b/cockatrice/src/game/game_view.cpp @@ -4,9 +4,32 @@ #include "game_scene.h" #include +#include #include #include +// QRubberBand calls raise() in showEvent() and changeEvent() to stay on top of siblings. +// This subclass disables that behavior so dragCountLabel can appear above it. +class SelectionRubberBand : public QRubberBand +{ +public: + using QRubberBand::QRubberBand; + +protected: + void showEvent(QShowEvent *event) override + { + QWidget::showEvent(event); // Skip QRubberBand's raise() + } + + void changeEvent(QEvent *event) override + { + if (event->type() == QEvent::ZOrderChange) { + return; // Skip QRubberBand's raise() on z-order changes + } + QRubberBand::changeEvent(event); + } +}; + GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, parent), rubberBand(0) { setBackgroundBrush(QBrush(QColor(0, 0, 0))); @@ -19,6 +42,7 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par connect(scene, &GameScene::sigStartRubberBand, this, &GameView::startRubberBand); connect(scene, &GameScene::sigResizeRubberBand, this, &GameView::resizeRubberBand); connect(scene, &GameScene::sigStopRubberBand, this, &GameView::stopRubberBand); + connect(scene, &QGraphicsScene::selectionChanged, this, [this]() { updateTotalSelectionCount(); }); aCloseMostRecentZoneView = new QAction(this); @@ -27,7 +51,23 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, &GameView::refreshShortcuts); refreshShortcuts(); - rubberBand = new QRubberBand(QRubberBand::Rectangle, this); + rubberBand = new SelectionRubberBand(QRubberBand::Rectangle, this); + + const QString countLabelStyle = "color: white; " + "font-size: 14px; " + "font-weight: bold; " + "background-color: rgba(0, 0, 0, 160); " + "border-radius: 3px; " + "padding: 1px 2px;"; + + dragCountLabel = new QLabel(this); + dragCountLabel->setStyleSheet(countLabelStyle); + dragCountLabel->hide(); + dragCountLabel->raise(); + + totalCountLabel = new QLabel(this); + totalCountLabel->setStyleSheet(countLabelStyle); + totalCountLabel->hide(); } void GameView::resizeEvent(QResizeEvent *event) @@ -39,6 +79,7 @@ void GameView::resizeEvent(QResizeEvent *event) s->processViewSizeChange(event->size()); updateSceneRect(scene()->sceneRect()); + updateTotalSelectionCount(event->size()); } void GameView::updateSceneRect(const QRectF &rect) @@ -48,20 +89,67 @@ void GameView::updateSceneRect(const QRectF &rect) void GameView::startRubberBand(const QPointF &_selectionOrigin) { + if (!rubberBand) + return; + selectionOrigin = _selectionOrigin; rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), QSize(0, 0))); rubberBand->show(); } -void GameView::resizeRubberBand(const QPointF &cursorPoint) +void GameView::resizeRubberBand(const QPointF &cursorPoint, int selectedCount) { - if (rubberBand) - rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), cursorPoint.toPoint()).normalized()); + if (!rubberBand) + return; + + constexpr int kLabelPaddingInPixels = 4; + + QPoint cursor = cursorPoint.toPoint(); + QRect rect = QRect(mapFromScene(selectionOrigin), cursor).normalized(); + rubberBand->setGeometry(rect); + + if (!SettingsCache::instance().getShowDragSelectionCount()) { + dragCountLabel->hide(); + return; + } + + if (selectedCount > 0) { + dragCountLabel->setText(QString::number(selectedCount)); + dragCountLabel->adjustSize(); + QSize labelSize = dragCountLabel->size(); + + if (rect.width() < labelSize.width() + 2 * kLabelPaddingInPixels || + rect.height() < labelSize.height() + 2 * kLabelPaddingInPixels) { + dragCountLabel->hide(); + return; + } + + const int minX = rect.left() + kLabelPaddingInPixels; + const int minY = rect.top() + kLabelPaddingInPixels; + + int x = qMax(minX, cursor.x() - labelSize.width() - kLabelPaddingInPixels); + int y = qMax(minY, cursor.y() - labelSize.height() - kLabelPaddingInPixels); + + bool isAtTopLeftCorner = (x == minX) && (y == minY); + if (isAtTopLeftCorner) { + constexpr int kCursorClearanceInPixels = 16; + x = qMin(cursor.x() + kCursorClearanceInPixels, rect.right() - labelSize.width() - kLabelPaddingInPixels); + } + + dragCountLabel->move(x, y); + dragCountLabel->show(); + } else { + dragCountLabel->hide(); + } } void GameView::stopRubberBand() { + if (!rubberBand) + return; + rubberBand->hide(); + dragCountLabel->hide(); } void GameView::refreshShortcuts() @@ -69,3 +157,28 @@ void GameView::refreshShortcuts() aCloseMostRecentZoneView->setShortcuts( SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView")); } + +void GameView::updateTotalSelectionCount(const QSize &viewSize) +{ + if (!SettingsCache::instance().getShowTotalSelectionCount()) { + totalCountLabel->hide(); + return; + } + + int count = scene()->selectedItems().count(); + + if (count > 1) { + totalCountLabel->setText(QString::number(count)); + totalCountLabel->adjustSize(); + + constexpr int kMarginInPixels = 10; + int availableWidth = viewSize.isValid() ? viewSize.width() : viewport()->width(); + int availableHeight = viewSize.isValid() ? viewSize.height() : viewport()->height(); + int x = availableWidth - totalCountLabel->width() - kMarginInPixels; + int y = availableHeight - totalCountLabel->height() - kMarginInPixels; + totalCountLabel->move(x, y); + totalCountLabel->show(); + } else { + totalCountLabel->hide(); + } +} diff --git a/cockatrice/src/game/game_view.h b/cockatrice/src/game/game_view.h index 72df9cd08..a77ab9257 100644 --- a/cockatrice/src/game/game_view.h +++ b/cockatrice/src/game/game_view.h @@ -10,6 +10,7 @@ #include class GameScene; +class QLabel; class QRubberBand; class GameView : public QGraphicsView @@ -18,15 +19,18 @@ class GameView : public QGraphicsView private: QAction *aCloseMostRecentZoneView; QRubberBand *rubberBand; + QLabel *dragCountLabel; + QLabel *totalCountLabel; QPointF selectionOrigin; protected: void resizeEvent(QResizeEvent *event) override; private slots: void startRubberBand(const QPointF &selectionOrigin); - void resizeRubberBand(const QPointF &cursorPoint); + void resizeRubberBand(const QPointF &cursorPoint, int selectedCount); void stopRubberBand(); void refreshShortcuts(); + void updateTotalSelectionCount(const QSize &viewSize = QSize()); public slots: void updateSceneRect(const QRectF &rect); diff --git a/cockatrice/src/game/zones/select_zone.cpp b/cockatrice/src/game/zones/select_zone.cpp index 719eec148..9bf5f9faf 100644 --- a/cockatrice/src/game/zones/select_zone.cpp +++ b/cockatrice/src/game/zones/select_zone.cpp @@ -68,7 +68,8 @@ void SelectZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event) } } static_cast(scene())->resizeRubberBand( - deviceTransform(static_cast(scene())->getViewportTransform()).map(pos)); + deviceTransform(static_cast(scene())->getViewportTransform()).map(pos), + cardsInSelectionRect.size()); event->accept(); } } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp index e7286f078..e3bf209dc 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp @@ -821,6 +821,14 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() connect(&annotateTokensCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setAnnotateTokens); + showDragSelectionCountCheckBox.setChecked(SettingsCache::instance().getShowDragSelectionCount()); + connect(&showDragSelectionCountCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowDragSelectionCount); + + showTotalSelectionCountCheckBox.setChecked(SettingsCache::instance().getShowTotalSelectionCount()); + connect(&showTotalSelectionCountCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowTotalSelectionCount); + useTearOffMenusCheckBox.setChecked(SettingsCache::instance().getUseTearOffMenus()); connect(&useTearOffMenusCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), [](const QT_STATE_CHANGED_T state) { SettingsCache::instance().setUseTearOffMenus(state == Qt::Checked); }); @@ -833,7 +841,9 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() generalGrid->addWidget(&closeEmptyCardViewCheckBox, 4, 0); generalGrid->addWidget(&focusCardViewSearchBarCheckBox, 5, 0); generalGrid->addWidget(&annotateTokensCheckBox, 6, 0); - generalGrid->addWidget(&useTearOffMenusCheckBox, 7, 0); + generalGrid->addWidget(&showDragSelectionCountCheckBox, 7, 0); + generalGrid->addWidget(&showTotalSelectionCountCheckBox, 8, 0); + generalGrid->addWidget(&useTearOffMenusCheckBox, 9, 0); generalGroupBox = new QGroupBox; generalGroupBox->setLayout(generalGrid); @@ -955,6 +965,8 @@ void UserInterfaceSettingsPage::retranslateUi() closeEmptyCardViewCheckBox.setText(tr("Close card view window when last card is removed")); focusCardViewSearchBarCheckBox.setText(tr("Auto focus search bar when card view window is opened")); annotateTokensCheckBox.setText(tr("Annotate card text on tokens")); + showDragSelectionCountCheckBox.setText(tr("Show selection counter during drag selection")); + showTotalSelectionCountCheckBox.setText(tr("Show total selection counter")); useTearOffMenusCheckBox.setText(tr("Use tear-off menus, allowing right click menus to persist on screen")); notificationsGroupBox->setTitle(tr("Notifications settings")); notificationsEnabledCheckBox.setText(tr("Enable notifications in taskbar")); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h index db107c6e2..b655a30bc 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h @@ -171,6 +171,8 @@ private: QCheckBox closeEmptyCardViewCheckBox; QCheckBox focusCardViewSearchBarCheckBox; QCheckBox annotateTokensCheckBox; + QCheckBox showDragSelectionCountCheckBox; + QCheckBox showTotalSelectionCountCheckBox; QCheckBox useTearOffMenusCheckBox; QCheckBox tapAnimationCheckBox; QCheckBox openDeckInNewTabCheckBox; From c5cd7d87001ed5738c428dadb38f73999c033a87 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 18 Mar 2026 02:13:36 -0700 Subject: [PATCH 266/325] [ShortcutsSettings] Fix duplicate aPlay shortcut; change play shortcut's group (#6716) * [ShortcutsSettings] Fix duplicate aPlay shortcut; change play shortcut's group * update description --- cockatrice/src/client/settings/shortcuts_settings.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h index 47332b8c4..51615745b 100644 --- a/cockatrice/src/client/settings/shortcuts_settings.h +++ b/cockatrice/src/client/settings/shortcuts_settings.h @@ -513,6 +513,9 @@ private: {"Player/aPlay", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Play Card"), parseSequenceString(""), ShortcutGroup::Playing_Area)}, + {"Player/aPlayFacedown", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Play Card, Face Down"), + parseSequenceString(""), + ShortcutGroup::Playing_Area)}, {"Player/aAttach", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Attach Card..."), parseSequenceString("Ctrl+Alt+A"), ShortcutGroup::Playing_Area)}, @@ -560,12 +563,6 @@ private: {"Player/aMoveToTopLibrary", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Top of Library"), parseSequenceString(""), ShortcutGroup::Move_selected)}, - {"Player/aPlayFacedown", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Battlefield, Face Down"), - parseSequenceString(""), - ShortcutGroup::Move_selected)}, - {"Player/aPlay", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Battlefield"), - parseSequenceString(""), - ShortcutGroup::Move_selected)}, {"Player/aViewHand", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Hand"), parseSequenceString(""), ShortcutGroup::View)}, {"Player/aViewGraveyard", From 38c85e6db16dbc692dbc908c1a9693a11761788c Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Wed, 18 Mar 2026 10:24:34 -0700 Subject: [PATCH 267/325] [Dialog] Reduce spacing in create local game dialog (#6719) --- .../src/interface/widgets/dialogs/dlg_local_game_options.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp index 52466ff10..a9adfd907 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp @@ -20,6 +20,7 @@ DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent) numberPlayersLabel->setBuddy(numberPlayersEdit); auto *generalGrid = new QGridLayout; + generalGrid->setContentsMargins(5, 5, 5, 5); generalGrid->addWidget(numberPlayersLabel, 0, 0); generalGrid->addWidget(numberPlayersEdit, 0, 1); generalGroupBox = new QGroupBox(tr("General"), this); @@ -33,6 +34,7 @@ DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent) startingLifeTotalLabel->setBuddy(startingLifeTotalEdit); auto *gameSetupGrid = new QGridLayout; + gameSetupGrid->setContentsMargins(5, 5, 5, 5); gameSetupGrid->addWidget(startingLifeTotalLabel, 0, 0); gameSetupGrid->addWidget(startingLifeTotalEdit, 0, 1); gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options"), this); From 067fe9b5349a673f2d61f0cf5e2e92e0873013b4 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sat, 21 Mar 2026 10:13:43 +0100 Subject: [PATCH 268/325] Translate cockatrice/cockatrice_en@source.ts in it (#6724) 100% translated source file: 'cockatrice/cockatrice_en@source.ts' on 'it'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- cockatrice/translations/cockatrice_it.ts | 348 ++++++++++++----------- 1 file changed, 180 insertions(+), 168 deletions(-) diff --git a/cockatrice/translations/cockatrice_it.ts b/cockatrice/translations/cockatrice_it.ts index a0765fbfb..5893d4121 100644 --- a/cockatrice/translations/cockatrice_it.ts +++ b/cockatrice/translations/cockatrice_it.ts @@ -101,7 +101,7 @@ Controlla se la cartella è valida e prova ancora. Add Analytics Panel - + Aggiungi pannello di analisi
    @@ -156,7 +156,13 @@ You will not be able to manage printing preferences on a per-deck basis, or see You will have to use the Set Manager, available through Card Database -> Manage Sets. Are you sure you would like to enable this feature? - + Abilitare questa funzione disattiverà il Selettore di stampa. + +Non potrai gestire le preferenze delle stampe per i singoli mazzi, o vedere le stampe scelte dagli altri giocatori per i loro mazzi. + +Dovrai usare il Gestore dei set, raggiungibile tramite Database carte -> Organizza set. + +Sicuro di voler abilitare questa funzione? @@ -167,12 +173,18 @@ You can now choose printings on a per-deck basis in the Deck Editor and configur You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). Are you sure you would like to disable this feature? - + Disabilitare questa funzione attiverà il Selettore di stampa. + +Potrai gestire le preferenze delle stampe per i singoli mazzi nell'Editor, e configurare quale stampa viene aggiunta di default a un mazzo fissandola nel selettore. + +Potrai anche usare il Gestore dei set per personalizzare l'ordine delle stampe nel Selettore (sono disponibili anche ordinamenti predefiniti come alfabetico o per data di uscita). + +Sicuro di voler disabilitare questa funzione? Confirm Change - + Conferma modifica @@ -217,7 +229,7 @@ Are you sure you would like to disable this feature? Show game filter toolbar above list in room tab - + Mostra barra di filtro sopra la lista delle partite @@ -237,7 +249,7 @@ Are you sure you would like to disable this feature? Override all card art with personal set preference (Pre-ProviderID change behavior) - + Determina l'illustrazione della carta attraverso la priorità dei set (come prima dell'implementazione del ProviderID) @@ -326,7 +338,7 @@ Are you sure you would like to disable this feature? Open Deck in Deck Editor - + Apri mazzo nell'editor dei mazzi @@ -550,7 +562,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Name (Exact) - + Nome (esatto) @@ -674,12 +686,12 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Set: - + Set: Collector Number: - + Numero di collezione: @@ -822,7 +834,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Card Size - Dimensioni della carta + Dimensioni carte @@ -995,22 +1007,22 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Add Panel - + Aggiungi pannello Remove Panel - + Rimuovi pannello Save Layout - + Salva disposizione Load Layout - + Carica disposizione @@ -1018,7 +1030,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Card Database - + Database carte @@ -1082,7 +1094,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Loading Database... - + Caricamento database... @@ -1147,7 +1159,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Format: - + Formato: @@ -1488,32 +1500,32 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Undo - + Annulla Redo - + Ripeti Undo/Redo history - + Annulla/Ripeti Storico Click on an entry to revert to that point in the history. - + Clicca su una riga per ritornare a quel punto nello storico delle modifiche. [redo] - + [ripeti] [undo] - + [annulla] @@ -1746,57 +1758,57 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Rename deck to "%1" from "%2" - + Rinominato mazzo in "%1" da "%2" Updated comments (was %1 chars, now %2 chars) - + Commenti aggiornati (prima %1 caratteri, ora %2 caratteri) Set banner card to %1 (%2) - + Impostata carta copertina a %1 (%2) Tags changed - + Etichette modificate Set format to %1 - + Formato impostato a %1 Added (%1): %2 (%3) %4 - + Aggiunto (%1): %2 (%3) %4 Moved to %1 1 × "%2" (%3) - + Spostato in %1 1 × "%2" (%3) Removed "%1" (all copies) - + Rimosso "%1" (tutte le copie) %1 1 × "%2" (%3) - + %1 1 × "%2" (%3) Added - + Aggiunto Removed - + Rimosso @@ -2150,7 +2162,7 @@ Vuoi convertire il mazzo al formato .cod? Create game as judge - + Crea partita come arbitro @@ -2526,7 +2538,7 @@ Per rimuovere il tuo avatar attuale, conferma senza scegliere una nuova immagine The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Il nome scelto è in conflitto con una carta o pedina esistente. -Assicurati di abilitare il set "Pedine" nella finestra "Organizza set" per visualizzarle correttamente. +Assicurati di abilitare il set "Pedine" nella finestra "Gestisci Espansioni" per visualizzarle correttamente. @@ -2618,7 +2630,7 @@ Assicurati di abilitare il set "Pedine" nella finestra "Organizza Hide games not created by buddies Hide games not created by buddy - + Nascondi partite non create dagli amici @@ -3150,17 +3162,17 @@ La tua email verrà utilizzata per verificare il tuo account. Bulk modified printings. - + Stampe modificate massivamente. Cleared all printing information. - + Informazioni sulle stampe eliminate. Set all printings to preferred. - + Imposta tutte le stampe come preferite. @@ -3556,63 +3568,63 @@ Dovrai scaricare la nuova versione manualmente. Draw Probability Settings - + Impostazioni probabilità di pescare Criteria: - + Criterio: Card Name - + Nome carta Type - + Tipo Subtype - + Sottotipo Mana Value - + Valore di mana Exactness: - + Precisione: At least - + Almeno Exactly - + Esattamente Quantity (N): - + Quantità (N): Cards drawn (M): - + Carte pescate (M): cards - + carte @@ -3620,67 +3632,67 @@ Dovrai scaricare la nuova versione manualmente. Draw Probability - + Probabilità di pescare Probability of drawing - + Probabilità di pescare Card Name - + Nome carta Type - + Tipo Subtype - + Sottotipo Mana Value - + Valore di mana At least - + Almeno Exactly - + Esattamente card(s) having drawn at least - + carta/e avendo pescato almeno cards - + carte Category - + Categoria Qty - + Quantità Odds (%) - + Probabilità (%) @@ -3757,7 +3769,7 @@ Dovrai scaricare la nuova versione manualmente. Game Changers - + Carte decisive @@ -3765,7 +3777,7 @@ Dovrai scaricare la nuova versione manualmente. Budget - + Budget @@ -3916,12 +3928,12 @@ Dovrai scaricare la nuova versione manualmente. Join Game as Judge - + Unisciti a una partita come arbitro Spectate Game as Judge - + Osserva partita come arbitro @@ -3961,7 +3973,7 @@ Dovrai scaricare la nuova versione manualmente. Join as judge - + Unisciti come arbitro @@ -3971,7 +3983,7 @@ Dovrai scaricare la nuova versione manualmente. Join as judge spectator - + Unisciti come arbitro spettatore @@ -3989,32 +4001,32 @@ Dovrai scaricare la nuova versione manualmente. All types - + Tutti i tipi Filter by game name... - + Filtra per nome della partita... Filter by game type/format - + Filtra per tipo / formato di partita Hide games not created by buddies - + Nascondi le partite non create da amici Hide full games - + Nascondi partite al completo Hide started games - + Nascondi partite iniziate @@ -4297,7 +4309,7 @@ Dovrai scaricare la nuova versione manualmente. &All players - + &Tutti i giocatori @@ -4330,37 +4342,37 @@ Dovrai scaricare la nuova versione manualmente. Sort hand by... - + Ordina mano per... Name - + Nome Type - + Tipo Mana Value - + Valore di mana Take &mulligan (Choose hand size) - + Effettua un &mulligan (Scegli numero di carte) Take mulligan (Same hand size) - + Effettua un mulligan (Stessa dimensione della mano) Take mulligan (Hand size - 1) - + Effettua un mulligan (Dimensione della mano - 1) @@ -4396,7 +4408,7 @@ Dovrai scaricare la nuova versione manualmente. All players - + Tutti i giocatori @@ -4429,7 +4441,7 @@ Dovrai scaricare la nuova versione manualmente. Browse Archidekt - + Esplora Archidekt @@ -4638,17 +4650,17 @@ Dovrai scaricare la nuova versione manualmente. &All players - + &Tutti i giocatori Reveal top cards of library - + Rivela le carte in cima al grimorio Number of cards: (max. %1) - + Numero di carte: (max. %1) @@ -5186,7 +5198,7 @@ La tua versione è la %1, la versione online è la %2. &Manage sets... - &Organizza set... + &Gestisci Espansioni... @@ -5358,7 +5370,7 @@ All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. Ciao! Sembra che tu stia usando questa versione di Cockatrice per la prima volta Tutti i set nell'archivio delle carte sono stati abilitati. -Scopri metodi alternativi per visualizzare i set o disabilitare set ed effetti nella finestra "Organizza set". +Scopri metodi alternativi per visualizzare i set o disabilitare set ed effetti nella finestra "Gestisci Espansioni". @@ -5488,42 +5500,42 @@ Il database delle carte verrà ricaricato. Mana Base Configuration - + Configurazione Base di mana Display type: - + Modalità di visualizzazione: pie - + a torta bar - + a barre combinedBar - + a barre combinate Filter Colors (optional): - + Filtra per colori (opzionale): OK - + OK Cancel - + Annulla @@ -5539,47 +5551,47 @@ Il database delle carte verrà ricaricato. Group By: - + Raggruppa per: type - + tipo color - + colore subtype - + sottotipo power - + forza toughness - + costituzione Filters (optional): - + Filtri (opzionali): Show main bar row - + Mostra il grafico principale Show per-category rows - + Mostra i grafici per categorie @@ -5595,27 +5607,27 @@ Il database delle carte verrà ricaricato. Display type: - + Modalità di visualizzazione: pie - + A torta bar - + A barre combinedBar - + A barra combinata Filter Colors (optional): - + Filtri per colore (opzionali): @@ -5631,27 +5643,27 @@ Il database delle carte verrà ricaricato. Top display type: - + Modalità di visualizzazione principale: pie - + A torta bar - + A barre Colors: - + Colori: Show per-color rows - + Mostra grafici per colore @@ -5659,12 +5671,12 @@ Il database delle carte verrà ricaricato. %1 pips (%2 cards) - + %1 simboli (%2 carte) %1 mana (%2 cards) - + %1 mana (%2 carte) @@ -5672,12 +5684,12 @@ Il database delle carte verrà ricaricato. Mana Production + Devotion - + Produzione + Devozione di mana Mana Distribution Settings - + Impostazioni distribuzione di mana @@ -7637,58 +7649,58 @@ Controlla le impostazioni! Desc. - + Decrescente Asc. - + Crescente Any Bracket - + Qualsiasi fascia Deck name contains... - + Nome del mazzo contiene... Owner name contains... - + Nome del proprietario contiene... Disabled - + Disabilitato Search - + Cerca Formats - + Formati Min. # of Cards: - + Num. minimo di carte: Page: - + Pagina: Archidekt: - + Archidekt: @@ -7716,7 +7728,7 @@ Controlla le impostazioni! Card Database - + Database carte @@ -7819,12 +7831,12 @@ Controlla le impostazioni! Visual Deck View - Galleria mazzo + Mazzo visuale Visual Database Display - Galleria database + Database visuale @@ -8729,7 +8741,7 @@ Più informazioni inserisci, più specifici saranno i risultati. Archidekt - + Archidekt @@ -8857,7 +8869,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Database Display - + Visualizzazione Database @@ -9353,7 +9365,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Do not delete &arrows inside of subphases - + Non eliminare le &freccie al cambio di sottofase @@ -9555,7 +9567,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Search filter... - + Filtro di ricerca... @@ -9578,22 +9590,22 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Do not display formats with less than this amount of cards in the database - + Non mostrare i formati con meno di questo ammontare di carte nel database Filter mode (AND/OR/NOT conjunctions of filters) - + Modalità di filtraggio (utilizzabile con connettivi logici AND/OR/NOT) Mode: Exact Match - + Modalità: Corrispondenza esatta Mode: Includes - + Modalità: Include @@ -9624,7 +9636,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Filter by name... (Exact match) - + Filtra per nome... (Corrispondenza esatta) @@ -9714,7 +9726,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Visual - + Visuale @@ -9729,12 +9741,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Sort by: - + Ordina per: Filter by: - + Filtra per: @@ -9759,7 +9771,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Table - + Tabella @@ -9767,43 +9779,43 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Group by: - + Raggruppa per: Change how cards are divided into categories/groups. - + Modifica come le carte sono divise in categorie / gruppi. Sort by: - + Ordina per: Click and drag to change the sort order within the groups - + Clicca e trascina per modificare l'ordinamento interno ai gruppi Configure how cards are sorted within their groups - + Configura come le carte sono ordinate all'interno dei loro gruppi Toggle Layout: Overlap - + Disposizione: Sovrapposta Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Modifica come le carte vengono mostrate nelle zone (es. sovrapposte o completamente visibili.) Toggle Layout: Flat - + Disposizione: Piatta @@ -9824,7 +9836,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Type a card name here for suggestions from the database... - + Scrivi un nome di carta qui per avere suggerimenti dal database... @@ -10948,7 +10960,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Reveal Selected Cards to All Players - + Rivela Carta Selezionata a Tutti i Giocatori @@ -11103,12 +11115,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Mulligan (Same hand size) - + Mulligan (Come la mano attuale) Mulligan (Hand size - 1) - + Mulligan (Dimensione della mano - 1) @@ -11138,27 +11150,27 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Sort Hand by Name - + Ordina mano per Nome Sort Hand by Type - + Ordina mano per Tipo Sort Hand by Mana Value - + Ordina mano per Valore di Mana Reveal Hand to All Players - + Rivela Mano a Tutti i Giocatori Reveal Random Card to All Players - + Rivela Carta Casuale a Tutti i Giocatori From 652c8464a7c391884b1295fca96a82c08881cbb8 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sun, 22 Mar 2026 10:28:24 +0100 Subject: [PATCH 269/325] Docker compose: Link to our GHCR hosted `servatrice` image (#6728) * Point to our own image * Point to our own image * Readd build * Readd build --- docker-compose.yml | 2 +- docker-compose.yml.windows | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3d9f9f38f..6cbac61f0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,7 @@ services: build: context: . dockerfile: Dockerfile - image: servatrice + image: ghcr.io/cockatrice/servatrice:latest depends_on: - mysql ports: diff --git a/docker-compose.yml.windows b/docker-compose.yml.windows index 6663c90fd..3d29b1e5f 100644 --- a/docker-compose.yml.windows +++ b/docker-compose.yml.windows @@ -16,7 +16,7 @@ services: build: context: . dockerfile: Dockerfile - image: servatrice + image: ghcr.io/cockatrice/servatrice:latest depends_on: - mysql ports: From bc219191dbdcf5e870c36470b9c633558f483de9 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 22 Mar 2026 04:32:42 -0700 Subject: [PATCH 270/325] [Game] Refactor options in DlgMoveTopCardsUntil into struct (#6718) --- .../game/dialogs/dlg_move_top_cards_until.cpp | 28 ++++++++----------- .../game/dialogs/dlg_move_top_cards_until.h | 20 +++++++------ cockatrice/src/game/player/player_actions.cpp | 11 +++----- cockatrice/src/game/player/player_actions.h | 5 ++-- 4 files changed, 30 insertions(+), 34 deletions(-) diff --git a/cockatrice/src/game/dialogs/dlg_move_top_cards_until.cpp b/cockatrice/src/game/dialogs/dlg_move_top_cards_until.cpp index a9db4cef6..2b54452ac 100644 --- a/cockatrice/src/game/dialogs/dlg_move_top_cards_until.cpp +++ b/cockatrice/src/game/dialogs/dlg_move_top_cards_until.cpp @@ -12,8 +12,7 @@ #include #include -DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, QStringList exprs, uint _numberOfHits, bool autoPlay) - : QDialog(parent) +DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, const MoveTopCardsUntilOptions &options) : QDialog(parent) { exprLabel = new QLabel(tr("Card name (or search expressions):")); @@ -21,13 +20,13 @@ DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, QStringList exprs, u exprComboBox->setFocus(); exprComboBox->setEditable(true); exprComboBox->setInsertPolicy(QComboBox::InsertAtTop); - exprComboBox->insertItems(0, exprs); + exprComboBox->insertItems(0, options.exprs); exprLabel->setBuddy(exprComboBox); numberOfHitsLabel = new QLabel(tr("Number of hits:")); numberOfHitsEdit = new QSpinBox(this); numberOfHitsEdit->setRange(1, 99); - numberOfHitsEdit->setValue(_numberOfHits); + numberOfHitsEdit->setValue(options.numberOfHits); numberOfHitsLabel->setBuddy(numberOfHitsEdit); auto *grid = new QGridLayout; @@ -35,7 +34,7 @@ DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, QStringList exprs, u grid->addWidget(numberOfHitsEdit, 0, 1); autoPlayCheckBox = new QCheckBox(tr("Auto play hits")); - autoPlayCheckBox->setChecked(autoPlay); + autoPlayCheckBox->setChecked(options.autoPlay); buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgMoveTopCardsUntil::validateAndAccept); @@ -118,6 +117,13 @@ QString DlgMoveTopCardsUntil::getExpr() const return exprComboBox->currentText(); } +MoveTopCardsUntilOptions DlgMoveTopCardsUntil::getOptions() const +{ + return {.exprs = getExprs(), + .numberOfHits = numberOfHitsEdit->text().toInt(), + .autoPlay = autoPlayCheckBox->isChecked()}; +} + QStringList DlgMoveTopCardsUntil::getExprs() const { QStringList exprs; @@ -125,14 +131,4 @@ QStringList DlgMoveTopCardsUntil::getExprs() const exprs.append(exprComboBox->itemText(i)); } return exprs; -} - -uint DlgMoveTopCardsUntil::getNumberOfHits() const -{ - return numberOfHitsEdit->text().toUInt(); -} - -bool DlgMoveTopCardsUntil::isAutoPlay() const -{ - return autoPlayCheckBox->isChecked(); -} +} \ No newline at end of file diff --git a/cockatrice/src/game/dialogs/dlg_move_top_cards_until.h b/cockatrice/src/game/dialogs/dlg_move_top_cards_until.h index 9a9dc91e7..20ba11c5c 100644 --- a/cockatrice/src/game/dialogs/dlg_move_top_cards_until.h +++ b/cockatrice/src/game/dialogs/dlg_move_top_cards_until.h @@ -16,6 +16,13 @@ class FilterString; +struct MoveTopCardsUntilOptions +{ + QStringList exprs = {}; + int numberOfHits = 1; + bool autoPlay = false; +}; + class DlgMoveTopCardsUntil : public QDialog { Q_OBJECT @@ -29,15 +36,12 @@ class DlgMoveTopCardsUntil : public QDialog void validateAndAccept(); bool validateMatchExists(const FilterString &filterString); -public: - explicit DlgMoveTopCardsUntil(QWidget *parent = nullptr, - QStringList exprs = QStringList(), - uint numberOfHits = 1, - bool autoPlay = false); - [[nodiscard]] QString getExpr() const; [[nodiscard]] QStringList getExprs() const; - [[nodiscard]] uint getNumberOfHits() const; - [[nodiscard]] bool isAutoPlay() const; + +public: + explicit DlgMoveTopCardsUntil(QWidget *parent = nullptr, const MoveTopCardsUntilOptions &options = {}); + [[nodiscard]] QString getExpr() const; + [[nodiscard]] MoveTopCardsUntilOptions getOptions() const; }; #endif // DLG_MOVE_TOP_CARDS_UNTIL_H diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index 20e526727..287231402 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -485,22 +485,19 @@ void PlayerActions::actMoveTopCardsUntil() { stopMoveTopCardsUntil(); - DlgMoveTopCardsUntil dlg(player->getGame()->getTab(), movingCardsUntilExprs, movingCardsUntilNumberOfHits, - movingCardsUntilAutoPlay); + DlgMoveTopCardsUntil dlg(player->getGame()->getTab(), movingCardsUntilOptions); if (!dlg.exec()) { return; } auto expr = dlg.getExpr(); - movingCardsUntilExprs = dlg.getExprs(); - movingCardsUntilNumberOfHits = dlg.getNumberOfHits(); - movingCardsUntilAutoPlay = dlg.isAutoPlay(); + movingCardsUntilOptions = dlg.getOptions(); if (player->getDeckZone()->getCards().empty()) { stopMoveTopCardsUntil(); } else { movingCardsUntilFilter = FilterString(expr); - movingCardsUntilCounter = movingCardsUntilNumberOfHits; + movingCardsUntilCounter = movingCardsUntilOptions.numberOfHits; movingCardsUntil = true; actMoveTopCardToPlay(); } @@ -512,7 +509,7 @@ void PlayerActions::moveOneCardUntil(CardItem *card) const bool isMatch = card && movingCardsUntilFilter.check(card->getCard().getCardPtr()); - if (isMatch && movingCardsUntilAutoPlay) { + if (isMatch && movingCardsUntilOptions.autoPlay) { // Directly calling playCard will deadlock, since we are already in the middle of processing an event. // Use QTimer::singleShot to queue up the playCard on the event loop. QTimer::singleShot(0, this, [card, this] { playCard(card, false); }); diff --git a/cockatrice/src/game/player/player_actions.h b/cockatrice/src/game/player/player_actions.h index 1d695578d..d4c6daacf 100644 --- a/cockatrice/src/game/player/player_actions.h +++ b/cockatrice/src/game/player/player_actions.h @@ -8,6 +8,7 @@ #ifndef COCKATRICE_PLAYER_ACTIONS_H #define COCKATRICE_PLAYER_ACTIONS_H #include "../dialogs/dlg_create_token.h" +#include "../dialogs/dlg_move_top_cards_until.h" #include "event_processing_options.h" #include "player.h" @@ -178,11 +179,9 @@ private: bool movingCardsUntil; QTimer *moveTopCardTimer; - QStringList movingCardsUntilExprs = {}; - int movingCardsUntilNumberOfHits = 1; - bool movingCardsUntilAutoPlay = false; FilterString movingCardsUntilFilter; int movingCardsUntilCounter = 0; + MoveTopCardsUntilOptions movingCardsUntilOptions; void moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown); void moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown); From 414567f8b671c3f7487aa69d9bec96ce5d79d05c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:43:42 +0100 Subject: [PATCH 271/325] Bump docker/login-action from 3 to 4 (#6736) --- .github/workflows/docker-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 2a4aab1ea..cdcf77e97 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -48,7 +48,7 @@ jobs: - name: Login to GitHub Container Registry if: github.ref_type == 'tag' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} From fa2934373c556e4c485a8748cb5de412ccdae5c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:44:31 +0100 Subject: [PATCH 272/325] Bump microsoft/setup-msbuild from 2 to 3 (#6735) --- .github/workflows/desktop-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 02c3f7aec..820044059 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -341,7 +341,7 @@ jobs: - name: Add msbuild to PATH if: matrix.os == 'Windows' id: add-msbuild - uses: microsoft/setup-msbuild@v2 + uses: microsoft/setup-msbuild@v3 with: msbuild-architecture: x64 From 51c684251fa96dbeca4725e4687ae69097e7223d Mon Sep 17 00:00:00 2001 From: tooomm Date: Tue, 24 Mar 2026 00:16:26 +0100 Subject: [PATCH 273/325] Add Docker to release template (#6732) --- .ci/release_template.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.ci/release_template.md b/.ci/release_template.md index 0e6c05165..b0924b92a 100644 --- a/.ci/release_template.md +++ b/.ci/release_template.md @@ -27,6 +27,8 @@ Available pre-compiled binaries for installation: We are also packaged in Arch Linux's official extra repository, courtesy of @FFY00. General Linux support is available via a flatpak package at Flathub! + +We provide a Docker image for "Servatrice" in GHCR. You can docker pull it or use our Docker Compose files! From aa85a39d6add9b19983e2f5a1426a6f6114100d7 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Tue, 24 Mar 2026 00:16:48 +0100 Subject: [PATCH 274/325] expand local game life total limits (#6730) --- .../src/interface/widgets/dialogs/dlg_local_game_options.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp index a9adfd907..d2d291556 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp @@ -28,8 +28,8 @@ DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent) startingLifeTotalLabel = new QLabel(tr("Starting life total:"), this); startingLifeTotalEdit = new QSpinBox(this); - startingLifeTotalEdit->setMinimum(1); - startingLifeTotalEdit->setMaximum(99999); + startingLifeTotalEdit->setMinimum(-999999999); + startingLifeTotalEdit->setMaximum(999999999); startingLifeTotalEdit->setValue(20); startingLifeTotalLabel->setBuddy(startingLifeTotalEdit); From 70b41c20958c3a1e87195540756635ea4cdb4891 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Tue, 24 Mar 2026 15:31:34 -0400 Subject: [PATCH 275/325] refactor: extract AbstractPlayerComponent interface for polymorphic player component management. (#6696) Non-QObject polymorphic interface with setShortcutsActive(), setShortcutsInactive(), and retranslateUi(). Uses regular multiple inheritance to avoid diamond inheritance with Qt's MOC. All zone menus, SayMenu, and AbstractCounter implement this interface. PlayerMenu manages them via a managedComponents list with two template helpers (addManagedMenu/registerManagedComponent), replacing individual if-guarded lifecycle calls with a single polymorphic loop. SayMenu now owns its shortcut and translation lifecycle instead of having PlayerMenu manage its title and shortcuts externally. Counters are iterated via Player::getCounters() rather than managedComponents to avoid duplicating the authoritative owner's map. --- cockatrice/src/game/board/abstract_counter.h | 9 +- .../player/menu/abstract_player_component.h | 32 +++++++ .../src/game/player/menu/custom_zone_menu.h | 12 ++- cockatrice/src/game/player/menu/grave_menu.h | 9 +- cockatrice/src/game/player/menu/hand_menu.h | 9 +- .../src/game/player/menu/library_menu.h | 9 +- .../src/game/player/menu/player_menu.cpp | 89 ++++--------------- cockatrice/src/game/player/menu/player_menu.h | 27 +++++- cockatrice/src/game/player/menu/rfg_menu.h | 11 ++- cockatrice/src/game/player/menu/say_menu.cpp | 34 ++++++- cockatrice/src/game/player/menu/say_menu.h | 11 ++- .../src/game/player/menu/sideboard_menu.h | 10 ++- .../src/game/player/menu/utility_menu.h | 10 ++- 13 files changed, 164 insertions(+), 108 deletions(-) create mode 100644 cockatrice/src/game/player/menu/abstract_player_component.h diff --git a/cockatrice/src/game/board/abstract_counter.h b/cockatrice/src/game/board/abstract_counter.h index ea13cb00f..074650d54 100644 --- a/cockatrice/src/game/board/abstract_counter.h +++ b/cockatrice/src/game/board/abstract_counter.h @@ -8,6 +8,7 @@ #define COUNTER_H #include "../../interface/widgets/menus/tearoff_menu.h" +#include "../player/menu/abstract_player_component.h" #include #include @@ -18,7 +19,7 @@ class QKeyEvent; class QMenu; class QString; -class AbstractCounter : public QObject, public QGraphicsItem +class AbstractCounter : public QObject, public QGraphicsItem, public AbstractPlayerComponent { Q_OBJECT Q_INTERFACES(QGraphicsItem) @@ -56,10 +57,10 @@ public: QGraphicsItem *parent = nullptr); ~AbstractCounter() override; - void retranslateUi(); + void retranslateUi() override; void setValue(int _value); - void setShortcutsActive(); - void setShortcutsInactive(); + void setShortcutsActive() override; + void setShortcutsInactive() override; void delCounter(); QMenu *getMenu() const diff --git a/cockatrice/src/game/player/menu/abstract_player_component.h b/cockatrice/src/game/player/menu/abstract_player_component.h new file mode 100644 index 000000000..989300d41 --- /dev/null +++ b/cockatrice/src/game/player/menu/abstract_player_component.h @@ -0,0 +1,32 @@ +/** + * @file abstract_player_component.h + * @ingroup GameMenusPlayers + * @brief Polymorphic interface for player-bound UI components managed by PlayerMenu. + */ + +#ifndef COCKATRICE_ABSTRACT_PLAYER_COMPONENT_H +#define COCKATRICE_ABSTRACT_PLAYER_COMPONENT_H + +/** + * @brief Interface for player-bound UI components that need shortcut and translation lifecycle management. + * + * Not a QObject — avoids diamond inheritance with Qt's MOC. Each concrete component + * inherits QObject through its Qt base class (QMenu, TearOffMenu, QGraphicsItem, etc.) + * and this interface through regular multiple inheritance. + */ +class AbstractPlayerComponent +{ +public: + virtual ~AbstractPlayerComponent() = default; + + /// Bind keyboard shortcuts. Called when this player gains focus. + virtual void setShortcutsActive() = 0; + + /// Unbind keyboard shortcuts. Called when this player loses focus. + virtual void setShortcutsInactive() = 0; + + /// Retranslate all user-visible strings. Called on language change. + virtual void retranslateUi() = 0; +}; + +#endif // COCKATRICE_ABSTRACT_PLAYER_COMPONENT_H diff --git a/cockatrice/src/game/player/menu/custom_zone_menu.h b/cockatrice/src/game/player/menu/custom_zone_menu.h index 0944029f4..c4e66754e 100644 --- a/cockatrice/src/game/player/menu/custom_zone_menu.h +++ b/cockatrice/src/game/player/menu/custom_zone_menu.h @@ -7,15 +7,23 @@ #ifndef COCKATRICE_CUSTOM_ZONE_MENU_H #define COCKATRICE_CUSTOM_ZONE_MENU_H +#include "abstract_player_component.h" + #include class Player; -class CustomZoneMenu : public QMenu +class CustomZoneMenu : public QMenu, public AbstractPlayerComponent { Q_OBJECT public: explicit CustomZoneMenu(Player *player); - void retranslateUi(); + void retranslateUi() override; + void setShortcutsActive() override + { + } + void setShortcutsInactive() override + { + } private: Player *player; diff --git a/cockatrice/src/game/player/menu/grave_menu.h b/cockatrice/src/game/player/menu/grave_menu.h index faaf497b6..429173afa 100644 --- a/cockatrice/src/game/player/menu/grave_menu.h +++ b/cockatrice/src/game/player/menu/grave_menu.h @@ -8,12 +8,13 @@ #define COCKATRICE_GRAVE_MENU_H #include "../../../interface/widgets/menus/tearoff_menu.h" +#include "abstract_player_component.h" #include #include class Player; -class GraveyardMenu : public TearOffMenu +class GraveyardMenu : public TearOffMenu, public AbstractPlayerComponent { Q_OBJECT signals: @@ -25,9 +26,9 @@ public: void createViewActions(); void populateRevealRandomMenuWithActivePlayers(); void onRevealRandomTriggered(); - void retranslateUi(); - void setShortcutsActive(); - void setShortcutsInactive(); + void retranslateUi() override; + void setShortcutsActive() override; + void setShortcutsInactive() override; QMenu *mRevealRandomGraveyardCard = nullptr; QMenu *moveGraveMenu = nullptr; diff --git a/cockatrice/src/game/player/menu/hand_menu.h b/cockatrice/src/game/player/menu/hand_menu.h index 51e071a62..76434cc98 100644 --- a/cockatrice/src/game/player/menu/hand_menu.h +++ b/cockatrice/src/game/player/menu/hand_menu.h @@ -8,6 +8,7 @@ #define COCKATRICE_HAND_MENU_H #include "../../../interface/widgets/menus/tearoff_menu.h" +#include "abstract_player_component.h" #include #include @@ -15,7 +16,7 @@ class Player; class PlayerActions; -class HandMenu : public TearOffMenu +class HandMenu : public TearOffMenu, public AbstractPlayerComponent { Q_OBJECT @@ -31,9 +32,9 @@ public: return mRevealRandomHandCard; } - void retranslateUi(); - void setShortcutsActive(); - void setShortcutsInactive(); + void retranslateUi() override; + void setShortcutsActive() override; + void setShortcutsInactive() override; private slots: void populateRevealHandMenuWithActivePlayers(); diff --git a/cockatrice/src/game/player/menu/library_menu.h b/cockatrice/src/game/player/menu/library_menu.h index c0883107c..444e8f516 100644 --- a/cockatrice/src/game/player/menu/library_menu.h +++ b/cockatrice/src/game/player/menu/library_menu.h @@ -8,6 +8,7 @@ #define COCKATRICE_LIBRARY_MENU_H #include "../../../interface/widgets/menus/tearoff_menu.h" +#include "abstract_player_component.h" #include #include @@ -15,7 +16,7 @@ class Player; class PlayerActions; -class LibraryMenu : public TearOffMenu +class LibraryMenu : public TearOffMenu, public AbstractPlayerComponent { Q_OBJECT public slots: @@ -28,15 +29,15 @@ public: void createShuffleActions(); void createMoveActions(); void createViewActions(); - void retranslateUi(); + void retranslateUi() override; void populateRevealLibraryMenuWithActivePlayers(); void populateLendLibraryMenuWithActivePlayers(); void populateRevealTopCardMenuWithActivePlayers(); void onRevealLibraryTriggered(); void onLendLibraryTriggered(); void onRevealTopCardTriggered(); - void setShortcutsActive(); - void setShortcutsInactive(); + void setShortcutsActive() override; + void setShortcutsInactive() override; [[nodiscard]] bool isAlwaysRevealTopCardChecked() const { diff --git a/cockatrice/src/game/player/menu/player_menu.cpp b/cockatrice/src/game/player/menu/player_menu.cpp index 3016a727f..7786ec3fc 100644 --- a/cockatrice/src/game/player/menu/player_menu.cpp +++ b/cockatrice/src/game/player/menu/player_menu.cpp @@ -15,33 +15,24 @@ PlayerMenu::PlayerMenu(Player *_player) : player(_player) playerMenu = new TearOffMenu(); if (player->getPlayerInfo()->getLocalOrJudge()) { - handMenu = new HandMenu(player, player->getPlayerActions(), playerMenu); - playerMenu->addMenu(handMenu); - - libraryMenu = new LibraryMenu(player, playerMenu); - playerMenu->addMenu(libraryMenu); + handMenu = addManagedMenu(player, player->getPlayerActions(), playerMenu); + libraryMenu = addManagedMenu(player, playerMenu); } else { handMenu = nullptr; libraryMenu = nullptr; } - graveMenu = new GraveyardMenu(player, playerMenu); - playerMenu->addMenu(graveMenu); - - rfgMenu = new RfgMenu(player, playerMenu); - playerMenu->addMenu(rfgMenu); + graveMenu = addManagedMenu(player, playerMenu); + rfgMenu = addManagedMenu(player, playerMenu); if (player->getPlayerInfo()->getLocalOrJudge()) { - sideboardMenu = new SideboardMenu(player, playerMenu); - playerMenu->addMenu(sideboardMenu); - - customZonesMenu = new CustomZoneMenu(player); - playerMenu->addMenu(customZonesMenu); + sideboardMenu = addManagedMenu(player, playerMenu); + customZonesMenu = addManagedMenu(player); playerMenu->addSeparator(); countersMenu = playerMenu->addMenu(QString()); - utilityMenu = new UtilityMenu(player, playerMenu); + utilityMenu = createManagedComponent(player, playerMenu); } else { sideboardMenu = nullptr; customZonesMenu = nullptr; @@ -50,8 +41,7 @@ PlayerMenu::PlayerMenu(Player *_player) : player(_player) } if (player->getPlayerInfo()->getLocal()) { - sayMenu = new SayMenu(player); - playerMenu->addMenu(sayMenu); + sayMenu = addManagedMenu(player); } else { sayMenu = nullptr; } @@ -99,40 +89,18 @@ void PlayerMenu::retranslateUi() { playerMenu->setTitle(tr("Player \"%1\"").arg(player->getPlayerInfo()->getName())); - if (handMenu) { - handMenu->retranslateUi(); - } - if (libraryMenu) { - libraryMenu->retranslateUi(); - } - - graveMenu->retranslateUi(); - rfgMenu->retranslateUi(); - - if (sideboardMenu) { - sideboardMenu->retranslateUi(); + for (auto *component : managedComponents) { + component->retranslateUi(); } if (countersMenu) { countersMenu->setTitle(tr("&Counters")); } - if (customZonesMenu) { - customZonesMenu->retranslateUi(); - } - QMapIterator counterIterator(player->getCounters()); while (counterIterator.hasNext()) { counterIterator.next().value()->retranslateUi(); } - - if (utilityMenu) { - utilityMenu->retranslateUi(); - } - - if (sayMenu) { - sayMenu->setTitle(tr("S&ay")); - } } void PlayerMenu::refreshShortcuts() @@ -153,52 +121,29 @@ void PlayerMenu::setShortcutsActive() { shortcutsActive = true; - if (handMenu) { - handMenu->setShortcutsActive(); - } - if (libraryMenu) { - libraryMenu->setShortcutsActive(); - } - graveMenu->setShortcutsActive(); - // No shortcuts for RfgMenu yet - - if (sideboardMenu) { - sideboardMenu->setShortcutsActive(); + for (auto *component : managedComponents) { + component->setShortcutsActive(); } + // Counters implement AbstractPlayerComponent but are iterated via Player::counters + // (the authoritative source) rather than managedComponents to avoid a redundant + // list that must stay in sync with the map. QMapIterator counterIterator(player->getCounters()); while (counterIterator.hasNext()) { counterIterator.next().value()->setShortcutsActive(); } - - if (utilityMenu) { - utilityMenu->setShortcutsActive(); - } } void PlayerMenu::setShortcutsInactive() { shortcutsActive = false; - if (handMenu) { - handMenu->setShortcutsInactive(); - } - if (libraryMenu) { - libraryMenu->setShortcutsInactive(); - } - graveMenu->setShortcutsInactive(); - // No shortcuts for RfgMenu yet - - if (sideboardMenu) { - sideboardMenu->setShortcutsInactive(); + for (auto *component : managedComponents) { + component->setShortcutsInactive(); } QMapIterator counterIterator(player->getCounters()); while (counterIterator.hasNext()) { counterIterator.next().value()->setShortcutsInactive(); } - - if (utilityMenu) { - utilityMenu->setShortcutsInactive(); - } } \ No newline at end of file diff --git a/cockatrice/src/game/player/menu/player_menu.h b/cockatrice/src/game/player/menu/player_menu.h index 882bfedc5..5fce27158 100644 --- a/cockatrice/src/game/player/menu/player_menu.h +++ b/cockatrice/src/game/player/menu/player_menu.h @@ -1,7 +1,7 @@ /** * @file player_menu.h * @ingroup GameMenusPlayers - * @brief TODO: Document this. + * @brief Orchestrates lifecycle management for all player-bound UI components. */ #ifndef COCKATRICE_PLAYER_MENU_H @@ -18,6 +18,7 @@ #include "sideboard_menu.h" #include "utility_menu.h" +#include #include #include @@ -37,6 +38,7 @@ private slots: public: PlayerMenu(Player *player); + /// Lifecycle methods: delegate to all managedComponents, plus counters separately via player->getCounters(). void retranslateUi(); QMenu *updateCardMenu(const CardItem *card); @@ -66,7 +68,9 @@ public: return shortcutsActive; } + /// Delegates to all managedComponents, plus counters separately. void setShortcutsActive(); + /// Delegates to all managedComponents, plus counters separately. void setShortcutsInactive(); private: @@ -82,9 +86,26 @@ private: SayMenu *sayMenu; CustomZoneMenu *customZonesMenu; - bool shortcutsActive; + /// Drives AbstractPlayerComponent lifecycle delegation. Counters are iterated separately via player->getCounters(). + QList managedComponents; + bool shortcutsActive = false; - void initSayMenu(); + /// Creates component, adds it as a submenu of playerMenu, and registers in managedComponents. + template MenuT *addManagedMenu(Args &&...args) + { + auto *menu = new MenuT(std::forward(args)...); + playerMenu->addMenu(menu); + managedComponents.append(menu); + return menu; + } + + /// Creates component and registers in managedComponents, but does NOT add it as a submenu. + template ComponentT *createManagedComponent(Args &&...args) + { + auto *component = new ComponentT(std::forward(args)...); + managedComponents.append(component); + return component; + } }; #endif // COCKATRICE_PLAYER_MENU_H diff --git a/cockatrice/src/game/player/menu/rfg_menu.h b/cockatrice/src/game/player/menu/rfg_menu.h index 0b4623d2a..8f79b2f4a 100644 --- a/cockatrice/src/game/player/menu/rfg_menu.h +++ b/cockatrice/src/game/player/menu/rfg_menu.h @@ -8,19 +8,26 @@ #define COCKATRICE_RFG_MENU_H #include "../../../interface/widgets/menus/tearoff_menu.h" +#include "abstract_player_component.h" #include #include class Player; -class RfgMenu : public TearOffMenu +class RfgMenu : public TearOffMenu, public AbstractPlayerComponent { Q_OBJECT public: explicit RfgMenu(Player *player, QWidget *parent = nullptr); void createMoveActions(); void createViewActions(); - void retranslateUi(); + void retranslateUi() override; + void setShortcutsActive() override + { + } + void setShortcutsInactive() override + { + } QMenu *moveRfgMenu = nullptr; diff --git a/cockatrice/src/game/player/menu/say_menu.cpp b/cockatrice/src/game/player/menu/say_menu.cpp index 3c4802aa5..116fba49a 100644 --- a/cockatrice/src/game/player/menu/say_menu.cpp +++ b/cockatrice/src/game/player/menu/say_menu.cpp @@ -8,6 +8,31 @@ SayMenu::SayMenu(Player *_player) : player(_player) { connect(&SettingsCache::instance().messages(), &MessageSettings::messageMacrosChanged, this, &SayMenu::initSayMenu); initSayMenu(); + retranslateUi(); +} + +void SayMenu::retranslateUi() +{ + setTitle(tr("S&ay")); +} + +void SayMenu::setShortcutsActive() +{ + shortcutsActive = true; + + const auto menuActions = actions(); + for (int i = 0; i < menuActions.size() && i < 10; ++i) { + menuActions[i]->setShortcut(QKeySequence("Ctrl+" + QString::number((i + 1) % 10))); + } +} + +void SayMenu::setShortcutsInactive() +{ + shortcutsActive = false; + + for (auto *action : actions()) { + action->setShortcut(QKeySequence()); + } } void SayMenu::initSayMenu() @@ -19,10 +44,11 @@ void SayMenu::initSayMenu() for (int i = 0; i < count; ++i) { auto *newAction = new QAction(SettingsCache::instance().messages().getMessageAt(i), this); - if (i < 10) { - newAction->setShortcut(QKeySequence("Ctrl+" + QString::number((i + 1) % 10))); - } connect(newAction, &QAction::triggered, player->getPlayerActions(), &PlayerActions::actSayMessage); addAction(newAction); } -} \ No newline at end of file + + if (shortcutsActive) { + setShortcutsActive(); + } +} diff --git a/cockatrice/src/game/player/menu/say_menu.h b/cockatrice/src/game/player/menu/say_menu.h index 5dbde2277..fadf5f368 100644 --- a/cockatrice/src/game/player/menu/say_menu.h +++ b/cockatrice/src/game/player/menu/say_menu.h @@ -7,18 +7,27 @@ #ifndef COCKATRICE_SAY_MENU_H #define COCKATRICE_SAY_MENU_H +#include "abstract_player_component.h" + #include class Player; -class SayMenu : public QMenu +class SayMenu : public QMenu, public AbstractPlayerComponent { Q_OBJECT public: explicit SayMenu(Player *player); + + void retranslateUi() override; + void setShortcutsActive() override; + void setShortcutsInactive() override; + +private slots: void initSayMenu(); private: Player *player; + bool shortcutsActive = false; }; #endif // COCKATRICE_SAY_MENU_H diff --git a/cockatrice/src/game/player/menu/sideboard_menu.h b/cockatrice/src/game/player/menu/sideboard_menu.h index 22d5a2d69..4a77d1b52 100644 --- a/cockatrice/src/game/player/menu/sideboard_menu.h +++ b/cockatrice/src/game/player/menu/sideboard_menu.h @@ -7,18 +7,20 @@ #ifndef COCKATRICE_SIDEBOARD_MENU_H #define COCKATRICE_SIDEBOARD_MENU_H +#include "abstract_player_component.h" + #include class Player; -class SideboardMenu : public QMenu +class SideboardMenu : public QMenu, public AbstractPlayerComponent { Q_OBJECT public: explicit SideboardMenu(Player *player, QMenu *playerMenu); - void retranslateUi(); - void setShortcutsActive(); - void setShortcutsInactive(); + void retranslateUi() override; + void setShortcutsActive() override; + void setShortcutsInactive() override; private: Player *player; diff --git a/cockatrice/src/game/player/menu/utility_menu.h b/cockatrice/src/game/player/menu/utility_menu.h index ff57e7252..f6577d7d1 100644 --- a/cockatrice/src/game/player/menu/utility_menu.h +++ b/cockatrice/src/game/player/menu/utility_menu.h @@ -7,17 +7,19 @@ #ifndef COCKATRICE_UTILITY_MENU_H #define COCKATRICE_UTILITY_MENU_H +#include "abstract_player_component.h" + #include class Player; -class UtilityMenu : public QMenu +class UtilityMenu : public QMenu, public AbstractPlayerComponent { Q_OBJECT public slots: void populatePredefinedTokensMenu(); - void retranslateUi(); - void setShortcutsActive(); - void setShortcutsInactive(); + void retranslateUi() override; + void setShortcutsActive() override; + void setShortcutsInactive() override; public: explicit UtilityMenu(Player *player, QMenu *playerMenu); From 94ea574c76ea214d7d74910b71795f225b28b75e Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Tue, 24 Mar 2026 16:45:52 -0400 Subject: [PATCH 276/325] Add moveToTable context menu action and extract tableRowToGridY helper (#6738) Adds a Table option to the Move menu, allowing cards to be moved directly to the battlefield from any zone. Extracts the repeated tableRow-to-grid-Y conversion logic into TableZone::tableRowToGridY(), consolidating five call sites and fixing a latent bug where cards with tableRow > 2 could land on the wrong row. --- .../src/client/settings/shortcuts_settings.h | 3 ++ .../src/game/player/card_menu_action_type.h | 5 ++- cockatrice/src/game/player/menu/move_menu.cpp | 9 +++- cockatrice/src/game/player/menu/move_menu.h | 1 + cockatrice/src/game/player/player_actions.cpp | 45 ++++++++++++++----- cockatrice/src/game/zones/table_zone.cpp | 8 ++++ cockatrice/src/game/zones/table_zone.h | 7 +++ 7 files changed, 64 insertions(+), 14 deletions(-) diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h index 51615745b..d0849042b 100644 --- a/cockatrice/src/client/settings/shortcuts_settings.h +++ b/cockatrice/src/client/settings/shortcuts_settings.h @@ -563,6 +563,9 @@ private: {"Player/aMoveToTopLibrary", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Top of Library"), parseSequenceString(""), ShortcutGroup::Move_selected)}, + {"Player/aMoveToTable", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Battlefield"), + parseSequenceString(""), + ShortcutGroup::Move_selected)}, {"Player/aViewHand", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Hand"), parseSequenceString(""), ShortcutGroup::View)}, {"Player/aViewGraveyard", diff --git a/cockatrice/src/game/player/card_menu_action_type.h b/cockatrice/src/game/player/card_menu_action_type.h index aec6d6397..1b63674fa 100644 --- a/cockatrice/src/game/player/card_menu_action_type.h +++ b/cockatrice/src/game/player/card_menu_action_type.h @@ -9,17 +9,20 @@ enum CardMenuActionType { + // Per-card attribute actions (must be <= cmClone for cardMenuAction() dispatch) cmTap, cmUntap, cmDoesntUntap, cmFlip, cmPeek, cmClone, + // Move actions (must be > cmClone for cardMenuAction() dispatch) cmMoveToTopLibrary, cmMoveToBottomLibrary, cmMoveToHand, cmMoveToGraveyard, - cmMoveToExile + cmMoveToExile, + cmMoveToTable }; #endif // COCKATRICE_CARD_MENU_ACTION_TYPE_H diff --git a/cockatrice/src/game/player/menu/move_menu.cpp b/cockatrice/src/game/player/menu/move_menu.cpp index d27e16009..91e2d8d10 100644 --- a/cockatrice/src/game/player/menu/move_menu.cpp +++ b/cockatrice/src/game/player/menu/move_menu.cpp @@ -11,6 +11,8 @@ MoveMenu::MoveMenu(Player *player) : QMenu(tr("Move to")) aMoveToBottomLibrary = new QAction(this); aMoveToBottomLibrary->setData(cmMoveToBottomLibrary); aMoveToXfromTopOfLibrary = new QAction(this); + aMoveToTable = new QAction(this); + aMoveToTable->setData(cmMoveToTable); aMoveToGraveyard = new QAction(this); aMoveToHand = new QAction(this); aMoveToHand->setData(cmMoveToHand); @@ -22,6 +24,7 @@ MoveMenu::MoveMenu(Player *player) : QMenu(tr("Move to")) connect(aMoveToBottomLibrary, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); connect(aMoveToXfromTopOfLibrary, &QAction::triggered, player->getPlayerActions(), &PlayerActions::actMoveCardXCardsFromTop); + connect(aMoveToTable, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); connect(aMoveToHand, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); connect(aMoveToGraveyard, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); connect(aMoveToExile, &QAction::triggered, player->getPlayerActions(), &PlayerActions::cardMenuAction); @@ -30,6 +33,8 @@ MoveMenu::MoveMenu(Player *player) : QMenu(tr("Move to")) addAction(aMoveToXfromTopOfLibrary); addAction(aMoveToBottomLibrary); addSeparator(); + addAction(aMoveToTable); + addSeparator(); addAction(aMoveToHand); addSeparator(); addAction(aMoveToGraveyard); @@ -47,6 +52,7 @@ void MoveMenu::setShortcutsActive() aMoveToTopLibrary->setShortcuts(shortcuts.getShortcut("Player/aMoveToTopLibrary")); aMoveToBottomLibrary->setShortcuts(shortcuts.getShortcut("Player/aMoveToBottomLibrary")); + aMoveToTable->setShortcuts(shortcuts.getShortcut("Player/aMoveToTable")); aMoveToHand->setShortcuts(shortcuts.getShortcut("Player/aMoveToHand")); aMoveToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveToGraveyard")); aMoveToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveToExile")); @@ -57,7 +63,8 @@ void MoveMenu::retranslateUi() aMoveToTopLibrary->setText(tr("&Top of library in random order")); aMoveToXfromTopOfLibrary->setText(tr("X cards from the top of library...")); aMoveToBottomLibrary->setText(tr("&Bottom of library in random order")); + aMoveToTable->setText(tr("T&able")); aMoveToHand->setText(tr("&Hand")); aMoveToGraveyard->setText(tr("&Graveyard")); aMoveToExile->setText(tr("&Exile")); -} \ No newline at end of file +} diff --git a/cockatrice/src/game/player/menu/move_menu.h b/cockatrice/src/game/player/menu/move_menu.h index 5bf657fa4..dc39cb6a5 100644 --- a/cockatrice/src/game/player/menu/move_menu.h +++ b/cockatrice/src/game/player/menu/move_menu.h @@ -23,6 +23,7 @@ public: QAction *aMoveToBottomLibrary = nullptr; QAction *aMoveToHand = nullptr; + QAction *aMoveToTable = nullptr; QAction *aMoveToGraveyard = nullptr; QAction *aMoveToExile = nullptr; }; diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index 287231402..cee04ac6b 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -75,7 +75,7 @@ void PlayerActions::playCard(CardItem *card, bool faceDown) cmd.set_y(0); } else { tableRow = faceDown ? 2 : info.getUiAttributes().tableRow; - QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - tableRow)); + QPoint gridPoint = QPoint(-1, TableZone::tableRowToGridY(tableRow)); cardToMove->set_face_down(faceDown); if (!faceDown) { cardToMove->set_pt(info.getPowTough().toStdString()); @@ -114,12 +114,7 @@ void PlayerActions::playCardToTable(const CardItem *card, bool faceDown) const CardInfo &info = exactCard.getInfo(); int tableRow = faceDown ? 2 : info.getUiAttributes().tableRow; - // default instant/sorcery cards to the noncreatures row - if (tableRow > 2) { - tableRow = 1; - } - - QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - tableRow)); + QPoint gridPoint = QPoint(-1, TableZone::tableRowToGridY(tableRow)); cardToMove->set_face_down(faceDown); if (!faceDown) { cardToMove->set_pt(info.getPowTough().toStdString()); @@ -866,7 +861,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().getUiAttributes().tableRow); + lastTokenTableRow = TableZone::tableRowToGridY(correctedCard.getInfo().getUiAttributes().tableRow); if (lastTokenInfo.pt.isEmpty()) { lastTokenInfo.pt = correctedCard.getInfo().getPowTough(); } @@ -917,7 +912,7 @@ void PlayerActions::setLastToken(CardInfoPtr cardInfo) .providerId = SettingsCache::instance().cardOverrides().getCardPreferenceOverride(cardInfo->getName())}; - lastTokenTableRow = TableZone::clampValidTableRow(2 - cardInfo->getUiAttributes().tableRow); + lastTokenTableRow = TableZone::tableRowToGridY(cardInfo->getUiAttributes().tableRow); utilityMenu->setAndEnableCreateAnotherTokenAction(tr("C&reate another %1 token").arg(lastTokenInfo.name)); } @@ -1085,9 +1080,7 @@ void PlayerActions::createCard(const CardItem *sourceCard, return; } - // 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->getUiAttributes().tableRow)); + QPoint gridPoint = QPoint(-1, TableZone::tableRowToGridY(cardInfo->getUiAttributes().tableRow)); // create the token for the related card Command_CreateToken cmd; @@ -1930,6 +1923,34 @@ void PlayerActions::cardMenuAction() commandList.append(cmd); break; } + case cmMoveToTable: { + // Each card needs its own command because table row, pt, and cipt vary per card + for (const auto &card : cardList) { + auto *cmd = new Command_MoveCard; + cmd->set_start_player_id(startPlayerId); + cmd->set_start_zone(startZone.toStdString()); + cmd->set_target_player_id(player->getPlayerInfo()->getId()); + cmd->set_target_zone(ZoneNames::TABLE); + cmd->set_x(-1); + + CardToMove *ctm = cmd->mutable_cards_to_move()->add_card(); + ctm->set_card_id(card->getId()); + ctm->set_face_down(false); + + int tableRow = 0; + ExactCard exactCard = card->getCard(); + if (exactCard) { + const CardInfo &info = exactCard.getInfo(); + tableRow = info.getUiAttributes().tableRow; + ctm->set_pt(info.getPowTough().toStdString()); + ctm->set_tapped(info.getUiAttributes().cipt); + } + + cmd->set_y(TableZone::tableRowToGridY(tableRow)); + commandList.append(cmd); + } + break; + } default: break; } diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index b6ac2150b..2a382fafe 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -382,3 +382,11 @@ int TableZone::clampValidTableRow(const int row) return TABLEROWS - 1; return row; } + +int TableZone::tableRowToGridY(int tableRow) +{ + if (tableRow > 2) { + tableRow = 1; + } + return clampValidTableRow(2 - tableRow); +} diff --git a/cockatrice/src/game/zones/table_zone.h b/cockatrice/src/game/zones/table_zone.h index 61eb48d7b..7a53a9eb4 100644 --- a/cockatrice/src/game/zones/table_zone.h +++ b/cockatrice/src/game/zones/table_zone.h @@ -151,6 +151,13 @@ public: static int clampValidTableRow(const int row); + /** + * Converts a card's logical table row (0=creatures, 1=noncreatures, 2=lands) + * to the corresponding grid Y coordinate. Cards with tableRow > 2 (e.g., + * instants/sorceries) default to the noncreatures row. + */ + static int tableRowToGridY(int tableRow); + /** Resizes the TableZone in case CardItems are within or outside of the TableZone constraints. From 5ef428b9d0fec65b1b264023ce2b2196945ea7c5 Mon Sep 17 00:00:00 2001 From: scotland0208 Date: Wed, 25 Mar 2026 16:15:08 -0500 Subject: [PATCH 277/325] Add visual indicator to toggle untap button (#6737) * Add visual indicator to toggle untap button * Rename button to match tooltip * Change name of string in shortcut settings --- cockatrice/src/client/settings/shortcuts_settings.h | 2 +- cockatrice/src/game/player/menu/card_menu.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h index d0849042b..1de73c165 100644 --- a/cockatrice/src/client/settings/shortcuts_settings.h +++ b/cockatrice/src/client/settings/shortcuts_settings.h @@ -501,7 +501,7 @@ private: {"Player/aUntapAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Untap All"), parseSequenceString("Ctrl+U"), ShortcutGroup::Playing_Area)}, - {"Player/aDoesntUntap", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Untap"), + {"Player/aDoesntUntap", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Skip Untapping"), parseSequenceString("Alt+U"), ShortcutGroup::Playing_Area)}, {"Player/aFlip", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Turn Card Over"), diff --git a/cockatrice/src/game/player/menu/card_menu.cpp b/cockatrice/src/game/player/menu/card_menu.cpp index cd77c2968..f2479e1da 100644 --- a/cockatrice/src/game/player/menu/card_menu.cpp +++ b/cockatrice/src/game/player/menu/card_menu.cpp @@ -35,6 +35,8 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive connect(aTap, &QAction::triggered, playerActions, &PlayerActions::cardMenuAction); aDoesntUntap = new QAction(this); aDoesntUntap->setData(cmDoesntUntap); + aDoesntUntap->setCheckable(true); + aDoesntUntap->setChecked(card != nullptr && card->getDoesntUntap()); connect(aDoesntUntap, &QAction::triggered, playerActions, &PlayerActions::cardMenuAction); aAttach = new QAction(this); connect(aAttach, &QAction::triggered, playerActions, &PlayerActions::actAttach); @@ -449,7 +451,7 @@ void CardMenu::retranslateUi() aRevealToAll->setText(tr("&All players")); //: Turn sideways or back again aTap->setText(tr("&Tap / Untap")); - aDoesntUntap->setText(tr("Toggle &normal untapping")); + aDoesntUntap->setText(tr("Skip &untapping")); //: Turn face up/face down aFlip->setText(tr("T&urn Over")); // Only the user facing names in client got renamed to "turn over" // All code and proto bits are still unchanged (flip) for compatibility reasons From dd053c76dfc357896b7a49a5ab636161b32a7aa7 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Wed, 25 Mar 2026 18:03:59 -0400 Subject: [PATCH 278/325] [Game] Improve context menus and fix face-down play from stack (#6739) Reorganize card context menus across table, stack, and graveyard/exile zones for better consistency: promote Draw Arrow and Clone actions, move related card entries to the bottom, add Play/Play Face Down to the stack menu, and flatten if/else blocks with early returns. Also fix playCard() ignoring the faceDown flag when routing instants/sorceries from the stack, which sent them to the graveyard instead of the table. --- cockatrice/src/game/player/menu/card_menu.cpp | 79 ++++++++++--------- cockatrice/src/game/player/player_actions.cpp | 2 +- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/cockatrice/src/game/player/menu/card_menu.cpp b/cockatrice/src/game/player/menu/card_menu.cpp index f2479e1da..66ca5e46b 100644 --- a/cockatrice/src/game/player/menu/card_menu.cpp +++ b/cockatrice/src/game/player/menu/card_menu.cpp @@ -110,6 +110,7 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive if (revealedCard) { addAction(aHide); + addSeparator(); addAction(aClone); addSeparator(); addAction(aSelectAll); @@ -148,16 +149,14 @@ void CardMenu::createTableMenu(bool canModifyCard) { // Card is on the battlefield if (!canModifyCard) { - addRelatedCardView(); - addRelatedCardActions(); - - addSeparator(); addAction(aDrawArrow); addSeparator(); addAction(aClone); addSeparator(); addAction(aSelectAll); addAction(aSelectRow); + addRelatedCardView(); + addRelatedCardActions(); return; } @@ -167,10 +166,9 @@ void CardMenu::createTableMenu(bool canModifyCard) if (card->getFaceDown()) { addAction(aPeek); } - - addRelatedCardView(); - addRelatedCardActions(); - + addSeparator(); + addAction(aClone); + addMenu(new MoveMenu(player)); addSeparator(); addAction(aAttach); if (card->getAttachedTo()) { @@ -181,9 +179,6 @@ void CardMenu::createTableMenu(bool canModifyCard) addMenu(new PtMenu(player)); addAction(aSetAnnotation); addSeparator(); - addAction(aClone); - addMenu(new MoveMenu(player)); - addSeparator(); addAction(aSelectAll); addAction(aSelectRow); @@ -199,27 +194,34 @@ void CardMenu::createTableMenu(bool canModifyCard) } addSeparator(); addMenu(mCardCounters); + addRelatedCardView(); + addRelatedCardActions(); } void CardMenu::createStackMenu(bool canModifyCard) { // Card is on the stack - if (canModifyCard) { - addAction(aAttach); - addAction(aDrawArrow); - addSeparator(); - addAction(aClone); - addMenu(new MoveMenu(player)); - addSeparator(); - addAction(aSelectAll); - } else { + if (!canModifyCard) { addAction(aDrawArrow); addSeparator(); addAction(aClone); addSeparator(); addAction(aSelectAll); + addRelatedCardView(); + addRelatedCardActions(); + return; } + addAction(aPlay); + addAction(aPlayFacedown); + addSeparator(); + addAction(aClone); + addMenu(new MoveMenu(player)); + addSeparator(); + addAction(aAttach); + addAction(aDrawArrow); + addSeparator(); + addAction(aSelectAll); addRelatedCardView(); addRelatedCardActions(); } @@ -227,29 +229,29 @@ void CardMenu::createStackMenu(bool canModifyCard) void CardMenu::createGraveyardOrExileMenu(bool canModifyCard) { // Card is in the graveyard or exile - if (canModifyCard) { - addAction(aPlay); - addAction(aPlayFacedown); - - addSeparator(); - addAction(aClone); - addMenu(new MoveMenu(player)); - addSeparator(); - addAction(aSelectAll); - addAction(aSelectColumn); - - addSeparator(); - addAction(aAttach); + if (!canModifyCard) { addAction(aDrawArrow); - } else { + addSeparator(); addAction(aClone); addSeparator(); addAction(aSelectAll); addAction(aSelectColumn); - addSeparator(); - addAction(aDrawArrow); + addRelatedCardView(); + addRelatedCardActions(); + return; } + addAction(aPlay); + addAction(aPlayFacedown); + addSeparator(); + addAction(aClone); + addMenu(new MoveMenu(player)); + addSeparator(); + addAction(aAttach); + addAction(aDrawArrow); + addSeparator(); + addAction(aSelectAll); + addAction(aSelectColumn); addRelatedCardView(); addRelatedCardActions(); } @@ -259,12 +261,11 @@ void CardMenu::createHandOrCustomZoneMenu(bool canModifyCard) if (!canModifyCard) { addAction(aDrawArrow); addSeparator(); - addRelatedCardView(); - addRelatedCardActions(); - addSeparator(); addAction(aClone); addSeparator(); addAction(aSelectAll); + addRelatedCardView(); + addRelatedCardActions(); return; } diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index cee04ac6b..ca0967636 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -64,7 +64,7 @@ void PlayerActions::playCard(CardItem *card, bool faceDown) int tableRow = info.getUiAttributes().tableRow; bool playToStack = SettingsCache::instance().getPlayToStack(); QString currentZone = card->getZone()->getName(); - if (currentZone == ZoneNames::STACK && tableRow == 3) { + if (!faceDown && currentZone == ZoneNames::STACK && tableRow == 3) { cmd.set_target_zone(ZoneNames::GRAVE); cmd.set_x(0); cmd.set_y(0); From 74cce5ccb2fbae2c4a6a424600b6ca4b155f2402 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 27 Mar 2026 09:12:49 -0700 Subject: [PATCH 279/325] [SettingsManager] Properly handle multithreaded access (#6747) --- .../client/settings/card_counter_settings.cpp | 4 +++- .../settings/settings_manager.cpp | 24 ++++++++++++++++--- .../libcockatrice/settings/settings_manager.h | 5 +++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/cockatrice/src/client/settings/card_counter_settings.cpp b/cockatrice/src/client/settings/card_counter_settings.cpp index 71ce4cfc6..399365c99 100644 --- a/cockatrice/src/client/settings/card_counter_settings.cpp +++ b/cockatrice/src/client/settings/card_counter_settings.cpp @@ -11,6 +11,8 @@ CardCounterSettings::CardCounterSettings(const QString &settingsPath, QObject *p void CardCounterSettings::setColor(int counterId, const QColor &color) { + QSettings settings = getSettings(); + QString key = QString("cards/counters/%1/color").arg(counterId); if (settings.value(key).value() == color) @@ -36,7 +38,7 @@ QColor CardCounterSettings::color(int counterId) const defaultColor = QColor::fromHsv(h, s, v); } - return settings.value(QString("cards/counters/%1/color").arg(counterId), defaultColor).value(); + return getSettings().value(QString("cards/counters/%1/color").arg(counterId), defaultColor).value(); } QString CardCounterSettings::displayName(int counterId) const diff --git a/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp b/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp index ede5a2027..3f3deb638 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp +++ b/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp @@ -1,16 +1,22 @@ #include "settings_manager.h" -SettingsManager::SettingsManager(const QString &settingPath, +SettingsManager::SettingsManager(const QString &_settingPath, const QString &_defaultGroup, const QString &_defaultSubGroup, QObject *parent) - : QObject(parent), settings(settingPath, QSettings::IniFormat), defaultGroup(_defaultGroup), - defaultSubGroup(_defaultSubGroup) + : QObject(parent), settingPath(_settingPath), defaultGroup(_defaultGroup), defaultSubGroup(_defaultSubGroup) { } +QSettings SettingsManager::getSettings() const +{ + return QSettings(settingPath, QSettings::IniFormat); +} + void SettingsManager::setValue(const QVariant &value, const QString &name) { + auto settings = getSettings(); + if (!defaultGroup.isEmpty()) { settings.beginGroup(defaultGroup); } @@ -35,6 +41,8 @@ void SettingsManager::setValue(const QVariant &value, const QString &group, const QString &subGroup) { + auto settings = getSettings(); + if (!group.isEmpty()) { settings.beginGroup(group); } @@ -56,6 +64,8 @@ void SettingsManager::setValue(const QVariant &value, void SettingsManager::deleteValue(const QString &name) { + auto settings = getSettings(); + if (!defaultGroup.isEmpty()) { settings.beginGroup(defaultGroup); } @@ -77,6 +87,8 @@ void SettingsManager::deleteValue(const QString &name) void SettingsManager::deleteValue(const QString &name, const QString &group, const QString &subGroup) { + auto settings = getSettings(); + if (!group.isEmpty()) { settings.beginGroup(group); } @@ -98,6 +110,8 @@ void SettingsManager::deleteValue(const QString &name, const QString &group, con QVariant SettingsManager::getValue(const QString &name) { + auto settings = getSettings(); + if (!defaultGroup.isEmpty()) { settings.beginGroup(defaultGroup); } @@ -121,6 +135,8 @@ QVariant SettingsManager::getValue(const QString &name) QVariant SettingsManager::getValue(const QString &name, const QString &group, const QString &subGroup) { + auto settings = getSettings(); + if (!group.isEmpty()) { settings.beginGroup(group); } @@ -147,5 +163,7 @@ QVariant SettingsManager::getValue(const QString &name, const QString &group, co */ void SettingsManager::sync() { + auto settings = getSettings(); + settings.sync(); } \ No newline at end of file diff --git a/libcockatrice_settings/libcockatrice/settings/settings_manager.h b/libcockatrice_settings/libcockatrice/settings/settings_manager.h index 3592d8f8c..18abb8dbf 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_manager.h +++ b/libcockatrice_settings/libcockatrice/settings/settings_manager.h @@ -24,9 +24,12 @@ public: void sync(); protected: - QSettings settings; + QString settingPath; QString defaultGroup; QString defaultSubGroup; + + QSettings getSettings() const; + void setValue(const QVariant &value, const QString &name); void setValue(const QVariant &value, const QString &name, const QString &group, const QString &subGroup = QString()); From abf6e72ad1b341feda04676554a751cdec97169c Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Fri, 27 Mar 2026 18:08:51 +0100 Subject: [PATCH 280/325] add a nulcheck in the card item animation timer (#6740) --- cockatrice/src/game/board/card_item.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cockatrice/src/game/board/card_item.cpp b/cockatrice/src/game/board/card_item.cpp index 62de4a02e..cf3c7db20 100644 --- a/cockatrice/src/game/board/card_item.cpp +++ b/cockatrice/src/game/board/card_item.cpp @@ -460,6 +460,9 @@ void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) bool CardItem::animationEvent() { + if (owner == nullptr) { + return false; + } int rotation = ROTATION_DEGREES_PER_FRAME; bool animationIncomplete = true; if (!tapped) From 42bd8164a0b9170a3ecfe68b43ca399e1e08e4a7 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Fri, 27 Mar 2026 18:10:29 +0100 Subject: [PATCH 281/325] remove hardcoded white in vde banner widget (#6684) * remove hardcoded white in vde banner widget * set text color to white on higher opacities --- .../widgets/general/display/banner_widget.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cockatrice/src/interface/widgets/general/display/banner_widget.cpp b/cockatrice/src/interface/widgets/general/display/banner_widget.cpp index f03869a4d..5de5457ea 100644 --- a/cockatrice/src/interface/widgets/general/display/banner_widget.cpp +++ b/cockatrice/src/interface/widgets/general/display/banner_widget.cpp @@ -7,8 +7,8 @@ #include #include -BannerWidget::BannerWidget(QWidget *parent, const QString &text, Qt::Orientation orientation, int transparency) - : QWidget(parent), gradientOrientation(orientation), transparency(qBound(0, transparency, 100)) +BannerWidget::BannerWidget(QWidget *parent, const QString &text, Qt::Orientation orientation, int transparency_) + : QWidget(parent), gradientOrientation(orientation), transparency(qBound(0, transparency_, 100)) { auto layout = new QHBoxLayout(this); @@ -18,7 +18,12 @@ BannerWidget::BannerWidget(QWidget *parent, const QString &text, Qt::Orientation // Create the banner label and set properties bannerLabel = new QLabel(text, this); bannerLabel->setAlignment(Qt::AlignCenter); - bannerLabel->setStyleSheet("font-size: 24px; font-weight: bold; color: white;"); + + QString textColor; + if (transparency > 50) { + textColor = " color: white;"; + } + bannerLabel->setStyleSheet("font-size: 24px; font-weight: bold;" + textColor); layout->addWidget(iconLabel); layout->addWidget(bannerLabel); From d8e3807ec50be2b384b2c07a552f9efd315b2bb1 Mon Sep 17 00:00:00 2001 From: tooomm Date: Fri, 27 Mar 2026 18:11:56 +0100 Subject: [PATCH 282/325] Dependabot: Enable git submodules tracking (#6727) * Enable gitsubmodules * update comment --- .github/dependabot.yml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1e278a418..fc25af67f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,19 +2,18 @@ version: 2 updates: - # # Enable version updates for git submodules - # Not yet possible to bump only on tags or releases, see: + # Enable version updates for git submodules + # If SemVer is used, updates will happen to new releases only (not HEAD) # https://github.com/dependabot/dependabot-core/issues/1639 # https://github.com/dependabot/dependabot-core/issues/2192 - # Alternative: Action that updates submodule and can be manually run on demand (workflow_dispatch) - # - package-ecosystem: "gitsubmodule" - # # Look for `.gitmodules` in the `root` directory - # directory: "/" - # # Check for updates once a month - # schedule: - # interval: "monthly" - # # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted) - # open-pull-requests-limit: 1 + - package-ecosystem: "gitsubmodule" + # Look for `.gitmodules` in the `root` directory + directory: "/" + # Check for updates once a month + schedule: + interval: "monthly" + # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted) + open-pull-requests-limit: 2 # # Enable version updates for Docker # Not yet possible to bump from one LTS version to the next and skip others, see: From 34a5b8b9ce95fce726301819638f52d1781b8088 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:13:25 -0700 Subject: [PATCH 283/325] [SettingsManager] Make setting getters const (#6748) * [SettingsManager] Make setting getters const * remove hashGameType from header --- .../interface_card_set_priority_controller.h | 6 +-- .../noop_card_set_priority_controller.h | 6 +-- .../settings/card_database_settings.cpp | 6 +-- .../settings/card_database_settings.h | 6 +-- .../settings/card_override_settings.cpp | 2 +- .../settings/card_override_settings.h | 2 +- .../libcockatrice/settings/debug_settings.cpp | 8 ++-- .../libcockatrice/settings/debug_settings.h | 8 ++-- .../settings/download_settings.cpp | 2 +- .../settings/download_settings.h | 2 +- .../settings/game_filters_settings.cpp | 38 +++++++++---------- .../settings/game_filters_settings.h | 36 +++++++++--------- .../settings/layouts_settings.cpp | 26 ++++++------- .../libcockatrice/settings/layouts_settings.h | 26 ++++++------- .../settings/message_settings.cpp | 4 +- .../libcockatrice/settings/message_settings.h | 4 +- .../settings/recents_settings.cpp | 4 +- .../libcockatrice/settings/recents_settings.h | 4 +- .../settings/servers_settings.cpp | 26 ++++++------- .../libcockatrice/settings/servers_settings.h | 26 ++++++------- .../settings/settings_manager.cpp | 4 +- .../libcockatrice/settings/settings_manager.h | 4 +- 22 files changed, 124 insertions(+), 126 deletions(-) diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h index 46a5897f7..b8fbbc74a 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_card_set_priority_controller.h @@ -12,9 +12,9 @@ public: virtual void setEnabled(QString shortName, bool enabled) = 0; virtual void setIsKnown(QString shortName, bool isknown) = 0; - virtual unsigned int getSortKey(QString shortName) = 0; - virtual bool isEnabled(QString shortName) = 0; - virtual bool isKnown(QString shortName) = 0; + virtual unsigned int getSortKey(QString shortName) const = 0; + virtual bool isEnabled(QString shortName) const = 0; + virtual bool isKnown(QString shortName) const = 0; }; #endif // COCKATRICE_INTERFACE_CARD_SET_PRIORITY_CONTROLLER_H diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h index 949ab5a91..e5027648c 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/noop_card_set_priority_controller.h @@ -16,15 +16,15 @@ public: { } - unsigned int getSortKey(QString /* shortName */) override + unsigned int getSortKey(QString /* shortName */) const override { return 0; } - bool isEnabled(QString /* shortName */) override + bool isEnabled(QString /* shortName */) const override { return true; } - bool isKnown(QString /* shortName */) override + bool isKnown(QString /* shortName */) const override { return true; } diff --git a/libcockatrice_settings/libcockatrice/settings/card_database_settings.cpp b/libcockatrice_settings/libcockatrice/settings/card_database_settings.cpp index 79738f1cd..26a91a4dd 100644 --- a/libcockatrice_settings/libcockatrice/settings/card_database_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/card_database_settings.cpp @@ -20,17 +20,17 @@ void CardDatabaseSettings::setIsKnown(QString shortName, bool isknown) setValue(isknown, "isknown", "sets", std::move(shortName)); } -unsigned int CardDatabaseSettings::getSortKey(QString shortName) +unsigned int CardDatabaseSettings::getSortKey(QString shortName) const { return getValue("sortkey", "sets", std::move(shortName)).toUInt(); } -bool CardDatabaseSettings::isEnabled(QString shortName) +bool CardDatabaseSettings::isEnabled(QString shortName) const { return getValue("enabled", "sets", std::move(shortName)).toBool(); } -bool CardDatabaseSettings::isKnown(QString shortName) +bool CardDatabaseSettings::isKnown(QString shortName) const { return getValue("isknown", "sets", std::move(shortName)).toBool(); } diff --git a/libcockatrice_settings/libcockatrice/settings/card_database_settings.h b/libcockatrice_settings/libcockatrice/settings/card_database_settings.h index 9a176a99b..bb946ea80 100644 --- a/libcockatrice_settings/libcockatrice/settings/card_database_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/card_database_settings.h @@ -22,9 +22,9 @@ public: void setEnabled(QString shortName, bool enabled) override; void setIsKnown(QString shortName, bool isknown) override; - unsigned int getSortKey(QString shortName) override; - bool isEnabled(QString shortName) override; - bool isKnown(QString shortName) override; + unsigned int getSortKey(QString shortName) const override; + bool isEnabled(QString shortName) const override; + bool isKnown(QString shortName) const override; private: explicit CardDatabaseSettings(const QString &settingPath, QObject *parent = nullptr); diff --git a/libcockatrice_settings/libcockatrice/settings/card_override_settings.cpp b/libcockatrice_settings/libcockatrice/settings/card_override_settings.cpp index 894358be6..a61a4693b 100644 --- a/libcockatrice_settings/libcockatrice/settings/card_override_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/card_override_settings.cpp @@ -15,7 +15,7 @@ void CardOverrideSettings::deleteCardPreferenceOverride(const QString &cardName) deleteValue(cardName); } -QString CardOverrideSettings::getCardPreferenceOverride(const QString &cardName) +QString CardOverrideSettings::getCardPreferenceOverride(const QString &cardName) const { return getValue(cardName).toString(); } \ No newline at end of file diff --git a/libcockatrice_settings/libcockatrice/settings/card_override_settings.h b/libcockatrice_settings/libcockatrice/settings/card_override_settings.h index d5ee0287b..3d9db4e65 100644 --- a/libcockatrice_settings/libcockatrice/settings/card_override_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/card_override_settings.h @@ -22,7 +22,7 @@ public: void deleteCardPreferenceOverride(const QString &cardName); - QString getCardPreferenceOverride(const QString &cardName); + QString getCardPreferenceOverride(const QString &cardName) const; private: explicit CardOverrideSettings(const QString &settingPath, QObject *parent = nullptr); diff --git a/libcockatrice_settings/libcockatrice/settings/debug_settings.cpp b/libcockatrice_settings/libcockatrice/settings/debug_settings.cpp index 084696dc1..5bf6eca30 100644 --- a/libcockatrice_settings/libcockatrice/settings/debug_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/debug_settings.cpp @@ -11,22 +11,22 @@ DebugSettings::DebugSettings(const QString &settingPath, QObject *parent) } } -bool DebugSettings::getShowCardId() +bool DebugSettings::getShowCardId() const { return getValue("showCardId").toBool(); } -bool DebugSettings::getLocalGameOnStartup() +bool DebugSettings::getLocalGameOnStartup() const { return getValue("onStartup", "localgame").toBool(); } -int DebugSettings::getLocalGamePlayerCount() +int DebugSettings::getLocalGamePlayerCount() const { return getValue("playerCount", "localgame").toInt(); } -QString DebugSettings::getDeckPathForPlayer(const QString &playerName) +QString DebugSettings::getDeckPathForPlayer(const QString &playerName) const { return getValue(playerName, "localgame", "deck").toString(); } \ No newline at end of file diff --git a/libcockatrice_settings/libcockatrice/settings/debug_settings.h b/libcockatrice_settings/libcockatrice/settings/debug_settings.h index 2087b16b3..30cdd5fa5 100644 --- a/libcockatrice_settings/libcockatrice/settings/debug_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/debug_settings.h @@ -17,12 +17,12 @@ class DebugSettings : public SettingsManager DebugSettings(const DebugSettings & /*other*/); public: - bool getShowCardId(); + bool getShowCardId() const; - bool getLocalGameOnStartup(); - int getLocalGamePlayerCount(); + bool getLocalGameOnStartup() const; + int getLocalGamePlayerCount() const; - QString getDeckPathForPlayer(const QString &playerName); + QString getDeckPathForPlayer(const QString &playerName) const; }; #endif // DEBUG_SETTINGS_H diff --git a/libcockatrice_settings/libcockatrice/settings/download_settings.cpp b/libcockatrice_settings/libcockatrice/settings/download_settings.cpp index ad4b81ec5..66525a598 100644 --- a/libcockatrice_settings/libcockatrice/settings/download_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/download_settings.cpp @@ -18,7 +18,7 @@ void DownloadSettings::setDownloadUrls(const QStringList &downloadURLs) setValue(QVariant::fromValue(downloadURLs), "urls"); } -QStringList DownloadSettings::getAllURLs() +QStringList DownloadSettings::getAllURLs() const { return getValue("urls").toStringList(); } diff --git a/libcockatrice_settings/libcockatrice/settings/download_settings.h b/libcockatrice_settings/libcockatrice/settings/download_settings.h index ed3634ea1..b7442301e 100644 --- a/libcockatrice_settings/libcockatrice/settings/download_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/download_settings.h @@ -19,7 +19,7 @@ class DownloadSettings : public SettingsManager public: explicit DownloadSettings(const QString &, QObject *); - QStringList getAllURLs(); + QStringList getAllURLs() const; void setDownloadUrls(const QStringList &downloadURLs); void resetToDefaultURLs(); }; diff --git a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp index e5db3010d..4f5bf52ee 100644 --- a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp @@ -8,11 +8,11 @@ GameFiltersSettings::GameFiltersSettings(const QString &settingPath, QObject *pa { } -/* +/** * The game type might contain special characters, so to use it in * QSettings we just hash it. */ -QString GameFiltersSettings::hashGameType(const QString &gameType) const +static QString hashGameType(const QString &gameType) { return QCryptographicHash::hash(gameType.toUtf8(), QCryptographicHash::Md5).toHex(); } @@ -22,7 +22,7 @@ void GameFiltersSettings::setHideBuddiesOnlyGames(bool hide) setValue(hide, "hide_buddies_only_games"); } -bool GameFiltersSettings::isHideBuddiesOnlyGames() +bool GameFiltersSettings::isHideBuddiesOnlyGames() const { QVariant previous = getValue("hide_buddies_only_games"); return previous == QVariant() ? false : previous.toBool(); @@ -33,7 +33,7 @@ void GameFiltersSettings::setHideFullGames(bool hide) setValue(hide, "hide_full_games"); } -bool GameFiltersSettings::isHideFullGames() +bool GameFiltersSettings::isHideFullGames() const { QVariant previous = getValue("hide_full_games"); return previous == QVariant() ? false : previous.toBool(); @@ -44,7 +44,7 @@ void GameFiltersSettings::setHideGamesThatStarted(bool hide) setValue(hide, "hide_games_that_started"); } -bool GameFiltersSettings::isHideGamesThatStarted() +bool GameFiltersSettings::isHideGamesThatStarted() const { QVariant previous = getValue("hide_games_that_started"); return previous == QVariant() ? false : previous.toBool(); @@ -55,7 +55,7 @@ void GameFiltersSettings::setHidePasswordProtectedGames(bool hide) setValue(hide, "hide_password_protected_games"); } -bool GameFiltersSettings::isHidePasswordProtectedGames() +bool GameFiltersSettings::isHidePasswordProtectedGames() const { QVariant previous = getValue("hide_password_protected_games"); return previous == QVariant() ? false : previous.toBool(); @@ -66,7 +66,7 @@ void GameFiltersSettings::setHideIgnoredUserGames(bool hide) setValue(hide, "hide_ignored_user_games"); } -bool GameFiltersSettings::isHideIgnoredUserGames() +bool GameFiltersSettings::isHideIgnoredUserGames() const { QVariant previous = getValue("hide_ignored_user_games"); return previous == QVariant() ? true : previous.toBool(); @@ -77,7 +77,7 @@ void GameFiltersSettings::setHideNotBuddyCreatedGames(bool hide) setValue(hide, "hide_not_buddy_created_games"); } -bool GameFiltersSettings::isHideNotBuddyCreatedGames() +bool GameFiltersSettings::isHideNotBuddyCreatedGames() const { QVariant previous = getValue("hide_not_buddy_created_games"); return previous == QVariant() ? false : previous.toBool(); @@ -88,7 +88,7 @@ void GameFiltersSettings::setHideOpenDecklistGames(bool hide) setValue(hide, "hide_open_decklist_games"); } -bool GameFiltersSettings::isHideOpenDecklistGames() +bool GameFiltersSettings::isHideOpenDecklistGames() const { QVariant previous = getValue("hide_open_decklist_games"); return previous == QVariant() ? false : previous.toBool(); @@ -99,7 +99,7 @@ void GameFiltersSettings::setGameNameFilter(QString gameName) setValue(gameName, "game_name_filter"); } -QString GameFiltersSettings::getGameNameFilter() +QString GameFiltersSettings::getGameNameFilter() const { return getValue("game_name_filter").toString(); } @@ -109,7 +109,7 @@ void GameFiltersSettings::setCreatorNameFilters(QStringList creatorName) setValue(creatorName, "creator_name_filter"); } -QStringList GameFiltersSettings::getCreatorNameFilters() +QStringList GameFiltersSettings::getCreatorNameFilters() const { return getValue("creator_name_filter").toStringList(); } @@ -119,7 +119,7 @@ void GameFiltersSettings::setMinPlayers(int min) setValue(min, "min_players"); } -int GameFiltersSettings::getMinPlayers() +int GameFiltersSettings::getMinPlayers() const { QVariant previous = getValue("min_players"); return previous == QVariant() ? 1 : previous.toInt(); @@ -130,7 +130,7 @@ void GameFiltersSettings::setMaxPlayers(int max) setValue(max, "max_players"); } -int GameFiltersSettings::getMaxPlayers() +int GameFiltersSettings::getMaxPlayers() const { QVariant previous = getValue("max_players"); return previous == QVariant() ? 99 : previous.toInt(); @@ -141,7 +141,7 @@ void GameFiltersSettings::setMaxGameAge(const QTime &maxGameAge) setValue(maxGameAge, "max_game_age_time"); } -QTime GameFiltersSettings::getMaxGameAge() +QTime GameFiltersSettings::getMaxGameAge() const { QVariant previous = getValue("max_game_age_time"); return previous.toTime(); @@ -157,7 +157,7 @@ void GameFiltersSettings::setGameHashedTypeEnabled(QString gametypeHASHED, bool setValue(enabled, gametypeHASHED); } -bool GameFiltersSettings::isGameTypeEnabled(QString gametype) +bool GameFiltersSettings::isGameTypeEnabled(QString gametype) const { QVariant previous = getValue("game_type/" + hashGameType(gametype)); return previous == QVariant() ? false : previous.toBool(); @@ -168,7 +168,7 @@ void GameFiltersSettings::setShowOnlyIfSpectatorsCanWatch(bool show) setValue(show, "show_only_if_spectators_can_watch"); } -bool GameFiltersSettings::isShowOnlyIfSpectatorsCanWatch() +bool GameFiltersSettings::isShowOnlyIfSpectatorsCanWatch() const { QVariant previous = getValue("show_only_if_spectators_can_watch"); return previous == QVariant() ? false : previous.toBool(); @@ -179,7 +179,7 @@ void GameFiltersSettings::setShowSpectatorPasswordProtected(bool show) setValue(show, "show_spectator_password_protected"); } -bool GameFiltersSettings::isShowSpectatorPasswordProtected() +bool GameFiltersSettings::isShowSpectatorPasswordProtected() const { QVariant previous = getValue("show_spectator_password_protected"); return previous == QVariant() ? false : previous.toBool(); @@ -190,7 +190,7 @@ void GameFiltersSettings::setShowOnlyIfSpectatorsCanChat(bool show) setValue(show, "show_only_if_spectators_can_chat"); } -bool GameFiltersSettings::isShowOnlyIfSpectatorsCanChat() +bool GameFiltersSettings::isShowOnlyIfSpectatorsCanChat() const { QVariant previous = getValue("show_only_if_spectators_can_chat"); return previous == QVariant() ? false : previous.toBool(); @@ -201,7 +201,7 @@ void GameFiltersSettings::setShowOnlyIfSpectatorsCanSeeHands(bool show) setValue(show, "show_only_if_spectators_can_see_hands"); } -bool GameFiltersSettings::isShowOnlyIfSpectatorsCanSeeHands() +bool GameFiltersSettings::isShowOnlyIfSpectatorsCanSeeHands() const { QVariant previous = getValue("show_only_if_spectators_can_see_hands"); return previous == QVariant() ? false : previous.toBool(); diff --git a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h index 45e9b7441..c0e60551a 100644 --- a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.h @@ -16,23 +16,23 @@ class GameFiltersSettings : public SettingsManager friend class SettingsCache; public: - bool isHideBuddiesOnlyGames(); - bool isHideFullGames(); - bool isHideGamesThatStarted(); - bool isHidePasswordProtectedGames(); - bool isHideIgnoredUserGames(); - bool isHideNotBuddyCreatedGames(); - bool isHideOpenDecklistGames(); - QString getGameNameFilter(); - QStringList getCreatorNameFilters(); - int getMinPlayers(); - int getMaxPlayers(); - QTime getMaxGameAge(); - bool isGameTypeEnabled(QString gametype); - bool isShowOnlyIfSpectatorsCanWatch(); - bool isShowSpectatorPasswordProtected(); - bool isShowOnlyIfSpectatorsCanChat(); - bool isShowOnlyIfSpectatorsCanSeeHands(); + bool isHideBuddiesOnlyGames() const; + bool isHideFullGames() const; + bool isHideGamesThatStarted() const; + bool isHidePasswordProtectedGames() const; + bool isHideIgnoredUserGames() const; + bool isHideNotBuddyCreatedGames() const; + bool isHideOpenDecklistGames() const; + QString getGameNameFilter() const; + QStringList getCreatorNameFilters() const; + int getMinPlayers() const; + int getMaxPlayers() const; + QTime getMaxGameAge() const; + bool isGameTypeEnabled(QString gametype) const; + bool isShowOnlyIfSpectatorsCanWatch() const; + bool isShowSpectatorPasswordProtected() const; + bool isShowOnlyIfSpectatorsCanChat() const; + bool isShowOnlyIfSpectatorsCanSeeHands() const; void setHideBuddiesOnlyGames(bool hide); void setHideIgnoredUserGames(bool hide); @@ -56,8 +56,6 @@ public: private: explicit GameFiltersSettings(const QString &settingPath, QObject *parent = nullptr); GameFiltersSettings(const GameFiltersSettings & /*other*/); - - [[nodiscard]] QString hashGameType(const QString &gameType) const; }; #endif // GAMEFILTERSSETTINGS_H diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp index f7704cbc7..e914dc2d8 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp @@ -23,12 +23,12 @@ void LayoutsSettings::setMainWindowGeometry(const QByteArray &value) setValue(value, GEOMETRY_PROP, GROUP_MAIN_WINDOW); } -QByteArray LayoutsSettings::getMainWindowGeometry() +QByteArray LayoutsSettings::getMainWindowGeometry() const { return getValue(GEOMETRY_PROP, GROUP_MAIN_WINDOW).toByteArray(); } -QByteArray LayoutsSettings::getDeckEditorLayoutState() +QByteArray LayoutsSettings::getDeckEditorLayoutState() const { return getValue(STATE_PROP, GROUP_DECK_EDITOR).toByteArray(); } @@ -38,7 +38,7 @@ void LayoutsSettings::setDeckEditorLayoutState(const QByteArray &value) setValue(value, STATE_PROP, GROUP_DECK_EDITOR); } -QByteArray LayoutsSettings::getDeckEditorGeometry() +QByteArray LayoutsSettings::getDeckEditorGeometry() const { return getValue(GEOMETRY_PROP, GROUP_DECK_EDITOR).toByteArray(); } @@ -48,7 +48,7 @@ void LayoutsSettings::setDeckEditorGeometry(const QByteArray &value) setValue(value, GEOMETRY_PROP, GROUP_DECK_EDITOR); } -QByteArray LayoutsSettings::getVisualDeckEditorLayoutState() +QByteArray LayoutsSettings::getVisualDeckEditorLayoutState() const { return getValue(STATE_PROP, GROUP_VISUAL_DECK_EDITOR).toByteArray(); } @@ -58,7 +58,7 @@ void LayoutsSettings::setVisualDeckEditorLayoutState(const QByteArray &value) setValue(value, STATE_PROP, GROUP_VISUAL_DECK_EDITOR); } -QByteArray LayoutsSettings::getVisualDeckEditorGeometry() +QByteArray LayoutsSettings::getVisualDeckEditorGeometry() const { return getValue(GEOMETRY_PROP, GROUP_VISUAL_DECK_EDITOR).toByteArray(); } @@ -68,7 +68,7 @@ void LayoutsSettings::setVisualDeckEditorGeometry(const QByteArray &value) setValue(value, GEOMETRY_PROP, GROUP_VISUAL_DECK_EDITOR); } -QByteArray LayoutsSettings::getDeckEditorDbHeaderState() +QByteArray LayoutsSettings::getDeckEditorDbHeaderState() const { return getValue(STATE_PROP, GROUP_DECK_EDITOR_DB, "header").toByteArray(); } @@ -78,7 +78,7 @@ void LayoutsSettings::setDeckEditorDbHeaderState(const QByteArray &value) setValue(value, STATE_PROP, GROUP_DECK_EDITOR_DB, "header"); } -QByteArray LayoutsSettings::getSetsDialogHeaderState() +QByteArray LayoutsSettings::getSetsDialogHeaderState() const { return getValue(STATE_PROP, GROUP_SETS_DIALOG, "header").toByteArray(); } @@ -93,7 +93,7 @@ void LayoutsSettings::setSetsDialogGeometry(const QByteArray &value) setValue(value, GEOMETRY_PROP, GROUP_SETS_DIALOG); } -QByteArray LayoutsSettings::getSetsDialogGeometry() +QByteArray LayoutsSettings::getSetsDialogGeometry() const { return getValue(GEOMETRY_PROP, GROUP_SETS_DIALOG).toByteArray(); } @@ -103,7 +103,7 @@ void LayoutsSettings::setTokenDialogGeometry(const QByteArray &value) setValue(value, GEOMETRY_PROP, GROUP_TOKEN_DIALOG); } -QByteArray LayoutsSettings::getTokenDialogGeometry() +QByteArray LayoutsSettings::getTokenDialogGeometry() const { return getValue(GEOMETRY_PROP, GROUP_TOKEN_DIALOG).toByteArray(); } @@ -118,12 +118,12 @@ void LayoutsSettings::setGamePlayAreaState(const QByteArray &value) setValue(value, STATE_PROP, GROUP_GAME_PLAY_AREA); } -QByteArray LayoutsSettings::getGamePlayAreaLayoutState() +QByteArray LayoutsSettings::getGamePlayAreaLayoutState() const { return getValue(STATE_PROP, GROUP_GAME_PLAY_AREA).toByteArray(); } -QByteArray LayoutsSettings::getGamePlayAreaGeometry() +QByteArray LayoutsSettings::getGamePlayAreaGeometry() const { return getValue(GEOMETRY_PROP, GROUP_GAME_PLAY_AREA).toByteArray(); } @@ -138,12 +138,12 @@ void LayoutsSettings::setReplayPlayAreaState(const QByteArray &value) setValue(value, STATE_PROP, GROUP_REPLAY_PLAY_AREA); } -QByteArray LayoutsSettings::getReplayPlayAreaLayoutState() +QByteArray LayoutsSettings::getReplayPlayAreaLayoutState() const { return getValue(STATE_PROP, GROUP_REPLAY_PLAY_AREA).toByteArray(); } -QByteArray LayoutsSettings::getReplayPlayAreaGeometry() +QByteArray LayoutsSettings::getReplayPlayAreaGeometry() const { return getValue(GEOMETRY_PROP, GROUP_REPLAY_PLAY_AREA).toByteArray(); } diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h index cab9a456e..5353ce15a 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.h @@ -36,24 +36,24 @@ public: void setReplayPlayAreaGeometry(const QByteArray &value); void setReplayPlayAreaState(const QByteArray &value); - QByteArray getMainWindowGeometry(); + QByteArray getMainWindowGeometry() const; - QByteArray getDeckEditorLayoutState(); - QByteArray getDeckEditorGeometry(); + QByteArray getDeckEditorLayoutState() const; + QByteArray getDeckEditorGeometry() const; - QByteArray getVisualDeckEditorLayoutState(); - QByteArray getVisualDeckEditorGeometry(); + QByteArray getVisualDeckEditorLayoutState() const; + QByteArray getVisualDeckEditorGeometry() const; - QByteArray getDeckEditorDbHeaderState(); - QByteArray getSetsDialogHeaderState(); - QByteArray getSetsDialogGeometry(); - QByteArray getTokenDialogGeometry(); + QByteArray getDeckEditorDbHeaderState() const; + QByteArray getSetsDialogHeaderState() const; + QByteArray getSetsDialogGeometry() const; + QByteArray getTokenDialogGeometry() const; - QByteArray getGamePlayAreaLayoutState(); - QByteArray getGamePlayAreaGeometry(); + QByteArray getGamePlayAreaLayoutState() const; + QByteArray getGamePlayAreaGeometry() const; - QByteArray getReplayPlayAreaLayoutState(); - QByteArray getReplayPlayAreaGeometry(); + QByteArray getReplayPlayAreaLayoutState() const; + QByteArray getReplayPlayAreaGeometry() const; signals: public slots: diff --git a/libcockatrice_settings/libcockatrice/settings/message_settings.cpp b/libcockatrice_settings/libcockatrice/settings/message_settings.cpp index 761c94484..50da39df6 100644 --- a/libcockatrice_settings/libcockatrice/settings/message_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/message_settings.cpp @@ -5,12 +5,12 @@ MessageSettings::MessageSettings(const QString &settingPath, QObject *parent) { } -QString MessageSettings::getMessageAt(int index) +QString MessageSettings::getMessageAt(int index) const { return getValue(QString("msg%1").arg(index)).toString(); } -int MessageSettings::getCount() +int MessageSettings::getCount() const { return getValue("count").toInt(); } diff --git a/libcockatrice_settings/libcockatrice/settings/message_settings.h b/libcockatrice_settings/libcockatrice/settings/message_settings.h index 265c455e1..ec70027af 100644 --- a/libcockatrice_settings/libcockatrice/settings/message_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/message_settings.h @@ -15,8 +15,8 @@ class MessageSettings : public SettingsManager friend class SettingsCache; public: - int getCount(); - QString getMessageAt(int index); + int getCount() const; + QString getMessageAt(int index) const; void setCount(int count); void setMessageAt(int index, QString message); diff --git a/libcockatrice_settings/libcockatrice/settings/recents_settings.cpp b/libcockatrice_settings/libcockatrice/settings/recents_settings.cpp index e64dd7494..76bc4069e 100644 --- a/libcockatrice_settings/libcockatrice/settings/recents_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/recents_settings.cpp @@ -7,7 +7,7 @@ RecentsSettings::RecentsSettings(const QString &settingPath, QObject *parent) { } -QStringList RecentsSettings::getRecentlyOpenedDeckPaths() +QStringList RecentsSettings::getRecentlyOpenedDeckPaths() const { return getValue("deckpaths").toStringList(); } @@ -31,7 +31,7 @@ void RecentsSettings::updateRecentlyOpenedDeckPaths(const QString &deckPath) emit recentlyOpenedDeckPathsChanged(); } -QString RecentsSettings::getLatestDeckDirPath() +QString RecentsSettings::getLatestDeckDirPath() const { return getValue("latestDeckDir", "dirs").toString(); } diff --git a/libcockatrice_settings/libcockatrice/settings/recents_settings.h b/libcockatrice_settings/libcockatrice/settings/recents_settings.h index 23c8f1a9f..3aebff334 100644 --- a/libcockatrice_settings/libcockatrice/settings/recents_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/recents_settings.h @@ -18,11 +18,11 @@ class RecentsSettings : public SettingsManager RecentsSettings(const RecentsSettings & /*other*/); public: - QStringList getRecentlyOpenedDeckPaths(); + QStringList getRecentlyOpenedDeckPaths() const; void clearRecentlyOpenedDeckPaths(); void updateRecentlyOpenedDeckPaths(const QString &deckPath); - QString getLatestDeckDirPath(); + QString getLatestDeckDirPath() const; void setLatestDeckDirPath(const QString &dirPath); signals: diff --git a/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp b/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp index 5f69f47c9..0140182be 100644 --- a/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp @@ -13,7 +13,7 @@ void ServersSettings::setPreviousHostLogin(int previous) setValue(previous, "previoushostlogin"); } -int ServersSettings::getPreviousHostLogin() +int ServersSettings::getPreviousHostLogin() const { QVariant previous = getValue("previoushostlogin"); return previous == QVariant() ? 1 : previous.toInt(); @@ -24,7 +24,7 @@ void ServersSettings::setPreviousHostList(QStringList list) setValue(list, "previoushosts"); } -QStringList ServersSettings::getPreviousHostList() +QStringList ServersSettings::getPreviousHostList() const { return getValue("previoushosts").toStringList(); } @@ -48,13 +48,13 @@ QString ServersSettings::getSite(QString defaultSite) return site == QVariant() ? std::move(defaultSite) : site.toString(); } -QString ServersSettings::getPrevioushostName() +QString ServersSettings::getPrevioushostName() const { QVariant value = getValue("previoushostName"); return value == QVariant() ? "Rooster Ranges" : value.toString(); } -int ServersSettings::getPrevioushostindex(const QString &saveName) +int ServersSettings::getPrevioushostindex(const QString &saveName) const { int size = getValue("totalServers", "server", "server_details").toInt(); @@ -65,14 +65,14 @@ int ServersSettings::getPrevioushostindex(const QString &saveName) return -1; } -QString ServersSettings::getHostname(QString defaultHost) +QString ServersSettings::getHostname(QString defaultHost) const { int index = getPrevioushostindex(getPrevioushostName()); QVariant hostname = getValue(QString("server%1").arg(index), "server", "server_details"); return hostname == QVariant() ? std::move(defaultHost) : hostname.toString(); } -QString ServersSettings::getPort(QString defaultPort) +QString ServersSettings::getPort(QString defaultPort) const { int index = getPrevioushostindex(getPrevioushostName()); QVariant port = getValue(QString("port%1").arg(index), "server", "server_details"); @@ -80,7 +80,7 @@ QString ServersSettings::getPort(QString defaultPort) return port == QVariant() ? std::move(defaultPort) : port.toString(); } -QString ServersSettings::getPlayerName(QString defaultName) +QString ServersSettings::getPlayerName(QString defaultName) const { int index = getPrevioushostindex(getPrevioushostName()); QVariant name = getValue(QString("username%1").arg(index), "server", "server_details"); @@ -98,7 +98,7 @@ QString ServersSettings::getPassword() return QString(); } -bool ServersSettings::getSavePassword() +bool ServersSettings::getSavePassword() const { int index = getPrevioushostindex(getPrevioushostName()); bool save = getValue(QString("savePassword%1").arg(index), "server", "server_details").toBool(); @@ -110,7 +110,7 @@ void ServersSettings::setAutoConnect(int autoconnect) setValue(autoconnect, "auto_connect"); } -int ServersSettings::getAutoConnect() +int ServersSettings::getAutoConnect() const { QVariant autoconnect = getValue("auto_connect"); return autoconnect == QVariant() ? 0 : autoconnect.toInt(); @@ -121,7 +121,7 @@ void ServersSettings::setFPHostName(QString hostname) setValue(hostname, "fphostname"); } -QString ServersSettings::getFPHostname(QString defaultHost) +QString ServersSettings::getFPHostname(QString defaultHost) const { QVariant hostname = getValue("fphostname"); return hostname == QVariant() ? std::move(defaultHost) : hostname.toString(); @@ -132,7 +132,7 @@ void ServersSettings::setFPPort(QString port) setValue(port, "fpport"); } -QString ServersSettings::getFPPort(QString defaultPort) +QString ServersSettings::getFPPort(QString defaultPort) const { QVariant port = getValue("fpport"); return port == QVariant() ? std::move(defaultPort) : port.toString(); @@ -143,7 +143,7 @@ void ServersSettings::setFPPlayerName(QString playerName) setValue(playerName, "fpplayername"); } -QString ServersSettings::getFPPlayerName(QString defaultName) +QString ServersSettings::getFPPlayerName(QString defaultName) const { QVariant name = getValue("fpplayername"); return name == QVariant() ? std::move(defaultName) : name.toString(); @@ -154,7 +154,7 @@ void ServersSettings::setClearDebugLogStatus(bool abIsChecked) setValue(abIsChecked, "save_debug_log"); } -bool ServersSettings::getClearDebugLogStatus(bool abDefaultValue) +bool ServersSettings::getClearDebugLogStatus(bool abDefaultValue) const { QVariant cbFlushLog = getValue("save_debug_log"); return cbFlushLog == QVariant() ? abDefaultValue : cbFlushLog.toBool(); diff --git a/libcockatrice_settings/libcockatrice/settings/servers_settings.h b/libcockatrice_settings/libcockatrice/settings/servers_settings.h index 4d92c4647..22603a356 100644 --- a/libcockatrice_settings/libcockatrice/settings/servers_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/servers_settings.h @@ -22,21 +22,21 @@ class ServersSettings : public SettingsManager friend class SettingsCache; public: - int getPreviousHostLogin(); - int getPrevioushostindex(const QString &); - QStringList getPreviousHostList(); - QString getPrevioushostName(); - QString getHostname(QString defaultHost = SERVERSETTINGS_DEFAULT_HOST); - QString getPort(QString defaultPort = SERVERSETTINGS_DEFAULT_PORT); - QString getPlayerName(QString defaultName = ""); - QString getFPHostname(QString defaultHost = SERVERSETTINGS_DEFAULT_HOST); - QString getFPPort(QString defaultPort = SERVERSETTINGS_DEFAULT_PORT); - QString getFPPlayerName(QString defaultName = ""); + int getPreviousHostLogin() const; + int getPrevioushostindex(const QString &) const; + QStringList getPreviousHostList() const; + QString getPrevioushostName() const; + QString getHostname(QString defaultHost = SERVERSETTINGS_DEFAULT_HOST) const; + QString getPort(QString defaultPort = SERVERSETTINGS_DEFAULT_PORT) const; + QString getPlayerName(QString defaultName = "") const; + QString getFPHostname(QString defaultHost = SERVERSETTINGS_DEFAULT_HOST) const; + QString getFPPort(QString defaultPort = SERVERSETTINGS_DEFAULT_PORT) const; + QString getFPPlayerName(QString defaultName = "") const; QString getPassword(); QString getSaveName(QString defaultname = ""); QString getSite(QString defaultName = ""); - bool getSavePassword(); - int getAutoConnect(); + bool getSavePassword() const; + int getAutoConnect() const; void setPreviousHostLogin(int previous); void setPrevioushostName(const QString &); @@ -67,7 +67,7 @@ public: QString port = QString(), QString site = QString()); void setClearDebugLogStatus(bool abIsChecked); - bool getClearDebugLogStatus(bool abDefaultValue); + bool getClearDebugLogStatus(bool abDefaultValue) const; private: explicit ServersSettings(const QString &settingPath, QObject *parent = nullptr); diff --git a/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp b/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp index 3f3deb638..2d4f1c441 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp +++ b/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp @@ -108,7 +108,7 @@ void SettingsManager::deleteValue(const QString &name, const QString &group, con } } -QVariant SettingsManager::getValue(const QString &name) +QVariant SettingsManager::getValue(const QString &name) const { auto settings = getSettings(); @@ -133,7 +133,7 @@ QVariant SettingsManager::getValue(const QString &name) return value; } -QVariant SettingsManager::getValue(const QString &name, const QString &group, const QString &subGroup) +QVariant SettingsManager::getValue(const QString &name, const QString &group, const QString &subGroup) const { auto settings = getSettings(); diff --git a/libcockatrice_settings/libcockatrice/settings/settings_manager.h b/libcockatrice_settings/libcockatrice/settings/settings_manager.h index 18abb8dbf..ad828f089 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_manager.h +++ b/libcockatrice_settings/libcockatrice/settings/settings_manager.h @@ -19,8 +19,8 @@ public: const QString &defaultGroup = QString(), const QString &defaultSubGroup = QString(), QObject *parent = nullptr); - QVariant getValue(const QString &name); - QVariant getValue(const QString &name, const QString &group, const QString &subGroup = QString()); + QVariant getValue(const QString &name) const; + QVariant getValue(const QString &name, const QString &group, const QString &subGroup = QString()) const; void sync(); protected: From 7caa88bc58b649b2536d83fed35fdcf2743dbdb6 Mon Sep 17 00:00:00 2001 From: Bruno Alexandre Rosa <1791393+brunoalr@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:37:30 -0300 Subject: [PATCH 284/325] fix: solve deprecated literal operator error by updating peglib (#6745) * fix: solve deprecated literal operator error * update peglib https://github.com/yhirose/cpp-peglib/commit/dcabc63cf4b966eb6f33abd571ae4cda6bf707f0 --- .../libcockatrice/utility/peglib.h | 8083 +++++++++-------- 1 file changed, 4452 insertions(+), 3631 deletions(-) diff --git a/libcockatrice_utility/libcockatrice/utility/peglib.h b/libcockatrice_utility/libcockatrice/utility/peglib.h index 6a5b87b2d..3ae6040c4 100644 --- a/libcockatrice_utility/libcockatrice/utility/peglib.h +++ b/libcockatrice_utility/libcockatrice/utility/peglib.h @@ -1,4 +1,4 @@ -// +// // peglib.h // // Copyright (c) 2022 Yuji Hirose. All rights reserved. @@ -17,6 +17,7 @@ #include #include +#include #include #include #if __has_include() @@ -30,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -51,28 +53,28 @@ namespace peg { // "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". template struct scope_exit { - explicit scope_exit(EF &&f) - : exit_function(std::move(f)), execute_on_destruction{true} {} + explicit scope_exit(EF &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} - scope_exit(scope_exit &&rhs) - : exit_function(std::move(rhs.exit_function)), - execute_on_destruction{rhs.execute_on_destruction} { - rhs.release(); - } + scope_exit(scope_exit &&rhs) + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } - ~scope_exit() { - if (execute_on_destruction) { this->exit_function(); } - } + ~scope_exit() { + if (execute_on_destruction) { this->exit_function(); } + } - void release() { this->execute_on_destruction = false; } + void release() { this->execute_on_destruction = false; } private: - scope_exit(const scope_exit &) = delete; - void operator=(const scope_exit &) = delete; - scope_exit &operator=(scope_exit &&) = delete; + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; - EF exit_function; - bool execute_on_destruction; + EF exit_function; + bool execute_on_destruction; }; /*----------------------------------------------------------------------------- @@ -80,130 +82,136 @@ private: *---------------------------------------------------------------------------*/ inline size_t codepoint_length(const char *s8, size_t l) { - if (l) { - auto b = static_cast(s8[0]); - if ((b & 0x80) == 0) { - return 1; - } else if ((b & 0xE0) == 0xC0 && l >= 2) { - return 2; - } else if ((b & 0xF0) == 0xE0 && l >= 3) { - return 3; - } else if ((b & 0xF8) == 0xF0 && l >= 4) { - return 4; - } + if (l) { + auto b = static_cast(s8[0]); + if ((b & 0x80) == 0) { + return 1; + } else if ((b & 0xE0) == 0xC0 && l >= 2) { + return 2; + } else if ((b & 0xF0) == 0xE0 && l >= 3) { + return 3; + } else if ((b & 0xF8) == 0xF0 && l >= 4) { + return 4; } - return 0; + } + return 0; } inline size_t codepoint_count(const char *s8, size_t l) { - size_t count = 0; - for (size_t i = 0; i < l; i += codepoint_length(s8 + i, l - i)) { - count++; + size_t count = 0; + for (size_t i = 0; i < l;) { + auto len = codepoint_length(s8 + i, l - i); + if (len == 0) { + // Invalid UTF-8 byte, treat as single byte to avoid infinite loop + len = 1; } - return count; + i += len; + count++; + } + return count; } inline size_t encode_codepoint(char32_t cp, char *buff) { - if (cp < 0x0080) { - buff[0] = static_cast(cp & 0x7F); - return 1; - } else if (cp < 0x0800) { - buff[0] = static_cast(0xC0 | ((cp >> 6) & 0x1F)); - buff[1] = static_cast(0x80 | (cp & 0x3F)); - return 2; - } else if (cp < 0xD800) { - buff[0] = static_cast(0xE0 | ((cp >> 12) & 0xF)); - buff[1] = static_cast(0x80 | ((cp >> 6) & 0x3F)); - buff[2] = static_cast(0x80 | (cp & 0x3F)); - return 3; - } else if (cp < 0xE000) { - // D800 - DFFF is invalid... - return 0; - } else if (cp < 0x10000) { - buff[0] = static_cast(0xE0 | ((cp >> 12) & 0xF)); - buff[1] = static_cast(0x80 | ((cp >> 6) & 0x3F)); - buff[2] = static_cast(0x80 | (cp & 0x3F)); - return 3; - } else if (cp < 0x110000) { - buff[0] = static_cast(0xF0 | ((cp >> 18) & 0x7)); - buff[1] = static_cast(0x80 | ((cp >> 12) & 0x3F)); - buff[2] = static_cast(0x80 | ((cp >> 6) & 0x3F)); - buff[3] = static_cast(0x80 | (cp & 0x3F)); - return 4; - } + if (cp < 0x0080) { + buff[0] = static_cast(cp & 0x7F); + return 1; + } else if (cp < 0x0800) { + buff[0] = static_cast(0xC0 | ((cp >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (cp & 0x3F)); + return 2; + } else if (cp < 0xD800) { + buff[0] = static_cast(0xE0 | ((cp >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((cp >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (cp & 0x3F)); + return 3; + } else if (cp < 0xE000) { + // D800 - DFFF is invalid... return 0; + } else if (cp < 0x10000) { + buff[0] = static_cast(0xE0 | ((cp >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((cp >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (cp & 0x3F)); + return 3; + } else if (cp < 0x110000) { + buff[0] = static_cast(0xF0 | ((cp >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((cp >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((cp >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (cp & 0x3F)); + return 4; + } + return 0; } inline std::string encode_codepoint(char32_t cp) { - char buff[4]; - auto l = encode_codepoint(cp, buff); - return std::string(buff, l); + char buff[4]; + auto l = encode_codepoint(cp, buff); + return std::string(buff, l); } inline bool decode_codepoint(const char *s8, size_t l, size_t &bytes, char32_t &cp) { - if (l) { - auto b = static_cast(s8[0]); - if ((b & 0x80) == 0) { - bytes = 1; - cp = b; - return true; - } else if ((b & 0xE0) == 0xC0) { - if (l >= 2) { - bytes = 2; - cp = ((static_cast(s8[0] & 0x1F)) << 6) | - (static_cast(s8[1] & 0x3F)); - return true; - } - } else if ((b & 0xF0) == 0xE0) { - if (l >= 3) { - bytes = 3; - cp = ((static_cast(s8[0] & 0x0F)) << 12) | - ((static_cast(s8[1] & 0x3F)) << 6) | - (static_cast(s8[2] & 0x3F)); - return true; - } - } else if ((b & 0xF8) == 0xF0) { - if (l >= 4) { - bytes = 4; - cp = ((static_cast(s8[0] & 0x07)) << 18) | - ((static_cast(s8[1] & 0x3F)) << 12) | - ((static_cast(s8[2] & 0x3F)) << 6) | - (static_cast(s8[3] & 0x3F)); - return true; - } - } + if (l) { + auto b = static_cast(s8[0]); + if ((b & 0x80) == 0) { + bytes = 1; + cp = b; + return true; + } else if ((b & 0xE0) == 0xC0) { + if (l >= 2) { + bytes = 2; + cp = ((static_cast(s8[0] & 0x1F)) << 6) | + (static_cast(s8[1] & 0x3F)); + return true; + } + } else if ((b & 0xF0) == 0xE0) { + if (l >= 3) { + bytes = 3; + cp = ((static_cast(s8[0] & 0x0F)) << 12) | + ((static_cast(s8[1] & 0x3F)) << 6) | + (static_cast(s8[2] & 0x3F)); + return true; + } + } else if ((b & 0xF8) == 0xF0) { + if (l >= 4) { + bytes = 4; + cp = ((static_cast(s8[0] & 0x07)) << 18) | + ((static_cast(s8[1] & 0x3F)) << 12) | + ((static_cast(s8[2] & 0x3F)) << 6) | + (static_cast(s8[3] & 0x3F)); + return true; + } } - return false; + } + return false; } inline size_t decode_codepoint(const char *s8, size_t l, char32_t &cp) { - size_t bytes; - if (decode_codepoint(s8, l, bytes, cp)) { return bytes; } - return 0; + size_t bytes; + if (decode_codepoint(s8, l, bytes, cp)) { return bytes; } + return 0; } inline char32_t decode_codepoint(const char *s8, size_t l) { - char32_t cp = 0; - decode_codepoint(s8, l, cp); - return cp; + char32_t cp = 0; + decode_codepoint(s8, l, cp); + return cp; } inline std::u32string decode(const char *s8, size_t l) { - std::u32string out; - size_t i = 0; - while (i < l) { - auto beg = i++; - while (i < l && (s8[i] & 0xc0) == 0x80) { - i++; - } - out += decode_codepoint(&s8[beg], (i - beg)); + std::u32string out; + size_t i = 0; + while (i < l) { + auto beg = i++; + while (i < l && (s8[i] & 0xc0) == 0x80) { + i++; } - return out; + out += decode_codepoint(&s8[beg], (i - beg)); + } + return out; } template const char *u8(const T *s) { - return reinterpret_cast(s); + return reinterpret_cast(s); } /*----------------------------------------------------------------------------- @@ -211,23 +219,23 @@ template const char *u8(const T *s) { *---------------------------------------------------------------------------*/ inline std::string escape_characters(const char *s, size_t n) { - std::string str; - for (size_t i = 0; i < n; i++) { - auto c = s[i]; - switch (c) { - case '\f': str += "\\f"; break; - case '\n': str += "\\n"; break; - case '\r': str += "\\r"; break; - case '\t': str += "\\t"; break; - case '\v': str += "\\v"; break; - default: str += c; break; - } + std::string str; + for (size_t i = 0; i < n; i++) { + auto c = s[i]; + switch (c) { + case '\f': str += "\\f"; break; + case '\n': str += "\\n"; break; + case '\r': str += "\\r"; break; + case '\t': str += "\\t"; break; + case '\v': str += "\\v"; break; + default: str += c; break; } - return str; + } + return str; } inline std::string escape_characters(std::string_view sv) { - return escape_characters(sv.data(), sv.size()); + return escape_characters(sv.data(), sv.size()); } /*----------------------------------------------------------------------------- @@ -235,120 +243,121 @@ inline std::string escape_characters(std::string_view sv) { *---------------------------------------------------------------------------*/ inline bool is_hex(char c, int &v) { - if ('0' <= c && c <= '9') { - v = c - '0'; - return true; - } else if ('a' <= c && c <= 'f') { - v = c - 'a' + 10; - return true; - } else if ('A' <= c && c <= 'F') { - v = c - 'A' + 10; - return true; - } - return false; + if ('0' <= c && c <= '9') { + v = c - '0'; + return true; + } else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; + return true; + } else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } + return false; } inline bool is_digit(char c, int &v) { - if ('0' <= c && c <= '9') { - v = c - '0'; - return true; - } - return false; + if ('0' <= c && c <= '9') { + v = c - '0'; + return true; + } + return false; } inline std::pair parse_hex_number(const char *s, size_t n, size_t i) { - int ret = 0; - int val; - while (i < n && is_hex(s[i], val)) { - ret = static_cast(ret * 16 + val); - i++; - } - return std::pair(ret, i); + int ret = 0; + int val; + while (i < n && is_hex(s[i], val)) { + ret = static_cast(ret * 16 + val); + i++; + } + return std::pair(ret, i); } inline std::pair parse_octal_number(const char *s, size_t n, size_t i) { - int ret = 0; - int val; - while (i < n && is_digit(s[i], val)) { - ret = static_cast(ret * 8 + val); - i++; - } - return std::pair(ret, i); + int ret = 0; + int val; + while (i < n && is_digit(s[i], val)) { + ret = static_cast(ret * 8 + val); + i++; + } + return std::pair(ret, i); } inline std::string resolve_escape_sequence(const char *s, size_t n) { - std::string r; - r.reserve(n); + std::string r; + r.reserve(n); - size_t i = 0; - while (i < n) { - auto ch = s[i]; - if (ch == '\\') { - i++; - if (i == n) { throw std::runtime_error("Invalid escape sequence..."); } - switch (s[i]) { - case 'f': - r += '\f'; - i++; - break; - case 'n': - r += '\n'; - i++; - break; - case 'r': - r += '\r'; - i++; - break; - case 't': - r += '\t'; - i++; - break; - case 'v': - r += '\v'; - i++; - break; - case '\'': - r += '\''; - i++; - break; - case '"': - r += '"'; - i++; - break; - case '[': - r += '['; - i++; - break; - case ']': - r += ']'; - i++; - break; - case '\\': - r += '\\'; - i++; - break; - case 'x': - case 'u': { - char32_t cp; - std::tie(cp, i) = parse_hex_number(s, n, i + 1); - r += encode_codepoint(cp); - break; - } - default: { - char32_t cp; - std::tie(cp, i) = parse_octal_number(s, n, i); - r += encode_codepoint(cp); - break; - } - } - } else { - r += ch; - i++; - } + size_t i = 0; + while (i < n) { + auto ch = s[i]; + if (ch == '\\') { + i++; + assert(i < n); + + switch (s[i]) { + case 'f': + r += '\f'; + i++; + break; + case 'n': + r += '\n'; + i++; + break; + case 'r': + r += '\r'; + i++; + break; + case 't': + r += '\t'; + i++; + break; + case 'v': + r += '\v'; + i++; + break; + case '\'': + r += '\''; + i++; + break; + case '"': + r += '"'; + i++; + break; + case '[': + r += '['; + i++; + break; + case ']': + r += ']'; + i++; + break; + case '\\': + r += '\\'; + i++; + break; + case 'x': + case 'u': { + char32_t cp; + std::tie(cp, i) = parse_hex_number(s, n, i + 1); + r += encode_codepoint(cp); + break; + } + default: { + char32_t cp; + std::tie(cp, i) = parse_octal_number(s, n, i); + r += encode_codepoint(cp); + break; + } + } + } else { + r += ch; + i++; } - return r; + } + return r; } /*----------------------------------------------------------------------------- @@ -356,19 +365,26 @@ inline std::string resolve_escape_sequence(const char *s, size_t n) { *---------------------------------------------------------------------------*/ template T token_to_number_(std::string_view sv) { - T n = 0; + T n = 0; #if __has_include() - if constexpr (!std::is_floating_point::value) { - std::from_chars(sv.data(), sv.data() + sv.size(), n); + if constexpr (!std::is_floating_point::value) { + std::from_chars(sv.data(), sv.data() + sv.size(), n); #else - if constexpr (false) { + if constexpr (false) { #endif - } else { - auto s = std::string(sv); - std::istringstream ss(s); - ss >> n; - } - return n; + } else { + auto s = std::string(sv); + std::istringstream ss(s); + ss >> n; + } + return n; +} + +inline std::string to_lower(std::string s) { + for (auto &c : s) { + c = static_cast(std::tolower(static_cast(c))); + } + return s; } /*----------------------------------------------------------------------------- @@ -377,75 +393,75 @@ template T token_to_number_(std::string_view sv) { class Trie { public: - Trie(const std::vector &items, bool ignore_case) - : ignore_case_(ignore_case) { - size_t id = 0; - for (const auto &item : items) { - const auto &s = ignore_case ? to_lower(item) : item; - for (size_t len = 1; len <= item.size(); len++) { - auto last = len == item.size(); - std::string_view sv(s.data(), len); - auto it = dic_.find(sv); - if (it == dic_.end()) { - dic_.emplace(sv, Info{last, last, id}); - } else if (last) { - it->second.match = true; - } else { - it->second.done = false; - } - } - id++; + Trie(const std::vector &items, bool ignore_case) + : ignore_case_(ignore_case), items_count_(items.size()) { + size_t id = 0; + for (const auto &item : items) { + const auto &s = ignore_case ? to_lower(item) : item; + if (item.size() > max_len_) { max_len_ = item.size(); } + for (size_t len = 1; len <= item.size(); len++) { + auto last = len == item.size(); + std::string_view sv(s.data(), len); + auto it = dic_.find(sv); + if (it == dic_.end()) { + dic_.emplace(sv, Info{last, last, id}); + } else if (last) { + it->second.match = true; + } else { + it->second.done = false; } + } + id++; + } + } + + size_t match(const char *text, size_t text_len, size_t &id) const { + auto limit = std::min(text_len, max_len_); + std::string lower_text; + if (ignore_case_) { + lower_text = to_lower(std::string(text, limit)); + text = lower_text.data(); } - size_t match(const char *text, size_t text_len, size_t &id) const { - std::string lower_text; - if (ignore_case_) { - lower_text = to_lower(text); - text = lower_text.data(); + size_t match_len = 0; + auto done = false; + size_t len = 1; + while (!done && len <= limit) { + std::string_view sv(text, len); + auto it = dic_.find(sv); + if (it == dic_.end()) { + done = true; + } else { + if (it->second.match) { + match_len = len; + id = it->second.id; } - - size_t match_len = 0; - auto done = false; - size_t len = 1; - while (!done && len <= text_len) { - std::string_view sv(text, len); - auto it = dic_.find(sv); - if (it == dic_.end()) { - done = true; - } else { - if (it->second.match) { - match_len = len; - id = it->second.id; - } - if (it->second.done) { done = true; } - } - len += 1; - } - return match_len; + if (it->second.done) { done = true; } + } + len += 1; } + return match_len; + } - size_t size() const { return dic_.size(); } + size_t size() const { return dic_.size(); } + size_t items_count() const { return items_count_; } + + friend struct ComputeFirstSet; private: - std::string to_lower(std::string s) const { - for (char &c : s) { - c = std::tolower(c); - } - return s; - } + struct Info { + bool done; + bool match; + size_t id; + }; - struct Info { - bool done; - bool match; - size_t id; - }; + // TODO: Use unordered_map when heterogeneous lookup is supported in C++20 + // std::unordered_map dic_; + std::map> dic_; - //! \todo Use unordered_map when heterogeneous lookup is supported in C++20 - //! \todo std::unordered_map dic_; - std::map> dic_; - - bool ignore_case_; + bool ignore_case_; + size_t items_count_; + size_t max_len_ = 0; }; /*----------------------------------------------------------------------------- @@ -456,21 +472,21 @@ private: * Line information utility function */ inline std::pair line_info(const char *start, const char *cur) { - auto p = start; - auto col_ptr = p; - auto no = 1; + auto p = start; + auto col_ptr = p; + auto no = 1; - while (p < cur) { - if (*p == '\n') { - no++; - col_ptr = p + 1; - } - p++; + while (p < cur) { + if (*p == '\n') { + no++; + col_ptr = p + 1; } + p++; + } - auto col = codepoint_count(col_ptr, p - col_ptr) + 1; + auto col = codepoint_count(col_ptr, p - col_ptr) + 1; - return std::pair(no, col); + return std::pair(no, col); } /* @@ -478,19 +494,19 @@ inline std::pair line_info(const char *start, const char *cur) { */ inline constexpr unsigned int str2tag_core(const char *s, size_t l, unsigned int h) { - return (l == 0) ? h - : str2tag_core(s + 1, l - 1, - (h * 33) ^ static_cast(*s)); + return (l == 0) ? h + : str2tag_core(s + 1, l - 1, + (h * 33) ^ static_cast(*s)); } inline constexpr unsigned int str2tag(std::string_view sv) { - return str2tag_core(sv.data(), sv.size(), 0); + return str2tag_core(sv.data(), sv.size(), 0); } namespace udl { -inline constexpr unsigned int operator"" _(const char *s, size_t l) { - return str2tag_core(s, l, 0); +inline constexpr unsigned int operator""_(const char *s, size_t l) { + return str2tag_core(s, l, 0); } } // namespace udl @@ -501,126 +517,113 @@ inline constexpr unsigned int operator"" _(const char *s, size_t l) { class Context; struct SemanticValues : protected std::vector { - SemanticValues() = default; - SemanticValues(Context *c) : c_(c) {} + SemanticValues() = default; + SemanticValues(Context *c) : c_(c) {} - // Input text - const char *path = nullptr; - const char *ss = nullptr; + // Input text + const char *path = nullptr; + const char *ss = nullptr; - // Matched string - std::string_view sv() const { return sv_; } + // Matched string + std::string_view sv() const { return sv_; } - // Definition name - const std::string &name() const { return name_; } + // Definition name + const std::string &name() const { return name_; } - std::vector tags; + std::vector tags; - // Line number and column at which the matched string is - std::pair line_info() const; + // Line number and column at which the matched string is + std::pair line_info() const; - // Choice count - size_t choice_count() const { return choice_count_; } + // Choice count + size_t choice_count() const { return choice_count_; } - // Choice number (0 based index) - size_t choice() const { return choice_; } + // Choice number (0 based index) + size_t choice() const { return choice_; } - // Tokens - std::vector tokens; + // Tokens + std::vector tokens; - std::string_view token(size_t id = 0) const { - if (tokens.empty()) { return sv_; } - assert(id < tokens.size()); - return tokens[id]; + std::string_view token(size_t id = 0) const { + if (tokens.empty()) { return sv_; } + assert(id < tokens.size()); + return tokens[id]; + } + + // Token conversion + std::string token_to_string(size_t id = 0) const { + return std::string(token(id)); + } + + template T token_to_number() const { + return token_to_number_(token()); + } + + // Transform the semantic value vector to another vector + template + std::vector transform(size_t beg = 0, + size_t end = static_cast(-1)) const { + std::vector r; + end = (std::min)(end, size()); + for (size_t i = beg; i < end; i++) { + r.emplace_back(std::any_cast((*this)[i])); } + return r; + } - // Token conversion - std::string token_to_string(size_t id = 0) const { - return std::string(token(id)); - } - - template T token_to_number() const { - return token_to_number_(token()); - } - - // Transform the semantic value vector to another vector - template - std::vector transform(size_t beg = 0, - size_t end = static_cast(-1)) const { - std::vector r; - end = (std::min)(end, size()); - for (size_t i = beg; i < end; i++) { - r.emplace_back(std::any_cast((*this)[i])); - } - return r; - } - - void append(SemanticValues &chvs) { - sv_ = chvs.sv_; - for (auto &v : chvs) { - emplace_back(std::move(v)); - } - for (auto &tag : chvs.tags) { - tags.emplace_back(std::move(tag)); - } - for (auto &tok : chvs.tokens) { - tokens.emplace_back(std::move(tok)); - } - } - - using std::vector::iterator; - using std::vector::const_iterator; - using std::vector::size; - using std::vector::empty; - using std::vector::assign; - using std::vector::begin; - using std::vector::end; - using std::vector::rbegin; - using std::vector::rend; - using std::vector::operator[]; - using std::vector::at; - using std::vector::resize; - using std::vector::front; - using std::vector::back; - using std::vector::push_back; - using std::vector::pop_back; - using std::vector::insert; - using std::vector::erase; - using std::vector::clear; - using std::vector::swap; - using std::vector::emplace; - using std::vector::emplace_back; + using std::vector::iterator; + using std::vector::const_iterator; + using std::vector::size; + using std::vector::empty; + using std::vector::assign; + using std::vector::begin; + using std::vector::end; + using std::vector::rbegin; + using std::vector::rend; + using std::vector::operator[]; + using std::vector::at; + using std::vector::resize; + using std::vector::front; + using std::vector::back; + using std::vector::push_back; + using std::vector::pop_back; + using std::vector::insert; + using std::vector::erase; + using std::vector::clear; + using std::vector::swap; + using std::vector::emplace; + using std::vector::emplace_back; private: - friend class Context; - friend class Dictionary; - friend class Sequence; - friend class PrioritizedChoice; - friend class Repetition; - friend class Holder; - friend class PrecedenceClimbing; + friend class Context; + friend class Dictionary; + friend class Sequence; + friend class PrioritizedChoice; + friend class Repetition; + friend class Holder; + friend class PrecedenceClimbing; - Context *c_ = nullptr; - std::string_view sv_; - size_t choice_count_ = 0; - size_t choice_ = 0; - std::string name_; + Context *c_ = nullptr; + std::string_view sv_; + size_t choice_count_ = 0; + size_t choice_ = 0; + std::string name_; }; /* * Semantic action */ template std::any call(F fn, Args &&...args) { - using R = decltype(fn(std::forward(args)...)); - if constexpr (std::is_void::value) { - fn(std::forward(args)...); - return std::any(); - } else if constexpr (std::is_same::type, - std::any>::value) { - return fn(std::forward(args)...); - } else { - return std::any(fn(std::forward(args)...)); - } + using R = decltype(fn(std::forward(args)...)); + if constexpr (std::is_void::value) { + fn(std::forward(args)...); + return std::any(); + } else if constexpr (std::is_same::type, + std::any>::value) { + return fn(std::forward(args)...); + } else { + return std::any(fn(std::forward(args)...)); + } } template @@ -637,30 +640,74 @@ struct argument_count class Action { public: - Action() = default; - Action(Action &&rhs) = default; - template Action(F fn) : fn_(make_adaptor(fn)) {} - template void operator=(F fn) { fn_ = make_adaptor(fn); } - Action &operator=(const Action &rhs) = default; + Action() = default; + Action(Action &&rhs) = default; + template Action(F fn) : fn_(make_adaptor(fn)) {} + template void operator=(F fn) { fn_ = make_adaptor(fn); } + Action &operator=(const Action &rhs) = default; - operator bool() const { return bool(fn_); } + operator bool() const { return bool(fn_); } - std::any operator()(SemanticValues &vs, std::any &dt) const { - return fn_(vs, dt); - } + std::any operator()(SemanticValues &vs, std::any &dt, + const std::any &predicate_data) const { + return fn_(vs, dt, predicate_data); + } private: - using Fty = std::function; + using Fty = std::function; - template Fty make_adaptor(F fn) { - if constexpr (argument_count::value == 1) { - return [fn](auto &vs, auto & /*dt*/) { return call(fn, vs); }; - } else { - return [fn](auto &vs, auto &dt) { return call(fn, vs, dt); }; - } + template Fty make_adaptor(F fn) { + if constexpr (argument_count::value == 1) { + return [fn](auto &vs, auto & /*dt*/, const auto & /*predicate_data*/) { + return call(fn, vs); + }; + } else if constexpr (argument_count::value == 2) { + return [fn](auto &vs, auto &dt, const auto & /*predicate_data*/) { + return call(fn, vs, dt); + }; + } else { + return [fn](auto &vs, auto &dt, const auto &predicate_data) { + return call(fn, vs, dt, predicate_data); + }; } + } - Fty fn_; + Fty fn_; +}; + +class Predicate { +public: + Predicate() = default; + Predicate(Predicate &&rhs) = default; + template Predicate(F fn) : fn_(make_adaptor(fn)) {} + template void operator=(F fn) { fn_ = make_adaptor(fn); } + Predicate &operator=(const Predicate &rhs) = default; + + operator bool() const { return bool(fn_); } + + bool operator()(const SemanticValues &vs, const std::any &dt, + std::string &msg, std::any &predicate_data) const { + return fn_(vs, dt, msg, predicate_data); + } + +private: + using Fty = std::function; + + template Fty make_adaptor(F fn) { + if constexpr (argument_count::value == 3) { + return [fn](const auto &vs, const auto &dt, auto &msg, + auto & /*predicate_data*/) { return fn(vs, dt, msg); }; + } else { + return [fn](const auto &vs, const auto &dt, auto &msg, + auto &predicate_data) { + return fn(vs, dt, msg, predicate_data); + }; + } + } + + Fty fn_; }; /* @@ -682,67 +729,67 @@ using Log = std::function> expected_tokens; - const char *message_pos = nullptr; - std::string message; - std::string label; - const char *last_output_pos = nullptr; - bool keep_previous_token = false; + const char *error_pos = nullptr; + std::vector> expected_tokens; + const char *message_pos = nullptr; + std::string message; + std::string label; + const char *last_output_pos = nullptr; + bool keep_previous_token = false; - void clear() { - error_pos = nullptr; - expected_tokens.clear(); - message_pos = nullptr; - message.clear(); + void clear() { + error_pos = nullptr; + expected_tokens.clear(); + message_pos = nullptr; + message.clear(); + } + + void add(const char *error_literal, const Definition *error_rule) { + for (const auto &[t, r] : expected_tokens) { + if (t == error_literal && r == error_rule) { return; } } + expected_tokens.emplace_back(error_literal, error_rule); + } - void add(const char *error_literal, const Definition *error_rule) { - for (const auto &[t, r] : expected_tokens) { - if (t == error_literal && r == error_rule) { return; } - } - expected_tokens.emplace_back(error_literal, error_rule); - } - - void output_log(const Log &log, const char *s, size_t n); + void output_log(const Log &log, const char *s, size_t n); private: - int cast_char(char c) const { return static_cast(c); } + int cast_char(char c) const { return static_cast(c); } - std::string heuristic_error_token(const char *s, size_t n, - const char *pos) const { - auto len = n - std::distance(s, pos); - if (len) { - size_t i = 0; - auto c = cast_char(pos[i++]); - if (!std::ispunct(c) && !std::isspace(c)) { - while (i < len && !std::ispunct(cast_char(pos[i])) && - !std::isspace(cast_char(pos[i]))) { - i++; - } - } - - size_t count = CPPPEGLIB_HEURISTIC_ERROR_TOKEN_MAX_CHAR_COUNT; - size_t j = 0; - while (count > 0 && j < i) { - j += codepoint_length(&pos[j], i - j); - count--; - } - - return escape_characters(pos, j); + std::string heuristic_error_token(const char *s, size_t n, + const char *pos) const { + auto len = n - std::distance(s, pos); + if (len) { + size_t i = 0; + auto c = cast_char(pos[i++]); + if (!std::ispunct(c) && !std::isspace(c)) { + while (i < len && !std::ispunct(cast_char(pos[i])) && + !std::isspace(cast_char(pos[i]))) { + i++; } - return std::string(); - } + } - std::string replace_all(std::string str, const std::string &from, - const std::string &to) const { - size_t pos = 0; - while ((pos = str.find(from, pos)) != std::string::npos) { - str.replace(pos, from.length(), to); - pos += to.length(); - } - return str; + size_t count = CPPPEGLIB_HEURISTIC_ERROR_TOKEN_MAX_CHAR_COUNT; + size_t j = 0; + while (count > 0 && j < i) { + j += codepoint_length(&pos[j], i - j); + count--; + } + + return escape_characters(pos, j); } + return std::string(); + } + + std::string replace_all(std::string str, const std::string &from, + const std::string &to) const { + size_t pos = 0; + while ((pos = str.find(from, pos)) != std::string::npos) { + str.replace(pos, from.length(), to); + pos += to.length(); + } + return str; + } }; /* @@ -762,211 +809,269 @@ using TracerStartOrEnd = std::function; class Context { public: - const char *path; - const char *s; - const size_t l; + const char *path; + const char *s; + const size_t l; - ErrorInfo error_info; - bool recovered = false; + ErrorInfo error_info; + bool recovered = false; - std::vector> value_stack; - size_t value_stack_size = 0; + std::vector> value_stack; + size_t value_stack_size = 0; - std::vector rule_stack; - std::vector>> args_stack; + std::vector rule_stack; + std::vector>> args_stack; - size_t in_token_boundary_count = 0; + size_t in_token_boundary_count = 0; - std::shared_ptr whitespaceOpe; - bool in_whitespace = false; + std::shared_ptr whitespaceOpe; + bool in_whitespace = false; - std::shared_ptr wordOpe; + std::shared_ptr wordOpe; - std::vector> capture_scope_stack; - size_t capture_scope_stack_size = 0; + std::vector> capture_entries; - std::vector cut_stack; + std::vector cut_stack; - const size_t def_count; - const bool enablePackratParsing; - std::vector cache_registered; - std::vector cache_success; + const size_t def_count; + const bool enablePackratParsing; + std::vector cache_registered; + std::vector cache_success; - std::map, std::tuple> - cache_values; + std::map, std::tuple> + cache_values; - TracerEnter tracer_enter; - TracerLeave tracer_leave; - std::any trace_data; - const bool verbose_trace; + // Left recursion support + struct LRMemo { + size_t len = static_cast(-1); + std::any val; + }; + std::map, LRMemo> lr_memo; - Log log; + // Rules whose lr_memo was hit during the current parse scope. + // Used to track LR cycle membership. + std::set lr_refs_hit; - Context(const char *path, const char *s, size_t l, size_t def_count, - std::shared_ptr whitespaceOpe, std::shared_ptr wordOpe, - bool enablePackratParsing, TracerEnter tracer_enter, - TracerLeave tracer_leave, std::any trace_data, bool verbose_trace, - Log log) - : path(path), s(s), l(l), whitespaceOpe(whitespaceOpe), wordOpe(wordOpe), - def_count(def_count), enablePackratParsing(enablePackratParsing), - cache_registered(enablePackratParsing ? def_count * (l + 1) : 0), - cache_success(enablePackratParsing ? def_count * (l + 1) : 0), - tracer_enter(tracer_enter), tracer_leave(tracer_leave), - trace_data(trace_data), verbose_trace(verbose_trace), log(log) { + // Rules currently in their seeding/growing phase at a given position. + // Protected from having their lr_memo erased by inner growers. + std::set> lr_active_seeds; - push_args({}); - push_capture_scope(); + void clear_packrat_cache(const char *pos, size_t def_id) { + if (!enablePackratParsing) { return; } + auto col = static_cast(pos - s); + auto idx = def_count * col + def_id; + if (idx < cache_registered.size()) { + cache_registered[idx] = false; + cache_success[idx] = false; + } + cache_values.erase(std::make_pair(col, def_id)); + } + + void write_packrat_cache(const char *pos, size_t def_id, size_t len, + const std::any &val) { + if (!enablePackratParsing) { return; } + auto col = pos - s; + auto idx = def_count * static_cast(col) + def_id; + if (idx >= cache_registered.size()) { return; } + cache_registered[idx] = true; + cache_success[idx] = true; + auto key = std::pair(col, def_id); + cache_values[key] = std::pair(len, val); + } + + TracerEnter tracer_enter; + TracerLeave tracer_leave; + std::any trace_data; + const bool verbose_trace; + + Log log; + + Context(const char *path, const char *s, size_t l, size_t def_count, + std::shared_ptr whitespaceOpe, std::shared_ptr wordOpe, + bool enablePackratParsing, TracerEnter tracer_enter, + TracerLeave tracer_leave, std::any trace_data, bool verbose_trace, + Log log) + : path(path), s(s), l(l), whitespaceOpe(whitespaceOpe), wordOpe(wordOpe), + def_count(def_count), enablePackratParsing(enablePackratParsing), + cache_registered(enablePackratParsing ? def_count * (l + 1) : 0), + cache_success(enablePackratParsing ? def_count * (l + 1) : 0), + tracer_enter(tracer_enter), tracer_leave(tracer_leave), + trace_data(trace_data), verbose_trace(verbose_trace), log(log) { + + push_args({}); + } + + ~Context() { + assert(!value_stack_size); + assert(cut_stack.empty()); + } + + Context(const Context &) = delete; + Context(Context &&) = delete; + Context operator=(const Context &) = delete; + + // Per-rule packrat stats (populated when packrat_stats is non-null) + struct PackratStats { + size_t hits = 0; + size_t misses = 0; + }; + std::vector *packrat_stats = nullptr; + + // Per-rule packrat filter: if set, only rules with filter[def_id]=true + // use full memoization (cache_values map). Others use bitvector-only + // re-entry guard. + const std::vector *packrat_rule_filter = nullptr; + + template + void packrat(const char *a_s, size_t def_id, size_t &len, std::any &val, + T fn) { + if (!enablePackratParsing) { + fn(val); + return; } - ~Context() { - pop_capture_scope(); + auto col = a_s - s; + auto idx = def_count * static_cast(col) + def_id; - assert(!value_stack_size); - assert(!capture_scope_stack_size); - assert(cut_stack.empty()); + if (cache_registered[idx]) { + if (packrat_stats && def_id < packrat_stats->size()) { + (*packrat_stats)[def_id].hits++; + } + if (cache_success[idx]) { + auto key = std::pair(col, def_id); + std::tie(len, val) = cache_values[key]; + return; + } else { + len = static_cast(-1); + return; + } + } else { + // Pre-register as failure (re-entry guard for all rules) + cache_registered[idx] = true; + cache_success[idx] = false; + + if (packrat_stats && def_id < packrat_stats->size()) { + (*packrat_stats)[def_id].misses++; + } + + fn(val); + + bool full_memo = + !packrat_rule_filter || (def_id < packrat_rule_filter->size() && + (*packrat_rule_filter)[def_id]); + if (full_memo) { + if (success(len)) { write_packrat_cache(a_s, def_id, len, val); } + } else { + // Guard-only: undo registration so future calls re-parse + cache_registered[idx] = false; + } + return; + } + } + + // Semantic values + SemanticValues &push_semantic_values_scope() { + assert(value_stack_size <= value_stack.size()); + if (value_stack_size == value_stack.size()) { + value_stack.emplace_back(std::make_shared(this)); + } else { + auto &vs = *value_stack[value_stack_size]; + if (!vs.empty()) { + vs.clear(); + if (!vs.tags.empty()) { vs.tags.clear(); } + } + vs.sv_ = std::string_view(); + vs.choice_count_ = 0; + vs.choice_ = 0; + if (!vs.tokens.empty()) { vs.tokens.clear(); } } - Context(const Context &) = delete; - Context(Context &&) = delete; - Context operator=(const Context &) = delete; + auto &vs = *value_stack[value_stack_size++]; + vs.path = path; + vs.ss = s; + return vs; + } - template - void packrat(const char *a_s, size_t def_id, size_t &len, std::any &val, - T fn) { - if (!enablePackratParsing) { - fn(val); - return; - } + void pop_semantic_values_scope() { value_stack_size--; } - auto col = a_s - s; - auto idx = def_count * static_cast(col) + def_id; + // Arguments + void push_args(std::vector> &&args) { + args_stack.emplace_back(std::move(args)); + } - if (cache_registered[idx]) { - if (cache_success[idx]) { - auto key = std::pair(col, def_id); - std::tie(len, val) = cache_values[key]; - return; - } else { - len = static_cast(-1); - return; - } - } else { - fn(val); - cache_registered[idx] = true; - cache_success[idx] = success(len); - if (success(len)) { - auto key = std::pair(col, def_id); - cache_values[key] = std::pair(len, val); - } - return; - } - } + void pop_args() { args_stack.pop_back(); } - SemanticValues &push() { - push_capture_scope(); - return push_semantic_values_scope(); - } + const std::vector> &top_args() const { + return args_stack[args_stack.size() - 1]; + } - void pop() { - pop_capture_scope(); - pop_semantic_values_scope(); - } + // Snapshot/Rollback + struct Snapshot { + size_t sv_size; + size_t sv_tags_size; + size_t sv_tokens_size; + std::string_view sv_sv; + size_t choice_count; + size_t choice; + size_t capture_size; + }; - // Semantic values - SemanticValues &push_semantic_values_scope() { - assert(value_stack_size <= value_stack.size()); - if (value_stack_size == value_stack.size()) { - value_stack.emplace_back(std::make_shared(this)); - } else { - auto &vs = *value_stack[value_stack_size]; - if (!vs.empty()) { - vs.clear(); - if (!vs.tags.empty()) { vs.tags.clear(); } - } - vs.sv_ = std::string_view(); - vs.choice_count_ = 0; - vs.choice_ = 0; - if (!vs.tokens.empty()) { vs.tokens.clear(); } - } + Snapshot snapshot(const SemanticValues &vs) const { + return {vs.size(), vs.tags.size(), vs.tokens.size(), vs.sv_, + vs.choice_count_, vs.choice_, capture_entries.size()}; + } - auto &vs = *value_stack[value_stack_size++]; - vs.path = path; - vs.ss = s; - return vs; - } + void rollback(SemanticValues &vs, const Snapshot &snap) { + vs.resize(snap.sv_size); + vs.tags.resize(snap.sv_tags_size); + vs.tokens.resize(snap.sv_tokens_size); + vs.sv_ = snap.sv_sv; + vs.choice_count_ = snap.choice_count; + vs.choice_ = snap.choice; + capture_entries.resize(snap.capture_size); + } - void pop_semantic_values_scope() { value_stack_size--; } + // Skip trailing whitespace with trace suppression. + // Returns whitespace length, or -1 on failure. + // No-op (returns 0) if inside a token boundary or no whitespaceOpe. + size_t skip_whitespace(const char *a_s, size_t n, SemanticValues &vs, + std::any &dt); - // Arguments - void push_args(std::vector> &&args) { - args_stack.emplace_back(args); - } + // Error + void set_error_pos(const char *a_s, const char *literal = nullptr); - void pop_args() { args_stack.pop_back(); } + // Trace + void trace_enter(const Ope &ope, const char *a_s, size_t n, + const SemanticValues &vs, std::any &dt); + void trace_leave(const Ope &ope, const char *a_s, size_t n, + const SemanticValues &vs, std::any &dt, size_t len); + bool is_traceable(const Ope &ope) const; - const std::vector> &top_args() const { - return args_stack[args_stack.size() - 1]; - } + // Line info + std::pair line_info(const char *cur) const { + std::call_once(source_line_index_init_, [this]() { + for (size_t pos = 0; pos < l; pos++) { + if (s[pos] == '\n') { source_line_index.push_back(pos); } + } + source_line_index.push_back(l); + }); - // Capture scope - void push_capture_scope() { - assert(capture_scope_stack_size <= capture_scope_stack.size()); - if (capture_scope_stack_size == capture_scope_stack.size()) { - capture_scope_stack.emplace_back( - std::map()); - } else { - auto &cs = capture_scope_stack[capture_scope_stack_size]; - if (!cs.empty()) { cs.clear(); } - } - capture_scope_stack_size++; - } + auto pos = static_cast(std::distance(s, cur)); - void pop_capture_scope() { capture_scope_stack_size--; } + auto it = std::lower_bound( + source_line_index.begin(), source_line_index.end(), pos, + [](size_t element, size_t value) { return element < value; }); - void shift_capture_values() { - assert(capture_scope_stack_size >= 2); - auto curr = &capture_scope_stack[capture_scope_stack_size - 1]; - auto prev = curr - 1; - for (const auto &[k, v] : *curr) { - (*prev)[k] = v; - } - } + auto id = static_cast(std::distance(source_line_index.begin(), it)); + auto off = pos - (id == 0 ? 0 : source_line_index[id - 1] + 1); + return std::pair(id + 1, off + 1); + } - // Error - void set_error_pos(const char *a_s, const char *literal = nullptr); - - // Trace - void trace_enter(const Ope &ope, const char *a_s, size_t n, - const SemanticValues &vs, std::any &dt); - void trace_leave(const Ope &ope, const char *a_s, size_t n, - const SemanticValues &vs, std::any &dt, size_t len); - bool is_traceable(const Ope &ope) const; - - // Line info - std::pair line_info(const char *cur) const { - std::call_once(source_line_index_init_, [this]() { - for (size_t pos = 0; pos < l; pos++) { - if (s[pos] == '\n') { source_line_index.push_back(pos); } - } - source_line_index.push_back(l); - }); - - auto pos = static_cast(std::distance(s, cur)); - - auto it = std::lower_bound( - source_line_index.begin(), source_line_index.end(), pos, - [](size_t element, size_t value) { return element < value; }); - - auto id = static_cast(std::distance(source_line_index.begin(), it)); - auto off = pos - (id == 0 ? 0 : source_line_index[id - 1] + 1); - return std::pair(id + 1, off + 1); - } - - size_t next_trace_id = 0; - std::vector trace_ids; - bool ignore_trace_state = false; - mutable std::once_flag source_line_index_init_; - mutable std::vector source_line_index; + size_t next_trace_id = 0; + std::vector trace_ids; + bool ignore_trace_state = false; + mutable std::once_flag source_line_index_init_; + mutable std::vector source_line_index; }; /* @@ -974,416 +1079,590 @@ public: */ class Ope { public: - struct Visitor; + struct Visitor; - virtual ~Ope() = default; - size_t parse(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const; - virtual size_t parse_core(const char *s, size_t n, SemanticValues &vs, - Context &c, std::any &dt) const = 0; - virtual void accept(Visitor &v) = 0; + virtual ~Ope() = default; + size_t parse(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const; + virtual size_t parse_core(const char *s, size_t n, SemanticValues &vs, + Context &c, std::any &dt) const = 0; + virtual void accept(Visitor &v) = 0; + + bool is_token_boundary = false; + bool is_choice_like = false; +}; + +// Keyword-guarded identifier data, heap-allocated only for matching Sequences. +// Avoids bloating all Sequence objects with bitsets and keyword sets. +struct KeywordGuardData { + std::bitset<256> identifier_first; // first char of identifier + std::bitset<256> identifier_rest; // subsequent chars of identifier + std::vector exact_keywords; // single-word keywords (lowercase) + std::vector prefix_keywords; // first word of compound keywords + size_t min_keyword_len = 0; + size_t max_keyword_len = 0; + + static bool matches_any(const std::vector &keywords, + std::string_view input) { + return std::any_of(keywords.begin(), keywords.end(), + [&](const auto &kw) { return kw == input; }); + } }; class Sequence : public Ope { public: - template - Sequence(const Args &...args) - : opes_{static_cast>(args)...} {} - Sequence(const std::vector> &opes) : opes_(opes) {} - Sequence(std::vector> &&opes) : opes_(opes) {} + template + Sequence(const Args &...args) + : opes_{static_cast>(args)...} {} + Sequence(const std::vector> &opes) : opes_(opes) {} + Sequence(std::vector> &&opes) : opes_(std::move(opes)) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override { - auto &chvs = c.push_semantic_values_scope(); - auto se = scope_exit([&]() { c.pop_semantic_values_scope(); }); - size_t i = 0; - for (const auto &ope : opes_) { - auto len = ope->parse(s + i, n - i, chvs, c, dt); - if (fail(len)) { return len; } - i += len; - } - vs.append(chvs); - return i; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + // Keyword-guarded identifier fast path: + // Fuses !ReservedKeyword into scan-then-lookup + if (kw_guard_) { + if (auto result = parse_keyword_guarded(s, n, vs, c, dt)) { + return *result; + } + // nullopt means prefix keyword match — fall through to normal path } + size_t i = 0; + for (const auto &ope : opes_) { + auto len = ope->parse(s + i, n - i, vs, c, dt); + if (fail(len)) { return len; } + i += len; + } + return i; + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::vector> opes_; + std::vector> opes_; + +private: + friend struct SetupFirstSets; + std::unique_ptr kw_guard_; + + // Returns parse result, or nullopt to fall through to normal path + std::optional parse_keyword_guarded(const char *s, size_t n, + SemanticValues &vs, Context &c, + std::any &dt) const { + const auto &kw = *kw_guard_; + if (n < 1 || !kw.identifier_first.test(static_cast(*s))) { + c.set_error_pos(s); + return static_cast(-1); + } + // Scan identifier using bitset + size_t id_len = 1; + while (id_len < n && + kw.identifier_rest.test(static_cast(s[id_len]))) { + id_len++; + } + // Skip keyword matching if identifier length is out of range + if (id_len >= kw.min_keyword_len && id_len <= kw.max_keyword_len) { + char lower_buf[64]; + std::unique_ptr lower_heap; + char *lower = lower_buf; + if (id_len > sizeof(lower_buf)) { + lower_heap.reset(new char[id_len]); + lower = lower_heap.get(); + } + std::transform(s, s + id_len, lower, [](unsigned char ch) { + return static_cast(std::tolower(ch)); + }); + std::string_view lower_sv(lower, id_len); + + if (KeywordGuardData::matches_any(kw.exact_keywords, lower_sv)) { + c.set_error_pos(s); + return static_cast(-1); + } + if (KeywordGuardData::matches_any(kw.prefix_keywords, lower_sv)) { + return std::nullopt; + } + } + // Success: emit token and consume trailing whitespace + vs.tokens.emplace_back(std::string_view(s, id_len)); + auto wl = c.skip_whitespace(s + id_len, n - id_len, vs, dt); + if (fail(wl)) { return wl; } + return id_len + wl; + } +}; + +struct FirstSet { + // First-Set: set of possible first bytes for an expression. + // Used by PrioritizedChoice to skip alternatives that cannot match. + std::bitset<256> chars; // byte values that can appear as the first byte + bool can_be_empty = false; // true if the expression can match empty string + bool any_char = false; // true if any character can appear (cannot filter) + const char *first_literal = nullptr; // first literal for error reporting + const Definition *first_rule = + nullptr; // first token rule for error reporting + + void merge(const FirstSet &other) { + chars |= other.chars; + if (other.can_be_empty) { can_be_empty = true; } + if (other.any_char) { any_char = true; } + // Note: first_literal/first_rule are NOT merged — per-alternative + } }; class PrioritizedChoice : public Ope { public: - template - PrioritizedChoice(bool for_label, const Args &...args) - : opes_{static_cast>(args)...}, - for_label_(for_label) {} - PrioritizedChoice(const std::vector> &opes) - : opes_(opes) {} - PrioritizedChoice(std::vector> &&opes) : opes_(opes) {} + template + PrioritizedChoice(bool for_label, const Args &...args) + : opes_{static_cast>(args)...}, + for_label_(for_label) { + is_choice_like = true; + } + PrioritizedChoice(const std::vector> &opes) + : opes_(opes) { + is_choice_like = true; + } + PrioritizedChoice(std::vector> &&opes) + : opes_(std::move(opes)) { + is_choice_like = true; + } - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override { - size_t len = static_cast(-1); + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + size_t len = static_cast(-1); - if (!for_label_) { c.cut_stack.push_back(false); } - auto se1 = scope_exit([&]() { - if (!for_label_) { c.cut_stack.pop_back(); } - }); + if (!for_label_) { c.cut_stack.push_back(false); } + auto se = scope_exit([&]() { + if (!for_label_) { c.cut_stack.pop_back(); } + }); - size_t id = 0; - for (const auto &ope : opes_) { - if (!c.cut_stack.empty()) { c.cut_stack.back() = false; } - - auto &chvs = c.push(); - c.error_info.keep_previous_token = id > 0; - auto se2 = scope_exit([&]() { - c.pop(); - c.error_info.keep_previous_token = false; - }); - - len = ope->parse(s, n, chvs, c, dt); - - if (success(len)) { - vs.append(chvs); - vs.choice_count_ = opes_.size(); - vs.choice_ = id; - c.shift_capture_values(); - break; - } else if (!c.cut_stack.empty() && c.cut_stack.back()) { - break; + size_t id = 0; + for (const auto &ope : opes_) { + // First-Set filtering: skip if next byte cannot start this alternative + if (n > 0 && id < first_sets_.size()) { + const auto &fs = first_sets_[id]; + if (!fs.any_char && !fs.can_be_empty && + !fs.chars.test(static_cast(*s))) { + if (c.log && (fs.first_literal || fs.first_rule)) { + if (c.error_info.error_pos <= s) { + if (c.error_info.error_pos < s || !(id > 0)) { + c.error_info.error_pos = s; + c.error_info.expected_tokens.clear(); + } + if (fs.first_literal) { + c.error_info.add(fs.first_literal, nullptr); + } else { + c.error_info.add(nullptr, fs.first_rule); + } } - - id++; + } + id++; + continue; } + } - return len; + if (!c.cut_stack.empty()) { c.cut_stack.back() = false; } + + auto snap = c.snapshot(vs); + c.error_info.keep_previous_token = id > 0; + + len = ope->parse(s, n, vs, c, dt); + + if (success(len)) { + vs.choice_count_ = opes_.size(); + vs.choice_ = id; + break; + } + + c.rollback(vs, snap); + + if (!c.cut_stack.empty() && c.cut_stack.back()) { break; } + + id++; } - void accept(Visitor &v) override; + c.error_info.keep_previous_token = false; + return len; + } - size_t size() const { return opes_.size(); } + void accept(Visitor &v) override; - std::vector> opes_; - bool for_label_ = false; + size_t size() const { return opes_.size(); } + + std::vector> opes_; + bool for_label_ = false; + std::vector first_sets_; }; class Repetition : public Ope { public: - Repetition(const std::shared_ptr &ope, size_t min, size_t max) - : ope_(ope), min_(min), max_(max) {} + Repetition(const std::shared_ptr &ope, size_t min, size_t max) + : ope_(ope), min_(min), max_(max) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override { - size_t count = 0; - size_t i = 0; - while (count < min_) { - auto &chvs = c.push(); - auto se = scope_exit([&]() { c.pop(); }); - - auto len = ope_->parse(s + i, n - i, chvs, c, dt); - - if (success(len)) { - vs.append(chvs); - c.shift_capture_values(); - } else { - return len; - } - i += len; - count++; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + // ISpan fast path: tight loop for ASCII CharacterClass repetition. + // Safe because each ASCII match is exactly 1 byte, so byte count == match + // count. + if (span_bitset_) { + const auto &bitset = *span_bitset_; + size_t i = 0; + if (max_ == std::numeric_limits::max()) { + // Unbounded repetition (*, +): no per-iteration max check + while (i < n && bitset.test(static_cast(s[i]))) { + i++; } - - while (count < max_) { - auto &chvs = c.push(); - auto se = scope_exit([&]() { c.pop(); }); - - auto len = ope_->parse(s + i, n - i, chvs, c, dt); - - if (success(len)) { - vs.append(chvs); - c.shift_capture_values(); - } else { - break; - } - i += len; - count++; + } else { + auto limit = std::min(n, max_); + while (i < limit && bitset.test(static_cast(s[i]))) { + i++; } - return i; + } + if (i < min_) { + c.set_error_pos(s + i); + return static_cast(-1); + } + return i; } - void accept(Visitor &v) override; - - bool is_zom() const { - return min_ == 0 && max_ == std::numeric_limits::max(); + size_t count = 0; + size_t i = 0; + while (count < min_) { + auto len = ope_->parse(s + i, n - i, vs, c, dt); + if (fail(len)) { return len; } + i += len; + count++; } - static std::shared_ptr zom(const std::shared_ptr &ope) { - return std::make_shared(ope, 0, - std::numeric_limits::max()); + while (count < max_) { + auto snap = c.snapshot(vs); + auto len = ope_->parse(s + i, n - i, vs, c, dt); + if (fail(len)) { + c.rollback(vs, snap); + break; + } + i += len; + count++; } + return i; + } - static std::shared_ptr oom(const std::shared_ptr &ope) { - return std::make_shared(ope, 1, - std::numeric_limits::max()); - } + void accept(Visitor &v) override; - static std::shared_ptr opt(const std::shared_ptr &ope) { - return std::make_shared(ope, 0, 1); - } + bool is_zom() const { + return min_ == 0 && max_ == std::numeric_limits::max(); + } - std::shared_ptr ope_; - size_t min_; - size_t max_; + static std::shared_ptr zom(const std::shared_ptr &ope) { + return std::make_shared(ope, 0, + std::numeric_limits::max()); + } + + static std::shared_ptr oom(const std::shared_ptr &ope) { + return std::make_shared(ope, 1, + std::numeric_limits::max()); + } + + static std::shared_ptr opt(const std::shared_ptr &ope) { + return std::make_shared(ope, 0, 1); + } + + std::shared_ptr ope_; + size_t min_; + size_t max_; + const std::bitset<256> *span_bitset_ = + nullptr; // non-owning, set by SetupFirstSets }; class AndPredicate : public Ope { public: - AndPredicate(const std::shared_ptr &ope) : ope_(ope) {} + AndPredicate(const std::shared_ptr &ope) : ope_(ope) {} - size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, - Context &c, std::any &dt) const override { - auto &chvs = c.push(); - auto se = scope_exit([&]() { c.pop(); }); - - auto len = ope_->parse(s, n, chvs, c, dt); - - if (success(len)) { - return 0; - } else { - return len; - } + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + auto snap = c.snapshot(vs); + auto len = ope_->parse(s, n, vs, c, dt); + c.rollback(vs, snap); // Always rollback — predicates consume nothing + if (success(len)) { + return 0; + } else { + return len; } + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr ope_; + std::shared_ptr ope_; }; class NotPredicate : public Ope { public: - NotPredicate(const std::shared_ptr &ope) : ope_(ope) {} + NotPredicate(const std::shared_ptr &ope) : ope_(ope) {} - size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, - Context &c, std::any &dt) const override { - auto &chvs = c.push(); - auto se = scope_exit([&]() { c.pop(); }); - auto len = ope_->parse(s, n, chvs, c, dt); - if (success(len)) { - c.set_error_pos(s); - return static_cast(-1); - } else { - return 0; - } + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + auto snap = c.snapshot(vs); + auto len = ope_->parse(s, n, vs, c, dt); + c.rollback(vs, snap); // Always rollback — predicates consume nothing + if (success(len)) { + c.set_error_pos(s); + return static_cast(-1); + } else { + return 0; } + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr ope_; + std::shared_ptr ope_; }; class Dictionary : public Ope, public std::enable_shared_from_this { public: - Dictionary(const std::vector &v, bool ignore_case) - : trie_(v, ignore_case) {} + Dictionary(const std::vector &v, bool ignore_case) + : trie_(v, ignore_case) { + is_choice_like = true; + } - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor &v) override; + void accept(Visitor &v) override; - Trie trie_; + Trie trie_; }; class LiteralString : public Ope, public std::enable_shared_from_this { public: - LiteralString(std::string &&s, bool ignore_case) - : lit_(s), ignore_case_(ignore_case), is_word_(false) {} + LiteralString(std::string &&s, bool ignore_case) + : lit_(std::move(s)), ignore_case_(ignore_case), + lower_lit_(ignore_case ? to_lower(lit_) : std::string()), + is_word_(false) {} - LiteralString(const std::string &s, bool ignore_case) - : lit_(s), ignore_case_(ignore_case), is_word_(false) {} + LiteralString(const std::string &s, bool ignore_case) + : lit_(s), ignore_case_(ignore_case), + lower_lit_(ignore_case ? to_lower(lit_) : std::string()), + is_word_(false) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::string lit_; - bool ignore_case_; - mutable std::once_flag init_is_word_; - mutable bool is_word_; + std::string lit_; + bool ignore_case_; + std::string lower_lit_; // pre-computed for ignore_case + mutable std::once_flag init_is_word_; + mutable bool is_word_; }; class CharacterClass : public Ope, public std::enable_shared_from_this { public: - CharacterClass(const std::string &s, bool negated, bool ignore_case) - : negated_(negated), ignore_case_(ignore_case) { - auto chars = decode(s.data(), s.length()); - auto i = 0u; - while (i < chars.size()) { - if (i + 2 < chars.size() && chars[i + 1] == '-') { - auto cp1 = chars[i]; - auto cp2 = chars[i + 2]; - ranges_.emplace_back(std::pair(cp1, cp2)); - i += 3; - } else { - auto cp = chars[i]; - ranges_.emplace_back(std::pair(cp, cp)); - i += 1; - } - } - assert(!ranges_.empty()); + CharacterClass(const std::string &s, bool negated, bool ignore_case) + : negated_(negated), ignore_case_(ignore_case) { + auto chars = decode(s.data(), s.length()); + auto i = 0u; + while (i < chars.size()) { + if (i + 2 < chars.size() && chars[i + 1] == '-') { + auto cp1 = chars[i]; + auto cp2 = chars[i + 2]; + ranges_.emplace_back(std::pair(cp1, cp2)); + i += 3; + } else { + auto cp = chars[i]; + ranges_.emplace_back(std::pair(cp, cp)); + i += 1; + } + } + assert(!ranges_.empty()); + setup_ascii_bitset(); + } + + CharacterClass(const std::vector> &ranges, + bool negated, bool ignore_case) + : ranges_(ranges), negated_(negated), ignore_case_(ignore_case) { + assert(!ranges_.empty()); + setup_ascii_bitset(); + } + + size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, + Context &c, std::any & /*dt*/) const override { + if (n < 1) { + c.set_error_pos(s); + return static_cast(-1); } - CharacterClass(const std::vector> &ranges, - bool negated, bool ignore_case) - : ranges_(ranges), negated_(negated), ignore_case_(ignore_case) { - assert(!ranges_.empty()); - } - - size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, - Context &c, std::any & /*dt*/) const override { - if (n < 1) { - c.set_error_pos(s); - return static_cast(-1); - } - - char32_t cp = 0; - auto len = decode_codepoint(s, n, cp); - - for (const auto &range : ranges_) { - if (in_range(range, cp)) { - if (negated_) { - c.set_error_pos(s); - return static_cast(-1); - } else { - return len; - } - } - } + char32_t cp = 0; + auto len = decode_codepoint(s, n, cp); + for (const auto &range : ranges_) { + if (in_range(range, cp)) { if (negated_) { - return len; + c.set_error_pos(s); + return static_cast(-1); } else { - c.set_error_pos(s); - return static_cast(-1); + return len; } + } } - void accept(Visitor &v) override; + if (negated_) { + return len; + } else { + c.set_error_pos(s); + return static_cast(-1); + } + } + + void accept(Visitor &v) override; + + friend struct ComputeFirstSet; + + bool is_ascii_only() const { return is_ascii_only_; } + const std::bitset<256> &ascii_bitset() const { return ascii_bitset_; } private: - bool in_range(const std::pair &range, char32_t cp) const { - if (ignore_case_) { - auto cpl = std::tolower(cp); - return std::tolower(range.first) <= cpl && - cpl <= std::tolower(range.second); - } else { - return range.first <= cp && cp <= range.second; - } + bool in_range(const std::pair &range, char32_t cp) const { + if (ignore_case_) { + auto cpl = std::tolower(cp); + return std::tolower(range.first) <= cpl && + cpl <= std::tolower(range.second); + } else { + return range.first <= cp && cp <= range.second; } + } - std::vector> ranges_; - bool negated_; - bool ignore_case_; + void setup_ascii_bitset() { + if (negated_) { return; } // negated classes can match non-ASCII + for (const auto &[lo, hi] : ranges_) { + if (lo > 0x7F || hi > 0x7F) { return; } + } + is_ascii_only_ = true; + for (const auto &[lo, hi] : ranges_) { + for (auto cp = lo; cp <= hi; cp++) { + auto ch = static_cast(cp); + ascii_bitset_.set(ch); + if (ignore_case_) { + ascii_bitset_.set(static_cast(std::toupper(ch))); + ascii_bitset_.set(static_cast(std::tolower(ch))); + } + } + } + } + + std::vector> ranges_; + bool negated_; + bool ignore_case_; + std::bitset<256> ascii_bitset_; + bool is_ascii_only_ = false; }; class Character : public Ope, public std::enable_shared_from_this { public: - Character(char ch) : ch_(ch) {} + Character(char32_t ch) : ch_(ch) {} - size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, - Context &c, std::any & /*dt*/) const override { - if (n < 1 || s[0] != ch_) { - c.set_error_pos(s); - return static_cast(-1); - } - return 1; + size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, + Context &c, std::any & /*dt*/) const override { + if (n < 1) { + c.set_error_pos(s); + return static_cast(-1); } - void accept(Visitor &v) override; + char32_t cp = 0; + auto len = decode_codepoint(s, n, cp); - char ch_; + if (cp != ch_) { + c.set_error_pos(s); + return static_cast(-1); + } + return len; + } + + void accept(Visitor &v) override; + + char32_t ch_; }; class AnyCharacter : public Ope, public std::enable_shared_from_this { public: - size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, - Context &c, std::any & /*dt*/) const override { - auto len = codepoint_length(s, n); - if (len < 1) { - c.set_error_pos(s); - return static_cast(-1); - } - return len; + size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, + Context &c, std::any & /*dt*/) const override { + auto len = codepoint_length(s, n); + if (len < 1) { + c.set_error_pos(s); + return static_cast(-1); } + return len; + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; }; class CaptureScope : public Ope { public: - CaptureScope(const std::shared_ptr &ope) : ope_(ope) {} + CaptureScope(const std::shared_ptr &ope) : ope_(ope) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override { - c.push_capture_scope(); - auto se = scope_exit([&]() { c.pop_capture_scope(); }); - return ope_->parse(s, n, vs, c, dt); - } + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + auto cap_snap = c.capture_entries.size(); + auto len = ope_->parse(s, n, vs, c, dt); + c.capture_entries.resize(cap_snap); // Always rollback (isolation) + return len; + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr ope_; + std::shared_ptr ope_; }; class Capture : public Ope { public: - using MatchAction = std::function; + using MatchAction = std::function; - Capture(const std::shared_ptr &ope, MatchAction ma) - : ope_(ope), match_action_(ma) {} + Capture(const std::shared_ptr &ope, MatchAction ma) + : ope_(ope), match_action_(ma) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override { - auto len = ope_->parse(s, n, vs, c, dt); - if (success(len) && match_action_) { match_action_(s, len, c); } - return len; - } + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + auto len = ope_->parse(s, n, vs, c, dt); + if (success(len) && match_action_) { match_action_(s, len, c); } + return len; + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr ope_; - MatchAction match_action_; + std::shared_ptr ope_; + MatchAction match_action_; }; class TokenBoundary : public Ope { public: - TokenBoundary(const std::shared_ptr &ope) : ope_(ope) {} + TokenBoundary(const std::shared_ptr &ope) : ope_(ope) { + is_token_boundary = true; + } - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr ope_; + std::shared_ptr ope_; }; class Ignore : public Ope { public: - Ignore(const std::shared_ptr &ope) : ope_(ope) {} + Ignore(const std::shared_ptr &ope) : ope_(ope) {} - size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, - Context &c, std::any &dt) const override { - auto &chvs = c.push_semantic_values_scope(); - auto se = scope_exit([&]() { c.pop_semantic_values_scope(); }); - return ope_->parse(s, n, chvs, c, dt); - } + size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, + Context &c, std::any &dt) const override { + auto &chvs = c.push_semantic_values_scope(); + auto se = scope_exit([&]() { c.pop_semantic_values_scope(); }); + return ope_->parse(s, n, chvs, c, dt); + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr ope_; + std::shared_ptr ope_; }; using Parser = std::function - fn_; + User(Parser fn) : fn_(fn) {} + size_t parse_core(const char *s, size_t n, SemanticValues &vs, + Context & /*c*/, std::any &dt) const override { + assert(fn_); + return fn_(s, n, vs, dt); + } + void accept(Visitor &v) override; + std::function + fn_; }; class WeakHolder : public Ope { public: - WeakHolder(const std::shared_ptr &ope) : weak_(ope) {} + WeakHolder(const std::shared_ptr &ope) : weak_(ope) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override { - auto ope = weak_.lock(); - assert(ope); - return ope->parse(s, n, vs, c, dt); - } + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + auto ope = weak_.lock(); + assert(ope); + return ope->parse(s, n, vs, c, dt); + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::weak_ptr weak_; + std::weak_ptr weak_; }; class Holder : public Ope { public: - Holder(Definition *outer) : outer_(outer) {} + Holder(Definition *outer) : outer_(outer) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::any reduce(SemanticValues &vs, std::any &dt) const; + std::any reduce(SemanticValues &vs, std::any &dt, + const std::any &predicate_data) const; - const std::string &name() const; - const std::string &trace_name() const; + const std::string &name() const; + const std::string &trace_name() const; - std::shared_ptr ope_; - Definition *outer_; - mutable std::once_flag trace_name_init_; - mutable std::string trace_name_; + std::shared_ptr ope_; + Definition *outer_; + mutable std::once_flag trace_name_init_; + mutable std::string trace_name_; - friend class Definition; + friend class Definition; }; using Grammar = std::unordered_map; class Reference : public Ope, public std::enable_shared_from_this { public: - Reference(const Grammar &grammar, const std::string &name, const char *s, - bool is_macro, const std::vector> &args) - : grammar_(grammar), name_(name), s_(s), is_macro_(is_macro), args_(args), - rule_(nullptr), iarg_(0) {} + Reference(const Grammar &grammar, const std::string &name, const char *s, + bool is_macro, const std::vector> &args) + : grammar_(grammar), name_(name), s_(s), is_macro_(is_macro), args_(args), + rule_(nullptr), iarg_(0) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr get_core_operator() const; + std::shared_ptr get_core_operator() const; - const Grammar &grammar_; - const std::string name_; - const char *s_; + const Grammar &grammar_; + const std::string name_; + const char *s_; - const bool is_macro_; - const std::vector> args_; + const bool is_macro_; + const std::vector> args_; - Definition *rule_; - size_t iarg_; + Definition *rule_; + size_t iarg_; }; class Whitespace : public Ope { public: - Whitespace(const std::shared_ptr &ope) : ope_(ope) {} + Whitespace(const std::shared_ptr &ope) : ope_(ope) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override { - if (c.in_whitespace) { return 0; } - c.in_whitespace = true; - auto se = scope_exit([&]() { c.in_whitespace = false; }); - return ope_->parse(s, n, vs, c, dt); - } + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + if (c.in_whitespace) { return 0; } + c.in_whitespace = true; + auto se = scope_exit([&]() { c.in_whitespace = false; }); + return ope_->parse(s, n, vs, c, dt); + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr ope_; + std::shared_ptr ope_; }; class BackReference : public Ope { public: - BackReference(std::string &&name) : name_(name) {} + BackReference(std::string &&name) : name_(std::move(name)) {} - BackReference(const std::string &name) : name_(name) {} + BackReference(const std::string &name) : name_(name) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::string name_; + std::string name_; }; class PrecedenceClimbing : public Ope { public: - using BinOpeInfo = std::map>; + using BinOpeInfo = std::map>; - PrecedenceClimbing(const std::shared_ptr &atom, - const std::shared_ptr &binop, const BinOpeInfo &info, - const Definition &rule) - : atom_(atom), binop_(binop), info_(info), rule_(rule) {} + PrecedenceClimbing(const std::shared_ptr &atom, + const std::shared_ptr &binop, const BinOpeInfo &info, + const Definition &rule) + : atom_(atom), binop_(binop), info_(info), rule_(rule) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override { - return parse_expression(s, n, vs, c, dt, 0); - } + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + return parse_expression(s, n, vs, c, dt, 0); + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr atom_; - std::shared_ptr binop_; - BinOpeInfo info_; - const Definition &rule_; + std::shared_ptr atom_; + std::shared_ptr binop_; + BinOpeInfo info_; + const Definition &rule_; private: - size_t parse_expression(const char *s, size_t n, SemanticValues &vs, - Context &c, std::any &dt, size_t min_prec) const; + size_t parse_expression(const char *s, size_t n, SemanticValues &vs, + Context &c, std::any &dt, size_t min_prec) const; - Definition &get_reference_for_binop(Context &c) const; + Definition &get_reference_for_binop(Context &c) const; }; class Recovery : public Ope { public: - Recovery(const std::shared_ptr &ope) : ope_(ope) {} + Recovery(const std::shared_ptr &ope) : ope_(ope) {} - size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, - std::any &dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor &v) override; + void accept(Visitor &v) override; - std::shared_ptr ope_; + std::shared_ptr ope_; }; class Cut : public Ope, public std::enable_shared_from_this { public: - size_t parse_core(const char * /*s*/, size_t /*n*/, SemanticValues & /*vs*/, - Context &c, std::any & /*dt*/) const override { - if (!c.cut_stack.empty()) { c.cut_stack.back() = true; } - return 0; - } + size_t parse_core(const char * /*s*/, size_t /*n*/, SemanticValues & /*vs*/, + Context &c, std::any & /*dt*/) const override { + if (!c.cut_stack.empty()) { c.cut_stack.back() = true; } + return 0; + } - void accept(Visitor &v) override; + void accept(Visitor &v) override; }; /* * Factories */ template std::shared_ptr seq(Args &&...args) { - return std::make_shared(static_cast>(args)...); + return std::make_shared(static_cast>(args)...); } template std::shared_ptr cho(Args &&...args) { - return std::make_shared( - false, static_cast>(args)...); + return std::make_shared( + false, static_cast>(args)...); } template std::shared_ptr cho4label_(Args &&...args) { - return std::make_shared( - true, static_cast>(args)...); + return std::make_shared( + true, static_cast>(args)...); } inline std::shared_ptr zom(const std::shared_ptr &ope) { - return Repetition::zom(ope); + return Repetition::zom(ope); } inline std::shared_ptr oom(const std::shared_ptr &ope) { - return Repetition::oom(ope); + return Repetition::oom(ope); } inline std::shared_ptr opt(const std::shared_ptr &ope) { - return Repetition::opt(ope); + return Repetition::opt(ope); } inline std::shared_ptr rep(const std::shared_ptr &ope, size_t min, size_t max) { - return std::make_shared(ope, min, max); + return std::make_shared(ope, min, max); } inline std::shared_ptr apd(const std::shared_ptr &ope) { - return std::make_shared(ope); + return std::make_shared(ope); } inline std::shared_ptr npd(const std::shared_ptr &ope) { - return std::make_shared(ope); + return std::make_shared(ope); } inline std::shared_ptr dic(const std::vector &v, bool ignore_case) { - return std::make_shared(v, ignore_case); + return std::make_shared(v, ignore_case); } inline std::shared_ptr lit(std::string &&s) { - return std::make_shared(s, false); + return std::make_shared(s, false); } inline std::shared_ptr liti(std::string &&s) { - return std::make_shared(s, true); + return std::make_shared(s, true); } inline std::shared_ptr cls(const std::string &s) { - return std::make_shared(s, false, false); + return std::make_shared(s, false, false); } inline std::shared_ptr cls(const std::vector> &ranges, bool ignore_case = false) { - return std::make_shared(ranges, false, ignore_case); + return std::make_shared(ranges, false, ignore_case); } inline std::shared_ptr ncls(const std::string &s) { - return std::make_shared(s, true, false); + return std::make_shared(s, true, false); } inline std::shared_ptr ncls(const std::vector> &ranges, bool ignore_case = false) { - return std::make_shared(ranges, true, ignore_case); + return std::make_shared(ranges, true, ignore_case); } -inline std::shared_ptr chr(char dt) { - return std::make_shared(dt); +inline std::shared_ptr chr(char32_t dt) { + return std::make_shared(dt); } inline std::shared_ptr dot() { return std::make_shared(); } inline std::shared_ptr csc(const std::shared_ptr &ope) { - return std::make_shared(ope); + return std::make_shared(ope); } inline std::shared_ptr cap(const std::shared_ptr &ope, Capture::MatchAction ma) { - return std::make_shared(ope, ma); + return std::make_shared(ope, ma); } inline std::shared_ptr tok(const std::shared_ptr &ope) { - return std::make_shared(ope); + return std::make_shared(ope); } inline std::shared_ptr ign(const std::shared_ptr &ope) { - return std::make_shared(ope); + return std::make_shared(ope); } inline std::shared_ptr usr(std::function fn) { - return std::make_shared(fn); + return std::make_shared(fn); } inline std::shared_ptr ref(const Grammar &grammar, const std::string &name, const char *s, bool is_macro, const std::vector> &args) { - return std::make_shared(grammar, name, s, is_macro, args); + return std::make_shared(grammar, name, s, is_macro, args); } inline std::shared_ptr wsp(const std::shared_ptr &ope) { - return std::make_shared(std::make_shared(ope)); + return std::make_shared(std::make_shared(ope)); } inline std::shared_ptr bkr(std::string &&name) { - return std::make_shared(name); + return std::make_shared(name); } inline std::shared_ptr pre(const std::shared_ptr &atom, const std::shared_ptr &binop, const PrecedenceClimbing::BinOpeInfo &info, const Definition &rule) { - return std::make_shared(atom, binop, info, rule); + return std::make_shared(atom, binop, info, rule); } inline std::shared_ptr rec(const std::shared_ptr &ope) { - return std::make_shared(ope); + return std::make_shared(ope); } inline std::shared_ptr cut() { return std::make_shared(); } @@ -1686,511 +1966,600 @@ inline std::shared_ptr cut() { return std::make_shared(); } * Visitor */ struct Ope::Visitor { - virtual ~Visitor() {} - virtual void visit(Sequence &) {} - virtual void visit(PrioritizedChoice &) {} - virtual void visit(Repetition &) {} - virtual void visit(AndPredicate &) {} - virtual void visit(NotPredicate &) {} - virtual void visit(Dictionary &) {} - virtual void visit(LiteralString &) {} - virtual void visit(CharacterClass &) {} - virtual void visit(Character &) {} - virtual void visit(AnyCharacter &) {} - virtual void visit(CaptureScope &) {} - virtual void visit(Capture &) {} - virtual void visit(TokenBoundary &) {} - virtual void visit(Ignore &) {} - virtual void visit(User &) {} - virtual void visit(WeakHolder &) {} - virtual void visit(Holder &) {} - virtual void visit(Reference &) {} - virtual void visit(Whitespace &) {} - virtual void visit(BackReference &) {} - virtual void visit(PrecedenceClimbing &) {} - virtual void visit(Recovery &) {} - virtual void visit(Cut &) {} + virtual ~Visitor() {} + virtual void visit(Sequence &) {} + virtual void visit(PrioritizedChoice &) {} + virtual void visit(Repetition &) {} + virtual void visit(AndPredicate &) {} + virtual void visit(NotPredicate &) {} + virtual void visit(Dictionary &) {} + virtual void visit(LiteralString &) {} + virtual void visit(CharacterClass &) {} + virtual void visit(Character &) {} + virtual void visit(AnyCharacter &) {} + virtual void visit(CaptureScope &) {} + virtual void visit(Capture &) {} + virtual void visit(TokenBoundary &) {} + virtual void visit(Ignore &) {} + virtual void visit(User &) {} + virtual void visit(WeakHolder &) {} + virtual void visit(Holder &) {} + virtual void visit(Reference &) {} + virtual void visit(Whitespace &) {} + virtual void visit(BackReference &) {} + virtual void visit(PrecedenceClimbing &) {} + virtual void visit(Recovery &) {} + virtual void visit(Cut &) {} +}; + +struct TraversalVisitor : public Ope::Visitor { + using Ope::Visitor::visit; + void visit(Sequence &ope) override { + for (auto &op : ope.opes_) { + op->accept(*this); + } + } + void visit(PrioritizedChoice &ope) override { + for (auto &op : ope.opes_) { + op->accept(*this); + } + } + void visit(Repetition &ope) override { ope.ope_->accept(*this); } + void visit(AndPredicate &ope) override { ope.ope_->accept(*this); } + void visit(NotPredicate &ope) override { ope.ope_->accept(*this); } + void visit(CaptureScope &ope) override { ope.ope_->accept(*this); } + void visit(Capture &ope) override { ope.ope_->accept(*this); } + void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } + void visit(Ignore &ope) override { ope.ope_->accept(*this); } + void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } + void visit(Holder &ope) override { ope.ope_->accept(*this); } + void visit(Whitespace &ope) override { ope.ope_->accept(*this); } + void visit(Recovery &ope) override { ope.ope_->accept(*this); } + void visit(PrecedenceClimbing &ope) override { ope.atom_->accept(*this); } }; struct TraceOpeName : public Ope::Visitor { - using Ope::Visitor::visit; + using Ope::Visitor::visit; - void visit(Sequence &) override { name_ = "Sequence"; } - void visit(PrioritizedChoice &) override { name_ = "PrioritizedChoice"; } - void visit(Repetition &) override { name_ = "Repetition"; } - void visit(AndPredicate &) override { name_ = "AndPredicate"; } - void visit(NotPredicate &) override { name_ = "NotPredicate"; } - void visit(Dictionary &) override { name_ = "Dictionary"; } - void visit(LiteralString &) override { name_ = "LiteralString"; } - void visit(CharacterClass &) override { name_ = "CharacterClass"; } - void visit(Character &) override { name_ = "Character"; } - void visit(AnyCharacter &) override { name_ = "AnyCharacter"; } - void visit(CaptureScope &) override { name_ = "CaptureScope"; } - void visit(Capture &) override { name_ = "Capture"; } - void visit(TokenBoundary &) override { name_ = "TokenBoundary"; } - void visit(Ignore &) override { name_ = "Ignore"; } - void visit(User &) override { name_ = "User"; } - void visit(WeakHolder &) override { name_ = "WeakHolder"; } - void visit(Holder &ope) override { name_ = ope.trace_name().data(); } - void visit(Reference &) override { name_ = "Reference"; } - void visit(Whitespace &) override { name_ = "Whitespace"; } - void visit(BackReference &) override { name_ = "BackReference"; } - void visit(PrecedenceClimbing &) override { name_ = "PrecedenceClimbing"; } - void visit(Recovery &) override { name_ = "Recovery"; } - void visit(Cut &) override { name_ = "Cut"; } + void visit(Sequence &) override { name_ = "Sequence"; } + void visit(PrioritizedChoice &) override { name_ = "PrioritizedChoice"; } + void visit(Repetition &) override { name_ = "Repetition"; } + void visit(AndPredicate &) override { name_ = "AndPredicate"; } + void visit(NotPredicate &) override { name_ = "NotPredicate"; } + void visit(Dictionary &) override { name_ = "Dictionary"; } + void visit(LiteralString &) override { name_ = "LiteralString"; } + void visit(CharacterClass &) override { name_ = "CharacterClass"; } + void visit(Character &) override { name_ = "Character"; } + void visit(AnyCharacter &) override { name_ = "AnyCharacter"; } + void visit(CaptureScope &) override { name_ = "CaptureScope"; } + void visit(Capture &) override { name_ = "Capture"; } + void visit(TokenBoundary &) override { name_ = "TokenBoundary"; } + void visit(Ignore &) override { name_ = "Ignore"; } + void visit(User &) override { name_ = "User"; } + void visit(WeakHolder &) override { name_ = "WeakHolder"; } + void visit(Holder &ope) override { name_ = ope.trace_name().data(); } + void visit(Reference &) override { name_ = "Reference"; } + void visit(Whitespace &) override { name_ = "Whitespace"; } + void visit(BackReference &) override { name_ = "BackReference"; } + void visit(PrecedenceClimbing &) override { name_ = "PrecedenceClimbing"; } + void visit(Recovery &) override { name_ = "Recovery"; } + void visit(Cut &) override { name_ = "Cut"; } - static std::string get(Ope &ope) { - TraceOpeName vis; - ope.accept(vis); - return vis.name_; - } + static std::string get(Ope &ope) { + TraceOpeName vis; + ope.accept(vis); + return vis.name_; + } private: - const char *name_ = nullptr; + const char *name_ = nullptr; }; -struct AssignIDToDefinition : public Ope::Visitor { - using Ope::Visitor::visit; +struct AssignIDToDefinition : public TraversalVisitor { + using TraversalVisitor::visit; - void visit(Sequence &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - } - } - void visit(PrioritizedChoice &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - } - } - void visit(Repetition &ope) override { ope.ope_->accept(*this); } - void visit(AndPredicate &ope) override { ope.ope_->accept(*this); } - void visit(NotPredicate &ope) override { ope.ope_->accept(*this); } - void visit(CaptureScope &ope) override { ope.ope_->accept(*this); } - void visit(Capture &ope) override { ope.ope_->accept(*this); } - void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } - void visit(Ignore &ope) override { ope.ope_->accept(*this); } - void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } - void visit(Holder &ope) override; - void visit(Reference &ope) override; - void visit(Whitespace &ope) override { ope.ope_->accept(*this); } - void visit(PrecedenceClimbing &ope) override; - void visit(Recovery &ope) override { ope.ope_->accept(*this); } + void visit(Holder &ope) override; + void visit(Reference &ope) override; + void visit(PrecedenceClimbing &ope) override; - std::unordered_map ids; + std::unordered_map ids; }; struct IsLiteralToken : public Ope::Visitor { - using Ope::Visitor::visit; + using Ope::Visitor::visit; - void visit(PrioritizedChoice &ope) override { - for (auto op : ope.opes_) { - if (!IsLiteralToken::check(*op)) { return; } - } - result_ = true; + void visit(PrioritizedChoice &ope) override { + for (const auto &op : ope.opes_) { + if (!IsLiteralToken::check(*op)) { return; } } + result_ = true; + } - void visit(Dictionary &) override { result_ = true; } - void visit(LiteralString &) override { result_ = true; } + void visit(Dictionary &) override { result_ = true; } + void visit(LiteralString &) override { result_ = true; } - static bool check(Ope &ope) { - IsLiteralToken vis; - ope.accept(vis); - return vis.result_; - } + static bool check(Ope &ope) { + IsLiteralToken vis; + ope.accept(vis); + return vis.result_; + } private: - bool result_ = false; + bool result_ = false; }; -struct TokenChecker : public Ope::Visitor { - using Ope::Visitor::visit; +struct TokenChecker : public TraversalVisitor { + using TraversalVisitor::visit; - void visit(Sequence &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - } - } - void visit(PrioritizedChoice &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - } - } - void visit(Repetition &ope) override { ope.ope_->accept(*this); } - void visit(CaptureScope &ope) override { ope.ope_->accept(*this); } - void visit(Capture &ope) override { ope.ope_->accept(*this); } - void visit(TokenBoundary &) override { has_token_boundary_ = true; } - void visit(Ignore &ope) override { ope.ope_->accept(*this); } - void visit(WeakHolder &) override { has_rule_ = true; } - void visit(Holder &ope) override { ope.ope_->accept(*this); } - void visit(Reference &ope) override; - void visit(Whitespace &ope) override { ope.ope_->accept(*this); } - void visit(PrecedenceClimbing &ope) override { ope.atom_->accept(*this); } - void visit(Recovery &ope) override { ope.ope_->accept(*this); } + void visit(TokenBoundary &) override { has_token_boundary_ = true; } + void visit(AndPredicate &) override {} + void visit(NotPredicate &) override {} + void visit(WeakHolder &) override { has_rule_ = true; } + void visit(Reference &ope) override; - static bool is_token(Ope &ope) { - if (IsLiteralToken::check(ope)) { return true; } + static bool is_token(Ope &ope) { + if (IsLiteralToken::check(ope)) { return true; } - TokenChecker vis; - ope.accept(vis); - return vis.has_token_boundary_ || !vis.has_rule_; - } + TokenChecker vis; + ope.accept(vis); + return vis.has_token_boundary_ || !vis.has_rule_; + } private: - bool has_token_boundary_ = false; - bool has_rule_ = false; + bool has_token_boundary_ = false; + bool has_rule_ = false; }; struct FindLiteralToken : public Ope::Visitor { - using Ope::Visitor::visit; + using Ope::Visitor::visit; - void visit(LiteralString &ope) override { token_ = ope.lit_.data(); } - void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } - void visit(Ignore &ope) override { ope.ope_->accept(*this); } - void visit(Reference &ope) override; - void visit(Recovery &ope) override { ope.ope_->accept(*this); } + void visit(LiteralString &ope) override { token_ = ope.lit_.data(); } + void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } + void visit(Ignore &ope) override { ope.ope_->accept(*this); } + void visit(Reference &ope) override; + void visit(Recovery &ope) override { ope.ope_->accept(*this); } - static const char *token(Ope &ope) { - FindLiteralToken vis; - ope.accept(vis); - return vis.token_; - } + static const char *token(Ope &ope) { + FindLiteralToken vis; + ope.accept(vis); + return vis.token_; + } private: - const char *token_ = nullptr; + const char *token_ = nullptr; }; -struct DetectLeftRecursion : public Ope::Visitor { - using Ope::Visitor::visit; +struct DetectLeftRecursion : public TraversalVisitor { + using TraversalVisitor::visit; - DetectLeftRecursion(const std::string &name) : name_(name) {} + DetectLeftRecursion(const std::string &name) : name_(name) {} - void visit(Sequence &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - if (done_) { - break; - } else if (error_s) { - done_ = true; - break; - } - } + void visit(Sequence &ope) override { + for (const auto &op : ope.opes_) { + op->accept(*this); + if (done_) { + break; + } else if (error_s) { + done_ = true; + break; + } } - void visit(PrioritizedChoice &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - if (error_s) { - done_ = true; - break; - } - } + } + void visit(PrioritizedChoice &ope) override { + for (const auto &op : ope.opes_) { + op->accept(*this); + if (error_s) { + done_ = true; + break; + } } - void visit(Repetition &ope) override { - ope.ope_->accept(*this); - done_ = ope.min_ > 0; - } - void visit(AndPredicate &ope) override { - ope.ope_->accept(*this); - done_ = false; - } - void visit(NotPredicate &ope) override { - ope.ope_->accept(*this); - done_ = false; - } - void visit(Dictionary &) override { done_ = true; } - void visit(LiteralString &ope) override { done_ = !ope.lit_.empty(); } - void visit(CharacterClass &) override { done_ = true; } - void visit(Character &) override { done_ = true; } - void visit(AnyCharacter &) override { done_ = true; } - void visit(CaptureScope &ope) override { ope.ope_->accept(*this); } - void visit(Capture &ope) override { ope.ope_->accept(*this); } - void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } - void visit(Ignore &ope) override { ope.ope_->accept(*this); } - void visit(User &) override { done_ = true; } - void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } - void visit(Holder &ope) override { ope.ope_->accept(*this); } - void visit(Reference &ope) override; - void visit(Whitespace &ope) override { ope.ope_->accept(*this); } - void visit(BackReference &) override { done_ = true; } - void visit(PrecedenceClimbing &ope) override { ope.atom_->accept(*this); } - void visit(Recovery &ope) override { ope.ope_->accept(*this); } - void visit(Cut &) override { done_ = true; } + } + void visit(Repetition &ope) override { + ope.ope_->accept(*this); + done_ = ope.min_ > 0; + } + void visit(AndPredicate &ope) override { + ope.ope_->accept(*this); + done_ = false; + } + void visit(NotPredicate &ope) override { + ope.ope_->accept(*this); + done_ = false; + } + void visit(Dictionary &) override { done_ = true; } + void visit(LiteralString &ope) override { done_ = !ope.lit_.empty(); } + void visit(CharacterClass &) override { done_ = true; } + void visit(Character &) override { done_ = true; } + void visit(AnyCharacter &) override { done_ = true; } + void visit(User &) override { done_ = true; } + void visit(Reference &ope) override; + void visit(BackReference &) override { done_ = true; } + void visit(Cut &) override { done_ = true; } - const char *error_s = nullptr; + const char *error_s = nullptr; + + std::shared_ptr resolve_macro_arg(size_t iarg) const; private: - std::string name_; - std::unordered_set refs_; - bool done_ = false; + std::string name_; + std::unordered_set refs_; + bool done_ = false; + std::vector> *> macro_args_stack_; }; -struct HasEmptyElement : public Ope::Visitor { - using Ope::Visitor::visit; +struct ComputeCanBeEmpty : public TraversalVisitor { + using TraversalVisitor::visit; - HasEmptyElement(std::vector> &refs, - std::unordered_map &has_error_cache) - : refs_(refs), has_error_cache_(has_error_cache) {} + bool result = false; - void visit(Sequence &ope) override; - void visit(PrioritizedChoice &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - if (is_empty) { return; } - } - } - void visit(Repetition &ope) override { - if (ope.min_ == 0) { - set_error(); - } else { - ope.ope_->accept(*this); - } - } - void visit(AndPredicate &) override { set_error(); } - void visit(NotPredicate &) override { set_error(); } - void visit(LiteralString &ope) override { - if (ope.lit_.empty()) { set_error(); } - } - void visit(CaptureScope &ope) override { ope.ope_->accept(*this); } - void visit(Capture &ope) override { ope.ope_->accept(*this); } - void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } - void visit(Ignore &ope) override { ope.ope_->accept(*this); } - void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } - void visit(Holder &ope) override { ope.ope_->accept(*this); } - void visit(Reference &ope) override; - void visit(Whitespace &ope) override { ope.ope_->accept(*this); } - void visit(PrecedenceClimbing &ope) override { ope.atom_->accept(*this); } - void visit(Recovery &ope) override { ope.ope_->accept(*this); } - - bool is_empty = false; - const char *error_s = nullptr; - std::string error_name; - -private: - void set_error() { - is_empty = true; - tie(error_s, error_name) = refs_.back(); - } - std::vector> &refs_; - std::unordered_map &has_error_cache_; + void visit(Sequence &ope) override { + result = std::all_of(ope.opes_.begin(), ope.opes_.end(), [](auto &op) { + ComputeCanBeEmpty vis; + op->accept(vis); + return vis.result; + }); + } + void visit(PrioritizedChoice &ope) override { + result = std::any_of(ope.opes_.begin(), ope.opes_.end(), [](auto &op) { + ComputeCanBeEmpty vis; + op->accept(vis); + return vis.result; + }); + } + void visit(Repetition &ope) override { result = ope.min_ == 0; } + void visit(AndPredicate &) override { result = true; } + void visit(NotPredicate &) override { result = true; } + void visit(Dictionary &) override { result = false; } + void visit(LiteralString &ope) override { result = ope.lit_.empty(); } + void visit(CharacterClass &) override { result = false; } + void visit(Character &) override { result = false; } + void visit(AnyCharacter &) override { result = false; } + void visit(User &) override { result = false; } + void visit(Reference &ope) override; + void visit(BackReference &) override { result = false; } + void visit(Cut &) override { result = false; } }; -struct DetectInfiniteLoop : public Ope::Visitor { - using Ope::Visitor::visit; +struct HasEmptyElement : public TraversalVisitor { + using TraversalVisitor::visit; - DetectInfiniteLoop(const char *s, const std::string &name, - std::vector> &refs, - std::unordered_map &has_error_cache) - : refs_(refs), has_error_cache_(has_error_cache) { - refs_.emplace_back(s, name); - } + HasEmptyElement(std::vector> &refs, + std::unordered_map &has_error_cache) + : refs_(refs), has_error_cache_(has_error_cache) {} - DetectInfiniteLoop(std::vector> &refs, - std::unordered_map &has_error_cache) - : refs_(refs), has_error_cache_(has_error_cache) {} + void visit(Sequence &ope) override; + void visit(PrioritizedChoice &ope) override { + for (const auto &op : ope.opes_) { + op->accept(*this); + if (is_empty) { return; } + } + } + void visit(Repetition &ope) override { + if (ope.min_ == 0) { + set_error(); + } else { + ope.ope_->accept(*this); + } + } + void visit(AndPredicate &) override { set_error(); } + void visit(NotPredicate &) override { set_error(); } + void visit(LiteralString &ope) override { + if (ope.lit_.empty()) { set_error(); } + } + void visit(Reference &ope) override; - void visit(Sequence &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - if (has_error) { return; } - } - } - void visit(PrioritizedChoice &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - if (has_error) { return; } - } - } - void visit(Repetition &ope) override { - if (ope.max_ == std::numeric_limits::max()) { - HasEmptyElement vis(refs_, has_error_cache_); - ope.ope_->accept(vis); - if (vis.is_empty) { - has_error = true; - error_s = vis.error_s; - error_name = vis.error_name; - } - } else { - ope.ope_->accept(*this); - } - } - void visit(AndPredicate &ope) override { ope.ope_->accept(*this); } - void visit(NotPredicate &ope) override { ope.ope_->accept(*this); } - void visit(CaptureScope &ope) override { ope.ope_->accept(*this); } - void visit(Capture &ope) override { ope.ope_->accept(*this); } - void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } - void visit(Ignore &ope) override { ope.ope_->accept(*this); } - void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } - void visit(Holder &ope) override { ope.ope_->accept(*this); } - void visit(Reference &ope) override; - void visit(Whitespace &ope) override { ope.ope_->accept(*this); } - void visit(PrecedenceClimbing &ope) override { ope.atom_->accept(*this); } - void visit(Recovery &ope) override { ope.ope_->accept(*this); } - - bool has_error = false; - const char *error_s = nullptr; - std::string error_name; + bool is_empty = false; + const char *error_s = nullptr; + std::string error_name; private: - std::vector> &refs_; - std::unordered_map &has_error_cache_; + void set_error() { + is_empty = true; + tie(error_s, error_name) = refs_.back(); + } + std::vector> &refs_; + std::unordered_map &has_error_cache_; }; -struct ReferenceChecker : public Ope::Visitor { - using Ope::Visitor::visit; +struct DetectInfiniteLoop : public TraversalVisitor { + using TraversalVisitor::visit; - ReferenceChecker(const Grammar &grammar, - const std::vector ¶ms) - : grammar_(grammar), params_(params) {} + DetectInfiniteLoop(const char *s, const std::string &name, + std::vector> &refs, + std::unordered_map &has_error_cache) + : refs_(refs), has_error_cache_(has_error_cache) { + refs_.emplace_back(s, name); + } - void visit(Sequence &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - } + DetectInfiniteLoop(std::vector> &refs, + std::unordered_map &has_error_cache) + : refs_(refs), has_error_cache_(has_error_cache) {} + + void visit(Sequence &ope) override { + for (const auto &op : ope.opes_) { + op->accept(*this); + if (has_error) { return; } } - void visit(PrioritizedChoice &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - } + } + void visit(PrioritizedChoice &ope) override { + for (const auto &op : ope.opes_) { + op->accept(*this); + if (has_error) { return; } } - void visit(Repetition &ope) override { ope.ope_->accept(*this); } - void visit(AndPredicate &ope) override { ope.ope_->accept(*this); } - void visit(NotPredicate &ope) override { ope.ope_->accept(*this); } - void visit(CaptureScope &ope) override { ope.ope_->accept(*this); } - void visit(Capture &ope) override { ope.ope_->accept(*this); } - void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } - void visit(Ignore &ope) override { ope.ope_->accept(*this); } - void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } - void visit(Holder &ope) override { ope.ope_->accept(*this); } - void visit(Reference &ope) override; - void visit(Whitespace &ope) override { ope.ope_->accept(*this); } - void visit(PrecedenceClimbing &ope) override { ope.atom_->accept(*this); } - void visit(Recovery &ope) override { ope.ope_->accept(*this); } + } + void visit(Repetition &ope) override { + if (ope.max_ == std::numeric_limits::max()) { + HasEmptyElement vis(refs_, has_error_cache_); + ope.ope_->accept(vis); + if (vis.is_empty) { + has_error = true; + error_s = vis.error_s; + error_name = vis.error_name; + } + } else { + ope.ope_->accept(*this); + } + } + void visit(Reference &ope) override; - std::unordered_map error_s; - std::unordered_map error_message; - std::unordered_set referenced; + bool has_error = false; + const char *error_s = nullptr; + std::string error_name; private: - const Grammar &grammar_; - const std::vector ¶ms_; + std::vector> &refs_; + std::unordered_map &has_error_cache_; }; -struct LinkReferences : public Ope::Visitor { - using Ope::Visitor::visit; +struct ReferenceChecker : public TraversalVisitor { + using TraversalVisitor::visit; - LinkReferences(Grammar &grammar, const std::vector ¶ms) - : grammar_(grammar), params_(params) {} + ReferenceChecker(const Grammar &grammar, + const std::vector ¶ms) + : grammar_(grammar), params_(params) {} - void visit(Sequence &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - } - } - void visit(PrioritizedChoice &ope) override { - for (auto op : ope.opes_) { - op->accept(*this); - } - } - void visit(Repetition &ope) override { ope.ope_->accept(*this); } - void visit(AndPredicate &ope) override { ope.ope_->accept(*this); } - void visit(NotPredicate &ope) override { ope.ope_->accept(*this); } - void visit(CaptureScope &ope) override { ope.ope_->accept(*this); } - void visit(Capture &ope) override { ope.ope_->accept(*this); } - void visit(TokenBoundary &ope) override { ope.ope_->accept(*this); } - void visit(Ignore &ope) override { ope.ope_->accept(*this); } - void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } - void visit(Holder &ope) override { ope.ope_->accept(*this); } - void visit(Reference &ope) override; - void visit(Whitespace &ope) override { ope.ope_->accept(*this); } - void visit(PrecedenceClimbing &ope) override { ope.atom_->accept(*this); } - void visit(Recovery &ope) override { ope.ope_->accept(*this); } + void visit(Reference &ope) override; + + std::unordered_map error_s; + std::unordered_map error_message; + std::unordered_set referenced; private: - Grammar &grammar_; - const std::vector ¶ms_; + const Grammar &grammar_; + const std::vector ¶ms_; +}; + +struct LinkReferences : public TraversalVisitor { + using TraversalVisitor::visit; + + LinkReferences(Grammar &grammar, const std::vector ¶ms) + : grammar_(grammar), params_(params) {} + + void visit(Reference &ope) override; + +private: + Grammar &grammar_; + const std::vector ¶ms_; }; struct FindReference : public Ope::Visitor { - using Ope::Visitor::visit; + using Ope::Visitor::visit; - FindReference(const std::vector> &args, - const std::vector ¶ms) - : args_(args), params_(params) {} + FindReference(const std::vector> &args, + const std::vector ¶ms) + : args_(args), params_(params) {} - void visit(Sequence &ope) override { - std::vector> opes; - for (auto o : ope.opes_) { - o->accept(*this); - opes.push_back(found_ope); - } - found_ope = std::make_shared(opes); + void visit(Sequence &ope) override { + std::vector> opes; + for (const auto &o : ope.opes_) { + o->accept(*this); + opes.emplace_back(std::move(found_ope)); } - void visit(PrioritizedChoice &ope) override { - std::vector> opes; - for (auto o : ope.opes_) { - o->accept(*this); - opes.push_back(found_ope); - } - found_ope = std::make_shared(opes); + found_ope = std::make_shared(opes); + } + void visit(PrioritizedChoice &ope) override { + std::vector> opes; + for (const auto &o : ope.opes_) { + o->accept(*this); + opes.emplace_back(std::move(found_ope)); } - void visit(Repetition &ope) override { - ope.ope_->accept(*this); - found_ope = rep(found_ope, ope.min_, ope.max_); - } - void visit(AndPredicate &ope) override { - ope.ope_->accept(*this); - found_ope = apd(found_ope); - } - void visit(NotPredicate &ope) override { - ope.ope_->accept(*this); - found_ope = npd(found_ope); - } - void visit(Dictionary &ope) override { found_ope = ope.shared_from_this(); } - void visit(LiteralString &ope) override { - found_ope = ope.shared_from_this(); - } - void visit(CharacterClass &ope) override { - found_ope = ope.shared_from_this(); - } - void visit(Character &ope) override { found_ope = ope.shared_from_this(); } - void visit(AnyCharacter &ope) override { found_ope = ope.shared_from_this(); } - void visit(CaptureScope &ope) override { - ope.ope_->accept(*this); - found_ope = csc(found_ope); - } - void visit(Capture &ope) override { - ope.ope_->accept(*this); - found_ope = cap(found_ope, ope.match_action_); - } - void visit(TokenBoundary &ope) override { - ope.ope_->accept(*this); - found_ope = tok(found_ope); - } - void visit(Ignore &ope) override { - ope.ope_->accept(*this); - found_ope = ign(found_ope); - } - void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } - void visit(Holder &ope) override { ope.ope_->accept(*this); } - void visit(Reference &ope) override; - void visit(Whitespace &ope) override { - ope.ope_->accept(*this); - found_ope = wsp(found_ope); - } - void visit(PrecedenceClimbing &ope) override { - ope.atom_->accept(*this); - found_ope = csc(found_ope); - } - void visit(Recovery &ope) override { - ope.ope_->accept(*this); - found_ope = rec(found_ope); - } - void visit(Cut &ope) override { found_ope = ope.shared_from_this(); } + found_ope = std::make_shared(opes); + } + void visit(Repetition &ope) override { + ope.ope_->accept(*this); + found_ope = rep(found_ope, ope.min_, ope.max_); + } + void visit(AndPredicate &ope) override { + ope.ope_->accept(*this); + found_ope = apd(found_ope); + } + void visit(NotPredicate &ope) override { + ope.ope_->accept(*this); + found_ope = npd(found_ope); + } + void visit(Dictionary &ope) override { found_ope = ope.shared_from_this(); } + void visit(LiteralString &ope) override { + found_ope = ope.shared_from_this(); + } + void visit(CharacterClass &ope) override { + found_ope = ope.shared_from_this(); + } + void visit(Character &ope) override { found_ope = ope.shared_from_this(); } + void visit(AnyCharacter &ope) override { found_ope = ope.shared_from_this(); } + void visit(CaptureScope &ope) override { + ope.ope_->accept(*this); + found_ope = csc(found_ope); + } + void visit(Capture &ope) override { + ope.ope_->accept(*this); + found_ope = cap(found_ope, ope.match_action_); + } + void visit(TokenBoundary &ope) override { + ope.ope_->accept(*this); + found_ope = tok(found_ope); + } + void visit(Ignore &ope) override { + ope.ope_->accept(*this); + found_ope = ign(found_ope); + } + void visit(WeakHolder &ope) override { ope.weak_.lock()->accept(*this); } + void visit(Holder &ope) override { ope.ope_->accept(*this); } + void visit(Reference &ope) override; + void visit(Whitespace &ope) override { + ope.ope_->accept(*this); + found_ope = wsp(found_ope); + } + void visit(PrecedenceClimbing &ope) override { + ope.atom_->accept(*this); + found_ope = csc(found_ope); + } + void visit(Recovery &ope) override { + ope.ope_->accept(*this); + found_ope = rec(found_ope); + } + void visit(Cut &ope) override { found_ope = ope.shared_from_this(); } - std::shared_ptr found_ope; + std::shared_ptr found_ope; private: - const std::vector> &args_; - const std::vector ¶ms_; + const std::vector> &args_; + const std::vector ¶ms_; +}; + +/* + * First-Set computation + */ +struct ComputeFirstSet : public TraversalVisitor { + using TraversalVisitor::visit; + + void visit(Sequence &ope) override { + for (const auto &op : ope.opes_) { + auto save = result_; + result_ = FirstSet{}; + op->accept(*this); + auto element_fs = result_; + result_ = save; + result_.chars |= element_fs.chars; + if (element_fs.any_char) { result_.any_char = true; } + if (!result_.first_literal) { + result_.first_literal = element_fs.first_literal; + } + if (!result_.first_rule) { result_.first_rule = element_fs.first_rule; } + if (!element_fs.can_be_empty) { return; } + // This element can be empty, continue to next + } + result_.can_be_empty = true; + } + void visit(PrioritizedChoice &ope) override { + auto save = result_; + for (const auto &op : ope.opes_) { + result_ = FirstSet{}; + op->accept(*this); + save.merge(result_); + } + result_ = save; + } + void visit(Repetition &ope) override { + ope.ope_->accept(*this); + if (ope.min_ == 0) { result_.can_be_empty = true; } + } + void visit(AndPredicate &) override { result_.can_be_empty = true; } + void visit(NotPredicate &) override { result_.can_be_empty = true; } + void visit(Dictionary &ope) override { + for (const auto &[key, info] : ope.trie_.dic_) { + if (!key.empty()) { + auto ch = static_cast(key[0]); + result_.chars.set(ch); + if (ope.trie_.ignore_case_) { + result_.chars.set(static_cast(std::toupper(ch))); + result_.chars.set(static_cast(std::tolower(ch))); + } + } + } + } + void visit(LiteralString &ope) override { + if (ope.lit_.empty()) { + result_.can_be_empty = true; + } else { + auto ch = static_cast(ope.lit_[0]); + result_.chars.set(ch); + if (ope.ignore_case_) { + result_.chars.set(static_cast(std::toupper(ch))); + result_.chars.set(static_cast(std::tolower(ch))); + } + if (!result_.first_literal) { result_.first_literal = ope.lit_.c_str(); } + } + } + void visit(CharacterClass &ope) override { + for (const auto &range : ope.ranges_) { + auto cp1 = range.first; + auto cp2 = range.second; + if (cp1 > 0x7F || cp2 > 0x7F) { + // Non-ASCII range: conservative fallback + result_.any_char = true; + return; + } + for (auto cp = cp1; cp <= cp2; cp++) { + auto ch = static_cast(cp); + result_.chars.set(ch); + if (ope.ignore_case_) { + result_.chars.set(static_cast(std::toupper(ch))); + result_.chars.set(static_cast(std::tolower(ch))); + } + } + } + if (ope.negated_) { + result_.chars.flip(); + result_.any_char = true; // negated class can match non-ASCII + } + } + void visit(Character &ope) override { + if (ope.ch_ > 0x7F) { + result_.any_char = true; + } else { + result_.chars.set(static_cast(ope.ch_)); + } + } + void visit(AnyCharacter &) override { result_.any_char = true; } + void visit(User &) override { result_.any_char = true; } + void visit(Reference &ope) override; + void visit(BackReference &) override { result_.any_char = true; } + void visit(Cut &) override { result_.can_be_empty = true; } + + FirstSet result_; + +private: + std::unordered_set refs_; +}; + +struct SetupFirstSets : public TraversalVisitor { + using TraversalVisitor::visit; + + void visit(Sequence &ope) override; + void setup_keyword_guarded_identifier(Sequence &ope); + + void visit(PrioritizedChoice &ope) override { + ope.first_sets_.clear(); + ope.first_sets_.reserve(ope.opes_.size()); + for (const auto &op : ope.opes_) { + ComputeFirstSet cfs; + op->accept(cfs); + ope.first_sets_.push_back(cfs.result_); + } + for (const auto &op : ope.opes_) { + op->accept(*this); + } + } + void visit(Repetition &ope) override { + ope.ope_->accept(*this); + // ISpan optimization: detect Repetition + ASCII CharacterClass + auto cc = dynamic_cast(ope.ope_.get()); + if (cc && cc->is_ascii_only()) { ope.span_bitset_ = &cc->ascii_bitset(); } + } + void visit(Reference &ope) override; + +private: + std::unordered_set refs_; }; /* @@ -2205,279 +2574,299 @@ static const char *RECOVER_DEFINITION_NAME = "%recover"; */ class Definition { public: - struct Result { - bool ret; - bool recovered; - size_t len; - ErrorInfo error_info; - }; + struct Result { + bool ret; + bool recovered; + size_t len; + ErrorInfo error_info; + }; - Definition() : holder_(std::make_shared(this)) {} + Definition() : holder_(std::make_shared(this)) {} - Definition(const Definition &rhs) : name(rhs.name), holder_(rhs.holder_) { - holder_->outer_ = this; + Definition(const Definition &rhs) : name(rhs.name), holder_(rhs.holder_) { + holder_->outer_ = this; + } + + Definition(const std::shared_ptr &ope) + : holder_(std::make_shared(this)) { + *this <= ope; + } + + operator std::shared_ptr() { + return std::make_shared(holder_); + } + + Definition &operator<=(const std::shared_ptr &ope) { + holder_->ope_ = ope; + return *this; + } + + Result parse(const char *s, size_t n, const char *path = nullptr, + Log log = nullptr) const { + SemanticValues vs; + std::any dt; + return parse_core(s, n, vs, dt, path, log); + } + + Result parse(const char *s, const char *path = nullptr, + Log log = nullptr) const { + auto n = strlen(s); + return parse(s, n, path, log); + } + + Result parse(const char *s, size_t n, std::any &dt, + const char *path = nullptr, Log log = nullptr) const { + SemanticValues vs; + return parse_core(s, n, vs, dt, path, log); + } + + Result parse(const char *s, std::any &dt, const char *path = nullptr, + Log log = nullptr) const { + auto n = strlen(s); + return parse(s, n, dt, path, log); + } + + template + Result parse_and_get_value(const char *s, size_t n, T &val, + const char *path = nullptr, + Log log = nullptr) const { + SemanticValues vs; + std::any dt; + auto r = parse_core(s, n, vs, dt, path, log); + if (r.ret && !vs.empty() && vs.front().has_value()) { + val = std::any_cast(vs[0]); } + return r; + } - Definition(const std::shared_ptr &ope) - : holder_(std::make_shared(this)) { - *this <= ope; - } + template + Result parse_and_get_value(const char *s, T &val, const char *path = nullptr, + Log log = nullptr) const { + auto n = strlen(s); + return parse_and_get_value(s, n, val, path, log); + } - operator std::shared_ptr() { - return std::make_shared(holder_); + template + Result parse_and_get_value(const char *s, size_t n, std::any &dt, T &val, + const char *path = nullptr, + Log log = nullptr) const { + SemanticValues vs; + auto r = parse_core(s, n, vs, dt, path, log); + if (r.ret && !vs.empty() && vs.front().has_value()) { + val = std::any_cast(vs[0]); } + return r; + } - Definition &operator<=(const std::shared_ptr &ope) { - holder_->ope_ = ope; - return *this; - } - - Result parse(const char *s, size_t n, const char *path = nullptr, - Log log = nullptr) const { - SemanticValues vs; - std::any dt; - return parse_core(s, n, vs, dt, path, log); - } - - Result parse(const char *s, const char *path = nullptr, - Log log = nullptr) const { - auto n = strlen(s); - return parse(s, n, path, log); - } - - Result parse(const char *s, size_t n, std::any &dt, - const char *path = nullptr, Log log = nullptr) const { - SemanticValues vs; - return parse_core(s, n, vs, dt, path, log); - } - - Result parse(const char *s, std::any &dt, const char *path = nullptr, - Log log = nullptr) const { - auto n = strlen(s); - return parse(s, n, dt, path, log); - } - - template - Result parse_and_get_value(const char *s, size_t n, T &val, - const char *path = nullptr, - Log log = nullptr) const { - SemanticValues vs; - std::any dt; - auto r = parse_core(s, n, vs, dt, path, log); - if (r.ret && !vs.empty() && vs.front().has_value()) { - val = std::any_cast(vs[0]); - } - return r; - } - - template - Result parse_and_get_value(const char *s, T &val, const char *path = nullptr, - Log log = nullptr) const { - auto n = strlen(s); - return parse_and_get_value(s, n, val, path, log); - } - - template - Result parse_and_get_value(const char *s, size_t n, std::any &dt, T &val, - const char *path = nullptr, - Log log = nullptr) const { - SemanticValues vs; - auto r = parse_core(s, n, vs, dt, path, log); - if (r.ret && !vs.empty() && vs.front().has_value()) { - val = std::any_cast(vs[0]); - } - return r; - } - - template - Result parse_and_get_value(const char *s, std::any &dt, T &val, - const char *path = nullptr, - Log log = nullptr) const { - auto n = strlen(s); - return parse_and_get_value(s, n, dt, val, path, log); - } + template + Result parse_and_get_value(const char *s, std::any &dt, T &val, + const char *path = nullptr, + Log log = nullptr) const { + auto n = strlen(s); + return parse_and_get_value(s, n, dt, val, path, log); + } #if defined(__cpp_lib_char8_t) - Result parse(const char8_t *s, size_t n, const char *path = nullptr, - Log log = nullptr) const { - return parse(reinterpret_cast(s), n, path, log); - } + Result parse(const char8_t *s, size_t n, const char *path = nullptr, + Log log = nullptr) const { + return parse(reinterpret_cast(s), n, path, log); + } - Result parse(const char8_t *s, const char *path = nullptr, - Log log = nullptr) const { - return parse(reinterpret_cast(s), path, log); - } + Result parse(const char8_t *s, const char *path = nullptr, + Log log = nullptr) const { + return parse(reinterpret_cast(s), path, log); + } - Result parse(const char8_t *s, size_t n, std::any &dt, - const char *path = nullptr, Log log = nullptr) const { - return parse(reinterpret_cast(s), n, dt, path, log); - } + Result parse(const char8_t *s, size_t n, std::any &dt, + const char *path = nullptr, Log log = nullptr) const { + return parse(reinterpret_cast(s), n, dt, path, log); + } - Result parse(const char8_t *s, std::any &dt, const char *path = nullptr, - Log log = nullptr) const { - return parse(reinterpret_cast(s), dt, path, log); - } + Result parse(const char8_t *s, std::any &dt, const char *path = nullptr, + Log log = nullptr) const { + return parse(reinterpret_cast(s), dt, path, log); + } - template - Result parse_and_get_value(const char8_t *s, size_t n, T &val, - const char *path = nullptr, - Log log = nullptr) const { - return parse_and_get_value(reinterpret_cast(s), n, val, *path, - log); - } + template + Result parse_and_get_value(const char8_t *s, size_t n, T &val, + const char *path = nullptr, + Log log = nullptr) const { + return parse_and_get_value(reinterpret_cast(s), n, val, path, + log); + } - template - Result parse_and_get_value(const char8_t *s, T &val, - const char *path = nullptr, - Log log = nullptr) const { - return parse_and_get_value(reinterpret_cast(s), val, *path, - log); - } + template + Result parse_and_get_value(const char8_t *s, T &val, + const char *path = nullptr, + Log log = nullptr) const { + return parse_and_get_value(reinterpret_cast(s), val, path, + log); + } - template - Result parse_and_get_value(const char8_t *s, size_t n, std::any &dt, T &val, - const char *path = nullptr, - Log log = nullptr) const { - return parse_and_get_value(reinterpret_cast(s), n, dt, val, - *path, log); - } + template + Result parse_and_get_value(const char8_t *s, size_t n, std::any &dt, T &val, + const char *path = nullptr, + Log log = nullptr) const { + return parse_and_get_value(reinterpret_cast(s), n, dt, val, + path, log); + } - template - Result parse_and_get_value(const char8_t *s, std::any &dt, T &val, - const char *path = nullptr, - Log log = nullptr) const { - return parse_and_get_value(reinterpret_cast(s), dt, val, - *path, log); - } + template + Result parse_and_get_value(const char8_t *s, std::any &dt, T &val, + const char *path = nullptr, + Log log = nullptr) const { + return parse_and_get_value(reinterpret_cast(s), dt, val, path, + log); + } #endif - void operator=(Action a) { action = a; } + void operator=(Action a) { action = a; } - template Definition &operator,(T fn) { - operator=(fn); - return *this; - } + template Definition &operator,(T fn) { + operator=(fn); + return *this; + } - Definition &operator~() { - ignoreSemanticValue = true; - return *this; - } + Definition &operator~() { + ignoreSemanticValue = true; + return *this; + } - void accept(Ope::Visitor &v) { holder_->accept(v); } + void accept(Ope::Visitor &v) { holder_->accept(v); } - std::shared_ptr get_core_operator() const { return holder_->ope_; } + std::shared_ptr get_core_operator() const { return holder_->ope_; } - bool is_token() const { - std::call_once(is_token_init_, [this]() { - is_token_ = TokenChecker::is_token(*get_core_operator()); - }); - return is_token_; - } + bool is_token() const { + std::call_once(is_token_init_, [this]() { + is_token_ = TokenChecker::is_token(*get_core_operator()); + }); + return is_token_; + } - std::string name; - const char *s_ = nullptr; - std::pair line_ = {1, 1}; + std::string name; + const char *s_ = nullptr; + std::pair line_ = {1, 1}; - std::function - predicate; + Predicate predicate; - size_t id = 0; - Action action; - std::function - enter; - std::function - leave; - bool ignoreSemanticValue = false; - std::shared_ptr whitespaceOpe; - std::shared_ptr wordOpe; - bool enablePackratParsing = false; - bool is_macro = false; - std::vector params; - bool disable_action = false; + size_t id = 0; + Action action; + std::function + enter; + std::function + leave; + bool ignoreSemanticValue = false; + std::shared_ptr whitespaceOpe; + std::shared_ptr wordOpe; + bool enablePackratParsing = false; + bool is_macro = false; + std::vector params; + bool disable_action = false; + bool is_left_recursive = false; + bool can_be_empty = false; - TracerEnter tracer_enter; - TracerLeave tracer_leave; - bool verbose_trace = false; - TracerStartOrEnd tracer_start; - TracerStartOrEnd tracer_end; + TracerEnter tracer_enter; + TracerLeave tracer_leave; + bool verbose_trace = false; + TracerStartOrEnd tracer_start; + TracerStartOrEnd tracer_end; - std::string error_message; - bool no_ast_opt = false; + std::string error_message; + bool no_ast_opt = false; - bool eoi_check = true; + bool eoi_check = true; + + // Per-rule packrat stats (optional, for profiling) + mutable bool collect_packrat_stats = false; + mutable std::vector packrat_stats_; private: - friend class Reference; - friend class ParserGenerator; + friend class Reference; + friend class ParserGenerator; - Definition &operator=(const Definition &rhs); - Definition &operator=(Definition &&rhs); + Definition &operator=(const Definition &rhs); + Definition &operator=(Definition &&rhs); - void initialize_definition_ids() const { - std::call_once(definition_ids_init_, [&]() { - AssignIDToDefinition vis; - holder_->accept(vis); - if (whitespaceOpe) { whitespaceOpe->accept(vis); } - if (wordOpe) { wordOpe->accept(vis); } - definition_ids_.swap(vis.ids); - }); + void initialize_definition_ids() const { + std::call_once(definition_ids_init_, [&]() { + AssignIDToDefinition vis; + holder_->accept(vis); + if (whitespaceOpe) { whitespaceOpe->accept(vis); } + if (wordOpe) { wordOpe->accept(vis); } + definition_ids_.swap(vis.ids); + }); + } + + void initialize_packrat_filter() const; + + Result parse_core(const char *s, size_t n, SemanticValues &vs, std::any &dt, + const char *path, Log log) const { + initialize_definition_ids(); + + std::shared_ptr ope = holder_; + + std::any trace_data; + if (tracer_start) { tracer_start(trace_data); } + auto se = scope_exit([&]() { + if (tracer_end) { tracer_end(trace_data); } + }); + + Context c(path, s, n, definition_ids_.size(), whitespaceOpe, wordOpe, + enablePackratParsing, tracer_enter, tracer_leave, trace_data, + verbose_trace, log); + + if (collect_packrat_stats) { + packrat_stats_.resize(definition_ids_.size()); + c.packrat_stats = &packrat_stats_; } - Result parse_core(const char *s, size_t n, SemanticValues &vs, std::any &dt, - const char *path, Log log) const { - initialize_definition_ids(); - - std::shared_ptr ope = holder_; - - std::any trace_data; - if (tracer_start) { tracer_start(trace_data); } - auto se1 = scope_exit([&]() { - if (tracer_end) { tracer_end(trace_data); } - }); - - Context c(path, s, n, definition_ids_.size(), whitespaceOpe, wordOpe, - enablePackratParsing, tracer_enter, tracer_leave, trace_data, - verbose_trace, log); - - size_t i = 0; - - if (whitespaceOpe) { - auto save_ignore_trace_state = c.ignore_trace_state; - c.ignore_trace_state = !c.verbose_trace; - auto se2 = - scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); - - auto len = whitespaceOpe->parse(s, n, vs, c, dt); - if (fail(len)) { return Result{false, c.recovered, i, c.error_info}; } - - i = len; - } - - auto len = ope->parse(s + i, n - i, vs, c, dt); - auto ret = success(len); - if (ret) { - i += len; - if (eoi_check) { - if (i < n) { - if (c.error_info.error_pos - c.s < s + i - c.s) { - c.error_info.message_pos = s + i; - c.error_info.message = "expected end of input"; - } - ret = false; - } - } - } - return Result{ret, c.recovered, i, c.error_info}; + if (enablePackratParsing) { + initialize_packrat_filter(); + if (!packrat_filter_.empty()) { + c.packrat_rule_filter = &packrat_filter_; + } } - std::shared_ptr holder_; - mutable std::once_flag is_token_init_; - mutable bool is_token_ = false; - mutable std::once_flag assign_id_to_definition_init_; - mutable std::once_flag definition_ids_init_; - mutable std::unordered_map definition_ids_; + size_t i = 0; + + if (whitespaceOpe) { + auto save_ignore_trace_state = c.ignore_trace_state; + c.ignore_trace_state = !c.verbose_trace; + auto se = + scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); + + auto len = whitespaceOpe->parse(s, n, vs, c, dt); + if (fail(len)) { return Result{false, c.recovered, i, c.error_info}; } + + i = len; + } + + auto len = ope->parse(s + i, n - i, vs, c, dt); + auto ret = success(len); + if (ret) { + i += len; + if (eoi_check) { + if (i < n) { + if (c.error_info.error_pos - c.s < s + i - c.s) { + c.error_info.message_pos = s + i; + c.error_info.message = "expected end of input"; + } + ret = false; + } + } + } + return Result{ret, c.recovered, i, c.error_info}; + } + + std::shared_ptr holder_; + mutable std::once_flag is_token_init_; + mutable bool is_token_ = false; + mutable std::once_flag assign_id_to_definition_init_; + mutable std::once_flag definition_ids_init_; + mutable std::unordered_map definition_ids_; + mutable std::once_flag packrat_filter_init_; + mutable std::vector packrat_filter_; }; /* @@ -2487,592 +2876,704 @@ private: inline size_t parse_literal(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt, const std::string &lit, std::once_flag &init_is_word, bool &is_word, - bool ignore_case) { - size_t i = 0; - for (; i < lit.size(); i++) { - if (i >= n || (ignore_case ? (std::tolower(s[i]) != std::tolower(lit[i])) - : (s[i] != lit[i]))) { - c.set_error_pos(s, lit.data()); - return static_cast(-1); - } + bool ignore_case, const std::string &lower_lit) { + size_t i = 0; + for (; i < lit.size(); i++) { + if (i >= n || + (ignore_case ? (static_cast(std::tolower( + static_cast(s[i]))) != lower_lit[i]) + : (s[i] != lit[i]))) { + c.set_error_pos(s, lit.data()); + return static_cast(-1); } + } - // Word check - if (c.wordOpe) { - auto save_ignore_trace_state = c.ignore_trace_state; - c.ignore_trace_state = !c.verbose_trace; - auto se = - scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); + // Word check + if (c.wordOpe) { + auto save_ignore_trace_state = c.ignore_trace_state; + c.ignore_trace_state = !c.verbose_trace; + auto se = + scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); - std::call_once(init_is_word, [&]() { - SemanticValues dummy_vs; - Context dummy_c(nullptr, c.s, c.l, 0, nullptr, nullptr, false, nullptr, - nullptr, nullptr, false, nullptr); - std::any dummy_dt; + std::call_once(init_is_word, [&]() { + SemanticValues dummy_vs; + Context dummy_c(nullptr, c.s, c.l, 0, nullptr, nullptr, false, nullptr, + nullptr, nullptr, false, nullptr); + std::any dummy_dt; - auto len = - c.wordOpe->parse(lit.data(), lit.size(), dummy_vs, dummy_c, dummy_dt); - is_word = success(len); - }); + auto len = + c.wordOpe->parse(lit.data(), lit.size(), dummy_vs, dummy_c, dummy_dt); + is_word = success(len); + }); - if (is_word) { - SemanticValues dummy_vs; - Context dummy_c(nullptr, c.s, c.l, 0, nullptr, nullptr, false, nullptr, - nullptr, nullptr, false, nullptr); - std::any dummy_dt; + if (is_word) { + SemanticValues dummy_vs; + Context dummy_c(nullptr, c.s, c.l, 0, nullptr, nullptr, false, nullptr, + nullptr, nullptr, false, nullptr); + std::any dummy_dt; - NotPredicate ope(c.wordOpe); - auto len = ope.parse(s + i, n - i, dummy_vs, dummy_c, dummy_dt); - if (fail(len)) { - c.set_error_pos(s, lit.data()); - return len; - } - i += len; - } + NotPredicate ope(c.wordOpe); + auto len = ope.parse(s + i, n - i, dummy_vs, dummy_c, dummy_dt); + if (fail(len)) { + c.set_error_pos(s, lit.data()); + return len; + } + i += len; } + } - // Skip whitespace - if (!c.in_token_boundary_count && c.whitespaceOpe) { - auto save_ignore_trace_state = c.ignore_trace_state; - c.ignore_trace_state = !c.verbose_trace; - auto se = - scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); + // Skip whitespace + auto wl = c.skip_whitespace(s + i, n - i, vs, dt); + if (fail(wl)) { return wl; } + i += wl; - auto len = c.whitespaceOpe->parse(s + i, n - i, vs, c, dt); - if (fail(len)) { return len; } - i += len; - } - - return i; + return i; } inline std::pair SemanticValues::line_info() const { - assert(c_); - return c_->line_info(sv_.data()); + assert(c_); + return c_->line_info(sv_.data()); } inline void ErrorInfo::output_log(const Log &log, const char *s, size_t n) { - if (message_pos) { - if (message_pos > last_output_pos) { - last_output_pos = message_pos; - auto line = line_info(s, message_pos); - std::string msg; - if (auto unexpected_token = heuristic_error_token(s, n, message_pos); - !unexpected_token.empty()) { - msg = replace_all(message, "%t", unexpected_token); + if (message_pos) { + if (message_pos > last_output_pos) { + last_output_pos = message_pos; + auto line = line_info(s, message_pos); + std::string msg; + if (auto unexpected_token = heuristic_error_token(s, n, message_pos); + !unexpected_token.empty()) { + msg = replace_all(message, "%t", unexpected_token); - auto unexpected_char = unexpected_token.substr( - 0, - codepoint_length(unexpected_token.data(), unexpected_token.size())); + auto unexpected_char = unexpected_token.substr( + 0, + codepoint_length(unexpected_token.data(), unexpected_token.size())); - msg = replace_all(msg, "%c", unexpected_char); - } else { - msg = message; - } - log(line.first, line.second, msg, label); - } - } else if (error_pos) { - if (error_pos > last_output_pos) { - last_output_pos = error_pos; - auto line = line_info(s, error_pos); - - std::string msg; - if (expected_tokens.empty()) { - msg = "syntax error."; - } else { - msg = "syntax error"; - - // unexpected token - if (auto unexpected_token = heuristic_error_token(s, n, error_pos); - !unexpected_token.empty()) { - msg += ", unexpected '"; - msg += unexpected_token; - msg += "'"; - } - - auto first_item = true; - size_t i = 0; - while (i < expected_tokens.size()) { - auto [error_literal, error_rule] = expected_tokens[i]; - - // Skip rules start with '_' - if (!(error_rule && error_rule->name[0] == '_')) { - msg += (first_item ? ", expecting " : ", "); - if (error_literal) { - msg += "'"; - msg += error_literal; - msg += "'"; - } else { - msg += "<" + error_rule->name + ">"; - if (label.empty()) { label = error_rule->name; } - } - first_item = false; - } - - i++; - } - msg += "."; - } - log(line.first, line.second, msg, label); - } + msg = replace_all(msg, "%c", unexpected_char); + } else { + msg = message; + } + log(line.first, line.second, msg, label); } + } else if (error_pos) { + if (error_pos > last_output_pos) { + last_output_pos = error_pos; + auto line = line_info(s, error_pos); + + std::string msg; + if (expected_tokens.empty()) { + msg = "syntax error."; + } else { + msg = "syntax error"; + + // unexpected token + if (auto unexpected_token = heuristic_error_token(s, n, error_pos); + !unexpected_token.empty()) { + msg += ", unexpected '"; + msg += unexpected_token; + msg += "'"; + } + + auto first_item = true; + size_t i = 0; + while (i < expected_tokens.size()) { + auto [error_literal, error_rule] = expected_tokens[i]; + + // Skip rules start with '_' + if (!(error_rule && error_rule->name[0] == '_')) { + msg += (first_item ? ", expecting " : ", "); + if (error_literal) { + msg += "'"; + msg += error_literal; + msg += "'"; + } else { + msg += "<" + error_rule->name + ">"; + if (label.empty()) { label = error_rule->name; } + } + first_item = false; + } + + i++; + } + msg += "."; + } + log(line.first, line.second, msg, label); + } + } +} + +inline size_t Context::skip_whitespace(const char *a_s, size_t n, + SemanticValues &vs, std::any &dt) { + if (in_token_boundary_count || !whitespaceOpe) { return 0; } + auto save = ignore_trace_state; + ignore_trace_state = !verbose_trace; + auto se = scope_exit([&]() { ignore_trace_state = save; }); + return whitespaceOpe->parse(a_s, n, vs, *this, dt); } inline void Context::set_error_pos(const char *a_s, const char *literal) { - if (log) { - if (error_info.error_pos <= a_s) { - if (error_info.error_pos < a_s || !error_info.keep_previous_token) { - error_info.error_pos = a_s; - error_info.expected_tokens.clear(); - } + if (log) { + if (error_info.error_pos <= a_s) { + if (error_info.error_pos < a_s || !error_info.keep_previous_token) { + error_info.error_pos = a_s; + error_info.expected_tokens.clear(); + } - const char *error_literal = nullptr; - const Definition *error_rule = nullptr; + const char *error_literal = nullptr; + const Definition *error_rule = nullptr; - if (literal) { - error_literal = literal; - } else if (!rule_stack.empty()) { - auto rule = rule_stack.back(); - auto ope = rule->get_core_operator(); - if (auto token = FindLiteralToken::token(*ope); - token && token[0] != '\0') { - error_literal = token; - } - } - - for (auto r : rule_stack) { - error_rule = r; - if (r->is_token()) { break; } - } - - if (error_literal || error_rule) { - error_info.add(error_literal, error_rule); - } + if (literal) { + error_literal = literal; + } else if (!rule_stack.empty()) { + auto rule = rule_stack.back(); + auto ope = rule->get_core_operator(); + if (auto token = FindLiteralToken::token(*ope); + token && token[0] != '\0') { + error_literal = token; } + } + + for (auto r : rule_stack) { + error_rule = r; + if (r->is_token()) { break; } + } + + if (error_literal || error_rule) { + error_info.add(error_literal, error_rule); + } } + } } inline void Context::trace_enter(const Ope &ope, const char *a_s, size_t n, const SemanticValues &vs, std::any &dt) { - trace_ids.push_back(next_trace_id++); - tracer_enter(ope, a_s, n, vs, *this, dt, trace_data); + trace_ids.push_back(next_trace_id++); + tracer_enter(ope, a_s, n, vs, *this, dt, trace_data); } inline void Context::trace_leave(const Ope &ope, const char *a_s, size_t n, const SemanticValues &vs, std::any &dt, size_t len) { - tracer_leave(ope, a_s, n, vs, *this, dt, len, trace_data); - trace_ids.pop_back(); + tracer_leave(ope, a_s, n, vs, *this, dt, len, trace_data); + trace_ids.pop_back(); } inline bool Context::is_traceable(const Ope &ope) const { - if (tracer_enter && tracer_leave) { - if (ignore_trace_state) { return false; } - return !dynamic_cast(&ope); - } - return false; + if (tracer_enter && tracer_leave) { + if (ignore_trace_state) { return false; } + return !dynamic_cast(&ope); + } + return false; } inline size_t Ope::parse(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt) const { - if (c.is_traceable(*this)) { - c.trace_enter(*this, s, n, vs, dt); - auto len = parse_core(s, n, vs, c, dt); - c.trace_leave(*this, s, n, vs, dt, len); - return len; - } - return parse_core(s, n, vs, c, dt); + if (c.is_traceable(*this)) { + c.trace_enter(*this, s, n, vs, dt); + auto len = parse_core(s, n, vs, c, dt); + c.trace_leave(*this, s, n, vs, dt, len); + return len; + } + return parse_core(s, n, vs, c, dt); } inline size_t Dictionary::parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt) const { - size_t id; - auto i = trie_.match(s, n, id); + size_t id; + auto i = trie_.match(s, n, id); - if (i == 0) { + if (i == 0) { + c.set_error_pos(s); + return static_cast(-1); + } + + vs.choice_count_ = trie_.items_count(); + vs.choice_ = id; + + // Word check + if (c.wordOpe) { + auto save_ignore_trace_state = c.ignore_trace_state; + c.ignore_trace_state = !c.verbose_trace; + auto se = + scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); + + { + SemanticValues dummy_vs; + Context dummy_c(nullptr, c.s, c.l, 0, nullptr, nullptr, false, nullptr, + nullptr, nullptr, false, nullptr); + std::any dummy_dt; + + NotPredicate ope(c.wordOpe); + auto len = ope.parse(s + i, n - i, dummy_vs, dummy_c, dummy_dt); + if (fail(len)) { c.set_error_pos(s); - return static_cast(-1); + return len; + } + i += len; } + } - vs.choice_count_ = trie_.size(); - vs.choice_ = id; + // Skip whitespace + auto wl = c.skip_whitespace(s + i, n - i, vs, dt); + if (fail(wl)) { return wl; } + i += wl; - // Word check - if (c.wordOpe) { - auto save_ignore_trace_state = c.ignore_trace_state; - c.ignore_trace_state = !c.verbose_trace; - auto se = - scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); - - { - SemanticValues dummy_vs; - Context dummy_c(nullptr, c.s, c.l, 0, nullptr, nullptr, false, nullptr, - nullptr, nullptr, false, nullptr); - std::any dummy_dt; - - NotPredicate ope(c.wordOpe); - auto len = ope.parse(s + i, n - i, dummy_vs, dummy_c, dummy_dt); - if (fail(len)) { - c.set_error_pos(s); - return len; - } - i += len; - } - } - - // Skip whitespace - if (!c.in_token_boundary_count && c.whitespaceOpe) { - auto save_ignore_trace_state = c.ignore_trace_state; - c.ignore_trace_state = !c.verbose_trace; - auto se = - scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); - - auto len = c.whitespaceOpe->parse(s + i, n - i, vs, c, dt); - if (fail(len)) { return len; } - i += len; - } - - return i; + return i; } inline size_t LiteralString::parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt) const { - return parse_literal(s, n, vs, c, dt, lit_, init_is_word_, is_word_, - ignore_case_); + return parse_literal(s, n, vs, c, dt, lit_, init_is_word_, is_word_, + ignore_case_, lower_lit_); } inline size_t TokenBoundary::parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt) const { - auto save_ignore_trace_state = c.ignore_trace_state; - c.ignore_trace_state = !c.verbose_trace; - auto se1 = - scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); + auto save_ignore_trace_state = c.ignore_trace_state; + c.ignore_trace_state = !c.verbose_trace; + auto se = + scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); - size_t len; - { - c.in_token_boundary_count++; - auto se2 = scope_exit([&]() { c.in_token_boundary_count--; }); - len = ope_->parse(s, n, vs, c, dt); - } + size_t len; + { + c.in_token_boundary_count++; + auto se = scope_exit([&]() { c.in_token_boundary_count--; }); + len = ope_->parse(s, n, vs, c, dt); + } - if (success(len)) { - vs.tokens.emplace_back(std::string_view(s, len)); + if (success(len)) { + vs.tokens.emplace_back(std::string_view(s, len)); - if (!c.in_token_boundary_count) { - if (c.whitespaceOpe) { - auto l = c.whitespaceOpe->parse(s + len, n - len, vs, c, dt); - if (fail(l)) { return l; } - len += l; - } - } - } - return len; + auto wl = c.skip_whitespace(s + len, n - len, vs, dt); + if (fail(wl)) { return wl; } + len += wl; + } + return len; } inline size_t Holder::parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt) const { - if (!ope_) { - throw std::logic_error("Uninitialized definition ope was used..."); - } + if (!ope_) { + throw std::logic_error("Uninitialized definition ope was used..."); + } - // Macro reference - if (outer_->is_macro) { - c.rule_stack.push_back(outer_); - auto len = ope_->parse(s, n, vs, c, dt); - c.rule_stack.pop_back(); - return len; - } + // Macro reference + if (outer_->is_macro) { + c.rule_stack.push_back(outer_); + auto len = ope_->parse(s, n, vs, c, dt); + c.rule_stack.pop_back(); + return len; + } - size_t len; - std::any val; + size_t len; + std::any val; - c.packrat(s, outer_->id, len, val, [&](std::any &a_val) { - if (outer_->enter) { outer_->enter(c, s, n, dt); } - auto &chvs = c.push_semantic_values_scope(); - auto se = scope_exit([&]() { - c.pop_semantic_values_scope(); - if (outer_->leave) { outer_->leave(c, s, n, len, a_val, dt); } - }); + // Shared parse body: invokes enter/leave callbacks, parses the rule's + // operator, handles actions/predicates/errors, and calls reduce. + // Returns {parse_len, parse_val}. + auto do_parse = [&]() { + size_t parse_len; + std::any parse_val; - c.rule_stack.push_back(outer_); - len = ope_->parse(s, n, chvs, c, dt); - c.rule_stack.pop_back(); - - // Invoke action - if (success(len)) { - chvs.sv_ = std::string_view(s, len); - chvs.name_ = outer_->name; - - auto ope_ptr = ope_.get(); - { - auto tok_ptr = dynamic_cast(ope_ptr); - if (tok_ptr) { ope_ptr = tok_ptr->ope_.get(); } - } - if (!dynamic_cast(ope_ptr) && - !dynamic_cast(ope_ptr)) { - chvs.choice_count_ = 0; - chvs.choice_ = 0; - } - - std::string msg; - if (outer_->predicate && !outer_->predicate(chvs, dt, msg)) { - if (c.log && !msg.empty() && c.error_info.message_pos < s) { - c.error_info.message_pos = s; - c.error_info.message = msg; - c.error_info.label = outer_->name; - } - len = static_cast(-1); - } - - if (success(len)) { - if (!c.recovered) { a_val = reduce(chvs, dt); } - } else { - if (c.log && !msg.empty() && c.error_info.message_pos < s) { - c.error_info.message_pos = s; - c.error_info.message = msg; - c.error_info.label = outer_->name; - } - } - } else { - if (c.log && !outer_->error_message.empty() && - c.error_info.message_pos < s) { - c.error_info.message_pos = s; - c.error_info.message = outer_->error_message; - c.error_info.label = outer_->name; - } - } + if (outer_->enter) { outer_->enter(c, s, n, dt); } + auto &chvs = c.push_semantic_values_scope(); + auto se = scope_exit([&]() { + c.pop_semantic_values_scope(); + if (outer_->leave) { outer_->leave(c, s, n, parse_len, parse_val, dt); } }); - if (success(len)) { - if (!outer_->ignoreSemanticValue) { - vs.emplace_back(std::move(val)); - vs.tags.emplace_back(str2tag(outer_->name)); + c.rule_stack.push_back(outer_); + parse_len = ope_->parse(s, n, chvs, c, dt); + c.rule_stack.pop_back(); + + if (success(parse_len)) { + chvs.sv_ = std::string_view(s, parse_len); + chvs.name_ = outer_->name; + + auto ope_ptr = ope_.get(); + if (ope_ptr->is_token_boundary) { + ope_ptr = static_cast(ope_ptr)->ope_.get(); + } + if (!ope_ptr->is_choice_like) { + chvs.choice_count_ = 0; + chvs.choice_ = 0; + } + + std::string msg; + std::any predicate_data; + if (outer_->predicate) { + if (!outer_->predicate(chvs, dt, msg, predicate_data)) { + if (c.log && !msg.empty() && c.error_info.message_pos < s) { + c.error_info.message_pos = s; + c.error_info.message = msg; + c.error_info.label = outer_->name; + } + parse_len = static_cast(-1); } + } + + if (success(parse_len)) { + if (!c.recovered) { parse_val = reduce(chvs, dt, predicate_data); } + } else { + if (c.log && !msg.empty() && c.error_info.message_pos < s) { + c.error_info.message_pos = s; + c.error_info.message = msg; + c.error_info.label = outer_->name; + } + } + } else { + if (c.log && !outer_->error_message.empty() && + c.error_info.message_pos < s) { + c.error_info.message_pos = s; + c.error_info.message = outer_->error_message; + c.error_info.label = outer_->name; + } } - return len; + return std::make_pair(parse_len, std::move(parse_val)); + }; + + if (outer_->is_left_recursive) { + auto lr_key = std::make_pair(outer_, s); + + // Check LR memo first + auto it = c.lr_memo.find(lr_key); + if (it != c.lr_memo.end()) { + if (success(it->second.len)) { + len = it->second.len; + val = it->second.val; + } else { + len = static_cast(-1); + } + // Record that this rule's lr_memo was accessed. + // Any LR rule currently seeding will know we're in its cycle. + c.lr_refs_hit.insert(outer_); + } else { + // Seed with FAIL + c.lr_memo[lr_key] = {static_cast(-1), {}}; + + // Mark as active seed (protects our lr_memo from inner growers) + c.lr_active_seeds.insert(lr_key); + auto seed_guard = scope_exit([&]() { c.lr_active_seeds.erase(lr_key); }); + + // Track which LR rules are referenced during our parse + // to identify cycle members + auto saved_refs = std::move(c.lr_refs_hit); + c.lr_refs_hit.clear(); + + // Initial parse (self-references will hit the FAIL seed) + auto [initial_len, initial_val] = do_parse(); + + // Rules whose lr_memo was hit during our parse are in our cycle. + // If we detected cycle members, we ourselves are also part of + // the cycle, so add self — this lets parent seeders see us as + // a transitive cycle member. + auto cycle_rules = c.lr_refs_hit; + if (!cycle_rules.empty()) { cycle_rules.insert(outer_); } + + // Restore parent's refs and propagate cycle info upward + c.lr_refs_hit = std::move(saved_refs); + c.lr_refs_hit.insert(cycle_rules.begin(), cycle_rules.end()); + + if (!success(initial_len)) { + // Keep FAIL in lr_memo so we don't re-seed + len = static_cast(-1); + } else { + // Got initial seed, now grow + len = initial_len; + val = std::move(initial_val); + c.lr_memo[lr_key] = {len, val}; + + while (true) { + // Clear this rule's packrat cache + c.clear_packrat_cache(s, outer_->id); + + // Clear lr_memo for cycle-dependent rules at this position, + // but NOT for rules currently in their own seeding phase + // (lr_active_seeds) — those are outer growers we must not + // interfere with. + for (auto memo_it = c.lr_memo.begin(); memo_it != c.lr_memo.end();) { + if (memo_it->first.second == s && memo_it->first.first != outer_ && + cycle_rules.count(memo_it->first.first) && + !c.lr_active_seeds.count(memo_it->first)) { + memo_it = c.lr_memo.erase(memo_it); + } else { + ++memo_it; + } + } + + auto [new_len, new_val] = do_parse(); + + if (!success(new_len) || new_len <= len) { + break; // No improvement, done growing + } + + len = new_len; + val = std::move(new_val); + c.lr_memo[lr_key] = {len, val}; + } + } + + // Write final result to packrat cache (lr_memo entry is kept as + // the primary lookup for LR rules at this position) + if (success(len)) { c.write_packrat_cache(s, outer_->id, len, val); } + } + } else { + if (c.enablePackratParsing) { + // Packrat cache acts as re-entry guard (pre-registered as + // failure before fn is called). + c.packrat(s, outer_->id, len, val, [&](std::any &a_val) { + auto [parse_len, parse_val] = do_parse(); + len = parse_len; + if (success(len)) { a_val = std::move(parse_val); } + }); + } else { + // Without packrat, use lr_memo as re-entry guard to prevent + // stack overflow from undetected left recursion. + auto guard_key = std::make_pair(outer_, s); + if (c.lr_memo.count(guard_key)) { + len = static_cast(-1); + } else { + c.lr_memo[guard_key] = {static_cast(-1), {}}; + auto [parse_len, parse_val] = do_parse(); + len = parse_len; + val = std::move(parse_val); + c.lr_memo.erase(guard_key); + } + } + } + + if (success(len)) { + if (!outer_->ignoreSemanticValue) { + vs.emplace_back(std::move(val)); + vs.tags.emplace_back(str2tag(outer_->name)); + } + } + + return len; } -inline std::any Holder::reduce(SemanticValues &vs, std::any &dt) const { - if (outer_->action && !outer_->disable_action) { - return outer_->action(vs, dt); - } else if (vs.empty()) { - return std::any(); - } else { - return std::move(vs.front()); - } +inline std::any Holder::reduce(SemanticValues &vs, std::any &dt, + const std::any &predicate_data) const { + if (outer_->action && !outer_->disable_action) { + return outer_->action(vs, dt, predicate_data); + } else if (vs.empty()) { + return std::any(); + } else { + return std::move(vs.front()); + } } inline const std::string &Holder::name() const { return outer_->name; } inline const std::string &Holder::trace_name() const { - std::call_once(trace_name_init_, - [this]() { trace_name_ = "[" + outer_->name + "]"; }); - return trace_name_; + std::call_once(trace_name_init_, + [this]() { trace_name_ = "[" + outer_->name + "]"; }); + return trace_name_; } inline size_t Reference::parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt) const { - auto save_ignore_trace_state = c.ignore_trace_state; - if (rule_ && rule_->ignoreSemanticValue) { - c.ignore_trace_state = !c.verbose_trace; - } - auto se1 = - scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); + auto save_ignore_trace_state = c.ignore_trace_state; + if (rule_ && rule_->ignoreSemanticValue) { + c.ignore_trace_state = !c.verbose_trace; + } + auto se = + scope_exit([&]() { c.ignore_trace_state = save_ignore_trace_state; }); - if (rule_) { - // Reference rule - if (rule_->is_macro) { - // Macro - FindReference vis(c.top_args(), c.rule_stack.back()->params); + if (rule_) { + // Reference rule + if (rule_->is_macro) { + // Macro + FindReference vis(c.top_args(), c.rule_stack.back()->params); - // Collect arguments - std::vector> args; - for (auto arg : args_) { - arg->accept(vis); - args.emplace_back(std::move(vis.found_ope)); - } + // Collect arguments + std::vector> args; + for (const auto &arg : args_) { + arg->accept(vis); + args.emplace_back(std::move(vis.found_ope)); + } - c.push_args(std::move(args)); - auto se2 = scope_exit([&]() { c.pop_args(); }); - auto ope = get_core_operator(); - return ope->parse(s, n, vs, c, dt); - } else { - // Definition - c.push_args(std::vector>()); - auto se3 = scope_exit([&]() { c.pop_args(); }); - auto ope = get_core_operator(); - return ope->parse(s, n, vs, c, dt); - } + c.push_args(std::move(args)); + auto se = scope_exit([&]() { c.pop_args(); }); + return rule_->holder_->parse(s, n, vs, c, dt); } else { - // Reference parameter in macro - const auto &args = c.top_args(); - return args[iarg_]->parse(s, n, vs, c, dt); + // Definition + c.push_args(std::vector>()); + auto se2 = scope_exit([&]() { c.pop_args(); }); + return rule_->holder_->parse(s, n, vs, c, dt); } + } else { + // Reference parameter in macro + const auto &args = c.top_args(); + return args[iarg_]->parse(s, n, vs, c, dt); + } } inline std::shared_ptr Reference::get_core_operator() const { - return rule_->holder_; + return rule_->holder_; } inline size_t BackReference::parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt) const { - auto size = static_cast(c.capture_scope_stack_size); - for (auto i = size - 1; i >= 0; i--) { - auto index = static_cast(i); - const auto &cs = c.capture_scope_stack[index]; - if (cs.find(name_) != cs.end()) { - const auto &lit = cs.at(name_); - std::once_flag init_is_word; - auto is_word = false; - return parse_literal(s, n, vs, c, dt, lit, init_is_word, is_word, false); - } + for (auto it = c.capture_entries.rbegin(); it != c.capture_entries.rend(); + ++it) { + if (it->first == name_) { + const auto &lit = it->second; + std::once_flag init_is_word; + auto is_word = false; + static const std::string empty; + return parse_literal(s, n, vs, c, dt, lit, init_is_word, is_word, false, + empty); } + } - c.error_info.message_pos = s; - c.error_info.message = "undefined back reference '$" + name_ + "'..."; - return static_cast(-1); + c.error_info.message_pos = s; + c.error_info.message = "undefined back reference '$" + name_ + "'..."; + return static_cast(-1); } inline Definition & PrecedenceClimbing::get_reference_for_binop(Context &c) const { - if (rule_.is_macro) { - // Reference parameter in macro - const auto &args = c.top_args(); - auto iarg = dynamic_cast(*binop_).iarg_; - auto arg = args[iarg]; - return *dynamic_cast(*arg).rule_; - } + if (rule_.is_macro) { + // Reference parameter in macro + const auto &args = c.top_args(); + auto iarg = dynamic_cast(*binop_).iarg_; + auto arg = args[iarg]; + return *dynamic_cast(*arg).rule_; + } - return *dynamic_cast(*binop_).rule_; + return *dynamic_cast(*binop_).rule_; } inline size_t PrecedenceClimbing::parse_expression(const char *s, size_t n, SemanticValues &vs, Context &c, std::any &dt, size_t min_prec) const { - auto len = atom_->parse(s, n, vs, c, dt); - if (fail(len)) { return len; } + auto len = atom_->parse(s, n, vs, c, dt); + if (fail(len)) { return len; } - std::string tok; - auto &rule = get_reference_for_binop(c); - auto action = std::move(rule.action); + std::string tok; + auto &rule = get_reference_for_binop(c); + auto action = std::move(rule.action); - rule.action = [&](SemanticValues &vs2, std::any &dt2) { - tok = vs2.token(); - if (action) { - return action(vs2, dt2); - } else if (!vs2.empty()) { - return vs2[0]; - } - return std::any(); - }; - auto action_se = scope_exit([&]() { rule.action = std::move(action); }); + rule.action = [&](SemanticValues &vs2, std::any &dt2, + const std::any &predicate_data2) { + tok = vs2.token(); + if (action) { + return action(vs2, dt2, predicate_data2); + } else if (!vs2.empty()) { + return vs2[0]; + } + return std::any(); + }; + auto action_se = scope_exit([&]() { rule.action = std::move(action); }); - auto i = len; - while (i < n) { - std::vector save_values(vs.begin(), vs.end()); - auto save_tokens = vs.tokens; + auto i = len; + while (i < n) { + std::vector save_values(vs.begin(), vs.end()); + auto save_tokens = vs.tokens; - auto chvs = c.push_semantic_values_scope(); - auto chlen = binop_->parse(s + i, n - i, chvs, c, dt); - c.pop_semantic_values_scope(); + auto chvs = c.push_semantic_values_scope(); + auto chlen = binop_->parse(s + i, n - i, chvs, c, dt); + c.pop_semantic_values_scope(); - if (fail(chlen)) { break; } + if (fail(chlen)) { break; } - auto it = info_.find(tok); - if (it == info_.end()) { break; } + auto it = info_.find(tok); + if (it == info_.end()) { break; } - auto level = std::get<0>(it->second); - auto assoc = std::get<1>(it->second); + auto level = std::get<0>(it->second); + auto assoc = std::get<1>(it->second); - if (level < min_prec) { break; } + if (level < min_prec) { break; } - vs.emplace_back(std::move(chvs[0])); - i += chlen; + vs.emplace_back(std::move(chvs[0])); + i += chlen; - auto next_min_prec = level; - if (assoc == 'L') { next_min_prec = level + 1; } + auto next_min_prec = level; + if (assoc == 'L') { next_min_prec = level + 1; } - chvs = c.push_semantic_values_scope(); - chlen = parse_expression(s + i, n - i, chvs, c, dt, next_min_prec); - c.pop_semantic_values_scope(); + chvs = c.push_semantic_values_scope(); + chlen = parse_expression(s + i, n - i, chvs, c, dt, next_min_prec); + c.pop_semantic_values_scope(); - if (fail(chlen)) { - vs.assign(save_values.begin(), save_values.end()); - vs.tokens = save_tokens; - i = chlen; - break; - } - - vs.emplace_back(std::move(chvs[0])); - i += chlen; - - std::any val; - if (rule_.action) { - vs.sv_ = std::string_view(s, i); - val = rule_.action(vs, dt); - } else if (!vs.empty()) { - val = vs[0]; - } - vs.clear(); - vs.emplace_back(std::move(val)); + if (fail(chlen)) { + vs.assign(save_values.begin(), save_values.end()); + vs.tokens = save_tokens; + i = chlen; + break; } - return i; + vs.emplace_back(std::move(chvs[0])); + i += chlen; + + std::any val; + if (rule_.action) { + vs.sv_ = std::string_view(s, i); + static const std::any empty_predicate_data; + val = rule_.action(vs, dt, empty_predicate_data); + } else if (!vs.empty()) { + val = vs[0]; + } + vs.clear(); + vs.emplace_back(std::move(val)); + } + + return i; } inline size_t Recovery::parse_core(const char *s, size_t n, SemanticValues & /*vs*/, Context &c, std::any & /*dt*/) const { - const auto &rule = dynamic_cast(*ope_); + const auto &rule = dynamic_cast(*ope_); + + // Custom error message + if (c.log) { + auto label = dynamic_cast(rule.args_[0].get()); + if (label && !label->rule_->error_message.empty()) { + c.error_info.message_pos = s; + c.error_info.message = label->rule_->error_message; + c.error_info.label = label->rule_->name; + } + } + + // Recovery + auto len = static_cast(-1); + { + auto save_log = c.log; + c.log = nullptr; + auto se = scope_exit([&]() { c.log = save_log; }); + + SemanticValues dummy_vs; + std::any dummy_dt; + + len = rule.parse(s, n, dummy_vs, c, dummy_dt); + } + + if (success(len)) { + c.recovered = true; - // Custom error message if (c.log) { - auto label = dynamic_cast(rule.args_[0].get()); - if (label && !label->rule_->error_message.empty()) { - c.error_info.message_pos = s; - c.error_info.message = label->rule_->error_message; - c.error_info.label = label->rule_->name; - } + c.error_info.output_log(c.log, c.s, c.l); + c.error_info.clear(); } + } - // Recovery - auto len = static_cast(-1); - { - auto save_log = c.log; - c.log = nullptr; - auto se = scope_exit([&]() { c.log = save_log; }); + // Cut + if (!c.cut_stack.empty()) { + c.cut_stack.back() = true; - SemanticValues dummy_vs; - std::any dummy_dt; - - len = rule.parse(s, n, dummy_vs, c, dummy_dt); + if (c.cut_stack.size() == 1) { + // TODO: Remove unneeded entries in packrat memoise table } + } - if (success(len)) { - c.recovered = true; - - if (c.log) { - c.error_info.output_log(c.log, c.s, c.l); - c.error_info.clear(); - } - } - - // Cut - if (!c.cut_stack.empty()) { - c.cut_stack.back() = true; - - if (c.cut_stack.size() == 1) { - //! \todo Remove unneeded entries in packrat memoise table - } - } - - return len; + return len; } inline void Sequence::accept(Visitor &v) { v.visit(*this); } @@ -3100,194 +3601,452 @@ inline void Recovery::accept(Visitor &v) { v.visit(*this); } inline void Cut::accept(Visitor &v) { v.visit(*this); } inline void AssignIDToDefinition::visit(Holder &ope) { - auto p = static_cast(ope.outer_); - if (ids.count(p)) { return; } - auto id = ids.size(); - ids[p] = id; - ope.outer_->id = id; - ope.ope_->accept(*this); + auto p = static_cast(ope.outer_); + if (ids.count(p)) { return; } + auto id = ids.size(); + ids[p] = id; + ope.outer_->id = id; + ope.ope_->accept(*this); } inline void AssignIDToDefinition::visit(Reference &ope) { - if (ope.rule_) { - for (auto arg : ope.args_) { - arg->accept(*this); - } - ope.rule_->accept(*this); + if (ope.rule_) { + for (const auto &arg : ope.args_) { + arg->accept(*this); } + ope.rule_->accept(*this); + } } inline void AssignIDToDefinition::visit(PrecedenceClimbing &ope) { - ope.atom_->accept(*this); - ope.binop_->accept(*this); + ope.atom_->accept(*this); + ope.binop_->accept(*this); } inline void TokenChecker::visit(Reference &ope) { - if (ope.is_macro_) { - for (auto arg : ope.args_) { - arg->accept(*this); - } - } else { - has_rule_ = true; + if (ope.is_macro_) { + for (const auto &arg : ope.args_) { + arg->accept(*this); } + } else { + has_rule_ = true; + } } inline void FindLiteralToken::visit(Reference &ope) { - if (ope.is_macro_) { - ope.rule_->accept(*this); - for (auto arg : ope.args_) { - arg->accept(*this); - } + if (ope.is_macro_) { + ope.rule_->accept(*this); + for (const auto &arg : ope.args_) { + arg->accept(*this); } + } +} + +inline void ComputeCanBeEmpty::visit(Reference &ope) { + result = ope.rule_ && ope.rule_->can_be_empty; } inline void DetectLeftRecursion::visit(Reference &ope) { - if (ope.name_ == name_) { - error_s = ope.s_; - } else if (!refs_.count(ope.name_)) { - refs_.insert(ope.name_); - if (ope.rule_) { - ope.rule_->accept(*this); - if (done_ == false) { return; } - } + if (ope.name_ == name_) { + error_s = ope.s_; + } else if (!ope.rule_ && !macro_args_stack_.empty()) { + // Macro parameter reference: resolve through nested macro arg + // stacks (e.g. B(X) <- C(X) where X is itself a param ref). + auto resolved = resolve_macro_arg(ope.iarg_); + if (resolved) { + resolved->accept(*this); + if (done_ == false) { return; } } - done_ = true; + } else if (!refs_.count(ope.name_)) { + refs_.insert(ope.name_); + if (ope.rule_) { + if (ope.is_macro_) { macro_args_stack_.push_back(&ope.args_); } + ope.rule_->accept(*this); + if (ope.is_macro_) { macro_args_stack_.pop_back(); } + if (done_ == false) { return; } + } + } + // If the referenced rule can match empty, don't mark as done — + // the sequence may continue past this element to find LR. + if (!ope.rule_ && !macro_args_stack_.empty()) { + auto resolved = resolve_macro_arg(ope.iarg_); + if (resolved) { + ComputeCanBeEmpty cbe; + resolved->accept(cbe); + done_ = !cbe.result; + } else { + done_ = true; + } + } else { + done_ = !(ope.rule_ && ope.rule_->can_be_empty); + } +} + +inline std::shared_ptr +DetectLeftRecursion::resolve_macro_arg(size_t iarg) const { + for (int i = static_cast(macro_args_stack_.size()) - 1; i >= 0; i--) { + auto &args = *macro_args_stack_[i]; + if (iarg >= args.size()) { return nullptr; } + auto ref = dynamic_cast(args[iarg].get()); + if (ref && !ref->rule_) { + // Another param ref — resolve using parent level's args + iarg = ref->iarg_; + continue; + } + return args[iarg]; + } + return nullptr; } inline void HasEmptyElement::visit(Sequence &ope) { - auto save_is_empty = false; - const char *save_error_s = nullptr; - std::string save_error_name; + auto save_is_empty = false; + const char *save_error_s = nullptr; + std::string save_error_name; - auto it = ope.opes_.begin(); - while (it != ope.opes_.end()) { - (*it)->accept(*this); - if (!is_empty) { - ++it; - while (it != ope.opes_.end()) { - DetectInfiniteLoop vis(refs_, has_error_cache_); - (*it)->accept(vis); - if (vis.has_error) { - is_empty = true; - error_s = vis.error_s; - error_name = vis.error_name; - } - ++it; - } - return; + auto it = ope.opes_.begin(); + while (it != ope.opes_.end()) { + (*it)->accept(*this); + if (!is_empty) { + ++it; + while (it != ope.opes_.end()) { + DetectInfiniteLoop vis(refs_, has_error_cache_); + (*it)->accept(vis); + if (vis.has_error) { + is_empty = true; + error_s = vis.error_s; + error_name = vis.error_name; } - - save_is_empty = is_empty; - save_error_s = error_s; - save_error_name = error_name; - - is_empty = false; - error_name.clear(); ++it; + } + return; } - is_empty = save_is_empty; - error_s = save_error_s; - error_name = save_error_name; + save_is_empty = is_empty; + save_error_s = error_s; + save_error_name = error_name; + + is_empty = false; + error_name.clear(); + ++it; + } + + is_empty = save_is_empty; + error_s = save_error_s; + error_name = save_error_name; } inline void HasEmptyElement::visit(Reference &ope) { - auto it = std::find_if(refs_.begin(), refs_.end(), - [&](const std::pair &ref) { - return ope.name_ == ref.second; - }); - if (it != refs_.end()) { return; } + auto it = std::find_if(refs_.begin(), refs_.end(), + [&](const std::pair &ref) { + return ope.name_ == ref.second; + }); + if (it != refs_.end()) { return; } - if (ope.rule_) { - refs_.emplace_back(ope.s_, ope.name_); - ope.rule_->accept(*this); - refs_.pop_back(); - } + if (ope.rule_) { + refs_.emplace_back(ope.s_, ope.name_); + ope.rule_->accept(*this); + refs_.pop_back(); + } } inline void DetectInfiniteLoop::visit(Reference &ope) { - auto it1 = std::find_if(refs_.begin(), refs_.end(), - [&](const std::pair &ref) { - return ope.name_ == ref.second; - }); - if (it1 != refs_.end()) { return; } + auto it = std::find_if(refs_.begin(), refs_.end(), + [&](const std::pair &ref) { + return ope.name_ == ref.second; + }); + if (it != refs_.end()) { return; } - if (ope.rule_) { - auto it = has_error_cache_.find(ope.name_); - if (it != has_error_cache_.end()) { - has_error = it->second; - } else { - refs_.emplace_back(ope.s_, ope.name_); - ope.rule_->accept(*this); - refs_.pop_back(); - has_error_cache_[ope.name_] = has_error; - } + if (ope.rule_) { + auto it = has_error_cache_.find(ope.name_); + if (it != has_error_cache_.end()) { + has_error = it->second; + } else { + refs_.emplace_back(ope.s_, ope.name_); + ope.rule_->accept(*this); + refs_.pop_back(); + has_error_cache_[ope.name_] = has_error; } + } - if (ope.is_macro_) { - for (auto arg : ope.args_) { - arg->accept(*this); - } + if (ope.is_macro_) { + for (const auto &arg : ope.args_) { + arg->accept(*this); } + } } inline void ReferenceChecker::visit(Reference &ope) { - auto it = std::find(params_.begin(), params_.end(), ope.name_); - if (it != params_.end()) { return; } + auto it = std::find(params_.begin(), params_.end(), ope.name_); + if (it != params_.end()) { return; } - if (!grammar_.count(ope.name_)) { + if (!grammar_.count(ope.name_)) { + error_s[ope.name_] = ope.s_; + error_message[ope.name_] = "'" + ope.name_ + "' is not defined."; + } else { + if (!referenced.count(ope.name_)) { referenced.insert(ope.name_); } + const auto &rule = grammar_.at(ope.name_); + if (rule.is_macro) { + if (!ope.is_macro_ || ope.args_.size() != rule.params.size()) { error_s[ope.name_] = ope.s_; - error_message[ope.name_] = "'" + ope.name_ + "' is not defined."; - } else { - if (!referenced.count(ope.name_)) { referenced.insert(ope.name_); } - const auto &rule = grammar_.at(ope.name_); - if (rule.is_macro) { - if (!ope.is_macro_ || ope.args_.size() != rule.params.size()) { - error_s[ope.name_] = ope.s_; - error_message[ope.name_] = "incorrect number of arguments."; - } - } else if (ope.is_macro_) { - error_s[ope.name_] = ope.s_; - error_message[ope.name_] = "'" + ope.name_ + "' is not macro."; - } - for (auto arg : ope.args_) { - arg->accept(*this); - } + error_message[ope.name_] = "incorrect number of arguments."; + } + } else if (ope.is_macro_) { + error_s[ope.name_] = ope.s_; + error_message[ope.name_] = "'" + ope.name_ + "' is not macro."; } + for (const auto &arg : ope.args_) { + arg->accept(*this); + } + } +} + +inline void ComputeFirstSet::visit(Reference &ope) { + if (!ope.rule_) { + // Macro parameter reference — can't predict what it will match + result_.any_char = true; + return; + } + if (refs_.count(ope.name_)) { return; } + refs_.insert(ope.name_); + ope.rule_->accept(*this); + if (!result_.first_rule && ope.rule_->is_token()) { + result_.first_rule = ope.rule_; + } + refs_.erase(ope.name_); +} + +inline void SetupFirstSets::visit(Reference &ope) { + if (!ope.rule_ || refs_.count(ope.name_)) { return; } + refs_.insert(ope.name_); + ope.rule_->accept(*this); + refs_.erase(ope.name_); +} + +inline void SetupFirstSets::visit(Sequence &ope) { + ope.kw_guard_.reset(); + setup_keyword_guarded_identifier(ope); + for (const auto &op : ope.opes_) { + op->accept(*this); + } +} + +inline void SetupFirstSets::setup_keyword_guarded_identifier(Sequence &seq) { + // Detect pattern: NotPredicate(Reference→PrioritizedChoice) + // TokenBoundary(Sequence[CharacterClass, + // Repetition(CharacterClass)]) + // This is the pattern used by: PlainIdentifier <- !ReservedKeyword + // <[a-z_]i[a-z0-9_]i*> + if (seq.opes_.size() != 2) { return; } + + // Child 0 must be NotPredicate + auto *not_pred = dynamic_cast(seq.opes_[0].get()); + if (!not_pred) { return; } + + // NotPredicate's child must be Reference to a rule + auto *ref = dynamic_cast(not_pred->ope_.get()); + if (!ref || !ref->rule_) { return; } + + // The referenced rule's inner operator (Holder) must contain + // PrioritizedChoice + auto *holder = dynamic_cast(ref->get_core_operator().get()); + if (!holder) { return; } + auto *choice = dynamic_cast(holder->ope_.get()); + if (!choice) { return; } + + // Extract keywords from PrioritizedChoice alternatives + std::vector exact_keywords; + std::vector prefix_keywords; + + for (const auto &alt : choice->opes_) { + auto *lit = dynamic_cast(alt.get()); + if (lit) { + if (!lit->ignore_case_) { return; } + exact_keywords.push_back(to_lower(lit->lit_)); + continue; + } + // Check for compound keyword (Sequence of LiteralStrings) + auto *sub_seq = dynamic_cast(alt.get()); + if (sub_seq && !sub_seq->opes_.empty()) { + auto *first_lit = dynamic_cast(sub_seq->opes_[0].get()); + if (first_lit) { + auto all_ignore_case_lits = + std::all_of(sub_seq->opes_.begin(), sub_seq->opes_.end(), + [](const auto &child) { + auto *l = dynamic_cast(child.get()); + return l && l->ignore_case_; + }); + if (all_ignore_case_lits) { + prefix_keywords.push_back(to_lower(first_lit->lit_)); + continue; + } + } + } + // Unrecognized alternative — bail out + return; + } + + if (exact_keywords.empty()) { return; } + + // Child 1 must be TokenBoundary + auto *tb = dynamic_cast(seq.opes_[1].get()); + if (!tb) { return; } + + // TokenBoundary content: Sequence[CharacterClass, Repetition(CharacterClass)] + // or just CharacterClass (single char identifier) + CharacterClass *first_cc = nullptr; + CharacterClass *rest_cc = nullptr; + + auto *inner_seq = dynamic_cast(tb->ope_.get()); + if (inner_seq && inner_seq->opes_.size() == 2) { + first_cc = dynamic_cast(inner_seq->opes_[0].get()); + auto *rep = dynamic_cast(inner_seq->opes_[1].get()); + if (rep) { rest_cc = dynamic_cast(rep->ope_.get()); } + } + + if (!first_cc || !rest_cc) { return; } + if (!first_cc->is_ascii_only() || !rest_cc->is_ascii_only()) { return; } + + // All conditions met — set up the fast path + auto kw = std::make_unique(); + kw->identifier_first = first_cc->ascii_bitset(); + kw->identifier_rest = rest_cc->ascii_bitset(); + + // Compute keyword length range for early-out in hot path + size_t min_len = SIZE_MAX, max_len = 0; + for (const auto &k : exact_keywords) { + min_len = std::min(min_len, k.size()); + max_len = std::max(max_len, k.size()); + } + for (const auto &k : prefix_keywords) { + min_len = std::min(min_len, k.size()); + max_len = std::max(max_len, k.size()); + } + kw->min_keyword_len = min_len; + kw->max_keyword_len = max_len; + + kw->exact_keywords = std::move(exact_keywords); + kw->prefix_keywords = std::move(prefix_keywords); + seq.kw_guard_ = std::move(kw); +} + +// Compute which rules benefit from packrat memoization. +// A rule benefits if it's reachable from 2+ alternatives of the same +// PrioritizedChoice (backtracking will re-visit it at the same position). +inline void Definition::initialize_packrat_filter() const { + std::call_once(packrat_filter_init_, [&]() { + auto def_count = definition_ids_.size(); + if (def_count == 0) { return; } + + // Collect rule IDs reachable from an Ope subtree (bitvector indexed by + // def_id) + struct CollectReachableRules : public TraversalVisitor { + using TraversalVisitor::visit; + std::vector reachable; // indexed by def_id + + CollectReachableRules(size_t n) : reachable(n, false) {} + + void visit(Holder &ope) override { + auto id = ope.outer_->id; + if (id < reachable.size()) { reachable[id] = true; } + ope.ope_->accept(*this); + } + void visit(Reference &ope) override { + if (ope.rule_ && ope.rule_->id < reachable.size() && + !reachable[ope.rule_->id]) { + reachable[ope.rule_->id] = true; + ope.rule_->accept(*this); + } + } + }; + + // Find rules that benefit: reachable from 2+ alternatives of same choice + std::vector benefits(def_count, false); + + struct FindBacktrackRules : public TraversalVisitor { + using TraversalVisitor::visit; + std::vector &benefits; + size_t def_count; + std::vector visited_rules; // indexed by def_id + + FindBacktrackRules(std::vector &b, size_t n) + : benefits(b), def_count(n), visited_rules(n, false) {} + + void visit(PrioritizedChoice &ope) override { + // For each alternative, collect reachable rules as bitvectors + std::vector> alt_reachable; + for (auto &op : ope.opes_) { + CollectReachableRules crr(def_count); + op->accept(crr); + alt_reachable.push_back(std::move(crr.reachable)); + } + + // Mark rules reachable from 2+ alternatives + for (size_t id = 0; id < def_count; id++) { + size_t count = 0; + for (auto &alt : alt_reachable) { + if (alt[id]) { count++; } + } + if (count >= 2) { benefits[id] = true; } + } + + // Recurse into alternatives + for (auto &op : ope.opes_) { + op->accept(*this); + } + } + void visit(Holder &ope) override { + auto id = ope.outer_->id; + if (id < visited_rules.size() && !visited_rules[id]) { + visited_rules[id] = true; + ope.ope_->accept(*this); + } + } + void visit(Reference &ope) override { + if (ope.rule_) { ope.rule_->accept(*this); } + } + }; + + FindBacktrackRules finder(benefits, def_count); + holder_->accept(finder); + if (whitespaceOpe) { whitespaceOpe->accept(finder); } + if (wordOpe) { wordOpe->accept(finder); } + + packrat_filter_ = std::move(benefits); + }); } inline void LinkReferences::visit(Reference &ope) { - // Check if the reference is a macro parameter - auto found_param = false; - for (size_t i = 0; i < params_.size(); i++) { - const auto ¶m = params_[i]; - if (param == ope.name_) { - ope.iarg_ = i; - found_param = true; - break; - } + // Check if the reference is a macro parameter + auto found_param = false; + for (size_t i = 0; i < params_.size(); i++) { + const auto ¶m = params_[i]; + if (param == ope.name_) { + ope.iarg_ = i; + found_param = true; + break; } + } - // Check if the reference is a definition rule - if (!found_param && grammar_.count(ope.name_)) { - auto &rule = grammar_.at(ope.name_); - ope.rule_ = &rule; - } + // Check if the reference is a definition rule + if (!found_param && grammar_.count(ope.name_)) { + auto &rule = grammar_.at(ope.name_); + ope.rule_ = &rule; + } - for (auto arg : ope.args_) { - arg->accept(*this); - } + for (const auto &arg : ope.args_) { + arg->accept(*this); + } } inline void FindReference::visit(Reference &ope) { - for (size_t i = 0; i < args_.size(); i++) { - const auto &name = params_[i]; - if (name == ope.name_) { - found_ope = args_[i]; - return; - } + for (size_t i = 0; i < args_.size(); i++) { + const auto &name = params_[i]; + if (name == ope.name_) { + found_ope = args_[i]; + return; } - found_ope = ope.shared_from_this(); + } + found_ope = ope.shared_from_this(); } /*----------------------------------------------------------------------------- @@ -3298,955 +4057,1011 @@ using Rules = std::unordered_map>; class ParserGenerator { public: - struct ParserContext { - std::shared_ptr grammar; - std::string start; - bool enablePackratParsing = false; - }; + struct ParserContext { + std::shared_ptr grammar; + std::string start; + bool enablePackratParsing = false; + }; - static ParserContext parse(const char *s, size_t n, const Rules &rules, - Log log, std::string_view start) { - return get_instance().perform_core(s, n, rules, log, std::string(start)); - } + static ParserContext parse(const char *s, size_t n, const Rules &rules, + Log log, std::string_view start, + bool enable_left_recursion = true) { + return get_instance().perform_core(s, n, rules, log, std::string(start), + enable_left_recursion); + } - // For debugging purpose - static bool parse_test(const char *d, const char *s) { - Data data; - std::any dt = &data; + // For debugging purpose + static bool parse_test(const char *d, const char *s) { + Data data; + std::any dt = &data; - auto n = strlen(s); - auto r = get_instance().g[d].parse(s, n, dt); - return r.ret && r.len == n; - } + auto n = strlen(s); + auto r = get_instance().g[d].parse(s, n, dt); + return r.ret && r.len == n; + } #if defined(__cpp_lib_char8_t) - static bool parse_test(const char *d, const char8_t *s) { - return parse_test(d, reinterpret_cast(s)); - } + static bool parse_test(const char *d, const char8_t *s) { + return parse_test(d, reinterpret_cast(s)); + } #endif private: - static ParserGenerator &get_instance() { - static ParserGenerator instance; - return instance; - } + static ParserGenerator &get_instance() { + static ParserGenerator instance; + return instance; + } - ParserGenerator() { - make_grammar(); - setup_actions(); - } + ParserGenerator() { + make_grammar(); + setup_actions(); + } - struct Instruction { - std::string type; - std::any data; - std::string_view sv; + struct Instruction { + std::string type; + std::any data; + std::string_view sv; + }; + + struct Data { + std::shared_ptr grammar; + std::string start; + const char *start_pos = nullptr; + + std::vector> duplicates_of_definition; + + std::vector> duplicates_of_instruction; + std::map> instructions; + + std::vector> undefined_back_references; + std::vector> captures_stack{{}}; + + std::set captures_in_current_definition; + bool enablePackratParsing = true; + + Data() : grammar(std::make_shared()) {} + }; + + class SyntaxErrorException : public std::runtime_error { + public: + SyntaxErrorException(const char *what_arg, std::pair r) + : std::runtime_error(what_arg), r_(r) {} + + std::pair line_info() const { return r_; } + + private: + std::pair r_; + }; + + void make_grammar() { + // Setup PEG syntax parser + g["Grammar"] <= seq(g["Spacing"], oom(g["Definition"]), g["EndOfFile"]); + g["Definition"] <= + cho(seq(g["Ignore"], g["IdentCont"], g["Parameters"], g["LEFTARROW"], + g["Expression"], opt(g["Instruction"])), + seq(g["Ignore"], g["Identifier"], g["LEFTARROW"], g["Expression"], + opt(g["Instruction"]))); + g["Expression"] <= seq(g["Sequence"], zom(seq(g["SLASH"], g["Sequence"]))); + g["Sequence"] <= zom(cho(g["CUT"], g["Prefix"])); + g["Prefix"] <= seq(opt(cho(g["AND"], g["NOT"])), g["SuffixWithLabel"]); + g["SuffixWithLabel"] <= + seq(g["Suffix"], opt(seq(g["LABEL"], g["Identifier"]))); + g["Suffix"] <= seq(g["Primary"], opt(g["Loop"])); + g["Loop"] <= cho(g["QUESTION"], g["STAR"], g["PLUS"], g["Repetition"]); + g["Primary"] <= cho(seq(g["Ignore"], g["IdentCont"], g["Arguments"], + npd(g["LEFTARROW"])), + seq(g["Ignore"], g["Identifier"], + npd(seq(opt(g["Parameters"]), g["LEFTARROW"]))), + seq(g["OPEN"], g["Expression"], g["CLOSE"]), + seq(g["BeginTok"], g["Expression"], g["EndTok"]), + g["CapScope"], + seq(g["BeginCap"], g["Expression"], g["EndCap"]), + g["BackRef"], g["DictionaryI"], g["LiteralI"], + g["Dictionary"], g["Literal"], g["NegatedClassI"], + g["NegatedClass"], g["ClassI"], g["Class"], g["DOT"]); + + g["Identifier"] <= seq(g["IdentCont"], g["Spacing"]); + g["IdentCont"] <= tok(seq(g["IdentStart"], zom(g["IdentRest"]))); + + const static std::vector> range = { + {0x0080, 0xFFFF}}; + g["IdentStart"] <= seq(npd(lit(u8(u8"↑"))), npd(lit(u8(u8"⇑"))), + cho(cls("a-zA-Z_%"), cls(range))); + + g["IdentRest"] <= cho(g["IdentStart"], cls("0-9")); + + g["Dictionary"] <= seq(g["LiteralD"], oom(seq(g["PIPE"], g["LiteralD"]))); + + g["DictionaryI"] <= + seq(g["LiteralID"], oom(seq(g["PIPE"], g["LiteralID"]))); + + auto lit_ope = cho(seq(cls("'"), tok(zom(seq(npd(cls("'")), g["Char"]))), + cls("'"), g["Spacing"]), + seq(cls("\""), tok(zom(seq(npd(cls("\"")), g["Char"]))), + cls("\""), g["Spacing"])); + g["Literal"] <= lit_ope; + g["LiteralD"] <= lit_ope; + + auto lit_case_ignore_ope = + cho(seq(cls("'"), tok(zom(seq(npd(cls("'")), g["Char"]))), lit("'i"), + g["Spacing"]), + seq(cls("\""), tok(zom(seq(npd(cls("\"")), g["Char"]))), lit("\"i"), + g["Spacing"])); + g["LiteralI"] <= lit_case_ignore_ope; + g["LiteralID"] <= lit_case_ignore_ope; + + // NOTE: The original Brian Ford's paper uses 'zom' instead of 'oom'. + g["Class"] <= seq(chr('['), npd(chr('^')), + tok(oom(seq(npd(chr(']')), g["Range"]))), chr(']'), + g["Spacing"]); + g["ClassI"] <= seq(chr('['), npd(chr('^')), + tok(oom(seq(npd(chr(']')), g["Range"]))), lit("]i"), + g["Spacing"]); + + g["NegatedClass"] <= seq(lit("[^"), + tok(oom(seq(npd(chr(']')), g["Range"]))), chr(']'), + g["Spacing"]); + g["NegatedClassI"] <= seq(lit("[^"), + tok(oom(seq(npd(chr(']')), g["Range"]))), + lit("]i"), g["Spacing"]); + + // NOTE: This is different from The original Brian Ford's paper, and this + // modification allows us to specify `[+-]` as a valid char class. + g["Range"] <= + cho(seq(g["Char"], chr('-'), npd(chr(']')), g["Char"]), g["Char"]); + + g["Char"] <= + cho(seq(chr('\\'), cls("fnrtv'\"[]\\^-")), + seq(chr('\\'), cls("0-3"), cls("0-7"), cls("0-7")), + seq(chr('\\'), cls("0-7"), opt(cls("0-7"))), + seq(lit("\\x"), cls("0-9a-fA-F"), opt(cls("0-9a-fA-F"))), + seq(lit("\\u"), + cho(seq(cho(seq(chr('0'), cls("0-9a-fA-F")), lit("10")), + rep(cls("0-9a-fA-F"), 4, 4)), + rep(cls("0-9a-fA-F"), 4, 5))), + seq(npd(chr('\\')), dot())); + + g["Repetition"] <= + seq(g["BeginBracket"], g["RepetitionRange"], g["EndBracket"]); + g["RepetitionRange"] <= cho(seq(g["Number"], g["COMMA"], g["Number"]), + seq(g["Number"], g["COMMA"]), g["Number"], + seq(g["COMMA"], g["Number"])); + g["Number"] <= seq(oom(cls("0-9")), g["Spacing"]); + + g["CapScope"] <= seq(g["BeginCapScope"], g["Expression"], g["EndCapScope"]); + + g["LEFTARROW"] <= seq(cho(lit("<-"), lit(u8(u8"←"))), g["Spacing"]); + ~g["SLASH"] <= seq(chr('/'), g["Spacing"]); + ~g["PIPE"] <= seq(chr('|'), g["Spacing"]); + g["AND"] <= seq(chr('&'), g["Spacing"]); + g["NOT"] <= seq(chr('!'), g["Spacing"]); + g["QUESTION"] <= seq(chr('?'), g["Spacing"]); + g["STAR"] <= seq(chr('*'), g["Spacing"]); + g["PLUS"] <= seq(chr('+'), g["Spacing"]); + ~g["OPEN"] <= seq(chr('('), g["Spacing"]); + ~g["CLOSE"] <= seq(chr(')'), g["Spacing"]); + g["DOT"] <= seq(chr('.'), g["Spacing"]); + + g["CUT"] <= seq(lit(u8(u8"↑")), g["Spacing"]); + ~g["LABEL"] <= seq(cho(chr('^'), lit(u8(u8"⇑"))), g["Spacing"]); + + ~g["Spacing"] <= zom(cho(g["Space"], g["Comment"])); + g["Comment"] <= seq(chr('#'), zom(seq(npd(g["EndOfLine"]), dot())), + opt(g["EndOfLine"])); + g["Space"] <= cho(chr(' '), chr('\t'), g["EndOfLine"]); + g["EndOfLine"] <= cho(lit("\r\n"), chr('\n'), chr('\r')); + g["EndOfFile"] <= npd(dot()); + + ~g["BeginTok"] <= seq(chr('<'), g["Spacing"]); + ~g["EndTok"] <= seq(chr('>'), g["Spacing"]); + + ~g["BeginCapScope"] <= seq(chr('$'), chr('('), g["Spacing"]); + ~g["EndCapScope"] <= seq(chr(')'), g["Spacing"]); + + g["BeginCap"] <= seq(chr('$'), tok(g["IdentCont"]), chr('<'), g["Spacing"]); + ~g["EndCap"] <= seq(chr('>'), g["Spacing"]); + + g["BackRef"] <= seq(chr('$'), tok(g["IdentCont"]), g["Spacing"]); + + g["IGNORE"] <= chr('~'); + + g["Ignore"] <= opt(g["IGNORE"]); + g["Parameters"] <= seq(g["OPEN"], g["Identifier"], + zom(seq(g["COMMA"], g["Identifier"])), g["CLOSE"]); + g["Arguments"] <= seq(g["OPEN"], g["Expression"], + zom(seq(g["COMMA"], g["Expression"])), g["CLOSE"]); + ~g["COMMA"] <= seq(chr(','), g["Spacing"]); + + // Instruction grammars + g["Instruction"] <= + seq(g["BeginBracket"], + opt(seq(g["InstructionItem"], zom(seq(g["InstructionItemSeparator"], + g["InstructionItem"])))), + g["EndBracket"]); + g["InstructionItem"] <= + cho(g["PrecedenceClimbing"], g["ErrorMessage"], g["NoAstOpt"]); + ~g["InstructionItemSeparator"] <= seq(chr(';'), g["Spacing"]); + + ~g["SpacesZom"] <= zom(g["Space"]); + ~g["SpacesOom"] <= oom(g["Space"]); + ~g["BeginBracket"] <= seq(chr('{'), g["Spacing"]); + ~g["EndBracket"] <= seq(chr('}'), g["Spacing"]); + + // PrecedenceClimbing instruction + g["PrecedenceClimbing"] <= + seq(lit("precedence"), g["SpacesOom"], g["PrecedenceInfo"], + zom(seq(g["SpacesOom"], g["PrecedenceInfo"])), g["SpacesZom"]); + g["PrecedenceInfo"] <= + seq(g["PrecedenceAssoc"], + oom(seq(ign(g["SpacesOom"]), g["PrecedenceOpe"]))); + g["PrecedenceOpe"] <= + cho(seq(cls("'"), + tok(zom(seq(npd(cho(g["Space"], cls("'"))), g["Char"]))), + cls("'")), + seq(cls("\""), + tok(zom(seq(npd(cho(g["Space"], cls("\""))), g["Char"]))), + cls("\"")), + tok(oom(seq(npd(cho(g["PrecedenceAssoc"], g["Space"], chr('}'))), + dot())))); + g["PrecedenceAssoc"] <= cls("LR"); + + // Error message instruction + g["ErrorMessage"] <= seq(lit("error_message"), g["SpacesOom"], + g["LiteralD"], g["SpacesZom"]); + + // No Ast node optimization instruction + g["NoAstOpt"] <= seq(lit("no_ast_opt"), g["SpacesZom"]); + + // Set definition names + for (auto &x : g) { + x.second.name = x.first; + } + } + + void setup_actions() { + g["Definition"] = [&](const SemanticValues &vs, std::any &dt) { + auto &data = *std::any_cast(dt); + + auto is_macro = vs.choice() == 0; + auto ignore = std::any_cast(vs[0]); + auto name = std::any_cast(vs[1]); + + std::vector params; + std::shared_ptr ope; + auto has_instructions = false; + + if (is_macro) { + params = std::any_cast>(vs[2]); + ope = std::any_cast>(vs[4]); + if (vs.size() == 6) { has_instructions = true; } + } else { + ope = std::any_cast>(vs[3]); + if (vs.size() == 5) { has_instructions = true; } + } + + if (has_instructions) { + auto index = is_macro ? 5 : 4; + std::unordered_set types; + for (const auto &instruction : + std::any_cast>(vs[index])) { + const auto &type = instruction.type; + if (types.find(type) == types.end()) { + data.instructions[name].push_back(instruction); + types.insert(instruction.type); + if (type == "declare_symbol" || type == "check_symbol") { + if (!TokenChecker::is_token(*ope)) { ope = tok(ope); } + } + } else { + data.duplicates_of_instruction.emplace_back(type, + instruction.sv.data()); + } + } + } + + auto &grammar = *data.grammar; + if (!grammar.count(name)) { + auto &rule = grammar[name]; + rule <= ope; + rule.name = name; + rule.s_ = vs.sv().data(); + rule.line_ = line_info(vs.ss, rule.s_); + rule.ignoreSemanticValue = ignore; + rule.is_macro = is_macro; + rule.params = params; + + if (data.start.empty()) { + data.start = rule.name; + data.start_pos = rule.s_; + } + } else { + data.duplicates_of_definition.emplace_back(name, vs.sv().data()); + } }; - struct Data { - std::shared_ptr grammar; - std::string start; - const char *start_pos = nullptr; - - std::vector> duplicates_of_definition; - - std::vector> duplicates_of_instruction; - std::map> instructions; - - std::vector> undefined_back_references; - std::vector> captures_stack{{}}; - - std::set captures_in_current_definition; - bool enablePackratParsing = true; - - Data() : grammar(std::make_shared()) {} + g["Definition"].enter = [](const Context & /*c*/, const char * /*s*/, + size_t /*n*/, std::any &dt) { + auto &data = *std::any_cast(dt); + data.captures_in_current_definition.clear(); }; - void make_grammar() { - // Setup PEG syntax parser - g["Grammar"] <= seq(g["Spacing"], oom(g["Definition"]), g["EndOfFile"]); - g["Definition"] <= - cho(seq(g["Ignore"], g["IdentCont"], g["Parameters"], g["LEFTARROW"], - g["Expression"], opt(g["Instruction"])), - seq(g["Ignore"], g["Identifier"], g["LEFTARROW"], g["Expression"], - opt(g["Instruction"]))); - g["Expression"] <= seq(g["Sequence"], zom(seq(g["SLASH"], g["Sequence"]))); - g["Sequence"] <= zom(cho(g["CUT"], g["Prefix"])); - g["Prefix"] <= seq(opt(cho(g["AND"], g["NOT"])), g["SuffixWithLabel"]); - g["SuffixWithLabel"] <= - seq(g["Suffix"], opt(seq(g["LABEL"], g["Identifier"]))); - g["Suffix"] <= seq(g["Primary"], opt(g["Loop"])); - g["Loop"] <= cho(g["QUESTION"], g["STAR"], g["PLUS"], g["Repetition"]); - g["Primary"] <= cho(seq(g["Ignore"], g["IdentCont"], g["Arguments"], - npd(g["LEFTARROW"])), - seq(g["Ignore"], g["Identifier"], - npd(seq(opt(g["Parameters"]), g["LEFTARROW"]))), - seq(g["OPEN"], g["Expression"], g["CLOSE"]), - seq(g["BeginTok"], g["Expression"], g["EndTok"]), - g["CapScope"], - seq(g["BeginCap"], g["Expression"], g["EndCap"]), - g["BackRef"], g["DictionaryI"], g["LiteralI"], - g["Dictionary"], g["Literal"], g["NegatedClassI"], - g["NegatedClass"], g["ClassI"], g["Class"], g["DOT"]); - - g["Identifier"] <= seq(g["IdentCont"], g["Spacing"]); - g["IdentCont"] <= tok(seq(g["IdentStart"], zom(g["IdentRest"]))); - - const static std::vector> range = { - {0x0080, 0xFFFF}}; - g["IdentStart"] <= seq(npd(lit(u8(u8"↑"))), npd(lit(u8(u8"⇑"))), - cho(cls("a-zA-Z_%"), cls(range))); - - g["IdentRest"] <= cho(g["IdentStart"], cls("0-9")); - - g["Dictionary"] <= seq(g["LiteralD"], oom(seq(g["PIPE"], g["LiteralD"]))); - - g["DictionaryI"] <= - seq(g["LiteralID"], oom(seq(g["PIPE"], g["LiteralID"]))); - - auto lit_ope = cho(seq(cls("'"), tok(zom(seq(npd(cls("'")), g["Char"]))), - cls("'"), g["Spacing"]), - seq(cls("\""), tok(zom(seq(npd(cls("\"")), g["Char"]))), - cls("\""), g["Spacing"])); - g["Literal"] <= lit_ope; - g["LiteralD"] <= lit_ope; - - auto lit_case_ignore_ope = - cho(seq(cls("'"), tok(zom(seq(npd(cls("'")), g["Char"]))), lit("'i"), - g["Spacing"]), - seq(cls("\""), tok(zom(seq(npd(cls("\"")), g["Char"]))), lit("\"i"), - g["Spacing"])); - g["LiteralI"] <= lit_case_ignore_ope; - g["LiteralID"] <= lit_case_ignore_ope; - - // NOTE: The original Brian Ford's paper uses 'zom' instead of 'oom'. - g["Class"] <= seq(chr('['), npd(chr('^')), - tok(oom(seq(npd(chr(']')), g["Range"]))), chr(']'), - g["Spacing"]); - g["ClassI"] <= seq(chr('['), npd(chr('^')), - tok(oom(seq(npd(chr(']')), g["Range"]))), lit("]i"), - g["Spacing"]); - - g["NegatedClass"] <= seq(lit("[^"), - tok(oom(seq(npd(chr(']')), g["Range"]))), chr(']'), - g["Spacing"]); - g["NegatedClassI"] <= seq(lit("[^"), - tok(oom(seq(npd(chr(']')), g["Range"]))), - lit("]i"), g["Spacing"]); - - // NOTE: This is different from The original Brian Ford's paper, and this - // modification allows us to specify `[+-]` as a valid char class. - g["Range"] <= - cho(seq(g["Char"], chr('-'), npd(chr(']')), g["Char"]), g["Char"]); - - g["Char"] <= - cho(seq(chr('\\'), cls("fnrtv'\"[]\\^")), - seq(chr('\\'), cls("0-3"), cls("0-7"), cls("0-7")), - seq(chr('\\'), cls("0-7"), opt(cls("0-7"))), - seq(lit("\\x"), cls("0-9a-fA-F"), opt(cls("0-9a-fA-F"))), - seq(lit("\\u"), - cho(seq(cho(seq(chr('0'), cls("0-9a-fA-F")), lit("10")), - rep(cls("0-9a-fA-F"), 4, 4)), - rep(cls("0-9a-fA-F"), 4, 5))), - seq(npd(chr('\\')), dot())); - - g["Repetition"] <= - seq(g["BeginBracket"], g["RepetitionRange"], g["EndBracket"]); - g["RepetitionRange"] <= cho(seq(g["Number"], g["COMMA"], g["Number"]), - seq(g["Number"], g["COMMA"]), g["Number"], - seq(g["COMMA"], g["Number"])); - g["Number"] <= seq(oom(cls("0-9")), g["Spacing"]); - - g["CapScope"] <= seq(g["BeginCapScope"], g["Expression"], g["EndCapScope"]); - - g["LEFTARROW"] <= seq(cho(lit("<-"), lit(u8(u8"←"))), g["Spacing"]); - ~g["SLASH"] <= seq(chr('/'), g["Spacing"]); - ~g["PIPE"] <= seq(chr('|'), g["Spacing"]); - g["AND"] <= seq(chr('&'), g["Spacing"]); - g["NOT"] <= seq(chr('!'), g["Spacing"]); - g["QUESTION"] <= seq(chr('?'), g["Spacing"]); - g["STAR"] <= seq(chr('*'), g["Spacing"]); - g["PLUS"] <= seq(chr('+'), g["Spacing"]); - ~g["OPEN"] <= seq(chr('('), g["Spacing"]); - ~g["CLOSE"] <= seq(chr(')'), g["Spacing"]); - g["DOT"] <= seq(chr('.'), g["Spacing"]); - - g["CUT"] <= seq(lit(u8(u8"↑")), g["Spacing"]); - ~g["LABEL"] <= seq(cho(chr('^'), lit(u8(u8"⇑"))), g["Spacing"]); - - ~g["Spacing"] <= zom(cho(g["Space"], g["Comment"])); - g["Comment"] <= - seq(chr('#'), zom(seq(npd(g["EndOfLine"]), dot())), g["EndOfLine"]); - g["Space"] <= cho(chr(' '), chr('\t'), g["EndOfLine"]); - g["EndOfLine"] <= cho(lit("\r\n"), chr('\n'), chr('\r')); - g["EndOfFile"] <= npd(dot()); - - ~g["BeginTok"] <= seq(chr('<'), g["Spacing"]); - ~g["EndTok"] <= seq(chr('>'), g["Spacing"]); - - ~g["BeginCapScope"] <= seq(chr('$'), chr('('), g["Spacing"]); - ~g["EndCapScope"] <= seq(chr(')'), g["Spacing"]); - - g["BeginCap"] <= seq(chr('$'), tok(g["IdentCont"]), chr('<'), g["Spacing"]); - ~g["EndCap"] <= seq(chr('>'), g["Spacing"]); - - g["BackRef"] <= seq(chr('$'), tok(g["IdentCont"]), g["Spacing"]); - - g["IGNORE"] <= chr('~'); - - g["Ignore"] <= opt(g["IGNORE"]); - g["Parameters"] <= seq(g["OPEN"], g["Identifier"], - zom(seq(g["COMMA"], g["Identifier"])), g["CLOSE"]); - g["Arguments"] <= seq(g["OPEN"], g["Expression"], - zom(seq(g["COMMA"], g["Expression"])), g["CLOSE"]); - ~g["COMMA"] <= seq(chr(','), g["Spacing"]); - - // Instruction grammars - g["Instruction"] <= - seq(g["BeginBracket"], - opt(seq(g["InstructionItem"], zom(seq(g["InstructionItemSeparator"], - g["InstructionItem"])))), - g["EndBracket"]); - g["InstructionItem"] <= - cho(g["PrecedenceClimbing"], g["ErrorMessage"], g["NoAstOpt"]); - ~g["InstructionItemSeparator"] <= seq(chr(';'), g["Spacing"]); - - ~g["SpacesZom"] <= zom(g["Space"]); - ~g["SpacesOom"] <= oom(g["Space"]); - ~g["BeginBracket"] <= seq(chr('{'), g["Spacing"]); - ~g["EndBracket"] <= seq(chr('}'), g["Spacing"]); - - // PrecedenceClimbing instruction - g["PrecedenceClimbing"] <= - seq(lit("precedence"), g["SpacesOom"], g["PrecedenceInfo"], - zom(seq(g["SpacesOom"], g["PrecedenceInfo"])), g["SpacesZom"]); - g["PrecedenceInfo"] <= - seq(g["PrecedenceAssoc"], - oom(seq(ign(g["SpacesOom"]), g["PrecedenceOpe"]))); - g["PrecedenceOpe"] <= - cho(seq(cls("'"), - tok(zom(seq(npd(cho(g["Space"], cls("'"))), g["Char"]))), - cls("'")), - seq(cls("\""), - tok(zom(seq(npd(cho(g["Space"], cls("\""))), g["Char"]))), - cls("\"")), - tok(oom(seq(npd(cho(g["PrecedenceAssoc"], g["Space"], chr('}'))), - dot())))); - g["PrecedenceAssoc"] <= cls("LR"); - - // Error message instruction - g["ErrorMessage"] <= seq(lit("error_message"), g["SpacesOom"], - g["LiteralD"], g["SpacesZom"]); - - // No Ast node optimization instruction - g["NoAstOpt"] <= seq(lit("no_ast_opt"), g["SpacesZom"]); - - // Set definition names - for (auto &x : g) { - x.second.name = x.first; + g["Expression"] = [&](const SemanticValues &vs) { + if (vs.size() == 1) { + return std::any_cast>(vs[0]); + } else { + std::vector> opes; + for (auto i = 0u; i < vs.size(); i++) { + opes.emplace_back(std::any_cast>(vs[i])); } - } + const std::shared_ptr ope = + std::make_shared(opes); + return ope; + } + }; - void setup_actions() { - g["Definition"] = [&](const SemanticValues &vs, std::any &dt) { - auto &data = *std::any_cast(dt); - - auto is_macro = vs.choice() == 0; - auto ignore = std::any_cast(vs[0]); - auto name = std::any_cast(vs[1]); - - std::vector params; - std::shared_ptr ope; - auto has_instructions = false; - - if (is_macro) { - params = std::any_cast>(vs[2]); - ope = std::any_cast>(vs[4]); - if (vs.size() == 6) { has_instructions = true; } - } else { - ope = std::any_cast>(vs[3]); - if (vs.size() == 5) { has_instructions = true; } - } - - if (has_instructions) { - auto index = is_macro ? 5 : 4; - std::unordered_set types; - for (const auto &instruction : - std::any_cast>(vs[index])) { - const auto &type = instruction.type; - if (types.find(type) == types.end()) { - data.instructions[name].push_back(instruction); - types.insert(instruction.type); - if (type == "declare_symbol" || type == "check_symbol") { - if (!TokenChecker::is_token(*ope)) { ope = tok(ope); } - } - } else { - data.duplicates_of_instruction.emplace_back(type, - instruction.sv.data()); - } - } - } - - auto &grammar = *data.grammar; - if (!grammar.count(name)) { - auto &rule = grammar[name]; - rule <= ope; - rule.name = name; - rule.s_ = vs.sv().data(); - rule.line_ = line_info(vs.ss, rule.s_); - rule.ignoreSemanticValue = ignore; - rule.is_macro = is_macro; - rule.params = params; - - if (data.start.empty()) { - data.start = rule.name; - data.start_pos = rule.s_; - } - } else { - data.duplicates_of_definition.emplace_back(name, vs.sv().data()); - } - }; - - g["Definition"].enter = [](const Context & /*c*/, const char * /*s*/, - size_t /*n*/, std::any &dt) { - auto &data = *std::any_cast(dt); - data.captures_in_current_definition.clear(); - }; - - g["Expression"] = [&](const SemanticValues &vs) { - if (vs.size() == 1) { - return std::any_cast>(vs[0]); - } else { - std::vector> opes; - for (auto i = 0u; i < vs.size(); i++) { - opes.emplace_back(std::any_cast>(vs[i])); - } - const std::shared_ptr ope = - std::make_shared(opes); - return ope; - } - }; - - g["Sequence"] = [&](const SemanticValues &vs) { - if (vs.empty()) { - return npd(lit("")); - } else if (vs.size() == 1) { - return std::any_cast>(vs[0]); - } else { - std::vector> opes; - for (const auto &x : vs) { - opes.emplace_back(std::any_cast>(x)); - } - const std::shared_ptr ope = std::make_shared(opes); - return ope; - } - }; - - g["Prefix"] = [&](const SemanticValues &vs) { - std::shared_ptr ope; - if (vs.size() == 1) { - ope = std::any_cast>(vs[0]); - } else { - assert(vs.size() == 2); - auto tok = std::any_cast(vs[0]); - ope = std::any_cast>(vs[1]); - if (tok == '&') { - ope = apd(ope); - } else { // '!' - ope = npd(ope); - } - } - return ope; - }; - - g["SuffixWithLabel"] = [&](const SemanticValues &vs, std::any &dt) { - auto ope = std::any_cast>(vs[0]); - if (vs.size() == 1) { - return ope; - } else { - assert(vs.size() == 2); - auto &data = *std::any_cast(dt); - const auto &ident = std::any_cast(vs[1]); - auto label = ref(*data.grammar, ident, vs.sv().data(), false, {}); - auto recovery = rec(ref(*data.grammar, RECOVER_DEFINITION_NAME, - vs.sv().data(), true, {label})); - return cho4label_(ope, recovery); - } - }; - - struct Loop { - enum class Type { opt = 0, zom, oom, rep }; - Type type; - std::pair range; - }; - - g["Suffix"] = [&](const SemanticValues &vs) { - auto ope = std::any_cast>(vs[0]); - if (vs.size() == 1) { - return ope; - } else { - assert(vs.size() == 2); - auto loop = std::any_cast(vs[1]); - switch (loop.type) { - case Loop::Type::opt: return opt(ope); - case Loop::Type::zom: return zom(ope); - case Loop::Type::oom: return oom(ope); - default: // Regex-like repetition - return rep(ope, loop.range.first, loop.range.second); - } - } - }; - - g["Loop"] = [&](const SemanticValues &vs) { - switch (vs.choice()) { - case 0: // Option - return Loop{Loop::Type::opt, std::pair()}; - case 1: // Zero or More - return Loop{Loop::Type::zom, std::pair()}; - case 2: // One or More - return Loop{Loop::Type::oom, std::pair()}; - default: // Regex-like repetition - return Loop{Loop::Type::rep, - std::any_cast>(vs[0])}; - } - }; - - g["Primary"] = [&](const SemanticValues &vs, std::any &dt) { - auto &data = *std::any_cast(dt); - - switch (vs.choice()) { - case 0: // Macro Reference - case 1: { // Reference - auto is_macro = vs.choice() == 0; - auto ignore = std::any_cast(vs[0]); - const auto &ident = std::any_cast(vs[1]); - - std::vector> args; - if (is_macro) { - args = std::any_cast>>(vs[2]); - } - - auto ope = ref(*data.grammar, ident, vs.sv().data(), is_macro, args); - if (ident == RECOVER_DEFINITION_NAME) { ope = rec(ope); } - - if (ignore) { - return ign(ope); - } else { - return ope; - } - } - case 2: { // (Expression) - return std::any_cast>(vs[0]); - } - case 3: { // TokenBoundary - return tok(std::any_cast>(vs[0])); - } - case 4: { // CaptureScope - return csc(std::any_cast>(vs[0])); - } - case 5: { // Capture - const auto &name = std::any_cast(vs[0]); - auto ope = std::any_cast>(vs[1]); - - data.captures_stack.back().insert(name); - data.captures_in_current_definition.insert(name); - - return cap(ope, [name](const char *a_s, size_t a_n, Context &c) { - auto &cs = c.capture_scope_stack[c.capture_scope_stack_size - 1]; - cs[name] = std::string(a_s, a_n); - }); - } - default: { - return std::any_cast>(vs[0]); - } - } - }; - - g["IdentCont"] = [](const SemanticValues &vs) { - return std::string(vs.sv().data(), vs.sv().length()); - }; - - g["Dictionary"] = [](const SemanticValues &vs) { - auto items = vs.transform(); - return dic(items, false); - }; - g["DictionaryI"] = [](const SemanticValues &vs) { - auto items = vs.transform(); - return dic(items, true); - }; - - g["Literal"] = [](const SemanticValues &vs) { - const auto &tok = vs.tokens.front(); - return lit(resolve_escape_sequence(tok.data(), tok.size())); - }; - g["LiteralI"] = [](const SemanticValues &vs) { - const auto &tok = vs.tokens.front(); - return liti(resolve_escape_sequence(tok.data(), tok.size())); - }; - g["LiteralD"] = [](const SemanticValues &vs) { - auto &tok = vs.tokens.front(); - return resolve_escape_sequence(tok.data(), tok.size()); - }; - g["LiteralID"] = [](const SemanticValues &vs) { - auto &tok = vs.tokens.front(); - return resolve_escape_sequence(tok.data(), tok.size()); - }; - - g["Class"] = [](const SemanticValues &vs) { - auto ranges = vs.transform>(); - return cls(ranges); - }; - g["ClassI"] = [](const SemanticValues &vs) { - auto ranges = vs.transform>(); - return cls(ranges, true); - }; - g["NegatedClass"] = [](const SemanticValues &vs) { - auto ranges = vs.transform>(); - return ncls(ranges); - }; - g["NegatedClassI"] = [](const SemanticValues &vs) { - auto ranges = vs.transform>(); - return ncls(ranges, true); - }; - g["Range"] = [](const SemanticValues &vs) { - switch (vs.choice()) { - case 0: { - auto s1 = std::any_cast(vs[0]); - auto s2 = std::any_cast(vs[1]); - auto cp1 = decode_codepoint(s1.data(), s1.length()); - auto cp2 = decode_codepoint(s2.data(), s2.length()); - return std::pair(cp1, cp2); - } - case 1: { - auto s = std::any_cast(vs[0]); - auto cp = decode_codepoint(s.data(), s.length()); - return std::pair(cp, cp); - } - } - return std::pair(0, 0); - }; - g["Char"] = [](const SemanticValues &vs) { - return resolve_escape_sequence(vs.sv().data(), vs.sv().length()); - }; - - g["RepetitionRange"] = [&](const SemanticValues &vs) { - switch (vs.choice()) { - case 0: { // Number COMMA Number - auto min = std::any_cast(vs[0]); - auto max = std::any_cast(vs[1]); - return std::pair(min, max); - } - case 1: // Number COMMA - return std::pair(std::any_cast(vs[0]), - std::numeric_limits::max()); - case 2: { // Number - auto n = std::any_cast(vs[0]); - return std::pair(n, n); - } - default: // COMMA Number - return std::pair(std::numeric_limits::min(), - std::any_cast(vs[0])); - } - }; - g["Number"] = [&](const SemanticValues &vs) { - return vs.token_to_number(); - }; - - g["CapScope"].enter = [](const Context & /*c*/, const char * /*s*/, - size_t /*n*/, std::any &dt) { - auto &data = *std::any_cast(dt); - data.captures_stack.emplace_back(); - }; - g["CapScope"].leave = [](const Context & /*c*/, const char * /*s*/, - size_t /*n*/, size_t /*matchlen*/, - std::any & /*value*/, std::any &dt) { - auto &data = *std::any_cast(dt); - data.captures_stack.pop_back(); - }; - - g["AND"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; - g["NOT"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; - g["QUESTION"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; - g["STAR"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; - g["PLUS"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; - - g["DOT"] = [](const SemanticValues & /*vs*/) { return dot(); }; - - g["CUT"] = [](const SemanticValues & /*vs*/) { return cut(); }; - - g["BeginCap"] = [](const SemanticValues &vs) { return vs.token(); }; - - g["BackRef"] = [&](const SemanticValues &vs, std::any &dt) { - auto &data = *std::any_cast(dt); - - // Undefined back reference check - { - auto found = false; - auto it = data.captures_stack.rbegin(); - while (it != data.captures_stack.rend()) { - if (it->find(vs.token()) != it->end()) { - found = true; - break; - } - ++it; - } - if (!found) { - auto ptr = vs.token().data() - 1; // include '$' symbol - data.undefined_back_references.emplace_back(vs.token(), ptr); - } - } - - // NOTE: Disable packrat parsing if a back reference is not defined in - // captures in the current definition rule. - if (data.captures_in_current_definition.find(vs.token()) == - data.captures_in_current_definition.end()) { - data.enablePackratParsing = false; - } - - return bkr(vs.token_to_string()); - }; - - g["Ignore"] = [](const SemanticValues &vs) { return vs.size() > 0; }; - - g["Parameters"] = [](const SemanticValues &vs) { - return vs.transform(); - }; - - g["Arguments"] = [](const SemanticValues &vs) { - return vs.transform>(); - }; - - g["PrecedenceClimbing"] = [](const SemanticValues &vs) { - PrecedenceClimbing::BinOpeInfo binOpeInfo; - size_t level = 1; - for (auto v : vs) { - auto tokens = std::any_cast>(v); - auto assoc = tokens[0][0]; - for (size_t i = 1; i < tokens.size(); i++) { - binOpeInfo[tokens[i]] = std::pair(level, assoc); - } - level++; - } - Instruction instruction; - instruction.type = "precedence"; - instruction.data = binOpeInfo; - instruction.sv = vs.sv(); - return instruction; - }; - g["PrecedenceInfo"] = [](const SemanticValues &vs) { - return vs.transform(); - }; - g["PrecedenceOpe"] = [](const SemanticValues &vs) { return vs.token(); }; - g["PrecedenceAssoc"] = [](const SemanticValues &vs) { return vs.token(); }; - - g["ErrorMessage"] = [](const SemanticValues &vs) { - Instruction instruction; - instruction.type = "error_message"; - instruction.data = std::any_cast(vs[0]); - instruction.sv = vs.sv(); - return instruction; - }; - - g["NoAstOpt"] = [](const SemanticValues &vs) { - Instruction instruction; - instruction.type = "no_ast_opt"; - instruction.sv = vs.sv(); - return instruction; - }; - - g["Instruction"] = [](const SemanticValues &vs) { - return vs.transform(); - }; - } - - bool apply_precedence_instruction(Definition &rule, - const PrecedenceClimbing::BinOpeInfo &info, - const char *s, Log log) { - try { - auto &seq = dynamic_cast(*rule.get_core_operator()); - auto atom = seq.opes_[0]; - auto &rep = dynamic_cast(*seq.opes_[1]); - auto &seq1 = dynamic_cast(*rep.ope_); - auto binop = seq1.opes_[0]; - auto atom1 = seq1.opes_[1]; - - auto atom_name = dynamic_cast(*atom).name_; - auto binop_name = dynamic_cast(*binop).name_; - auto atom1_name = dynamic_cast(*atom1).name_; - - if (!rep.is_zom() || atom_name != atom1_name || atom_name == binop_name) { - if (log) { - auto line = line_info(s, rule.s_); - log(line.first, line.second, - "'precedence' instruction cannot be applied to '" + rule.name + - "'.", - ""); - } - return false; - } - - rule.holder_->ope_ = pre(atom, binop, info, rule); - rule.disable_action = true; - } catch (...) { - if (log) { - auto line = line_info(s, rule.s_); - log(line.first, line.second, - "'precedence' instruction cannot be applied to '" + rule.name + - "'.", - ""); - } - return false; + g["Sequence"] = [&](const SemanticValues &vs) { + if (vs.empty()) { + return npd(lit("")); + } else if (vs.size() == 1) { + return std::any_cast>(vs[0]); + } else { + std::vector> opes; + for (const auto &x : vs) { + opes.emplace_back(std::any_cast>(x)); } - return true; - } + const std::shared_ptr ope = std::make_shared(opes); + return ope; + } + }; - ParserContext perform_core(const char *s, size_t n, const Rules &rules, - Log log, std::string requested_start) { - Data data; - auto &grammar = *data.grammar; + g["Prefix"] = [&](const SemanticValues &vs) { + std::shared_ptr ope; + if (vs.size() == 1) { + ope = std::any_cast>(vs[0]); + } else { + assert(vs.size() == 2); + auto tok = std::any_cast(vs[0]); + ope = std::any_cast>(vs[1]); + if (tok == '&') { + ope = apd(ope); + } else { // '!' + ope = npd(ope); + } + } + return ope; + }; - // Built-in macros - { - // `%recover` - { - auto &rule = grammar[RECOVER_DEFINITION_NAME]; - rule <= ref(grammar, "x", "", false, {}); - rule.name = RECOVER_DEFINITION_NAME; - rule.s_ = "[native]"; - rule.ignoreSemanticValue = true; - rule.is_macro = true; - rule.params = {"x"}; - } + g["SuffixWithLabel"] = [&](const SemanticValues &vs, std::any &dt) { + auto ope = std::any_cast>(vs[0]); + if (vs.size() == 1) { + return ope; + } else { + assert(vs.size() == 2); + auto &data = *std::any_cast(dt); + const auto &ident = std::any_cast(vs[1]); + auto label = ref(*data.grammar, ident, vs.sv().data(), false, {}); + auto recovery = rec(ref(*data.grammar, RECOVER_DEFINITION_NAME, + vs.sv().data(), true, {label})); + return cho4label_(ope, recovery); + } + }; + + struct Loop { + enum class Type { opt = 0, zom, oom, rep }; + Type type; + std::pair range; + }; + + g["Suffix"] = [&](const SemanticValues &vs) { + auto ope = std::any_cast>(vs[0]); + if (vs.size() == 1) { + return ope; + } else { + assert(vs.size() == 2); + auto loop = std::any_cast(vs[1]); + switch (loop.type) { + case Loop::Type::opt: return opt(ope); + case Loop::Type::zom: return zom(ope); + case Loop::Type::oom: return oom(ope); + default: // Regex-like repetition + return rep(ope, loop.range.first, loop.range.second); + } + } + }; + + g["Loop"] = [&](const SemanticValues &vs) { + switch (vs.choice()) { + case 0: // Option + return Loop{Loop::Type::opt, std::pair()}; + case 1: // Zero or More + return Loop{Loop::Type::zom, std::pair()}; + case 2: // One or More + return Loop{Loop::Type::oom, std::pair()}; + default: // Regex-like repetition + return Loop{Loop::Type::rep, + std::any_cast>(vs[0])}; + } + }; + + g["Primary"] = [&](const SemanticValues &vs, std::any &dt) { + auto &data = *std::any_cast(dt); + + switch (vs.choice()) { + case 0: // Macro Reference + case 1: { // Reference + auto is_macro = vs.choice() == 0; + auto ignore = std::any_cast(vs[0]); + const auto &ident = std::any_cast(vs[1]); + + std::vector> args; + if (is_macro) { + args = std::any_cast>>(vs[2]); } - std::any dt = &data; - auto r = g["Grammar"].parse(s, n, dt, nullptr, log); + auto ope = ref(*data.grammar, ident, vs.sv().data(), is_macro, args); + if (ident == RECOVER_DEFINITION_NAME) { ope = rec(ope); } - if (!r.ret) { - if (log) { - if (r.error_info.message_pos) { - auto line = line_info(s, r.error_info.message_pos); - log(line.first, line.second, r.error_info.message, - r.error_info.label); - } else { - auto line = line_info(s, r.error_info.error_pos); - log(line.first, line.second, "syntax error", r.error_info.label); - } - } - return {}; + if (ignore) { + return ign(ope); + } else { + return ope; } + } + case 2: { // (Expression) + return std::any_cast>(vs[0]); + } + case 3: { // TokenBoundary + return tok(std::any_cast>(vs[0])); + } + case 4: { // CaptureScope + return csc(std::any_cast>(vs[0])); + } + case 5: { // Capture + const auto &name = std::any_cast(vs[0]); + auto ope = std::any_cast>(vs[1]); - // User provided rules - for (auto [user_name, user_rule] : rules) { - auto name = user_name; - auto ignore = false; - if (!name.empty() && name[0] == '~') { - ignore = true; - name.erase(0, 1); - } - if (!name.empty()) { - auto &rule = grammar[name]; - rule <= user_rule; - rule.name = name; - rule.ignoreSemanticValue = ignore; - } + data.captures_stack.back().insert(name); + data.captures_in_current_definition.insert(name); + + return cap(ope, [name](const char *a_s, size_t a_n, Context &c) { + c.capture_entries.emplace_back(name, std::string(a_s, a_n)); + }); + } + default: { + return std::any_cast>(vs[0]); + } + } + }; + + g["IdentCont"] = [](const SemanticValues &vs) { + return std::string(vs.sv().data(), vs.sv().length()); + }; + + g["Dictionary"] = [](const SemanticValues &vs) { + auto items = vs.transform(); + return dic(items, false); + }; + g["DictionaryI"] = [](const SemanticValues &vs) { + auto items = vs.transform(); + return dic(items, true); + }; + + g["Literal"] = [](const SemanticValues &vs) { + const auto &tok = vs.tokens.front(); + return lit(resolve_escape_sequence(tok.data(), tok.size())); + }; + g["LiteralI"] = [](const SemanticValues &vs) { + const auto &tok = vs.tokens.front(); + return liti(resolve_escape_sequence(tok.data(), tok.size())); + }; + g["LiteralD"] = [](const SemanticValues &vs) { + auto &tok = vs.tokens.front(); + return resolve_escape_sequence(tok.data(), tok.size()); + }; + g["LiteralID"] = [](const SemanticValues &vs) { + auto &tok = vs.tokens.front(); + return resolve_escape_sequence(tok.data(), tok.size()); + }; + + g["Class"] = [](const SemanticValues &vs) { + auto ranges = vs.transform>(); + return cls(ranges); + }; + g["ClassI"] = [](const SemanticValues &vs) { + auto ranges = vs.transform>(); + return cls(ranges, true); + }; + g["NegatedClass"] = [](const SemanticValues &vs) { + auto ranges = vs.transform>(); + return ncls(ranges); + }; + g["NegatedClassI"] = [](const SemanticValues &vs) { + auto ranges = vs.transform>(); + return ncls(ranges, true); + }; + g["Range"] = [](const SemanticValues &vs) { + switch (vs.choice()) { + case 0: { + auto s1 = std::any_cast(vs[0]); + auto s2 = std::any_cast(vs[1]); + auto cp1 = decode_codepoint(s1.data(), s1.length()); + auto cp2 = decode_codepoint(s2.data(), s2.length()); + if (cp1 > cp2) { + throw SyntaxErrorException("characer range is out of order...", + vs.line_info()); } + return std::pair(cp1, cp2); + } + case 1: { + auto s = std::any_cast(vs[0]); + auto cp = decode_codepoint(s.data(), s.length()); + return std::pair(cp, cp); + } + } + return std::pair(0, 0); + }; + g["Char"] = [](const SemanticValues &vs) { + return resolve_escape_sequence(vs.sv().data(), vs.sv().length()); + }; - // Check duplicated definitions - auto ret = true; + g["RepetitionRange"] = [&](const SemanticValues &vs) { + switch (vs.choice()) { + case 0: { // Number COMMA Number + auto min = std::any_cast(vs[0]); + auto max = std::any_cast(vs[1]); + return std::pair(min, max); + } + case 1: // Number COMMA + return std::pair(std::any_cast(vs[0]), + std::numeric_limits::max()); + case 2: { // Number + auto n = std::any_cast(vs[0]); + return std::pair(n, n); + } + default: // COMMA Number + return std::pair(std::numeric_limits::min(), + std::any_cast(vs[0])); + } + }; + g["Number"] = [&](const SemanticValues &vs) { + return vs.token_to_number(); + }; - if (!data.duplicates_of_definition.empty()) { - for (const auto &[name, ptr] : data.duplicates_of_definition) { - if (log) { - auto line = line_info(s, ptr); - log(line.first, line.second, - "The definition '" + name + "' is already defined.", ""); - } - } - ret = false; + g["CapScope"].enter = [](const Context & /*c*/, const char * /*s*/, + size_t /*n*/, std::any &dt) { + auto &data = *std::any_cast(dt); + data.captures_stack.emplace_back(); + }; + g["CapScope"].leave = [](const Context & /*c*/, const char * /*s*/, + size_t /*n*/, size_t /*matchlen*/, + std::any & /*value*/, std::any &dt) { + auto &data = *std::any_cast(dt); + data.captures_stack.pop_back(); + }; + + g["AND"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; + g["NOT"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; + g["QUESTION"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; + g["STAR"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; + g["PLUS"] = [](const SemanticValues &vs) { return *vs.sv().data(); }; + + g["DOT"] = [](const SemanticValues & /*vs*/) { return dot(); }; + + g["CUT"] = [](const SemanticValues & /*vs*/) { return cut(); }; + + g["BeginCap"] = [](const SemanticValues &vs) { return vs.token(); }; + + g["BackRef"] = [&](const SemanticValues &vs, std::any &dt) { + auto &data = *std::any_cast(dt); + + // Undefined back reference check + { + auto found = false; + auto it = data.captures_stack.rbegin(); + while (it != data.captures_stack.rend()) { + if (it->find(vs.token()) != it->end()) { + found = true; + break; + } + ++it; } - - // Check duplicated instructions - if (!data.duplicates_of_instruction.empty()) { - for (const auto &[type, ptr] : data.duplicates_of_instruction) { - if (log) { - auto line = line_info(s, ptr); - log(line.first, line.second, - "The instruction '" + type + "' is already defined.", ""); - } - } - ret = false; + if (!found) { + auto ptr = vs.token().data() - 1; // include '$' symbol + data.undefined_back_references.emplace_back(vs.token(), ptr); } + } - // Check undefined back references - if (!data.undefined_back_references.empty()) { - for (const auto &[name, ptr] : data.undefined_back_references) { - if (log) { - auto line = line_info(s, ptr); - log(line.first, line.second, - "The back reference '" + name + "' is undefined.", ""); - } - } - ret = false; + // NOTE: Disable packrat parsing if a back reference is not defined in + // captures in the current definition rule. + if (data.captures_in_current_definition.find(vs.token()) == + data.captures_in_current_definition.end()) { + data.enablePackratParsing = false; + } + + return bkr(vs.token_to_string()); + }; + + g["Ignore"] = [](const SemanticValues &vs) { return vs.size() > 0; }; + + g["Parameters"] = [](const SemanticValues &vs) { + return vs.transform(); + }; + + g["Arguments"] = [](const SemanticValues &vs) { + return vs.transform>(); + }; + + g["PrecedenceClimbing"] = [](const SemanticValues &vs) { + PrecedenceClimbing::BinOpeInfo binOpeInfo; + size_t level = 1; + for (const auto &v : vs) { + auto tokens = std::any_cast>(v); + auto assoc = tokens[0][0]; + for (size_t i = 1; i < tokens.size(); i++) { + binOpeInfo[tokens[i]] = std::pair(level, assoc); } + level++; + } + Instruction instruction; + instruction.type = "precedence"; + instruction.data = binOpeInfo; + instruction.sv = vs.sv(); + return instruction; + }; + g["PrecedenceInfo"] = [](const SemanticValues &vs) { + return vs.transform(); + }; + g["PrecedenceOpe"] = [](const SemanticValues &vs) { return vs.token(); }; + g["PrecedenceAssoc"] = [](const SemanticValues &vs) { return vs.token(); }; - // Set root definition - auto start = data.start; + g["ErrorMessage"] = [](const SemanticValues &vs) { + Instruction instruction; + instruction.type = "error_message"; + instruction.data = std::any_cast(vs[0]); + instruction.sv = vs.sv(); + return instruction; + }; - if (!requested_start.empty()) { - if (grammar.count(requested_start)) { - start = requested_start; - } else { - if (log) { - auto line = line_info(s, s); - log(line.first, line.second, - "The specified start rule '" + requested_start + - "' is undefined.", - ""); - } - ret = false; - } - } + g["NoAstOpt"] = [](const SemanticValues &vs) { + Instruction instruction; + instruction.type = "no_ast_opt"; + instruction.sv = vs.sv(); + return instruction; + }; - if (!ret) { return {}; } + g["Instruction"] = [](const SemanticValues &vs) { + return vs.transform(); + }; + } - auto &start_rule = grammar[start]; + bool apply_precedence_instruction(Definition &rule, + const PrecedenceClimbing::BinOpeInfo &info, + const char *s, Log log) { + try { + auto &seq = dynamic_cast(*rule.get_core_operator()); + auto atom = seq.opes_[0]; + auto &rep = dynamic_cast(*seq.opes_[1]); + auto &seq1 = dynamic_cast(*rep.ope_); + auto binop = seq1.opes_[0]; + auto atom1 = seq1.opes_[1]; - // Check if the start rule has ignore operator - { - if (start_rule.ignoreSemanticValue) { - if (log) { - auto line = line_info(s, start_rule.s_); - log(line.first, line.second, - "Ignore operator cannot be applied to '" + start_rule.name + "'.", - ""); - } - ret = false; - } - } + auto atom_name = dynamic_cast(*atom).name_; + auto binop_name = dynamic_cast(*binop).name_; + auto atom1_name = dynamic_cast(*atom1).name_; - if (!ret) { return {}; } - - // Check missing definitions - auto referenced = std::unordered_set{ - WHITESPACE_DEFINITION_NAME, - WORD_DEFINITION_NAME, - RECOVER_DEFINITION_NAME, - start_rule.name, - }; - - for (auto &[_, rule] : grammar) { - ReferenceChecker vis(grammar, rule.params); - rule.accept(vis); - referenced.insert(vis.referenced.begin(), vis.referenced.end()); - for (const auto &[name, ptr] : vis.error_s) { - if (log) { - auto line = line_info(s, ptr); - log(line.first, line.second, vis.error_message[name], ""); - } - ret = false; - } - } - - for (auto &[name, rule] : grammar) { - if (!referenced.count(name)) { - if (log) { - auto line = line_info(s, rule.s_); - auto msg = "'" + name + "' is not referenced."; - log(line.first, line.second, msg, ""); - } - } - } - - if (!ret) { return {}; } - - // Link references - for (auto &x : grammar) { - auto &rule = x.second; - LinkReferences vis(grammar, rule.params); - rule.accept(vis); - } - - // Check left recursion - ret = true; - - for (auto &[name, rule] : grammar) { - DetectLeftRecursion vis(name); - rule.accept(vis); - if (vis.error_s) { - if (log) { - auto line = line_info(s, vis.error_s); - log(line.first, line.second, "'" + name + "' is left recursive.", ""); - } - ret = false; - } - } - - if (!ret) { return {}; } - - // Check infinite loop - if (detect_infiniteLoop(data, start_rule, log, s)) { return {}; } - - // Automatic whitespace skipping - if (grammar.count(WHITESPACE_DEFINITION_NAME)) { - for (auto &x : grammar) { - auto &rule = x.second; - auto ope = rule.get_core_operator(); - if (IsLiteralToken::check(*ope)) { rule <= tok(ope); } - } - - auto &rule = grammar[WHITESPACE_DEFINITION_NAME]; - start_rule.whitespaceOpe = wsp(rule.get_core_operator()); - - if (detect_infiniteLoop(data, rule, log, s)) { return {}; } - } - - // Word expression - if (grammar.count(WORD_DEFINITION_NAME)) { - auto &rule = grammar[WORD_DEFINITION_NAME]; - start_rule.wordOpe = rule.get_core_operator(); - - if (detect_infiniteLoop(data, rule, log, s)) { return {}; } - } - - // Apply instructions - for (const auto &[name, instructions] : data.instructions) { - auto &rule = grammar[name]; - - for (const auto &instruction : instructions) { - if (instruction.type == "precedence") { - const auto &info = - std::any_cast(instruction.data); - - if (!apply_precedence_instruction(rule, info, s, log)) { return {}; } - } else if (instruction.type == "error_message") { - rule.error_message = std::any_cast(instruction.data); - } else if (instruction.type == "no_ast_opt") { - rule.no_ast_opt = true; - } - } - } - - return {data.grammar, start, data.enablePackratParsing}; - } - - bool detect_infiniteLoop(const Data &data, Definition &rule, const Log &log, - const char *s) const { - std::vector> refs; - std::unordered_map has_error_cache; - DetectInfiniteLoop vis(data.start_pos, rule.name, refs, has_error_cache); - rule.accept(vis); - if (vis.has_error) { - if (log) { - auto line = line_info(s, vis.error_s); - log(line.first, line.second, - "infinite loop is detected in '" + vis.error_name + "'.", ""); - } - return true; + if (!rep.is_zom() || atom_name != atom1_name || atom_name == binop_name) { + if (log) { + auto line = line_info(s, rule.s_); + log(line.first, line.second, + "'precedence' instruction cannot be applied to '" + rule.name + + "'.", + ""); } return false; + } + + rule.holder_->ope_ = pre(atom, binop, info, rule); + rule.disable_action = true; + } catch (...) { + if (log) { + auto line = line_info(s, rule.s_); + log(line.first, line.second, + "'precedence' instruction cannot be applied to '" + rule.name + + "'.", + ""); + } + return false; + } + return true; + } + + ParserContext perform_core(const char *s, size_t n, const Rules &rules, + Log log, std::string requested_start, + bool enable_left_recursion = true) { + Data data; + auto &grammar = *data.grammar; + + // Built-in macros + { + // `%recover` + { + auto &rule = grammar[RECOVER_DEFINITION_NAME]; + rule <= ref(grammar, "x", "", false, {}); + rule.name = RECOVER_DEFINITION_NAME; + rule.s_ = "[native]"; + rule.ignoreSemanticValue = true; + rule.is_macro = true; + rule.params = {"x"}; + } } - Grammar g; + try { + std::any dt = &data; + auto r = g["Grammar"].parse(s, n, dt, nullptr, log); + + if (!r.ret) { + if (log) { + if (r.error_info.message_pos) { + auto line = line_info(s, r.error_info.message_pos); + log(line.first, line.second, r.error_info.message, + r.error_info.label); + } else { + auto line = line_info(s, r.error_info.error_pos); + log(line.first, line.second, "syntax error", r.error_info.label); + } + } + return {}; + } + } catch (const SyntaxErrorException &e) { + if (log) { + auto line = e.line_info(); + log(line.first, line.second, e.what(), ""); + } + return {}; + } + + // User provided rules + for (auto [user_name, user_rule] : rules) { + auto name = user_name; + auto ignore = false; + if (!name.empty() && name[0] == '~') { + ignore = true; + name.erase(0, 1); + } + if (!name.empty()) { + auto &rule = grammar[name]; + rule <= user_rule; + rule.name = name; + rule.ignoreSemanticValue = ignore; + } + } + + // Check duplicated definitions + auto ret = true; + + if (!data.duplicates_of_definition.empty()) { + for (const auto &[name, ptr] : data.duplicates_of_definition) { + if (log) { + auto line = line_info(s, ptr); + log(line.first, line.second, + "the definition '" + name + "' is already defined.", ""); + } + } + ret = false; + } + + // Check duplicated instructions + if (!data.duplicates_of_instruction.empty()) { + for (const auto &[type, ptr] : data.duplicates_of_instruction) { + if (log) { + auto line = line_info(s, ptr); + log(line.first, line.second, + "the instruction '" + type + "' is already defined.", ""); + } + } + ret = false; + } + + // Check undefined back references + if (!data.undefined_back_references.empty()) { + for (const auto &[name, ptr] : data.undefined_back_references) { + if (log) { + auto line = line_info(s, ptr); + log(line.first, line.second, + "the back reference '" + name + "' is undefined.", ""); + } + } + ret = false; + } + + // Set root definition + auto start = data.start; + + if (!requested_start.empty()) { + if (grammar.count(requested_start)) { + start = requested_start; + } else { + if (log) { + auto line = line_info(s, s); + log(line.first, line.second, + "the specified start rule '" + requested_start + + "' is undefined.", + ""); + } + ret = false; + } + } + + if (!ret) { return {}; } + + auto &start_rule = grammar[start]; + + // Check if the start rule has ignore operator + { + if (start_rule.ignoreSemanticValue) { + if (log) { + auto line = line_info(s, start_rule.s_); + log(line.first, line.second, + "ignore operator cannot be applied to '" + start_rule.name + "'.", + ""); + } + ret = false; + } + } + + if (!ret) { return {}; } + + // Check missing definitions + auto referenced = std::unordered_set{ + WHITESPACE_DEFINITION_NAME, + WORD_DEFINITION_NAME, + RECOVER_DEFINITION_NAME, + start_rule.name, + }; + + for (auto &[_, rule] : grammar) { + ReferenceChecker vis(grammar, rule.params); + rule.accept(vis); + referenced.insert(vis.referenced.begin(), vis.referenced.end()); + for (const auto &[name, ptr] : vis.error_s) { + if (log) { + auto line = line_info(s, ptr); + log(line.first, line.second, vis.error_message[name], ""); + } + ret = false; + } + } + + for (auto &[name, rule] : grammar) { + if (!referenced.count(name)) { + if (log) { + auto line = line_info(s, rule.s_); + auto msg = "'" + name + "' is not referenced."; + log(line.first, line.second, msg, ""); + } + } + } + + if (!ret) { return {}; } + + // Link references + for (auto &x : grammar) { + auto &rule = x.second; + LinkReferences vis(grammar, rule.params); + rule.accept(vis); + } + + // Compute can_be_empty for each rule (fixed-point iteration) + { + bool changed = true; + while (changed) { + changed = false; + for (auto &[name, rule] : grammar) { + ComputeCanBeEmpty vis; + rule.accept(vis); + if (vis.result != rule.can_be_empty) { + rule.can_be_empty = vis.result; + changed = true; + } + } + } + } + + // Check left recursion + if (enable_left_recursion) { + for (auto &[name, rule] : grammar) { + DetectLeftRecursion vis(name); + rule.accept(vis); + if (vis.error_s) { rule.is_left_recursive = true; } + } + } else { + ret = true; + + for (auto &[name, rule] : grammar) { + DetectLeftRecursion vis(name); + rule.accept(vis); + if (vis.error_s) { + if (log) { + auto line = line_info(s, vis.error_s); + log(line.first, line.second, "'" + name + "' is left recursive.", + ""); + } + ret = false; + } + } + + if (!ret) { return {}; } + } + + // Check infinite loop + if (detect_infiniteLoop(data, start_rule, log, s)) { return {}; } + + // Automatic whitespace skipping + if (grammar.count(WHITESPACE_DEFINITION_NAME)) { + for (auto &x : grammar) { + auto &rule = x.second; + auto ope = rule.get_core_operator(); + if (IsLiteralToken::check(*ope)) { rule <= tok(ope); } + } + + auto &rule = grammar[WHITESPACE_DEFINITION_NAME]; + start_rule.whitespaceOpe = wsp(rule.get_core_operator()); + + if (detect_infiniteLoop(data, rule, log, s)) { return {}; } + } + + // Word expression + if (grammar.count(WORD_DEFINITION_NAME)) { + auto &rule = grammar[WORD_DEFINITION_NAME]; + start_rule.wordOpe = rule.get_core_operator(); + + if (detect_infiniteLoop(data, rule, log, s)) { return {}; } + } + + // Apply instructions + for (const auto &[name, instructions] : data.instructions) { + auto &rule = grammar[name]; + + for (const auto &instruction : instructions) { + if (instruction.type == "precedence") { + const auto &info = + std::any_cast(instruction.data); + + if (!apply_precedence_instruction(rule, info, s, log)) { return {}; } + } else if (instruction.type == "error_message") { + rule.error_message = std::any_cast(instruction.data); + } else if (instruction.type == "no_ast_opt") { + rule.no_ast_opt = true; + } + } + } + + // Setup First-Set and ISpan optimizations + for (auto &x : grammar) { + SetupFirstSets vis; + x.second.accept(vis); + } + + return {data.grammar, start, data.enablePackratParsing}; + } + + bool detect_infiniteLoop(const Data &data, Definition &rule, const Log &log, + const char *s) const { + std::vector> refs; + std::unordered_map has_error_cache; + DetectInfiniteLoop vis(data.start_pos, rule.name, refs, has_error_cache); + rule.accept(vis); + if (vis.has_error) { + if (log) { + auto line = line_info(s, vis.error_s); + log(line.first, line.second, + "infinite loop is detected in '" + vis.error_name + "'.", ""); + } + return true; + } + return false; + } + + Grammar g; }; /*----------------------------------------------------------------------------- @@ -4254,163 +5069,163 @@ private: *---------------------------------------------------------------------------*/ template struct AstBase : public Annotation { - AstBase(const char *path, size_t line, size_t column, const char *name, - const std::vector> &nodes, - size_t position = 0, size_t length = 0, size_t choice_count = 0, - size_t choice = 0) - : path(path ? path : ""), line(line), column(column), name(name), - position(position), length(length), choice_count(choice_count), - choice(choice), original_name(name), - original_choice_count(choice_count), original_choice(choice), - tag(str2tag(name)), original_tag(tag), is_token(false), nodes(nodes) {} + AstBase(const char *path, size_t line, size_t column, const char *name, + const std::vector> &nodes, + size_t position = 0, size_t length = 0, size_t choice_count = 0, + size_t choice = 0) + : path(path ? path : ""), line(line), column(column), name(name), + position(position), length(length), choice_count(choice_count), + choice(choice), original_name(name), + original_choice_count(choice_count), original_choice(choice), + tag(str2tag(name)), original_tag(tag), is_token(false), nodes(nodes) {} - AstBase(const char *path, size_t line, size_t column, const char *name, - const std::string_view &token, size_t position = 0, size_t length = 0, - size_t choice_count = 0, size_t choice = 0) - : path(path ? path : ""), line(line), column(column), name(name), - position(position), length(length), choice_count(choice_count), - choice(choice), original_name(name), - original_choice_count(choice_count), original_choice(choice), - tag(str2tag(name)), original_tag(tag), is_token(true), token(token) {} + AstBase(const char *path, size_t line, size_t column, const char *name, + const std::string_view &token, size_t position = 0, size_t length = 0, + size_t choice_count = 0, size_t choice = 0) + : path(path ? path : ""), line(line), column(column), name(name), + position(position), length(length), choice_count(choice_count), + choice(choice), original_name(name), + original_choice_count(choice_count), original_choice(choice), + tag(str2tag(name)), original_tag(tag), is_token(true), token(token) {} - AstBase(const AstBase &ast, const char *original_name, size_t position = 0, - size_t length = 0, size_t original_choice_count = 0, - size_t original_choice = 0) - : path(ast.path), line(ast.line), column(ast.column), name(ast.name), - position(position), length(length), choice_count(ast.choice_count), - choice(ast.choice), original_name(original_name), - original_choice_count(original_choice_count), - original_choice(original_choice), tag(ast.tag), - original_tag(str2tag(original_name)), is_token(ast.is_token), - token(ast.token), nodes(ast.nodes), parent(ast.parent) {} + AstBase(const AstBase &ast, const char *original_name, size_t position = 0, + size_t length = 0, size_t original_choice_count = 0, + size_t original_choice = 0) + : path(ast.path), line(ast.line), column(ast.column), name(ast.name), + position(position), length(length), choice_count(ast.choice_count), + choice(ast.choice), original_name(original_name), + original_choice_count(original_choice_count), + original_choice(original_choice), tag(ast.tag), + original_tag(str2tag(original_name)), is_token(ast.is_token), + token(ast.token), nodes(ast.nodes), parent(ast.parent) {} - const std::string path; - const size_t line = 1; - const size_t column = 1; + const std::string path; + const size_t line = 1; + const size_t column = 1; - const std::string name; - size_t position; - size_t length; - const size_t choice_count; - const size_t choice; - const std::string original_name; - const size_t original_choice_count; - const size_t original_choice; - const unsigned int tag; - const unsigned int original_tag; + const std::string name; + size_t position; + size_t length; + const size_t choice_count; + const size_t choice; + const std::string original_name; + const size_t original_choice_count; + const size_t original_choice; + const unsigned int tag; + const unsigned int original_tag; - const bool is_token; - const std::string_view token; + const bool is_token; + const std::string_view token; - std::vector>> nodes; - std::weak_ptr> parent; + std::vector>> nodes; + std::weak_ptr> parent; - std::string token_to_string() const { - assert(is_token); - return std::string(token); - } + std::string token_to_string() const { + assert(is_token); + return std::string(token); + } - template T token_to_number() const { - return token_to_number_(token); - } + template T token_to_number() const { + return token_to_number_(token); + } }; template void ast_to_s_core(const std::shared_ptr &ptr, std::string &s, int level, std::function fn) { - const auto &ast = *ptr; - for (auto i = 0; i < level; i++) { - s += " "; - } - auto name = ast.original_name; - if (ast.original_choice_count > 0) { - name += "/" + std::to_string(ast.original_choice); - } - if (ast.name != ast.original_name) { name += "[" + ast.name + "]"; } - if (ast.is_token) { - s += "- " + name + " ("; - s += ast.token; - s += ")\n"; - } else { - s += "+ " + name + "\n"; - } - if (fn) { s += fn(ast, level + 1); } - for (auto node : ast.nodes) { - ast_to_s_core(node, s, level + 1, fn); - } + const auto &ast = *ptr; + for (auto i = 0; i < level; i++) { + s += " "; + } + auto name = ast.original_name; + if (ast.original_choice_count > 0) { + name += "/" + std::to_string(ast.original_choice); + } + if (ast.name != ast.original_name) { name += "[" + ast.name + "]"; } + if (ast.is_token) { + s += "- " + name + " ("; + s += ast.token; + s += ")\n"; + } else { + s += "+ " + name + "\n"; + } + if (fn) { s += fn(ast, level + 1); } + for (const auto &node : ast.nodes) { + ast_to_s_core(node, s, level + 1, fn); + } } template std::string ast_to_s(const std::shared_ptr &ptr, std::function fn = nullptr) { - std::string s; - ast_to_s_core(ptr, s, 0, fn); - return s; + std::string s; + ast_to_s_core(ptr, s, 0, fn); + return s; } struct AstOptimizer { - AstOptimizer(bool mode, const std::vector &rules = {}) - : mode_(mode), rules_(rules) {} + AstOptimizer(bool mode, const std::vector &rules = {}) + : mode_(mode), rules_(rules) {} - template - std::shared_ptr optimize(std::shared_ptr original, - std::shared_ptr parent = nullptr) { - auto found = - std::find(rules_.begin(), rules_.end(), original->name) != rules_.end(); - auto opt = mode_ ? !found : found; + template + std::shared_ptr optimize(std::shared_ptr original, + std::shared_ptr parent = nullptr) { + auto found = + std::find(rules_.begin(), rules_.end(), original->name) != rules_.end(); + auto opt = mode_ ? !found : found; - if (opt && original->nodes.size() == 1) { - auto child = optimize(original->nodes[0], parent); - auto ast = std::make_shared(*child, original->name.data(), - original->choice_count, original->position, - original->length, original->choice); - for (auto node : ast->nodes) { - node->parent = ast; - } - return ast; - } - - auto ast = std::make_shared(*original); - ast->parent = parent; - ast->nodes.clear(); - for (auto node : original->nodes) { - auto child = optimize(node, ast); - ast->nodes.push_back(child); - } - return ast; + if (opt && original->nodes.size() == 1) { + auto child = optimize(original->nodes[0], parent); + auto ast = std::make_shared(*child, original->name.data(), + original->position, original->length, + original->choice_count, original->choice); + for (auto &node : ast->nodes) { + node->parent = ast; + } + return ast; } + auto ast = std::make_shared(*original); + ast->parent = parent; + ast->nodes.clear(); + for (const auto &node : original->nodes) { + auto child = optimize(node, ast); + ast->nodes.push_back(child); + } + return ast; + } + private: - const bool mode_; - const std::vector rules_; + const bool mode_; + const std::vector rules_; }; struct EmptyType {}; using Ast = AstBase; template void add_ast_action(Definition &rule) { - rule.action = [&](const SemanticValues &vs) { - auto line = vs.line_info(); + rule.action = [&](const SemanticValues &vs) { + auto line = vs.line_info(); - if (rule.is_token()) { - return std::make_shared( - vs.path, line.first, line.second, rule.name.data(), vs.token(), - std::distance(vs.ss, vs.sv().data()), vs.sv().length(), - vs.choice_count(), vs.choice()); - } + if (rule.is_token()) { + return std::make_shared( + vs.path, line.first, line.second, rule.name.data(), vs.token(), + std::distance(vs.ss, vs.sv().data()), vs.sv().length(), + vs.choice_count(), vs.choice()); + } - auto ast = - std::make_shared(vs.path, line.first, line.second, rule.name.data(), - vs.transform>(), - std::distance(vs.ss, vs.sv().data()), - vs.sv().length(), vs.choice_count(), vs.choice()); + auto ast = + std::make_shared(vs.path, line.first, line.second, rule.name.data(), + vs.transform>(), + std::distance(vs.ss, vs.sv().data()), + vs.sv().length(), vs.choice_count(), vs.choice()); - for (auto node : ast->nodes) { - node->parent = ast; - } - return ast; - }; + for (auto &node : ast->nodes) { + node->parent = ast; + } + return ast; + }; } #define PEG_EXPAND(...) __VA_ARGS__ @@ -4550,229 +5365,235 @@ template void add_ast_action(Definition &rule) { class parser { public: - parser() = default; + parser() = default; - parser(const char *s, size_t n, const Rules &rules, - std::string_view start = {}) { - load_grammar(s, n, rules, start); - } + parser(const char *s, size_t n, const Rules &rules, + std::string_view start = {}) { + load_grammar(s, n, rules, start); + } - parser(const char *s, size_t n, std::string_view start = {}) - : parser(s, n, Rules(), start) {} + parser(const char *s, size_t n, std::string_view start = {}) + : parser(s, n, Rules(), start) {} - parser(std::string_view sv, const Rules &rules, std::string_view start = {}) - : parser(sv.data(), sv.size(), rules, start) {} + parser(std::string_view sv, const Rules &rules, std::string_view start = {}) + : parser(sv.data(), sv.size(), rules, start) {} - parser(std::string_view sv, std::string_view start = {}) - : parser(sv.data(), sv.size(), Rules(), start) {} + parser(std::string_view sv, std::string_view start = {}) + : parser(sv.data(), sv.size(), Rules(), start) {} #if defined(__cpp_lib_char8_t) - parser(std::u8string_view sv, const Rules &rules, std::string_view start = {}) - : parser(reinterpret_cast(sv.data()), sv.size(), rules, - start) {} + parser(std::u8string_view sv, const Rules &rules, std::string_view start = {}) + : parser(reinterpret_cast(sv.data()), sv.size(), rules, + start) {} - parser(std::u8string_view sv, std::string_view start = {}) - : parser(reinterpret_cast(sv.data()), sv.size(), Rules(), - start) {} + parser(std::u8string_view sv, std::string_view start = {}) + : parser(reinterpret_cast(sv.data()), sv.size(), Rules(), + start) {} #endif - operator bool() { return grammar_ != nullptr; } + operator bool() const { return grammar_ != nullptr; } - bool load_grammar(const char *s, size_t n, const Rules &rules, - std::string_view start = {}) { - auto cxt = ParserGenerator::parse(s, n, rules, log_, start); - grammar_ = cxt.grammar; - start_ = cxt.start; - enablePackratParsing_ = cxt.enablePackratParsing; - return grammar_ != nullptr; + bool load_grammar(const char *s, size_t n, const Rules &rules, + std::string_view start = {}) { + auto cxt = + ParserGenerator::parse(s, n, rules, log_, start, enableLeftRecursion_); + grammar_ = cxt.grammar; + start_ = cxt.start; + enablePackratParsing_ = cxt.enablePackratParsing; + return grammar_ != nullptr; + } + + bool load_grammar(const char *s, size_t n, std::string_view start = {}) { + return load_grammar(s, n, Rules(), start); + } + + bool load_grammar(std::string_view sv, const Rules &rules, + std::string_view start = {}) { + return load_grammar(sv.data(), sv.size(), rules, start); + } + + bool load_grammar(std::string_view sv, std::string_view start = {}) { + return load_grammar(sv.data(), sv.size(), Rules(), start); + } + + bool parse_n(const char *s, size_t n, const char *path = nullptr) const { + if (grammar_ != nullptr) { + const auto &rule = (*grammar_)[start_]; + auto result = rule.parse(s, n, path, log_); + return post_process(s, n, result); } + return false; + } - bool load_grammar(const char *s, size_t n, std::string_view start = {}) { - return load_grammar(s, n, Rules(), start); - } - - bool load_grammar(std::string_view sv, const Rules &rules, - std::string_view start = {}) { - return load_grammar(sv.data(), sv.size(), rules, start); - } - - bool load_grammar(std::string_view sv, std::string_view start = {}) { - return load_grammar(sv.data(), sv.size(), start); - } - - bool parse_n(const char *s, size_t n, const char *path = nullptr) const { - if (grammar_ != nullptr) { - const auto &rule = (*grammar_)[start_]; - auto result = rule.parse(s, n, path, log_); - return post_process(s, n, result); - } - return false; - } - - bool parse_n(const char *s, size_t n, std::any &dt, - const char *path = nullptr) const { - if (grammar_ != nullptr) { - const auto &rule = (*grammar_)[start_]; - auto result = rule.parse(s, n, dt, path, log_); - return post_process(s, n, result); - } - return false; - } - - template - bool parse_n(const char *s, size_t n, T &val, - const char *path = nullptr) const { - if (grammar_ != nullptr) { - const auto &rule = (*grammar_)[start_]; - auto result = rule.parse_and_get_value(s, n, val, path, log_); - return post_process(s, n, result); - } - return false; - } - - template - bool parse_n(const char *s, size_t n, std::any &dt, T &val, - const char *path = nullptr) const { - if (grammar_ != nullptr) { - const auto &rule = (*grammar_)[start_]; - auto result = rule.parse_and_get_value(s, n, dt, val, path, log_); - return post_process(s, n, result); - } - return false; - } - - bool parse(std::string_view sv, const char *path = nullptr) const { - return parse_n(sv.data(), sv.size(), path); - } - - bool parse(std::string_view sv, std::any &dt, + bool parse_n(const char *s, size_t n, std::any &dt, const char *path = nullptr) const { - return parse_n(sv.data(), sv.size(), dt, path); + if (grammar_ != nullptr) { + const auto &rule = (*grammar_)[start_]; + auto result = rule.parse(s, n, dt, path, log_); + return post_process(s, n, result); } + return false; + } - template - bool parse(std::string_view sv, T &val, const char *path = nullptr) const { - return parse_n(sv.data(), sv.size(), val, path); - } - - template - bool parse(std::string_view sv, std::any &dt, T &val, + template + bool parse_n(const char *s, size_t n, T &val, const char *path = nullptr) const { - return parse_n(sv.data(), sv.size(), dt, val, path); + if (grammar_ != nullptr) { + const auto &rule = (*grammar_)[start_]; + auto result = rule.parse_and_get_value(s, n, val, path, log_); + return post_process(s, n, result); } + return false; + } + + template + bool parse_n(const char *s, size_t n, std::any &dt, T &val, + const char *path = nullptr) const { + if (grammar_ != nullptr) { + const auto &rule = (*grammar_)[start_]; + auto result = rule.parse_and_get_value(s, n, dt, val, path, log_); + return post_process(s, n, result); + } + return false; + } + + bool parse(std::string_view sv, const char *path = nullptr) const { + return parse_n(sv.data(), sv.size(), path); + } + + bool parse(std::string_view sv, std::any &dt, + const char *path = nullptr) const { + return parse_n(sv.data(), sv.size(), dt, path); + } + + template + bool parse(std::string_view sv, T &val, const char *path = nullptr) const { + return parse_n(sv.data(), sv.size(), val, path); + } + + template + bool parse(std::string_view sv, std::any &dt, T &val, + const char *path = nullptr) const { + return parse_n(sv.data(), sv.size(), dt, val, path); + } #if defined(__cpp_lib_char8_t) - bool parse(std::u8string_view sv, const char *path = nullptr) const { - return parse_n(reinterpret_cast(sv.data()), sv.size(), path); - } + bool parse(std::u8string_view sv, const char *path = nullptr) const { + return parse_n(reinterpret_cast(sv.data()), sv.size(), path); + } - bool parse(std::u8string_view sv, std::any &dt, - const char *path = nullptr) const { - return parse_n(reinterpret_cast(sv.data()), sv.size(), dt, - path); - } + bool parse(std::u8string_view sv, std::any &dt, + const char *path = nullptr) const { + return parse_n(reinterpret_cast(sv.data()), sv.size(), dt, + path); + } - template - bool parse(std::u8string_view sv, T &val, const char *path = nullptr) const { - return parse_n(reinterpret_cast(sv.data()), sv.size(), val, - path); - } + template + bool parse(std::u8string_view sv, T &val, const char *path = nullptr) const { + return parse_n(reinterpret_cast(sv.data()), sv.size(), val, + path); + } - template - bool parse(std::u8string_view sv, std::any &dt, T &val, - const char *path = nullptr) const { - return parse_n(reinterpret_cast(sv.data()), sv.size(), dt, - val, path); - } + template + bool parse(std::u8string_view sv, std::any &dt, T &val, + const char *path = nullptr) const { + return parse_n(reinterpret_cast(sv.data()), sv.size(), dt, + val, path); + } #endif - Definition &operator[](const char *s) { return (*grammar_)[s]; } + Definition &operator[](const char *s) { return (*grammar_)[s]; } - const Definition &operator[](const char *s) const { return (*grammar_)[s]; } + const Definition &operator[](const char *s) const { return (*grammar_)[s]; } - const Grammar &get_grammar() const { return *grammar_; } + const Grammar &get_grammar() const { return *grammar_; } - void disable_eoi_check() { - if (grammar_ != nullptr) { - auto &rule = (*grammar_)[start_]; - rule.eoi_check = false; - } + void disable_eoi_check() { + if (grammar_ != nullptr) { + auto &rule = (*grammar_)[start_]; + rule.eoi_check = false; } + } - void enable_packrat_parsing() { - if (grammar_ != nullptr) { - auto &rule = (*grammar_)[start_]; - rule.enablePackratParsing = enablePackratParsing_; - } + void enable_left_recursion(bool enable = true) { + enableLeftRecursion_ = enable; + } + + void enable_packrat_parsing() { + if (grammar_ != nullptr) { + auto &rule = (*grammar_)[start_]; + rule.enablePackratParsing = enablePackratParsing_; } + } - void enable_trace(TracerEnter tracer_enter, TracerLeave tracer_leave) { - if (grammar_ != nullptr) { - auto &rule = (*grammar_)[start_]; - rule.tracer_enter = tracer_enter; - rule.tracer_leave = tracer_leave; - } + void enable_trace(TracerEnter tracer_enter, TracerLeave tracer_leave) { + if (grammar_ != nullptr) { + auto &rule = (*grammar_)[start_]; + rule.tracer_enter = tracer_enter; + rule.tracer_leave = tracer_leave; } + } - void enable_trace(TracerEnter tracer_enter, TracerLeave tracer_leave, - TracerStartOrEnd tracer_start, - TracerStartOrEnd tracer_end) { - if (grammar_ != nullptr) { - auto &rule = (*grammar_)[start_]; - rule.tracer_enter = tracer_enter; - rule.tracer_leave = tracer_leave; - rule.tracer_start = tracer_start; - rule.tracer_end = tracer_end; - } + void enable_trace(TracerEnter tracer_enter, TracerLeave tracer_leave, + TracerStartOrEnd tracer_start, + TracerStartOrEnd tracer_end) { + if (grammar_ != nullptr) { + auto &rule = (*grammar_)[start_]; + rule.tracer_enter = tracer_enter; + rule.tracer_leave = tracer_leave; + rule.tracer_start = tracer_start; + rule.tracer_end = tracer_end; } + } - void set_verbose_trace(bool verbose_trace) { - if (grammar_ != nullptr) { - auto &rule = (*grammar_)[start_]; - rule.verbose_trace = verbose_trace; - } + void set_verbose_trace(bool verbose_trace) { + if (grammar_ != nullptr) { + auto &rule = (*grammar_)[start_]; + rule.verbose_trace = verbose_trace; } + } - template parser &enable_ast() { - for (auto &[_, rule] : *grammar_) { - if (!rule.action) { add_ast_action(rule); } - } - return *this; + template parser &enable_ast() { + for (auto &[_, rule] : *grammar_) { + if (!rule.action) { add_ast_action(rule); } } + return *this; + } - template - std::shared_ptr optimize_ast(std::shared_ptr ast, - bool opt_mode = true) const { - return AstOptimizer(opt_mode, get_no_ast_opt_rules()).optimize(ast); - } + template + std::shared_ptr optimize_ast(std::shared_ptr ast, + bool opt_mode = true) const { + return AstOptimizer(opt_mode, get_no_ast_opt_rules()).optimize(ast); + } - void set_logger(Log log) { log_ = log; } + void set_logger(Log log) { log_ = log; } - void set_logger( - std::function - log) { - log_ = [log](size_t line, size_t col, const std::string &msg, - const std::string & /*rule*/) { log(line, col, msg); }; - } + void set_logger( + std::function + log) { + log_ = [log](size_t line, size_t col, const std::string &msg, + const std::string & /*rule*/) { log(line, col, msg); }; + } private: - bool post_process(const char *s, size_t n, Definition::Result &r) const { - if (log_ && !r.ret) { r.error_info.output_log(log_, s, n); } - return r.ret && !r.recovered; - } + bool post_process(const char *s, size_t n, Definition::Result &r) const { + if (log_ && !r.ret) { r.error_info.output_log(log_, s, n); } + return r.ret && !r.recovered; + } - std::vector get_no_ast_opt_rules() const { - std::vector rules; - for (auto &[name, rule] : *grammar_) { - if (rule.no_ast_opt) { rules.push_back(name); } - } - return rules; + std::vector get_no_ast_opt_rules() const { + std::vector rules; + for (auto &[name, rule] : *grammar_) { + if (rule.no_ast_opt) { rules.push_back(name); } } + return rules; + } - std::shared_ptr grammar_; - std::string start_; - bool enablePackratParsing_ = false; - Log log_; + std::shared_ptr grammar_; + std::string start_; + bool enableLeftRecursion_ = true; + bool enablePackratParsing_ = false; + Log log_; }; /*----------------------------------------------------------------------------- @@ -4780,59 +5601,59 @@ private: *---------------------------------------------------------------------------*/ inline void enable_tracing(parser &parser, std::ostream &os) { - parser.enable_trace( - [&](auto &ope, auto s, auto, auto &, auto &c, auto &, auto &trace_data) { - auto prev_pos = std::any_cast(trace_data); - auto pos = static_cast(s - c.s); - auto backtrack = (pos < prev_pos ? "*" : ""); - std::string indent; - auto level = c.trace_ids.size() - 1; - while (level--) { - indent += "│"; - } - std::string name; - { - name = peg::TraceOpeName::get(const_cast(ope)); + parser.enable_trace( + [&](auto &ope, auto s, auto, auto &, auto &c, auto &, auto &trace_data) { + auto prev_pos = std::any_cast(trace_data); + auto pos = static_cast(s - c.s); + auto backtrack = (pos < prev_pos ? "*" : ""); + std::string indent; + auto level = c.trace_ids.size() - 1; + while (level--) { + indent += "│"; + } + std::string name; + { + name = peg::TraceOpeName::get(const_cast(ope)); - auto lit = dynamic_cast(&ope); - if (lit) { name += " '" + peg::escape_characters(lit->lit_) + "'"; } - } - os << "E " << pos + 1 << backtrack << "\t" << indent << "┌" << name - << " #" << c.trace_ids.back() << std::endl; - trace_data = static_cast(pos); - }, - [&](auto &ope, auto s, auto, auto &sv, auto &c, auto &, auto len, - auto &) { - auto pos = static_cast(s - c.s); - if (len != static_cast(-1)) { pos += len; } - std::string indent; - auto level = c.trace_ids.size() - 1; - while (level--) { - indent += "│"; - } - auto ret = len != static_cast(-1) ? "└o " : "└x "; - auto name = peg::TraceOpeName::get(const_cast(ope)); - std::stringstream choice; - if (sv.choice_count() > 0) { - choice << " " << sv.choice() << "/" << sv.choice_count(); - } - std::string token; - if (!sv.tokens.empty()) { - token += ", token '"; - token += sv.tokens[0]; - token += "'"; - } - std::string matched; - if (peg::success(len) && - peg::TokenChecker::is_token(const_cast(ope))) { - matched = ", match '" + peg::escape_characters(s, len) + "'"; - } - os << "L " << pos + 1 << "\t" << indent << ret << name << " #" - << c.trace_ids.back() << choice.str() << token << matched - << std::endl; - }, - [&](auto &trace_data) { trace_data = static_cast(0); }, - [&](auto &) {}); + auto lit = dynamic_cast(&ope); + if (lit) { name += " '" + peg::escape_characters(lit->lit_) + "'"; } + } + os << "E " << pos + 1 << backtrack << "\t" << indent << "┌" << name + << " #" << c.trace_ids.back() << std::endl; + trace_data = static_cast(pos); + }, + [&](auto &ope, auto s, auto, auto &sv, auto &c, auto &, auto len, + auto &) { + auto pos = static_cast(s - c.s); + if (len != static_cast(-1)) { pos += len; } + std::string indent; + auto level = c.trace_ids.size() - 1; + while (level--) { + indent += "│"; + } + auto ret = len != static_cast(-1) ? "└o " : "└x "; + auto name = peg::TraceOpeName::get(const_cast(ope)); + std::stringstream choice; + if (sv.choice_count() > 0) { + choice << " " << sv.choice() << "/" << sv.choice_count(); + } + std::string token; + if (!sv.tokens.empty()) { + token += ", token '"; + token += sv.tokens[0]; + token += "'"; + } + std::string matched; + if (peg::success(len) && + peg::TokenChecker::is_token(const_cast(ope))) { + matched = ", match '" + peg::escape_characters(s, len) + "'"; + } + os << "L " << pos + 1 << "\t" << indent << ret << name << " #" + << c.trace_ids.back() << choice.str() << token << matched + << std::endl; + }, + [&](auto &trace_data) { trace_data = static_cast(0); }, + [&](auto &) {}); } /*----------------------------------------------------------------------------- @@ -4840,98 +5661,98 @@ inline void enable_tracing(parser &parser, std::ostream &os) { *---------------------------------------------------------------------------*/ inline void enable_profiling(parser &parser, std::ostream &os) { - struct Stats { - struct Item { - std::string name; - size_t success; - size_t fail; - }; - std::vector items; - std::map index; - size_t total = 0; - std::chrono::steady_clock::time_point start; + struct Stats { + struct Item { + std::string name; + size_t success; + size_t fail; }; + std::vector items; + std::map index; + size_t total = 0; + std::chrono::steady_clock::time_point start; + }; - parser.enable_trace( - [&](auto &ope, auto, auto, auto &, auto &, auto &, std::any &trace_data) { - if (auto holder = dynamic_cast(&ope)) { - auto &stats = *std::any_cast(trace_data); + parser.enable_trace( + [&](auto &ope, auto, auto, auto &, auto &, auto &, std::any &trace_data) { + if (auto holder = dynamic_cast(&ope)) { + auto &stats = *std::any_cast(trace_data); - auto &name = holder->name(); - if (stats.index.find(name) == stats.index.end()) { - stats.index[name] = stats.index.size(); - stats.items.push_back({name, 0, 0}); - } - stats.total++; + auto &name = holder->name(); + if (stats.index.find(name) == stats.index.end()) { + stats.index[name] = stats.index.size(); + stats.items.push_back({name, 0, 0}); + } + stats.total++; + } + }, + [&](auto &ope, auto, auto, auto &, auto &, auto &, auto len, + std::any &trace_data) { + if (auto holder = dynamic_cast(&ope)) { + auto &stats = *std::any_cast(trace_data); + + auto &name = holder->name(); + auto index = stats.index[name]; + auto &stat = stats.items[index]; + if (len != static_cast(-1)) { + stat.success++; + } else { + stat.fail++; + } + + if (index == 0) { + auto end = std::chrono::steady_clock::now(); + auto nano = std::chrono::duration_cast( + end - stats.start) + .count(); + auto sec = nano / 1000000.0; + os << "duration: " << sec << "s (" << nano << "µs)" << std::endl + << std::endl; + + char buff[BUFSIZ]; + size_t total_success = 0; + size_t total_fail = 0; + for (auto &[name, success, fail] : stats.items) { + total_success += success; + total_fail += fail; } - }, - [&](auto &ope, auto, auto, auto &, auto &, auto &, auto len, - std::any &trace_data) { - if (auto holder = dynamic_cast(&ope)) { - auto &stats = *std::any_cast(trace_data); - auto &name = holder->name(); - auto index = stats.index[name]; - auto &stat = stats.items[index]; - if (len != static_cast(-1)) { - stat.success++; - } else { - stat.fail++; - } + os << " id total % success fail " + "definition" + << std::endl; - if (index == 0) { - auto end = std::chrono::steady_clock::now(); - auto nano = std::chrono::duration_cast( - end - stats.start) - .count(); - auto sec = nano / 1000000.0; - os << "duration: " << sec << "s (" << nano << "µs)" << std::endl - << std::endl; + auto grand_total = total_success + total_fail; + snprintf(buff, BUFSIZ, "%4s %10zu %5s %10zu %10zu %s", "", + grand_total, "", total_success, total_fail, + "Total counters"); + os << buff << std::endl; - char buff[BUFSIZ]; - size_t total_success = 0; - size_t total_fail = 0; - for (auto &[name_, success, fail] : stats.items) { - total_success += success; - total_fail += fail; - } + snprintf(buff, BUFSIZ, "%4s %10s %5s %10.2f %10.2f %s", "", "", + "", total_success * 100.0 / grand_total, + total_fail * 100.0 / grand_total, "% success/fail"); + os << buff << std::endl << std::endl; + ; - os << " id total % success fail " - "definition" - << std::endl; - - auto grand_total = total_success + total_fail; - snprintf(buff, BUFSIZ, "%4s %10zu %5s %10zu %10zu %s", "", - grand_total, "", total_success, total_fail, - "Total counters"); - os << buff << std::endl; - - snprintf(buff, BUFSIZ, "%4s %10s %5s %10.2f %10.2f %s", "", "", - "", total_success * 100.0 / grand_total, - total_fail * 100.0 / grand_total, "% success/fail"); - os << buff << std::endl << std::endl; - ; - - size_t id = 0; - for (auto &[name_, success, fail] : stats.items) { - auto total = success + fail; - auto ratio = total * 100.0 / stats.total; - snprintf(buff, BUFSIZ, "%4zu %10zu %5.2f %10zu %10zu %s", id, - total, ratio, success, fail, name.c_str()); - os << buff << std::endl; - id++; - } - } + size_t id = 0; + for (auto &[name, success, fail] : stats.items) { + auto total = success + fail; + auto ratio = total * 100.0 / stats.total; + snprintf(buff, BUFSIZ, "%4zu %10zu %5.2f %10zu %10zu %s", id, + total, ratio, success, fail, name.c_str()); + os << buff << std::endl; + id++; } - }, - [&](auto &trace_data) { - auto stats = new Stats{}; - stats->start = std::chrono::steady_clock::now(); - trace_data = stats; - }, - [&](auto &trace_data) { - auto stats = std::any_cast(trace_data); - delete stats; - }); + } + } + }, + [&](auto &trace_data) { + auto stats = new Stats{}; + stats->start = std::chrono::steady_clock::now(); + trace_data = stats; + }, + [&](auto &trace_data) { + auto stats = std::any_cast(trace_data); + delete stats; + }); } } // namespace peg From f3370a4f52b3a419ac47ea0b848fbb0d3cbf05e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2026 07:05:24 +0100 Subject: [PATCH 285/325] Bump doc/doxygen/theme from `1f36200` to `d52eafe` (#6751) Bumps [doc/doxygen/theme](https://github.com/jothepro/doxygen-awesome-css) from `1f36200` to `d52eafe`. - [Release notes](https://github.com/jothepro/doxygen-awesome-css/releases) - [Commits](https://github.com/jothepro/doxygen-awesome-css/compare/1f3620084ff75734ed192101acf40e9dff01d848...d52eafe3e9303399fda15661f3d7bb8fe3d7eabc) --- updated-dependencies: - dependency-name: doc/doxygen/theme dependency-version: d52eafe3e9303399fda15661f3d7bb8fe3d7eabc dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- doc/doxygen/theme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/doxygen/theme b/doc/doxygen/theme index 1f3620084..d52eafe3e 160000 --- a/doc/doxygen/theme +++ b/doc/doxygen/theme @@ -1 +1 @@ -Subproject commit 1f3620084ff75734ed192101acf40e9dff01d848 +Subproject commit d52eafe3e9303399fda15661f3d7bb8fe3d7eabc From 9dfac77ba28ad9a91fb5df683fd671e7e9c6ee08 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 05:49:17 +0200 Subject: [PATCH 286/325] Update translation source strings (#6762) --- cockatrice/cockatrice_en@source.ts | 2447 ++++++++++++++++------------ oracle/oracle_en@source.ts | 4 +- 2 files changed, 1362 insertions(+), 1089 deletions(-) diff --git a/cockatrice/cockatrice_en@source.ts b/cockatrice/cockatrice_en@source.ts index 9a2f56f07..810b345df 100644 --- a/cockatrice/cockatrice_en@source.ts +++ b/cockatrice/cockatrice_en@source.ts @@ -25,12 +25,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -38,60 +38,60 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + The deck could not be saved. - + There are no cards in your deck to be exported @@ -133,190 +133,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error - + Could not create themes directory at '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - - - - + Theme settings - + Current theme: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + + Display card name of background in bottom right: + + + + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering - + Display card names on cards having a picture - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over - + Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout - + Display hand horizontally (wastes space) - + Enable left justification - + Table grid layout - + Invert vertical coordinate - + Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: @@ -324,7 +302,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + + + + Open Deck in Deck Editor @@ -643,22 +626,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -694,124 +677,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - - Toggle &normal untapping + + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -827,133 +810,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - - - %1's library - nominative - - + %1's library + nominative + + + + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - - - their sideboard - look at zone - - - - - %1's sideboard - look at zone - - their sideboard - nominative + look at zone %1's sideboard + look at zone + + + + + their sideboard nominative - + + %1's sideboard + nominative + + + + their custom zone '%1' nominative - + %1's custom zone '%2' nominative @@ -1015,7 +998,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1023,7 +1006,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardInfoDockWidget - + Card Info @@ -1046,32 +1029,32 @@ This is only saved for moderators and cannot be seen by the banned person. - + Select Printing - + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards - + Add card to &maindeck - + Add card to &sideboard @@ -1079,32 +1062,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + Select Printing @@ -1177,17 +1160,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1310,7 +1293,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1318,166 +1301,166 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers - - + + Success - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + Add URL - - + + URL: - - + + Edit URL - + Network Cache Size: - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + Add New URL - + Remove URL - + Day(s) - + Updating... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - + On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen @@ -1485,32 +1468,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1518,27 +1501,27 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Count - + Set - + Number - + Provider ID - + Card @@ -1546,12 +1529,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1648,94 +1631,94 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewWidget - + Banner Card - + Open in deck editor - + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -1768,32 +1751,32 @@ This is only saved for moderators and cannot be seen by the banned person. - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + %1 1 × "%2" (%3) - + Added - + Removed @@ -1860,29 +1843,29 @@ This is only saved for moderators and cannot be seen by the banned person. - - + + Error - + The selected file could not be loaded. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice @@ -2374,17 +2357,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2884,17 +2867,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard - + Error - + Invalid deck list. @@ -2902,43 +2885,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2957,40 +2940,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter @@ -3151,12 +3167,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3167,7 +3183,7 @@ Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -3178,7 +3194,7 @@ Would you like to change your database location setting? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3187,21 +3203,21 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3210,59 +3226,59 @@ Would you like to change your database location setting? - - - + + + Error - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings - + General - + Appearance - + User Interface - + Card Sources - + Chat - + Sound - + Shortcuts @@ -3575,67 +3591,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -4089,143 +4105,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + + + + Choose path - + Personal settings - + Language: - + Paths (editing disabled in portable mode) - + Paths - + How to help with translations - + Decks directory: - + Filters directory: - + Replays directory: - + Pictures directory: - + Card database: - + Custom database directory: - + Token database: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup - + Last update check on %1 (%2 days ago) @@ -4233,47 +4249,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4281,88 +4297,88 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - + + All players - + Reveal r&andom card to... @@ -4370,52 +4386,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play @@ -4423,193 +4439,213 @@ You may have to manually download the new version. LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4703,18 +4739,8 @@ Will now login. - - Number of players - - - - - Please enter the number of players. - - - - - + + Player %1 @@ -4817,8 +4843,8 @@ Will now login. - - + + Error @@ -5219,63 +5245,63 @@ Local version is %1, remote version is %2. - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5285,81 +5311,81 @@ Do you want to enable it/them? - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information - + A card database update is already running. - + Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5367,54 +5393,54 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -5465,7 +5491,7 @@ Cockatrice will now reload the card database. ManaBaseWidget - + Mana Base @@ -5619,277 +5645,297 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play - + from their graveyard - + from exile - + from their hand - + the top card of %1's library - + the top card of their library - + from the top of %1's library - + from the top of their library - + the bottom card of %1's library - + the bottom card of their library - + from the bottom of %1's library - + from the bottom of their library - + from %1's library - + from their library - + from sideboard - + from the stack - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. - + a card - + %1 gives %2 control over %3. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. + + + %1 exiles %2%3 face down. + + %1 exiles %2%3. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). @@ -5897,12 +5943,12 @@ Cockatrice will now reload the card database. - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural @@ -5911,72 +5957,72 @@ Cockatrice will now reload the card database. - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. - + The game has started. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. - + %1 is now watching the game. - + You have been kicked out of the game. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). @@ -5984,28 +6030,28 @@ Cockatrice will now reload the card database. - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural @@ -6014,107 +6060,107 @@ Cockatrice will now reload the card database. - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. - + %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). @@ -6122,7 +6168,7 @@ Cockatrice will now reload the card database. - + %1 removes %2 "%3" counter(s) from %4 (now %5). @@ -6130,97 +6176,97 @@ Cockatrice will now reload the card database. - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -6228,110 +6274,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message - - + + Message: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -6344,32 +6390,37 @@ Cockatrice will now reload the card database. - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -6511,57 +6562,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step - + Upkeep step - + Draw step - + First main phase - + Beginning of combat step - + Declare attackers step - + Declare blockers step - + Combat damage step - + End of combat step - + Second main phase - + End of turn step @@ -6578,134 +6629,138 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards + + + + + + grave + + - Move top cards to grave + + + + exile - - Move top cards to exile + + Move top cards to %1 - - Move bottom cards to grave + + Move bottom cards to %1 - - Move bottom cards to exile - - - - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - - + + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters @@ -6713,48 +6768,65 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + + PrintingDisabledInfoWidget - - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + Show Related cards @@ -6803,17 +6875,25 @@ Cockatrice will now reload the card database. - - + + Descending - + Ascending + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu @@ -6952,6 +7032,33 @@ Cockatrice will now reload the card database. A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + QPlatformTheme @@ -7100,37 +7207,37 @@ Cockatrice will now reload the card database. RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -7173,6 +7280,14 @@ Cockatrice will now reload the card database. + + SayMenu + + + S&ay + + + SequenceEdit @@ -7237,53 +7352,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7350,27 +7465,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -7586,59 +7701,123 @@ Please check your shortcut settings! TabArchidekt - - + + + Desc. - - Asc. + + + AND - - - Any Bracket + + + Require ALL selected colors - - Deck name contains... + + + Deck name... - - Owner name contains... + + + Owner... + + + + + + Packages + + + + + + Advanced Filters - Disabled + Bracket: - + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + Search - + + Formats - - Min. # of Cards: - - - - - Page: - - - - + Archidekt: @@ -7646,60 +7825,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info - + Deck - + Filters - + &View - + Card Database - + Printing - - - - - + Visible - - - - - + Floating - + Reset layout - + Deck: %1 @@ -7707,61 +7878,55 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7826,7 +7991,7 @@ Please check your shortcut settings! - + New folder @@ -7907,18 +8072,18 @@ Please enter a name: - + Delete remote decks - + Are you sure you want to delete the selected decks? - + Name of new folder: @@ -7936,12 +8101,12 @@ Please enter a name: - + Error - + Could not open deck at %1 @@ -7990,197 +8155,191 @@ Please enter a name: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + Un&concede - - - + + + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + Selected cards - + &View - - - - + Visible - - - - + Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game - + Are you sure you want to leave this game? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. @@ -9280,142 +9439,152 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens - - - Use tear-off menus, allowing right click menus to persist on screen - - - - - Notifications settings - - - - - Enable notifications in taskbar - - - - - Notify in the taskbar for game events while you are spectating - - - - - Notify in the taskbar when users in your buddy list connect - - - - - Animation settings - - - - - &Tap/untap animation - - - - - Deck editor/storage settings - - - - - Open deck in new tab by default - - - Use visual deck storage in game lobby + Show selection counter during drag selection - Use selection animation for Visual Deck Storage + Show total selection counter + + + + + Use tear-off menus, allowing right click menus to persist on screen - When adding a tag in the visual deck storage to a .txt deck: + Notifications settings + + + + + Enable notifications in taskbar - do nothing + Notify in the taskbar for game events while you are spectating + + + + + Notify in the taskbar when users in your buddy list connect - ask to convert to .cod + Animation settings + + + + + &Tap/untap animation - always convert to .cod + Deck editor/storage settings - Default deck editor type + Open deck in new tab by default - Classic Deck Editor + Use visual deck storage in game lobby - Visual Deck Editor - - - - - Replay settings + Use selection animation for Visual Deck Storage + When adding a tag in the visual deck storage to a .txt deck: + + + + + do nothing + + + + + ask to convert to .cod + + + + + always convert to .cod + + + + + Default deck editor type + + + + + Classic Deck Editor + + + + + Visual Deck Editor + + + + + Replay settings + + + + Buffer time for backwards skip via shortcut: @@ -9479,23 +9648,24 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match + + Exact match + + + + + Includes - Mode: Includes + Include / Exclude + Mode: Includes - - Mode: Include/Exclude - - - - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter @@ -9522,25 +9692,108 @@ Please refrain from engaging in this activity or further actions may be taken ag + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + + + + + cards + + + + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9548,22 +9801,32 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9599,7 +9862,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9607,19 +9870,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9627,27 +9890,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9661,52 +9934,22 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Visual - + Loading database ... - + Clear all filters - - Sort by: - - - - - Filter by: - - - - - Save and load filters - - - - - Filter by exact card name - - - - - Filter by card sub-type - - - - - Filter by set - - - - + Table @@ -9714,56 +9957,64 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9771,17 +10022,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9797,47 +10048,52 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9938,133 +10194,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -10072,72 +10328,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing - + pile view @@ -10172,7 +10428,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor @@ -10253,7 +10509,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays @@ -10405,7 +10661,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Reset Layout @@ -10826,7 +11082,8 @@ Please refrain from engaging in this activity or further actions may be taken ag - Toggle Untap + Toggle Skip Untapping + Toggle Untap @@ -10846,98 +11103,102 @@ Please refrain from engaging in this activity or further actions may be taken ag - Attach Card... + Play Card, Face Down - Unattach Card + Attach Card... - Clone Card + Unattach Card - Create Token... + Clone Card - Create All Related Tokens + Create Token... - Create Another Token + Create All Related Tokens - Set Annotation... + Create Another Token - Select All Cards in Zone + Set Annotation... - Select All Cards in Row + Select All Cards in Zone - Select All Cards in Column + Select All Cards in Row - Reveal Selected Cards to All Players + Select All Cards in Column - - Bottom of Library + Reveal Selected Cards to All Players - - - - Exile + + Bottom of Library + + + + Exile + + + + - + Graveyard - + Hand - - + + Top of Library - - + Battlefield, Face Down @@ -10973,234 +11234,246 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Stack - + Graveyard (Multiple) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) - + + + Exile (Multiple), Face Down + + + + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede - + Roll Dice... - + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/oracle/oracle_en@source.ts b/oracle/oracle_en@source.ts index 53ad27b4d..943b44a97 100644 --- a/oracle/oracle_en@source.ts +++ b/oracle/oracle_en@source.ts @@ -278,7 +278,7 @@ OracleImporter - + Dummy set containing tokens @@ -286,7 +286,7 @@ OracleWizard - + Oracle Importer From ba786a0289a8dbfcaf833d1452c018ff1575956a Mon Sep 17 00:00:00 2001 From: tooomm Date: Wed, 1 Apr 2026 14:54:27 +0200 Subject: [PATCH 287/325] Update dependabot.yml (#6756) --- .github/dependabot.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fc25af67f..88bed3663 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,6 +9,9 @@ updates: - package-ecosystem: "gitsubmodule" # Look for `.gitmodules` in the `root` directory directory: "/" + ignore: + # Ignore updates for vcpkg (Bump to latest tag not working (no SemVer used) & macOS Intel triplet broken with newer releases) + - dependency-name: "vcpkg" # Check for updates once a month schedule: interval: "monthly" From 690a00aa6c0779a8ffe67323bf21f6c793689920 Mon Sep 17 00:00:00 2001 From: SlightlyCircuitous <71394296+SlightlyCircuitous@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:40:42 -0400 Subject: [PATCH 288/325] Add Ubuntu 26.04 "Resolute Racoon" build (#6766) * Create 26.04 Dockerfile * Update desktop-build.yml * Update release_template.md * Add ca-certificates package to build --- .ci/Ubuntu26.04/Dockerfile | 29 +++++++++++++++++++++++++++++ .ci/release_template.md | 1 + .github/workflows/desktop-build.yml | 5 +++++ 3 files changed, 35 insertions(+) create mode 100644 .ci/Ubuntu26.04/Dockerfile diff --git a/.ci/Ubuntu26.04/Dockerfile b/.ci/Ubuntu26.04/Dockerfile new file mode 100644 index 000000000..7b0cd389f --- /dev/null +++ b/.ci/Ubuntu26.04/Dockerfile @@ -0,0 +1,29 @@ +FROM ubuntu:26.04 + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + ccache \ + clang-format \ + cmake \ + file \ + g++ \ + git \ + libgl-dev \ + liblzma-dev \ + libmariadb-dev-compat \ + libprotobuf-dev \ + libqt6multimedia6 \ + libqt6sql6-mysql \ + ninja-build \ + protobuf-compiler \ + qt6-image-formats-plugins \ + qt6-l10n-tools \ + qt6-multimedia-dev \ + qt6-svg-dev \ + qt6-tools-dev \ + qt6-tools-dev-tools \ + qt6-websockets-dev \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* diff --git a/.ci/release_template.md b/.ci/release_template.md index b0924b92a..1ce9f4bf7 100644 --- a/.ci/release_template.md +++ b/.ci/release_template.md @@ -17,6 +17,7 @@ Available pre-compiled binaries for installation: • macOS 13+ Ventura Intel Linux + • Ubuntu 26.04 LTS Resolute RacoonUbuntu 24.04 LTS Noble NumbatUbuntu 22.04 LTS Jammy JellyfishDebian 13 Trixie diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 820044059..7bd84eb21 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -142,11 +142,16 @@ jobs: - distro: Ubuntu version: 22.04 package: DEB + test: skip # Running tests on all distros is superfluous - distro: Ubuntu version: 24.04 package: DEB + - distro: Ubuntu + version: 26.04 + package: DEB + name: ${{matrix.distro}} ${{matrix.version}} needs: configure runs-on: ubuntu-latest From a46ab5cd68dbbead94379778a0b6dca3dfaa89dc Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 4 Apr 2026 02:40:38 -0700 Subject: [PATCH 289/325] [Game] Allow uppercase X in expressions (#6767) --- libcockatrice_utility/libcockatrice/utility/expression.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcockatrice_utility/libcockatrice/utility/expression.cpp b/libcockatrice_utility/libcockatrice/utility/expression.cpp index 3b2e815d1..42073670c 100644 --- a/libcockatrice_utility/libcockatrice/utility/expression.cpp +++ b/libcockatrice_utility/libcockatrice/utility/expression.cpp @@ -20,7 +20,7 @@ peg::parser math(R"( NUMBER <- < '-'? [0-9]+ > NAME <- < [a-z][a-z0-9]* > - VARIABLE <- < [x] > + VARIABLE <- < [xX] > FUNCTION <- NAME '(' EXPRESSION ( [,\n] EXPRESSION )* ')' %whitespace <- [ \t\r]* From 3ec9ae97723252b3d4d7d821968d775a739893af Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Sun, 5 Apr 2026 21:52:46 +0200 Subject: [PATCH 290/325] fix compiling on arch (#6768) * fix compiling on arch * redo all the logging in affected files --- .../interface/widgets/tabs/tab_replays.cpp | 17 ++- cockatrice/src/interface/window_main.cpp | 9 +- .../network/client/remote/remote_client.cpp | 46 +++--- .../protocol/debug_pb_message.cpp | 8 +- servatrice/src/isl_interface.cpp | 51 ++++--- .../src/servatrice_database_interface.cpp | 105 +++++++------- servatrice/src/serversocketinterface.cpp | 131 +++++++++++------- 7 files changed, 219 insertions(+), 148 deletions(-) diff --git a/cockatrice/src/interface/widgets/tabs/tab_replays.cpp b/cockatrice/src/interface/widgets/tabs/tab_replays.cpp index 2db9e83c5..570677ada 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_replays.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_replays.cpp @@ -30,6 +30,8 @@ #include #include +inline Q_LOGGING_CATEGORY(TabReplaysLog, "replays_tab"); + TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User *currentUserInfo) : Tab(_tabSupervisor), client(_client) { @@ -265,9 +267,11 @@ void TabReplays::actOpenLocalReplay() f.close(); GameReplay *replay = new GameReplay; - replay->ParseFromArray(_data.data(), _data.size()); - - emit openReplay(replay); + if (replay->ParseFromArray(_data.data(), _data.size())) { + emit openReplay(replay); + } else { + qCWarning(TabReplaysLog) << "could not parse replay!"; + } } } @@ -379,9 +383,12 @@ void TabReplays::openRemoteReplayFinished(const Response &r) const Response_ReplayDownload &resp = r.GetExtension(Response_ReplayDownload::ext); GameReplay *replay = new GameReplay; - replay->ParseFromString(resp.replay_data()); + if (replay->ParseFromString(resp.replay_data())) { - emit openReplay(replay); + emit openReplay(replay); + } else { + qCWarning(TabReplaysLog) << "could not parse remote replay!"; + } } void TabReplays::actDownload() diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index fbba6c3f5..86e6c1534 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -84,6 +84,7 @@ const QString MainWindow::appName = "Cockatrice"; const QStringList MainWindow::fileNameFilters = QStringList() << QObject::tr("Cockatrice card database (*.xml)") << QObject::tr("All files (*.*)"); +inline Q_LOGGING_CATEGORY(MainWindowLog, "main_window"); /** * Replaces the tab-specific menus that are shown in the menuBar. @@ -277,9 +278,11 @@ void MainWindow::actWatchReplay() file.close(); replay = new GameReplay; - replay->ParseFromArray(buf.data(), buf.size()); - - tabSupervisor->openReplay(replay); + if (replay->ParseFromArray(buf.data(), buf.size())) { + tabSupervisor->openReplay(replay); + } else { + qCWarning(MainWindowLog) << "failed to parse replay!"; + } } void MainWindow::localGameEnded() diff --git a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp index 4b5d3f1b8..3e3ec889d 100644 --- a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp +++ b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp @@ -390,14 +390,18 @@ void RemoteClient::readData() return; ServerMessage newServerMessage; - newServerMessage.ParseFromArray(inputBuffer.data(), messageLength); - - qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage); + bool ok = newServerMessage.ParseFromArray(inputBuffer.data(), messageLength); inputBuffer.remove(0, messageLength); messageInProgress = false; - processProtocolItem(newServerMessage); + if (ok) { + qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage); + + processProtocolItem(newServerMessage); + } else { + qCDebug(RemoteClientLog) << "parsing error!"; + } if (getStatus() == StatusDisconnecting) // use thread-safe getter doDisconnectFromServer(); @@ -408,11 +412,13 @@ void RemoteClient::websocketMessageReceived(const QByteArray &message) { lastDataReceived = timeRunning; ServerMessage newServerMessage; - newServerMessage.ParseFromArray(message.data(), message.length()); + if (newServerMessage.ParseFromArray(message.data(), message.length())) { + qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage); - qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage); - - processProtocolItem(newServerMessage); + processProtocolItem(newServerMessage); + } else { + qCDebug(RemoteClientLog) << "parsing error!"; + } } void RemoteClient::sendCommandContainer(const CommandContainer &cont) @@ -426,19 +432,27 @@ void RemoteClient::sendCommandContainer(const CommandContainer &cont) qCDebug(RemoteClientLog).noquote() << "OUT" << getSafeDebugString(cont); QByteArray buf; + bool ok; if (usingWebSocket) { buf.resize(size); - cont.SerializeToArray(buf.data(), size); - websocket->sendBinaryMessage(buf); + ok = cont.SerializeToArray(buf.data(), size); + if (ok) { + websocket->sendBinaryMessage(buf); + } } else { buf.resize(size + 4); - cont.SerializeToArray(buf.data() + 4, size); - buf.data()[3] = (unsigned char)size; - buf.data()[2] = (unsigned char)(size >> 8); - buf.data()[1] = (unsigned char)(size >> 16); - buf.data()[0] = (unsigned char)(size >> 24); + ok = cont.SerializeToArray(buf.data() + 4, size); + if (ok) { + buf.data()[3] = (unsigned char)size; + buf.data()[2] = (unsigned char)(size >> 8); + buf.data()[1] = (unsigned char)(size >> 16); + buf.data()[0] = (unsigned char)(size >> 24); - socket->write(buf); + socket->write(buf); + } + } + if (!ok) { + qCDebug(RemoteClientLog) << "transmit error!"; } } diff --git a/libcockatrice_protocol/libcockatrice/protocol/debug_pb_message.cpp b/libcockatrice_protocol/libcockatrice/protocol/debug_pb_message.cpp index f0c3448b5..718487c18 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/debug_pb_message.cpp +++ b/libcockatrice_protocol/libcockatrice/protocol/debug_pb_message.cpp @@ -101,6 +101,10 @@ QString getSafeDebugString(const ::google::protobuf::Message &message) #endif // GOOGLE_PROTOBUF_VERSION > 3004000 std::string debug_string; - printer.PrintToString(message, &debug_string); - return QString::number(size) + " bytes " + QString::fromStdString(debug_string); + bool ok = printer.PrintToString(message, &debug_string); + if (ok) { + return QString::number(size) + " bytes " + QString::fromStdString(debug_string); + } else { + return "[could not convert message to string]"; + } } diff --git a/servatrice/src/isl_interface.cpp b/servatrice/src/isl_interface.cpp index bcf71a98a..692a0fdba 100644 --- a/servatrice/src/isl_interface.cpp +++ b/servatrice/src/isl_interface.cpp @@ -3,6 +3,7 @@ #include "main.h" #include "server_logger.h" +#include #include #include #include @@ -21,6 +22,8 @@ #include #include +inline Q_LOGGING_CATEGORY(IslInterfaceLog, "isl_interface"); + void IslInterface::sharedCtor(const QSslCertificate &cert, const QSslKey &privateKey) { socket = new QSslSocket(this); @@ -113,10 +116,11 @@ void IslInterface::initServer() socket->startServerEncryption(); if (!socket->waitForEncrypted(5000)) { QList sslErrors(socket->sslHandshakeErrors()); - if (sslErrors.isEmpty()) - qDebug() << "[ISL] SSL handshake timeout, terminating connection"; - else - qDebug() << "[ISL] SSL errors:" << sslErrors; + if (sslErrors.isEmpty()) { + qCDebug(IslInterfaceLog) << "SSL handshake timeout, terminating connection"; + } else { + qCWarning(IslInterfaceLog) << "SSL errors:" << sslErrors; + } deleteLater(); return; } @@ -157,7 +161,7 @@ void IslInterface::initServer() server->islLock.lockForWrite(); if (server->islConnectionExists(serverId)) { - qDebug() << "[ISL] Duplicate connection to #" << serverId << "terminating connection"; + qCDebug(IslInterfaceLog) << "Duplicate connection to #" << serverId << "terminating connection"; deleteLater(); } else { transmitMessage(message); @@ -180,27 +184,28 @@ void IslInterface::initClient() expectedErrors.append(QSslError(QSslError::SelfSignedCertificate, peerCert)); socket->ignoreSslErrors(expectedErrors); - qDebug() << "[ISL] Connecting to #" << serverId << ":" << peerAddress << ":" << peerPort; + qCDebug(IslInterfaceLog) << "Connecting to #" << serverId << ":" << peerAddress << ":" << peerPort; socket->connectToHostEncrypted(peerAddress, peerPort, peerHostName); if (!socket->waitForConnected(5000)) { - qDebug() << "[ISL] Socket error:" << socket->errorString(); + qCDebug(IslInterfaceLog) << "Socket error:" << socket->errorString(); deleteLater(); return; } if (!socket->waitForEncrypted(5000)) { QList sslErrors(socket->sslHandshakeErrors()); - if (sslErrors.isEmpty()) - qDebug() << "[ISL] SSL handshake timeout, terminating connection"; - else - qDebug() << "[ISL] SSL errors:" << sslErrors; + if (sslErrors.isEmpty()) { + qCDebug(IslInterfaceLog) << "SSL handshake timeout, terminating connection"; + } else { + qCWarning(IslInterfaceLog) << "SSL errors:" << sslErrors; + } deleteLater(); return; } server->islLock.lockForWrite(); if (server->islConnectionExists(serverId)) { - qDebug() << "[ISL] Duplicate connection to #" << serverId << "terminating connection"; + qCDebug(IslInterfaceLog) << "Duplicate connection to #" << serverId << "terminating connection"; deleteLater(); return; } @@ -242,17 +247,21 @@ void IslInterface::readClient() return; IslMessage newMessage; - newMessage.ParseFromArray(inputBuffer.data(), messageLength); + bool ok = newMessage.ParseFromArray(inputBuffer.data(), messageLength); inputBuffer.remove(0, messageLength); messageInProgress = false; - processMessage(newMessage); + if (ok) { + processMessage(newMessage); + } else { + qCWarning(IslInterfaceLog) << "parsing error!"; + } } while (!inputBuffer.isEmpty()); } void IslInterface::catchSocketError(QAbstractSocket::SocketError socketError) { - qDebug() << "[ISL] Socket error:" << socketError; + qCWarning(IslInterfaceLog) << "Socket error:" << socketError; server->islLock.lockForWrite(); server->removeIslInterface(serverId); @@ -270,7 +279,10 @@ void IslInterface::transmitMessage(const IslMessage &item) unsigned int size = static_cast(item.ByteSize()); #endif buf.resize(size + 4); - item.SerializeToArray(buf.data() + 4, size); + if (!item.SerializeToArray(buf.data() + 4, size)) { + qCWarning(IslInterfaceLog) << "transmit error!"; + return; + } buf.data()[3] = (unsigned char)size; buf.data()[2] = (unsigned char)(size >> 8); buf.data()[1] = (unsigned char)(size >> 16); @@ -368,7 +380,7 @@ void IslInterface::processSessionEvent(const SessionEvent &event, qint64 session QReadLocker clientsLocker(&server->clientsLock); Server_AbstractUserInterface *client = server->getUsersBySessionId().value(sessionId); if (!client) { - qDebug() << "IslInterface::processSessionEvent: session id" << sessionId << "not found"; + qCDebug(IslInterfaceLog) << "IslInterface::processSessionEvent: session id" << sessionId << "not found"; break; } const Event_GameJoined &gameJoined = event.GetExtension(Event_GameJoined::ext); @@ -382,7 +394,8 @@ void IslInterface::processSessionEvent(const SessionEvent &event, qint64 session QReadLocker clientsLocker(&server->clientsLock); Server_AbstractUserInterface *client = server->getUsersBySessionId().value(sessionId); if (!client) { - qDebug() << "IslInterface::processSessionEvent: session id" << sessionId << "not found"; + qCWarning(IslInterfaceLog) + << "IslInterface::processSessionEvent: session id" << sessionId << "not found"; break; } @@ -430,7 +443,7 @@ void IslInterface::processRoomCommand(const CommandContainer &cont, qint64 sessi void IslInterface::processMessage(const IslMessage &item) { - qDebug() << getSafeDebugString(item); + qCDebug(IslInterfaceLog) << getSafeDebugString(item); switch (item.message_type()) { case IslMessage::ROOM_COMMAND_CONTAINER: { diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index fa9b14f31..bce3542e8 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -7,12 +7,15 @@ #include #include #include +#include #include #include #include #include #include +inline Q_LOGGING_CATEGORY(DatabaseInterfaceLog, "database_interface"); + Servatrice_DatabaseInterface::Servatrice_DatabaseInterface(int _instanceId, Servatrice *_server) : instanceId(_instanceId), sqlDatabase(QSqlDatabase()), server(_server) { @@ -56,17 +59,16 @@ bool Servatrice_DatabaseInterface::openDatabase() sqlDatabase.close(); const QString poolStr = instanceId == -1 ? QString("main") : QString("pool %1").arg(instanceId); - qDebug().noquote() << QString("[%1] Opening database...").arg(poolStr); + qCDebug(DatabaseInterfaceLog).noquote() << poolStr << "Opening database..."; if (!sqlDatabase.open()) { - qCritical() << QString("[%1] Error opening database: %2").arg(poolStr).arg(sqlDatabase.lastError().text()); + qCCritical(DatabaseInterfaceLog) << poolStr << "Error opening database:" << sqlDatabase.lastError().text(); return false; } QSqlQuery *versionQuery = prepareQuery("select version from {prefix}_schema_version limit 1"); if (!execSqlQuery(versionQuery)) { - qCritical() << QString("[%1] Error opening database: unable to load database schema version (hint: ensure the " - "cockatrice_schema_version exists)") - .arg(poolStr); + qCCritical(DatabaseInterfaceLog) << poolStr << "Error opening database: unable to load database schema version" + << "(hint: ensure the cockatrice_schema_version exists)"; return false; } @@ -74,24 +76,21 @@ bool Servatrice_DatabaseInterface::openDatabase() const int dbversion = versionQuery->value(0).toInt(); const int expectedversion = DATABASE_SCHEMA_VERSION; if (dbversion < expectedversion) { - qCritical() << QString("[%1] Error opening database: the database schema version is too old, you need to " - "run the migrations to update it from version %2 to version %3") - .arg(poolStr) - .arg(dbversion) - .arg(expectedversion); + qCCritical(DatabaseInterfaceLog) << poolStr + << "Error opening database: the database schema version is too old, you " + "need to run the migrations to update it from version" + << dbversion << "to version" << expectedversion; return false; } else if (dbversion > expectedversion) { - qCritical() << QString("[%1] Error opening database: the database schema version %2 is too new, you need " - "to update servatrice (this servatrice actually uses version %3)") - .arg(poolStr) - .arg(dbversion) - .arg(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 { - qCritical() << QString("[%1] Error opening database: unable to load database schema version (hint: ensure the " - "cockatrice_schema_version contains a single record)") - .arg(poolStr); + qCCritical(DatabaseInterfaceLog) << poolStr + << "Error opening database: unable to load database schema version (hint: " + "ensure the cockatrice_schema_version contains a single record)"; return false; } @@ -114,9 +113,7 @@ bool Servatrice_DatabaseInterface::checkSql() if (query.lastError().isValid()) { const auto &poolStr = instanceId == -1 ? QString("main") : QString("pool %1").arg(instanceId); - qCritical() << QString("[%1] Error executing query: %2, resetting connection") - .arg(poolStr) - .arg(query.lastError().text()); + qCCritical(DatabaseInterfaceLog) << poolStr << "Error executing query:" << query.lastError().text(); sqlDatabase.close(); return openDatabase(); @@ -145,7 +142,7 @@ bool Servatrice_DatabaseInterface::execSqlQuery(QSqlQuery *query) if (query->exec()) return true; const QString poolStr = instanceId == -1 ? QString("main") : QString("pool %1").arg(instanceId); - qCritical() << QString("[%1] Error executing query: %2").arg(poolStr).arg(query->lastError().text()); + qCCritical(DatabaseInterfaceLog) << poolStr << "Error executing query:" << query->lastError().text(); sqlDatabase.close(); openDatabase(); return false; @@ -252,7 +249,8 @@ bool Servatrice_DatabaseInterface::registerUser(const QString &userName, query->bindValue(":token", token); if (!execSqlQuery(query)) { - qDebug() << "Failed to insert user: " << query->lastError() << " sql: " << query->lastQuery(); + qCWarning(DatabaseInterfaceLog) << "Failed to insert user: " << query->lastError() + << " sql: " << query->lastQuery(); return false; } @@ -270,8 +268,8 @@ bool Servatrice_DatabaseInterface::activateUser(const QString &userName, const Q activateQuery->bindValue(":username", userName); activateQuery->bindValue(":token", token); if (!execSqlQuery(activateQuery)) { - qDebug() << "Account activation failed: SQL error." << activateQuery->lastError() - << " sql: " << activateQuery->lastQuery(); + qCWarning(DatabaseInterfaceLog) << "Account activation failed: SQL error." << activateQuery->lastError() + << " sql: " << activateQuery->lastQuery(); return false; } @@ -284,7 +282,8 @@ bool Servatrice_DatabaseInterface::activateUser(const QString &userName, const Q query->bindValue(":userName", userName); if (!execSqlQuery(query)) { - qDebug() << "Failed to activate user: " << query->lastError() << " sql: " << query->lastQuery(); + qCWarning(DatabaseInterfaceLog) + << "Failed to activate user: " << query->lastError() << " sql: " << query->lastQuery(); return false; } @@ -326,7 +325,7 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot prepareQuery("select password_sha512, active from {prefix}_users where name = :name"); passwordQuery->bindValue(":name", user); if (!execSqlQuery(passwordQuery)) { - qDebug("Login denied: SQL error"); + qCWarning(DatabaseInterfaceLog) << "Login denied: SQL error"; return NotLoggedIn; } @@ -334,7 +333,7 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot const QString correctPasswordSha512 = passwordQuery->value(0).toString(); const bool userIsActive = passwordQuery->value(1).toBool(); if (!userIsActive) { - qDebug("Login denied: user not active"); + qCWarning(DatabaseInterfaceLog) << "Login denied: user not active"; return UserIsInactive; } QString hashedPassword; @@ -344,14 +343,14 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot hashedPassword = password; } if (correctPasswordSha512 == hashedPassword) { - qDebug("Login accepted: password right"); + qCDebug(DatabaseInterfaceLog) << "Login accepted: password right"; return PasswordRight; } else { - qDebug("Login denied: password wrong"); + qCDebug(DatabaseInterfaceLog) << "Login denied: password wrong"; return NotLoggedIn; } } else { - qDebug("Login accepted: unknown user"); + qCDebug(DatabaseInterfaceLog) << "Login accepted: unknown user"; return UnknownUser; } } @@ -369,7 +368,7 @@ bool Servatrice_DatabaseInterface::checkUserIsBanned(const QString &ipAddress, return false; if (!checkSql()) { - qDebug("Failed to check if user is banned. Database invalid."); + qCWarning(DatabaseInterfaceLog) << "Failed to check if user is banned. Database invalid."; return false; } @@ -400,7 +399,7 @@ bool Servatrice_DatabaseInterface::checkUserIsIdBanned(const QString &clientId, idBanQuery->bindValue(":id", clientId); idBanQuery->bindValue(":id2", clientId); if (!execSqlQuery(idBanQuery)) { - qDebug() << "Id ban check failed: SQL error." << idBanQuery->lastError(); + qCWarning(DatabaseInterfaceLog) << "Id ban check failed: SQL error." << idBanQuery->lastError(); return false; } @@ -410,7 +409,7 @@ bool Servatrice_DatabaseInterface::checkUserIsIdBanned(const QString &clientId, if ((secondsLeft > 0) || permanentBan) { banReason = idBanQuery->value(2).toString(); banSecondsRemaining = permanentBan ? 0 : secondsLeft; - qDebug() << "User is banned by client id" << clientId; + qCDebug(DatabaseInterfaceLog) << "User is banned by client id" << clientId; return true; } } @@ -428,7 +427,7 @@ bool Servatrice_DatabaseInterface::checkUserIsNameBanned(const QString &userName nameBanQuery->bindValue(":name1", userName); nameBanQuery->bindValue(":name2", userName); if (!execSqlQuery(nameBanQuery)) { - qDebug() << "Name ban check failed: SQL error" << nameBanQuery->lastError(); + qCWarning(DatabaseInterfaceLog) << "Name ban check failed: SQL error" << nameBanQuery->lastError(); return false; } @@ -438,7 +437,7 @@ bool Servatrice_DatabaseInterface::checkUserIsNameBanned(const QString &userName if ((secondsLeft > 0) || permanentBan) { banReason = nameBanQuery->value(2).toString(); banSecondsRemaining = permanentBan ? 0 : secondsLeft; - qDebug() << "Username" << userName << "is banned by name"; + qCDebug(DatabaseInterfaceLog) << "Username" << userName << "is banned by name"; return true; } } @@ -464,7 +463,7 @@ bool Servatrice_DatabaseInterface::checkUserIsIpBanned(const QString &ipAddress, ipBanQuery->bindValue(":address", ipAddress); ipBanQuery->bindValue(":address2", ipAddress); if (!execSqlQuery(ipBanQuery)) { - qDebug() << "IP ban check failed: SQL error." << ipBanQuery->lastError(); + qCWarning(DatabaseInterfaceLog) << "IP ban check failed: SQL error." << ipBanQuery->lastError(); return false; } @@ -474,7 +473,7 @@ bool Servatrice_DatabaseInterface::checkUserIsIpBanned(const QString &ipAddress, if ((secondsLeft > 0) || permanentBan) { banReason = ipBanQuery->value(2).toString(); banSecondsRemaining = permanentBan ? 0 : secondsLeft; - qDebug() << "User is banned by address" << ipAddress; + qCDebug(DatabaseInterfaceLog) << "User is banned by address" << ipAddress; return true; } } @@ -865,12 +864,17 @@ void Servatrice_DatabaseInterface::storeGameInformation(const QString &roomName, const unsigned int size = static_cast(replayList[i]->ByteSize()); #endif blob.resize(size); - replayList[i]->SerializeToArray(blob.data(), size); + qulonglong replayId = replayList[i]->replay_id(); + if (replayList[i]->SerializeToArray(blob.data(), size)) { - replayIds.append(QVariant((qulonglong)replayList[i]->replay_id())); - replayGameIds.append(gameInfo.game_id()); - replayDurations.append(replayList[i]->duration_seconds()); - replayBlobs.append(blob); + replayIds.append(QVariant(replayId)); + replayGameIds.append(gameInfo.game_id()); + replayDurations.append(replayList[i]->duration_seconds()); + replayBlobs.append(blob); + } else { + qCWarning(DatabaseInterfaceLog) + << "failed to serialise replay, id:" << replayId << "game:" << gameInfo.game_id(); + } } { @@ -1017,7 +1021,7 @@ bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user, passwordQuery->bindValue(":name", user); if (!execSqlQuery(passwordQuery)) { - qDebug("Change password denied: SQL error"); + qCWarning(DatabaseInterfaceLog) << "Change password denied: SQL error"; return false; } @@ -1084,7 +1088,7 @@ void Servatrice_DatabaseInterface::updateUsersLastLoginData(const QString &userN QSqlQuery *query = prepareQuery("select id from {prefix}_users where name = :user_name"); query->bindValue(":user_name", userName); if (!execSqlQuery(query)) { - qDebug("Failed to locate user id when updating users last login data: SQL Error"); + qCWarning(DatabaseInterfaceLog) << "Failed to locate user id when updating users last login data: SQL Error"; return; } @@ -1133,7 +1137,7 @@ QList Servatrice_DatabaseInterface::getUserBanHistory(const QStr query->bindValue(":user_name", userName); if (!execSqlQuery(query)) { - qDebug("Failed to collect ban history information: SQL Error"); + qCWarning(DatabaseInterfaceLog) << "Failed to collect ban history information: SQL Error"; return results; } @@ -1168,7 +1172,7 @@ bool Servatrice_DatabaseInterface::addWarning(const QString userName, query->bindValue(":warn_reason", warningReason); query->bindValue(":client_id", clientID); if (!execSqlQuery(query)) { - qDebug("Failed to collect create warning history information: SQL Error"); + qCWarning(DatabaseInterfaceLog) << "Failed to collect create warning history information: SQL Error"; return false; } @@ -1189,7 +1193,7 @@ QList Servatrice_DatabaseInterface::getUserWarnHistory(const query->bindValue(":user_id", userID); if (!execSqlQuery(query)) { - qDebug("Failed to collect warning history information: SQL Error"); + qCWarning(DatabaseInterfaceLog) << "Failed to collect warning history information: SQL Error"; return results; } @@ -1296,7 +1300,7 @@ QList Servatrice_DatabaseInterface::getMessageLogHistory } if (!execSqlQuery(query)) { - qDebug("Failed to collect log history information: SQL Error"); + qCWarning(DatabaseInterfaceLog) << "Failed to collect log history information: SQL Error"; return results; } @@ -1324,7 +1328,8 @@ int Servatrice_DatabaseInterface::checkNumberOfUserAccounts(const QString &email query->bindValue(":user_email", email); if (!execSqlQuery(query)) { - qDebug("Failed to identify the number of users accounts for users email address: SQL Error"); + qCWarning(DatabaseInterfaceLog) + << "Failed to identify the number of users accounts for users email address: SQL Error"; return 0; } diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index 619d36d3a..41e61ddec 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -83,6 +84,10 @@ #include #include +inline Q_LOGGING_CATEGORY(AbstractServerSocketInterfaceLog, "abstract_server_socket_interface"); +inline Q_LOGGING_CATEGORY(TcpServerSocketInterfaceLog, "tcp_server_socket_interface"); +inline Q_LOGGING_CATEGORY(WebsocketServerSocketInterfaceLog, "websocket_server_socket_interface"); + static const int protocolVersion = 14; AbstractServerSocketInterface::AbstractServerSocketInterface(Servatrice *_server, @@ -130,7 +135,7 @@ bool AbstractServerSocketInterface::initSession() void AbstractServerSocketInterface::catchSocketError(QAbstractSocket::SocketError socketError) { - qDebug() << "Socket error:" << socketError; + qCWarning(AbstractServerSocketInterfaceLog) << "Socket error:" << socketError; prepareDestroy(); } @@ -1100,7 +1105,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdBanFromServer(const Com clientIdQuery->bindValue(":client_id", nameFromStdString(cmd.clientid())); sqlInterface->execSqlQuery(clientIdQuery); if (!sqlInterface->execSqlQuery(clientIdQuery)) { - qDebug("ClientID username ban lookup failed: SQL Error"); + qCWarning(AbstractServerSocketInterfaceLog) << "ClientID username ban lookup failed: SQL Error"; } else { while (clientIdQuery->next()) { userName = clientIdQuery->value(0).toString(); @@ -1152,7 +1157,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C { QString userName = nameFromStdString(cmd.user_name()); QString clientId = nameFromStdString(cmd.clientid()); - qDebug() << "Got register command for user:" << userName; + qCDebug(AbstractServerSocketInterfaceLog) << "Got register command for user:" << userName; bool registrationEnabled = settingsCache->value("registration/enabled", false).toBool(); if (!registrationEnabled) { @@ -1289,7 +1294,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C country, !requireEmailActivation); if (regSucceeded) { - qDebug() << "Accepted register command for user:" << userName; + qCDebug(AbstractServerSocketInterfaceLog) << "Accepted register command for user:" << userName; if (requireEmailActivation) { QSqlQuery *query = sqlInterface->prepareQuery("insert into {prefix}_activation_emails (name) values(:name)"); @@ -1337,7 +1342,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdActivateAccount(const C clientId = "UNKNOWN"; if (sqlInterface->activateUser(userName, token)) { - qDebug() << "Accepted activation for user" << userName; + qCDebug(AbstractServerSocketInterfaceLog) << "Accepted activation for user" << userName; if (servatrice->getEnableRegistrationAudit()) sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), @@ -1345,7 +1350,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdActivateAccount(const C return Response::RespActivationAccepted; } else { - qDebug() << "Failed activation for user" << userName; + qCDebug(AbstractServerSocketInterfaceLog) << "Failed activation for user" << userName; if (servatrice->getEnableRegistrationAudit()) sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), @@ -1493,7 +1498,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdForgotPasswordRequest(c const QString userName = nameFromStdString(cmd.user_name()); const QString clientId = nameFromStdString(cmd.clientid()); - qDebug() << "Received reset password request from user:" << userName; + qCDebug(AbstractServerSocketInterfaceLog) << "Received reset password request from user:" << userName; if (!servatrice->getEnableForgotPassword()) { if (servatrice->getEnableForgotPasswordAudit()) @@ -1575,7 +1580,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdForgotPasswordReset(con Q_UNUSED(rc); QString userName = nameFromStdString(cmd.user_name()); QString clientId = nameFromStdString(cmd.clientid()); - qDebug() << "Received reset password reset from user:" << userName; + qCDebug(AbstractServerSocketInterfaceLog) << "Received reset password reset from user:" << userName; if (!sqlInterface->doesForgotPasswordExist(userName)) { if (servatrice->getEnableForgotPasswordAudit()) @@ -1626,7 +1631,7 @@ AbstractServerSocketInterface::cmdForgotPasswordChallenge(const Command_ForgotPa const QString userName = nameFromStdString(cmd.user_name()); const QString clientId = nameFromStdString(cmd.clientid()); - qDebug() << "Received reset password challenge from user:" << userName; + qCDebug(AbstractServerSocketInterfaceLog) << "Received reset password challenge from user:" << userName; if (!servatrice->getEnableForgotPasswordChallenge()) { if (servatrice->getEnableForgotPasswordAudit()) { @@ -1971,13 +1976,16 @@ void TcpServerSocketInterface::flushOutputQueue() unsigned int size = static_cast(item.ByteSize()); #endif buf.resize(size + 4); - item.SerializeToArray(buf.data() + 4, size); - buf.data()[3] = (unsigned char)size; - buf.data()[2] = (unsigned char)(size >> 8); - buf.data()[1] = (unsigned char)(size >> 16); - buf.data()[0] = (unsigned char)(size >> 24); - // In case socket->write() calls catchSocketError(), the mutex must not be locked during this call. - writeToSocket(buf); + if (item.SerializeToArray(buf.data() + 4, size)) { + buf.data()[3] = (unsigned char)size; + buf.data()[2] = (unsigned char)(size >> 8); + buf.data()[1] = (unsigned char)(size >> 16); + buf.data()[0] = (unsigned char)(size >> 24); + // In case socket->write() calls catchSocketError(), the mutex must not be locked during this call. + writeToSocket(buf); + } else { + qCWarning(TcpServerSocketInterfaceLog) << "serialisation error!"; + } totalBytes += size + 4; locker.relock(); @@ -2010,41 +2018,48 @@ void TcpServerSocketInterface::readClient() return; CommandContainer newCommandContainer; + bool ok; try { - newCommandContainer.ParseFromArray(inputBuffer.data(), messageLength); + ok = newCommandContainer.ParseFromArray(inputBuffer.data(), messageLength); } catch (std::exception &e) { - qDebug() << "Caught std::exception in" << __FILE__ << __LINE__ << + qCWarning(TcpServerSocketInterfaceLog) << "Caught std::exception in" << __FILE__ << __LINE__ << #ifdef _MSC_VER // Visual Studio - __FUNCTION__; + __FUNCTION__ #else - __PRETTY_FUNCTION__; + __PRETTY_FUNCTION__ #endif - qDebug() << "Exception:" << e.what(); - qDebug() << "Message coming from:" << getAddress(); - qDebug() << "Message length:" << messageLength; - qDebug() << "Message content:" << inputBuffer.toHex(); + << Qt::endl + << "Exception:" << e.what() << Qt::endl + << "Message coming from:" << getAddress() << Qt::endl + << "Message length:" << messageLength << Qt::endl + << "Message content:" << inputBuffer.toHex(); } catch (...) { - qDebug() << "Unhandled exception in" << __FILE__ << __LINE__ << + qCWarning(TcpServerSocketInterfaceLog) << "Unhandled exception in" << __FILE__ << __LINE__ << #ifdef _MSC_VER // Visual Studio - __FUNCTION__; + __FUNCTION__ #else - __PRETTY_FUNCTION__; + __PRETTY_FUNCTION__ #endif - qDebug() << "Message coming from:" << getAddress(); + << Qt::endl + << "Message coming from:" << getAddress(); } - inputBuffer.remove(0, messageLength); messageInProgress = false; - // dirty hack to make v13 client display the correct error message - if (handshakeStarted) - processCommandContainer(newCommandContainer); - else if (!newCommandContainer.has_cmd_id()) { - handshakeStarted = true; - if (!initTcpSession()) - prepareDestroy(); + if (ok) { + // dirty hack to make v13 client display the correct error message + if (handshakeStarted) + processCommandContainer(newCommandContainer); + else if (!newCommandContainer.has_cmd_id()) { + handshakeStarted = true; + if (!initTcpSession()) + prepareDestroy(); + } + // end of hack + } else { + qCWarning(TcpServerSocketInterfaceLog) << "parsing error!"; } - // end of hack + } while (!inputBuffer.isEmpty()); } @@ -2172,9 +2187,12 @@ void WebsocketServerSocketInterface::flushOutputQueue() unsigned int size = static_cast(item.ByteSize()); #endif buf.resize(size); - item.SerializeToArray(buf.data(), size); - // In case socket->write() calls catchSocketError(), the mutex must not be locked during this call. - writeToSocket(buf); + if (item.SerializeToArray(buf.data(), size)) { + // In case socket->write() calls catchSocketError(), the mutex must not be locked during this call. + writeToSocket(buf); + } else { + qCWarning(TcpServerSocketInterfaceLog) << "serialisation error!"; + } totalBytes += size; locker.relock(); @@ -2190,30 +2208,37 @@ void WebsocketServerSocketInterface::binaryMessageReceived(const QByteArray &mes servatrice->incRxBytes(message.size()); CommandContainer newCommandContainer; + bool ok; try { - newCommandContainer.ParseFromArray(message.data(), message.size()); + ok = newCommandContainer.ParseFromArray(message.data(), message.size()); } catch (std::exception &e) { - qDebug() << "Caught std::exception in" << __FILE__ << __LINE__ << + qCWarning(WebsocketServerSocketInterfaceLog) << "Caught std::exception in" << __FILE__ << __LINE__ << #ifdef _MSC_VER // Visual Studio - __FUNCTION__; + __FUNCTION__ #else - __PRETTY_FUNCTION__; + __PRETTY_FUNCTION__ #endif - qDebug() << "Exception:" << e.what(); - qDebug() << "Message coming from:" << getAddress(); - qDebug() << "Message length:" << message.size(); - qDebug() << "Message content:" << message.toHex(); + << Qt::endl + << "Exception:" << e.what() << Qt::endl + << "Message coming from:" << getAddress() << Qt::endl + << "Message length:" << message.size() << Qt::endl + << "Message content:" << message.toHex(); } catch (...) { - qDebug() << "Unhandled exception in" << __FILE__ << __LINE__ << + qCWarning(WebsocketServerSocketInterfaceLog) << "Unhandled exception in" << __FILE__ << __LINE__ << #ifdef _MSC_VER // Visual Studio - __FUNCTION__; + __FUNCTION__ #else - __PRETTY_FUNCTION__; + __PRETTY_FUNCTION__ #endif - qDebug() << "Message coming from:" << getAddress(); + << Qt::endl + << "Message coming from:" << getAddress(); } - processCommandContainer(newCommandContainer); + if (ok) { + processCommandContainer(newCommandContainer); + } else { + qCWarning(WebsocketServerSocketInterfaceLog) << "parsing error!"; + } } bool AbstractServerSocketInterface::isPasswordLongEnough(const int passwordLength) From dbed6890dac7b308ef228362e63cb67413c14fcd Mon Sep 17 00:00:00 2001 From: Bruno Alexandre Rosa <1791393+brunoalr@users.noreply.github.com> Date: Tue, 7 Apr 2026 02:36:45 -0300 Subject: [PATCH 291/325] feat: support expressions when setting card counters (#6753) * feat: enable expressions in card counters * fix includes * fix multiple selection * cleanup useless conversions * const ref where possible * do not use const, be consistent with local patterns in the file --- cockatrice/src/game/player/player_actions.cpp | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index ca0967636..a44c28d40 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -3,6 +3,7 @@ #include "../../interface/widgets/tabs/tab_game.h" #include "../../interface/widgets/utility/get_text_with_max.h" #include "../board/card_item.h" +#include "../client/settings/card_counter_settings.h" #include "../dialogs/dlg_move_top_cards_until.h" #include "../dialogs/dlg_roll_dice.h" #include "../zones/hand_zone.h" @@ -27,6 +28,7 @@ #include #include #include +#include #include #include @@ -1572,23 +1574,34 @@ void PlayerActions::actCardCounterTrigger() break; } case 11: { // set counter with dialog - bool ok; player->setDialogSemaphore(true); - int oldValue = 0; - if (player->getGameScene()->selectedItems().size() == 1) { - auto *card = static_cast(player->getGameScene()->selectedItems().first()); - oldValue = card->getCounters().value(counterId, 0); + // If a single card is selected, we show the old value in the dialog. Otherwise, we show "x" + QList sel = player->getGameScene()->selectedItems(); + QString oldValueForDlg = "x"; + if (sel.size() == 1) { + auto *card = dynamic_cast(sel.first()); + oldValueForDlg = QString::number(card->getCounters().value(counterId, 0)); } - int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Set counters"), tr("Number:"), oldValue, - 0, MAX_COUNTERS_ON_CARD, 1, &ok); + + auto &cardCounterSettings = SettingsCache::instance().cardCounters(); + QString counterName = cardCounterSettings.displayName(counterId); + + AbstractCounterDialog dialog(counterName, oldValueForDlg, player->getGame()->getTab()); + int ok = dialog.exec(); + player->setDialogSemaphore(false); if (player->clearCardsToDelete() || !ok) { return; } - for (const auto &item : player->getGameScene()->selectedItems()) { - auto *card = static_cast(item); + for (const auto &item : sel) { + auto *card = dynamic_cast(item); + + int oldValue = card->getCounters().value(counterId, 0); + Expression exp(oldValue); + int number = static_cast(exp.parse(dialog.textValue())); + auto *cmd = new Command_SetCardCounter; cmd->set_zone(card->getZone()->getName().toStdString()); cmd->set_card_id(card->getId()); From 335022c4aa3ebda39eb0f42fbfc3728cc2b831aa Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:02:00 +0200 Subject: [PATCH 292/325] Include themes (#6781) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Properly reset default theme. Took 9 minutes * Descend in preference on windows. Took 9 minutes * Also reset style for custom themes. Took 3 minutes * Try things Took 9 minutes * Add modern windows style. Took 8 minutes * Update theme_manager.cpp --------- Co-authored-by: Lukas Brübach --- cockatrice/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 1ca3c77c2..12733afe6 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -564,6 +564,9 @@ if(WIN32) PATTERN "styles/qopensslbackend.dll" PATTERN "styles/qschannelbackend.dll" PATTERN "styles/qwindowsvistastyle.dll" + PATTERN "styles/qwindows11style.dll" + PATTERN "styles/qmodernwindowsstyle.dll" + PATTERN "styles/qmodernwindowsstyled.dll" PATTERN "tls/qcertonlybackend.dll" PATTERN "tls/qopensslbackend.dll" PATTERN "tls/qschannelbackend.dll" From 635fae101d2d5c97a52d774b90ffa6ffebd6f244 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:57:03 +0200 Subject: [PATCH 293/325] Use palette colors for resizable panel stylesheets. (#6782) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took 7 minutes Took 5 seconds Co-authored-by: Lukas Brübach --- .../deck_analytics/resizable_panel.cpp | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp b/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp index a0c971a75..f7bb5ac35 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp @@ -20,7 +20,6 @@ ResizablePanel::ResizablePanel(const QString &_typeId, AbstractAnalyticsPanelWid frame = new QFrame(this); frame->setFrameShape(QFrame::Box); frame->setLineWidth(2); - frame->setStyleSheet("border: none;"); auto *frameLayout = new QVBoxLayout(frame); frameLayout->setContentsMargins(0, 0, 0, 0); @@ -30,15 +29,13 @@ ResizablePanel::ResizablePanel(const QString &_typeId, AbstractAnalyticsPanelWid frameLayout->addWidget(analyticsPanel); dropIndicator = new QFrame(frame); - dropIndicator->setStyleSheet("background-color: #3daee9;"); dropIndicator->setFixedHeight(3); dropIndicator->hide(); // hidden by default dropIndicator->raise(); // make sure it's above children selectionOverlay = new QFrame(frame); - selectionOverlay->setStyleSheet("background-color: rgba(61,174,233,50);"); // semi-transparent blue - selectionOverlay->hide(); // hidden by default - selectionOverlay->raise(); // make sure it is above children + selectionOverlay->hide(); // hidden by default + selectionOverlay->raise(); // make sure it is above children selectionOverlay->setAttribute(Qt::WA_TransparentForMouseEvents); // Bottom bar with drag button and resize handle @@ -51,24 +48,41 @@ ResizablePanel::ResizablePanel(const QString &_typeId, AbstractAnalyticsPanelWid dragButton = new QPushButton("☰", bottomBar); dragButton->setFixedSize(40, 8); dragButton->setCursor(Qt::OpenHandCursor); - dragButton->setStyleSheet("QPushButton { " - "background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #4a4a4a, stop:1 #3a3a3a); " - "border: none; color: #888; font-size: 10px; }" - "QPushButton:hover { background: #5a5a5a; }"); bottomLayout->addWidget(dragButton); // Resize handle fills the rest resizeHandle = new QWidget(bottomBar); resizeHandle->setFixedHeight(8); resizeHandle->setCursor(Qt::SizeVerCursor); - resizeHandle->setStyleSheet("background: qlineargradient(x1:0, y1:0, x2:0, y2:1, " - "stop:0 #3a3a3a, stop:1 #2a2a2a);"); bottomLayout->addWidget(resizeHandle, 1); frameLayout->addWidget(bottomBar); mainLayout->addWidget(frame); + const QPalette &pal = QApplication::palette(); + QColor mid = pal.color(QPalette::Mid); + QColor dark = pal.color(QPalette::Dark); + QColor midLight = pal.color(QPalette::Midlight); + QColor shadow = pal.color(QPalette::Shadow); + QColor placeholderText = pal.color(QPalette::PlaceholderText); + + frame->setStyleSheet("QFrame { border: none; }"); + + dropIndicator->setStyleSheet("QFrame { background-color: #3daee9; }"); + + selectionOverlay->setStyleSheet("QFrame { background-color: rgba(61,174,233,50); }"); + + dragButton->setStyleSheet(QString("QPushButton { " + "background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 %1, stop:1 %2); " + "border: none; color: %3; font-size: 10px; }" + "QPushButton:hover { background: %4; }") + .arg(mid.name(), dark.name(), placeholderText.name(), midLight.name())); + + resizeHandle->setStyleSheet(QString("QWidget { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, " + "stop:0 %1, stop:1 %2); }") + .arg(dark.name(), shadow.name())); + // Set size policy setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); From d7b31f2f9d87ba8e9cfa563cd95229a922db1567 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:58:42 +0200 Subject: [PATCH 294/325] Set OracleWizard style to "Modern" instead of "Aero" (#6778) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use fusions own palette. Took 6 minutes * Start from default palette always. Took 4 minutes * Add modern style. Took 24 seconds * Scope this fix to Windows. Took 4 minutes --------- Co-authored-by: Lukas Brübach --- cockatrice/src/interface/theme_manager.cpp | 14 ++++++++------ oracle/src/oraclewizard.cpp | 4 ++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index 1dc61fdb7..5809ce350 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -179,7 +179,7 @@ QBrush ThemeManager::loadExtraBrush(QString fileName, QBrush &fallbackBrush) static inline QPalette createDarkGreenFusionPalette() { - QPalette p; + QPalette p = QStyleFactory::create("Fusion")->standardPalette(); // ---------- Core backgrounds ---------- p.setColor(QPalette::Window, QColor(30, 30, 30)); // #ff1e1e1e @@ -248,7 +248,7 @@ static inline QPalette createDarkGreenFusionPalette() static inline QPalette createLightGreenFusionPalette() { - QPalette p; + QPalette p = QStyleFactory::create("Fusion")->standardPalette(); // ---------- Core backgrounds ---------- p.setColor(QPalette::Window, QColor(240, 240, 240)); // #fff0f0f0 @@ -332,13 +332,15 @@ void ThemeManager::themeChangedSlot() } if (themeName == FUSION_THEME_NAME) { - qApp->setStyle(QStyleFactory::create("Fusion")); + QStyle *fusionStyle = QStyleFactory::create("Fusion"); + qApp->setStyle(fusionStyle); #if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) - QPalette palette; + // Start from Fusion's own palette so dark mode is handled correctly, + // then apply any tweaks on top of it. + QPalette palette = fusionStyle->standardPalette(); if (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark) { palette.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); } - qApp->setPalette(palette); #endif } else if (themeName == FUSION_THEME_NAME_LIGHT) { @@ -348,7 +350,7 @@ void ThemeManager::themeChangedSlot() qApp->setStyle(QStyleFactory::create("Fusion")); qApp->setPalette(createDarkGreenFusionPalette()); } else { - qApp->setStyle(defaultStyleName); // setting the style also sets the palette + qApp->setStyle(QStyleFactory::create(defaultStyleName)); // setting the style also sets the palette } if (dirPath.isEmpty()) { diff --git a/oracle/src/oraclewizard.cpp b/oracle/src/oraclewizard.cpp index 9d32a993b..2edb9e561 100644 --- a/oracle/src/oraclewizard.cpp +++ b/oracle/src/oraclewizard.cpp @@ -21,6 +21,10 @@ OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent) // define a dummy context that will be used where needed QString dummy = QT_TRANSLATE_NOOP("i18n", "English"); +#ifdef Q_OS_WIN + setWizardStyle(QWizard::ModernStyle); +#endif + QString oracleSettingsFile = SettingsCache::instance().getSettingsPath() + "oracle.ini"; settings = new QSettings(oracleSettingsFile, QSettings::IniFormat, this); From ac06fb9d1c945a311c1824bdaafc1ee99525ed9a Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 9 Apr 2026 06:02:00 -0700 Subject: [PATCH 295/325] [Game] Fix crash by properly parenting QObjects (#6788) --- cockatrice/src/game/abstract_game.cpp | 3 ++- cockatrice/src/game/player/menu/player_menu.cpp | 2 +- cockatrice/src/game/player/menu/player_menu.h | 2 +- cockatrice/src/game/player/player_actions.cpp | 3 ++- cockatrice/src/game/player/player_event_handler.cpp | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cockatrice/src/game/abstract_game.cpp b/cockatrice/src/game/abstract_game.cpp index 505cd5fde..9216f9174 100644 --- a/cockatrice/src/game/abstract_game.cpp +++ b/cockatrice/src/game/abstract_game.cpp @@ -1,8 +1,9 @@ #include "abstract_game.h" +#include "../interface/widgets/tabs/tab_game.h" #include "player/player.h" -AbstractGame::AbstractGame(TabGame *_tab) : tab(_tab) +AbstractGame::AbstractGame(TabGame *_tab) : QObject(_tab), tab(_tab) { gameMetaInfo = new GameMetaInfo(this); gameEventHandler = new GameEventHandler(this); diff --git a/cockatrice/src/game/player/menu/player_menu.cpp b/cockatrice/src/game/player/menu/player_menu.cpp index 7786ec3fc..0dc381e28 100644 --- a/cockatrice/src/game/player/menu/player_menu.cpp +++ b/cockatrice/src/game/player/menu/player_menu.cpp @@ -10,7 +10,7 @@ #include -PlayerMenu::PlayerMenu(Player *_player) : player(_player) +PlayerMenu::PlayerMenu(Player *_player) : QObject(_player), player(_player) { playerMenu = new TearOffMenu(); diff --git a/cockatrice/src/game/player/menu/player_menu.h b/cockatrice/src/game/player/menu/player_menu.h index 5fce27158..104c5a930 100644 --- a/cockatrice/src/game/player/menu/player_menu.h +++ b/cockatrice/src/game/player/menu/player_menu.h @@ -37,7 +37,7 @@ private slots: void refreshShortcuts(); public: - PlayerMenu(Player *player); + explicit PlayerMenu(Player *player); /// Lifecycle methods: delegate to all managedComponents, plus counters separately via player->getCounters(). void retranslateUi(); diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index a44c28d40..20034df16 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -35,7 +35,8 @@ // milliseconds in between triggers of the move top cards until action static constexpr int MOVE_TOP_CARD_UNTIL_INTERVAL = 100; -PlayerActions::PlayerActions(Player *_player) : player(_player), lastTokenTableRow(0), movingCardsUntil(false) +PlayerActions::PlayerActions(Player *_player) + : QObject(_player), player(_player), lastTokenTableRow(0), movingCardsUntil(false) { moveTopCardTimer = new QTimer(this); moveTopCardTimer->setInterval(MOVE_TOP_CARD_UNTIL_INTERVAL); diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp index f4c3840e0..5571010d1 100644 --- a/cockatrice/src/game/player/player_event_handler.cpp +++ b/cockatrice/src/game/player/player_event_handler.cpp @@ -32,7 +32,7 @@ #include #include -PlayerEventHandler::PlayerEventHandler(Player *_player) : player(_player) +PlayerEventHandler::PlayerEventHandler(Player *_player) : QObject(_player), player(_player) { } From 2d412bfe5266bb8c5ae00ff11b5baf1718bd0767 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Thu, 9 Apr 2026 21:16:38 +0200 Subject: [PATCH 296/325] fix cardloading on qt5 (#6790) --- .../src/interface/card_picture_loader/card_picture_loader.cpp | 1 + libcockatrice_card/libcockatrice/card/printing/exact_card.h | 1 + 2 files changed, 2 insertions(+) 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 06a0476c9..dbd51b973 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp @@ -28,6 +28,7 @@ CardPictureLoader::CardPictureLoader() : QObject(nullptr) connect(&SettingsCache::instance(), &SettingsCache::picDownloadChanged, this, &CardPictureLoader::picDownloadChanged); + qRegisterMetaType(); connect(worker, &CardPictureLoaderWorker::imageLoaded, this, &CardPictureLoader::imageLoaded); statusBar = new CardPictureLoaderStatusBar(nullptr); diff --git a/libcockatrice_card/libcockatrice/card/printing/exact_card.h b/libcockatrice_card/libcockatrice/card/printing/exact_card.h index 01c61a3a1..74fc75da3 100644 --- a/libcockatrice_card/libcockatrice/card/printing/exact_card.h +++ b/libcockatrice_card/libcockatrice/card/printing/exact_card.h @@ -114,5 +114,6 @@ public: */ void emitPixmapUpdated() const; }; +Q_DECLARE_METATYPE(ExactCard) #endif // EXACT_CARD_H From 9aa5702e14bcf17f72faa5e24d3eb16ee4b83bec Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 21:27:14 +0200 Subject: [PATCH 297/325] Translate cockatrice_en@source.ts in en_US (#6783) 100% translated source file: 'cockatrice_en@source.ts' on 'en_US'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- cockatrice/translations/cockatrice_en_US.ts | 2435 +++++++++++-------- 1 file changed, 1356 insertions(+), 1079 deletions(-) diff --git a/cockatrice/translations/cockatrice_en_US.ts b/cockatrice/translations/cockatrice_en_US.ts index c74e63114..254ead590 100644 --- a/cockatrice/translations/cockatrice_en_US.ts +++ b/cockatrice/translations/cockatrice_en_US.ts @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh &Refresh - + Parse Set Name and Number (if available) Parse Set Name and Number (if available) @@ -36,62 +36,62 @@ AbstractTabDeckEditor - + Open in new tab Open in new tab - + Are you sure? Are you sure? - + The decklist has been modified. Do you want to save the changes? The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error Error - + Could not open deck at %1 Could not open deck at %1 - + Could not save remote deck Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. The deck could not be saved. Please check that the directory is writable and try again. - + Save deck Save deck - + The deck could not be saved. The deck could not be saved. - + There are no cards in your deck to be exported There are no cards in your deck to be exported @@ -133,202 +133,168 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds seconds - + Error Error - + Could not create themes directory at '%1'. Could not create themes directory at '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - Confirm Change - Confirm Change - - - + Theme settings Theme settings - + Current theme: Current theme: - + Open themes folder Open themes folder - + Home tab background source: Home tab background source: - + Home tab background shuffle frequency: Home tab background shuffle frequency: - + Disabled Disabled - + + Display card name of background in bottom right: + Display card name of background in bottom right: + + + Menu settings Menu settings - + Show keyboard shortcuts in right-click menus Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab Show game filter toolbar above list in room tab - + Card rendering Card rendering - + Display card names on cards having a picture Display card names on cards having a picture - + Auto-Rotate cards with sideways layout Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) Override all card art with personal set preference (Pre-ProviderID change behavior) - + Bump sets that the deck contains cards from to the top in the printing selector Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Scale cards on mouse over - + Use rounded card corners Use rounded card corners - + Minimum overlap percentage of cards on the stack and in vertical hand Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: Maximum initial height for card view window: - - + + rows rows - + Maximum expanded height for card view window: Maximum expanded height for card view window: - + Card counters Card counters - + Counter %1 Counter %1 - + Hand layout Hand layout - + Display hand horizontally (wastes space) Display hand horizontally (wastes space) - + Enable left justification Enable left justification - + Table grid layout Table grid layout - + Invert vertical coordinate Invert vertical coordinate - + Minimum player count for multi-column layout: Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: Maximum font size for information displayed on cards: @@ -336,7 +302,12 @@ Are you sure you would like to disable this feature? ArchidektApiResponseDeckDisplayWidget - + + Back to results + Back to results + + + Open Deck in Deck Editor Open Deck in Deck Editor @@ -656,22 +627,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards View related cards - + Add card to deck Add card to deck - + Mainboard Mainboard - + Sideboard Sideboard @@ -707,124 +678,124 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... Re&veal to... - + &All players &All players - + View related cards View related cards - + Token: Token: - + All tokens All tokens - + &Select All &Select All - + S&elect Row S&elect Row - + S&elect Column S&elect Column - + &Play &Play - + &Hide &Hide - + Play &Face Down Play &Face Down - + &Tap / Untap Turn sideways or back again &Tap / Untap - - Toggle &normal untapping - Toggle &normal untapping + + Skip &untapping + Skip &untapping - + T&urn Over Turn face up/face down T&urn Over - + &Peek at card face &Peek at card face - + &Clone &Clone - + Attac&h to card... Attac&h to card... - + Unattac&h Unattac&h - + &Draw arrow... &Draw arrow... - + &Set annotation... &Set annotation... - + Ca&rd counters Ca&rd counters - + &Add counter (%1) &Add counter (%1) - + &Remove counter (%1) &Remove counter (%1) - + &Set counters (%1)... &Set counters (%1)... @@ -840,133 +811,133 @@ This is only saved for moderators and cannot be seen by the banned person. CardZoneLogic - + their hand nominative their hand - + %1's hand nominative %1's hand - + their library look at zone their library - + %1's library look at zone %1's library - + of their library top cards of zone, of their library - + of %1's library top cards of zone of %1's library - + their library reveal zone their library - + %1's library reveal zone %1's library - + their library shuffle their library - + %1's library shuffle %1's library - - - their library - nominative - their library - - - - %1's library - nominative - %1's library - + their library + nominative + their library + + + + %1's library + nominative + %1's library + + + their graveyard nominative their graveyard - + %1's graveyard nominative %1's graveyard - + their exile nominative their exile - + %1's exile nominative %1's exile - - - their sideboard - look at zone - their sideboard - - - - %1's sideboard - look at zone - %1's sideboard - their sideboard - nominative + look at zone their sideboard %1's sideboard + look at zone + %1's sideboard + + + + their sideboard + nominative + their sideboard + + + + %1's sideboard nominative %1's sideboard - + their custom zone '%1' nominative their custom zone '%1' - + %1's custom zone '%2' nominative %1's custom zone '%2' @@ -1028,7 +999,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardDatabaseDockWidget - + Card Database Card Database @@ -1036,7 +1007,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorCardInfoDockWidget - + Card Info Card Info @@ -1059,32 +1030,32 @@ This is only saved for moderators and cannot be seen by the banned person.Add to Sideboard - + Select Printing Select Printing - + Show on EDHRec (Commander) Show on EDHRec (Commander) - + Show on EDHRec (Card) Show on EDHRec (Card) - + Show Related cards Show Related cards - + Add card to &maindeck Add card to &maindeck - + Add card to &sideboard Add card to &sideboard @@ -1092,32 +1063,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorDeckDockWidget - + Loading Database... Loading Database... - + Banner Card Banner Card - + Main Type Main Type - + Mana Cost Mana Cost - + Colors Colors - + Select Printing Select Printing @@ -1190,17 +1161,17 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorFilterDockWidget - + Filters Filters - + &Clear all filters &Clear all filters - + Delete selected Delete selected @@ -1323,7 +1294,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorPrintingSelectorDockWidget - + Printing Selector Printing Selector @@ -1331,166 +1302,166 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers Update Spoilers - - + + Success Success - + Download URLs have been reset. Download URLs have been reset. - + Downloaded card pictures have been reset. Downloaded card pictures have been reset. - + Error Error - + One or more downloaded card pictures could not be cleared. One or more downloaded card pictures could not be cleared. - + Add URL Add URL - - + + URL: URL: - - + + Edit URL Edit URL - + Network Cache Size: Network Cache Size: - + Redirect Cache TTL: Redirect Cache TTL: - + How long cached redirects for urls are valid for. How long cached redirects for urls are valid for. - + Picture Cache Size: Picture Cache Size: - + Add New URL Add New URL - + Remove URL Remove URL - + Day(s) Day(s) - + Updating... Updating... - + Choose path Choose path - + URL Download Priority URL Download Priority - + Spoilers Spoilers - + Download Spoilers Automatically Download Spoilers Automatically - + Spoiler Location: Spoiler Location: - + Last Change Last Change - + Spoilers download automatically on launch Spoilers download automatically on launch - + Press the button to manually update without relaunching Press the button to manually update without relaunching - + Do not close settings until manual update is complete Do not close settings until manual update is complete - + Download card pictures on the fly Download card pictures on the fly - + How to add a custom URL How to add a custom URL - + Delete Downloaded Images Delete Downloaded Images - + Reset Download URLs Reset Download URLs - + On-disk cache for downloaded pictures On-disk cache for downloaded pictures - + In-memory cache for pictures not currently on screen In-memory cache for pictures not currently on screen @@ -1498,32 +1469,32 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListHistoryManagerWidget - + Undo Undo - + Redo Redo - + Undo/Redo history Undo/Redo history - + Click on an entry to revert to that point in the history. Click on an entry to revert to that point in the history. - + [redo] [redo] - + [undo] [undo] @@ -1531,27 +1502,27 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Count Count - + Set Set - + Number Number - + Provider ID Provider ID - + Card Card @@ -1559,12 +1530,12 @@ This is only saved for moderators and cannot be seen by the banned person. DeckLoader - + Common deck formats (%1) Common deck formats (%1) - + All files (*.*) All files (*.*) @@ -1661,94 +1632,94 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewWidget - + Banner Card Banner Card - + Open in deck editor Open in deck editor - + Edit Tags Edit Tags - + Rename Deck Rename Deck - + Save Deck to Clipboard Save Deck to Clipboard - + Annotated Annotated - + Annotated (No set info) Annotated (No set info) - + Not Annotated Not Annotated - + Not Annotated (No set info) Not Annotated (No set info) - + Rename File Rename File - + Delete File Delete File - + Set Banner Card Set Banner Card - - + + New name: New name: - - + + Error Error - + Rename failed Rename failed - + Delete file Delete file - + Are you sure you want to delete the selected file? Are you sure you want to delete the selected file? - + Delete failed Delete failed @@ -1781,32 +1752,32 @@ This is only saved for moderators and cannot be seen by the banned person.Set format to %1 - + Added (%1): %2 (%3) %4 Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) Removed "%1" (all copies) - + %1 1 × "%2" (%3) %1 1 × "%2" (%3) - + Added Added - + Removed Removed @@ -1873,30 +1844,30 @@ This is only saved for moderators and cannot be seen by the banned person.Sideboard locked - - + + Error Error - + The selected file could not be loaded. The selected file could not be loaded. - + Deck is greater than maximum file size. Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice Cockatrice @@ -2391,17 +2362,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard Edit deck in clipboard - + Error Error - + Invalid deck list. Invalid deck list. @@ -2902,17 +2873,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard Load deck from clipboard - + Error Error - + Invalid deck list. Invalid deck list. @@ -2920,45 +2891,45 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 Network error: %1 - + Received empty deck data. Received empty deck data. - + Failed to parse deck data: %1 Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2983,40 +2954,73 @@ https://tappedout.net/mtg-decks/your-deck-name/ Load deck + + DlgLocalGameOptions + + + Players: + Players: + + + + General + General + + + + Starting life total: + Starting life total: + + + + Game setup options + Game setup options + + + + Remember settings + Remember settings + + + + Local game options + Local game options + + DlgMoveTopCardsUntil - + Card name (or search expressions): Card name (or search expressions): - + Number of hits: Number of hits: - + Auto play hits Auto play hits - + Put top cards on stack until... Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice Cockatrice - + Invalid filter Invalid filter @@ -3178,12 +3182,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Unknown Error loading card database - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -3200,7 +3204,7 @@ You may need to rerun oracle to update your card database. Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -3217,7 +3221,7 @@ Usually this can be fixed by rerunning oracle to to update your card database. Would you like to change your database location setting? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3230,7 +3234,7 @@ Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with you Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3239,7 +3243,7 @@ Would you like to change your database location setting? Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3248,7 +3252,7 @@ Would you like to change your database location setting? Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3261,59 +3265,59 @@ Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues Would you like to change your database location setting? - - - + + + Error Error - + The path to your deck directory is invalid. Would you like to go back and set the correct path? The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings Settings - + General General - + Appearance Appearance - + User Interface User Interface - + Card Sources Card Sources - + Chat Chat - + Sound Sound - + Shortcuts Shortcuts @@ -3630,67 +3634,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability Draw Probability - + Probability of drawing Probability of drawing - + Card Name Card Name - + Type Type - + Subtype Subtype - + Mana Value Mana Value - + At least At least - + Exactly Exactly - + card(s) having drawn at least card(s) having drawn at least - + cards cards - + Category Category - + Qty Qty - + Odds (%) Odds (%) @@ -4138,143 +4142,143 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths Reset all paths - + All paths have been reset All paths have been reset - - - - - - - + + + + + + + Choose path Choose path - + Personal settings Personal settings - + Language: Language: - + Paths (editing disabled in portable mode) Paths (editing disabled in portable mode) - + Paths Paths - + How to help with translations How to help with translations - + Decks directory: Decks directory: - + Filters directory: Filters directory: - + Replays directory: Replays directory: - + Pictures directory: Pictures directory: - + Card database: Card database: - + Custom database directory: Custom database directory: - + Token database: Token database: - + Update channel Update channel - + Check for client updates on startup Check for client updates on startup - + Check for card database updates on startup Check for card database updates on startup - + Don't check Don't check - + Prompt for update Prompt for update - + Always update in the background Always update in the background - + Check for card database updates every Check for card database updates every - + days days - + Notify if a feature supported by the server is missing in my client Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup Show tips on startup - + Last update check on %1 (%2 days ago) Last update check on %1 (%2 days ago) @@ -4282,47 +4286,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard &Graveyard - + &View graveyard &View graveyard - + &Move graveyard to... &Move graveyard to... - + &Top of library &Top of library - + &Bottom of library &Bottom of library - + &All players &All players - + &Hand &Hand - + &Exile &Exile - + Reveal random card to... Reveal random card to... @@ -4330,88 +4334,88 @@ You may have to manually download the new version. HandMenu - + &Hand &Hand - + &View hand &View hand - + Sort hand by... Sort hand by... - + Name Name - + Type Type - + Mana Value Mana Value - + Take &mulligan (Choose hand size) Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) Take mulligan (Hand size - 1) - + &Move hand to... &Move hand to... - + &Top of library &Top of library - + &Bottom of library &Bottom of library - + &Graveyard &Graveyard - + &Exile &Exile - + &Reveal hand to... &Reveal hand to... - - + + All players All players - + Reveal r&andom card to... Reveal r&andom card to... @@ -4419,52 +4423,52 @@ You may have to manually download the new version. HomeWidget - + Create New Deck Create New Deck - + Browse Decks Browse Decks - + Browse Card Database Browse Card Database - + Browse EDHRec Browse EDHRec - + Browse Archidekt Browse Archidekt - + View Replays View Replays - + Quit Quit - + Connecting... Connecting... - + Connect Connect - + Play Play @@ -4472,193 +4476,213 @@ You may have to manually download the new version. LibraryMenu - + &Library &Library - + &View library &View library - + View &top cards of library... View &top cards of library... - + View bottom cards of library... View bottom cards of library... - + Reveal &library to... Reveal &library to... - + Lend library to... Lend library to... - + Reveal &top cards to... Reveal &top cards to... - + &Top of library... &Top of library... - + &Bottom of library... &Bottom of library... - + &Always reveal top card &Always reveal top card - + &Always look at top card &Always look at top card - + &Open deck in deck editor &Open deck in deck editor - + &Draw card &Draw card - + D&raw cards... D&raw cards... - + &Undo last draw &Undo last draw - + Shuffle Shuffle - + &Play top card &Play top card - + Play top card &face down Play top card &face down - + Put top card on &bottom Put top card on &bottom - + Move top card to grave&yard Move top card to grave&yard - + Move top card to e&xile Move top card to e&xile - + Move top cards to &graveyard... Move top cards to &graveyard... - + + Move top cards to graveyard face down... + Move top cards to graveyard face down... + + + Move top cards to &exile... Move top cards to &exile... - + + Move top cards to exile face down... + Move top cards to exile face down... + + + Put top cards on stack &until... Put top cards on stack &until... - + Shuffle top cards... Shuffle top cards... - + &Draw bottom card &Draw bottom card - + D&raw bottom cards... D&raw bottom cards... - + &Play bottom card &Play bottom card - + Play bottom card &face down Play bottom card &face down - + Move bottom card to grave&yard Move bottom card to grave&yard - + Move bottom card to e&xile Move bottom card to e&xile - + Move bottom cards to &graveyard... Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + Move bottom cards to graveyard face down... + + + Move bottom cards to &exile... Move bottom cards to &exile... - + + Move bottom cards to exile face down... + Move bottom cards to exile face down... + + + Put bottom card on &top Put bottom card on &top - + Shuffle bottom cards... Shuffle bottom cards... - - + + &All players &All players - + Reveal top cards of library Reveal top cards of library - + Number of cards: (max. %1) Number of cards: (max. %1) @@ -4756,18 +4780,8 @@ Will now login. Will now login. - - Number of players - Number of players - - - - Please enter the number of players. - Please enter the number of players. - - - - + + Player %1 Player %1 @@ -4870,8 +4884,8 @@ Will now login. - - + + Error Error @@ -5279,36 +5293,36 @@ Local version is %1, remote version is %2. Show/Hide - + New Version New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5317,29 +5331,29 @@ Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes Yes - - + + No No - + Open settings Open settings - + New sets found New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5350,17 +5364,17 @@ Set codes: %1 Do you want to enable them? - + View sets View sets - + Welcome Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5369,65 +5383,65 @@ All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information Information - + A card database update is already running. A card database update is already running. - + Unable to run the card database updater: Unable to run the card database updater: - + Card database update running. Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. Unknown error occurred. - + The card database updater exited with an error: %1 The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5438,55 +5452,55 @@ This is most likely not a problem, but this message might mean there is a new ve To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards Load sets/cards - + Selected file cannot be found. Selected file cannot be found. - + You can only import XML databases at this time. You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. Sets/cards failed to import. - - - + + + Reset Password Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. Activation request received, please check your email for an activation token. @@ -5537,7 +5551,7 @@ Cockatrice will now reload the card database. ManaBaseWidget - + Mana Base Mana Base @@ -5691,590 +5705,610 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play from play - + from their graveyard from their graveyard - + from exile from exile - + from their hand from their hand - + the top card of %1's library the top card of %1's library - + the top card of their library the top card of their library - + from the top of %1's library from the top of %1's library - + from the top of their library from the top of their library - + the bottom card of %1's library the bottom card of %1's library - + the bottom card of their library the bottom card of their library - + from the bottom of %1's library from the bottom of %1's library - + from the bottom of their library from the bottom of their library - + from %1's library from %1's library - + from their library from their library - + from sideboard from sideboard - + from the stack from the stack - + from custom zone '%1' from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 attaches %2 to %3's %4. - + %1 has conceded the game. %1 has conceded the game. - + %1 has unconceded the game. %1 has unconceded the game. - + %1 has restored connection to the game. %1 has restored connection to the game. - + %1 has lost connection to the game. %1 has lost connection to the game. - + %1 points from their %2 to themselves. %1 points from their %2 to themselves. - + %1 points from their %2 to %3. %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. %1 creates a face down token. - + %1 creates token: %2%3. %1 creates token: %2%3. - + %1 has loaded a deck (%2). %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. %1 destroys %2. - + a card a card - + %1 gives %2 control over %3. %1 gives %2 control over %3. - + %1 puts %2 into play%3 face down. %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 puts %2 into play%3. - + + %1 puts %2%3 into their graveyard face down. + %1 puts %2%3 into their graveyard face down. + + + %1 puts %2%3 into their graveyard. %1 puts %2%3 into their graveyard. + + + %1 exiles %2%3 face down. + %1 exiles %2%3 face down. + %1 exiles %2%3. %1 exiles %2%3. - + %1 moves %2%3 to their hand. %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. %1 moves %2%3 to sideboard. - + + %1 plays %2%3 face down. + %1 plays %2%3 face down. + + + %1 plays %2%3. %1 plays %2%3. - + + %1 moves %2%3 to custom zone '%4' face down. + %1 moves %2%3 to custom zone '%4' face down. + + + %1 moves %2%3 to custom zone '%4'. %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1 tries to draw from an empty library - + %1 draws %2 card(s). %1 draws %2 card.%1 draws %2 cards. - + %1 is looking at %2. %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 is looking at the %4 %3 card %2.%1 is looking at the %4 %3 cards %2. - + bottom bottom - + top top - + %1 turns %2 face-down. %1 turns %2 face-down. - + %1 turns %2 face-up. %1 turns %2 face-up. - + The game has been closed. The game has been closed. - + The game has started. The game has started. - + You are flooding the game. Please wait a couple of seconds. You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 has joined the game. - + %1 is now watching the game. %1 is now watching the game. - + You have been kicked out of the game. You have been kicked out of the game. - + %1 has left the game (%2). %1 has left the game (%2). - + %1 is not watching the game any more (%2). %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 shuffles their deck and draws a card.%1 shuffles their deck and draws a new hand of %2 cards. - + %1 shuffles their deck and draws a new hand. %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. You are watching a replay of game #%1. - + %1 is ready to start the game. %1 is ready to start the game. - + cards an unknown amount of cards cards - + %1 card(s) a card for singular, %1 cards for plural one card%1 cards - + %1 lends %2 to %3. %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 reveals %2 to %3. - + %1 reveals %2. %1 reveals %2. - + %1 randomly reveals %2%3 to %4. %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. %1 reveals %2%3 to %4. - + %1 reveals %2%3. %1 reveals %2%3. - + %1 reversed turn order, now it's %2. %1 reversed turn order, now it's %2. - + reversed reversed - + normal normal - + Heads Heads - + Tails Tails - + %1 flipped a coin. It landed as %2. %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. %1's turn. - + %1 sets annotation of %2 to %3. %1 sets annotation of %2 to %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). %1 places %2 "%3" counter on %4 (now %5).%1 places %2 "%3" counters on %4 (now %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). %1 removes %2 "%3" counter from %4 (now %5).%1 removes %2 "%3" counters from %4 (now %5). - + %1 sets counter %2 to %3 (%4%5). %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. %1 sets %2 to untap normally. - + %1 removes the PT of %2. %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. %1 has locked their sideboard. - + %1 has unlocked their sideboard. %1 has unlocked their sideboard. - + %1 taps their permanents. %1 taps their permanents. - + %1 untaps their permanents. %1 untaps their permanents. - + %1 taps %2. %1 taps %2. - + %1 untaps %2. %1 untaps %2. - + %1 shuffles %2. %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. %1 unattaches %2. - + %1 undoes their last draw. %1 undoes their last draw. - + %1 undoes their last draw (%2). %1 undoes their last draw (%2). @@ -6282,110 +6316,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 Word1 Word2 Word3 - + Add New Message Add New Message - + Edit Message Edit Message - + Remove Message Remove Message - + Add message Add message - - + + Message: Message: - + Edit message Edit message - + Chat settings Chat settings - + Custom alert words Custom alert words - + Enable chat mentions Enable chat mentions - + Enable mention completer Enable mention completer - + In-game message macros In-game message macros - + How to use in-game message macros How to use in-game message macros - + Ignore chat room messages sent by unregistered users Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users Ignore private messages sent by unregistered users - - + + Invert text color Invert text color - + Enable desktop notifications for private messages Enable desktop notifications for private messages - + Enable desktop notification for mentions Enable desktop notification for mentions - + Enable room message history on join Enable room message history on join - - + + (Color is hexadecimal) (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only Separate words with a space, alphanumeric characters only @@ -6398,32 +6432,37 @@ Cockatrice will now reload the card database. Move to - + &Top of library in random order &Top of library in random order - + X cards from the top of library... X cards from the top of library... - + &Bottom of library in random order &Bottom of library in random order - + + T&able + T&able + + + &Hand &Hand - + &Graveyard &Graveyard - + &Exile &Exile @@ -6565,57 +6604,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step Untap step - + Upkeep step Upkeep step - + Draw step Draw step - + First main phase First main phase - + Beginning of combat step Beginning of combat step - + Declare attackers step Declare attackers step - + Declare blockers step Declare blockers step - + Combat damage step Combat damage step - + End of combat step End of combat step - + Second main phase Second main phase - + End of turn step End of turn step @@ -6632,134 +6671,138 @@ Cockatrice will now reload the card database. PlayerActions - + View top cards of library View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) Number of cards: (max. %1) - + View bottom cards of library View bottom cards of library - + Shuffle top cards of library Shuffle top cards of library - + Shuffle bottom cards of library Shuffle bottom cards of library - + Draw hand Draw hand - + 0 and lower are in comparison to current hand size 0 and lower are in comparison to current hand size - + Draw cards Draw cards + + + + + + grave + grave + - Move top cards to grave - Move top cards to grave + + + + exile + exile - - Move top cards to exile - Move top cards to exile + + Move top cards to %1 + Move top cards to %1 - - Move bottom cards to grave - Move bottom cards to grave + + Move bottom cards to %1 + Move bottom cards to %1 - - Move bottom cards to exile - Move bottom cards to exile - - - + Draw bottom cards Draw bottom cards - - + + C&reate another %1 token C&reate another %1 token - + Create tokens Create tokens - - + + Number: Number: - + Place card X cards from top of library Place card X cards from top of library - + Which position should this card be placed: Which position should this card be placed: - + (max. %1) (max. %1) - + Change power/toughness Change power/toughness - + Change stats to: Change stats to: - + Set annotation Set annotation - + Please enter the new annotation: Please enter the new annotation: - + Set counters Set counters @@ -6767,48 +6810,69 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" Player "%1" - + &Counters &Counters + + + PrintingDisabledInfoWidget - - S&ay - S&ay + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + Enable printings again + Enable printings again PrintingSelector - + Display Navigation Buttons Display Navigation Buttons + + + Printing Selector + Printing Selector + PrintingSelectorCardOverlayWidget - + Preference Preference - + Pin Printing Pin Printing - + Unpin Printing Unpin Printing - + Show Related cards Show Related cards @@ -6857,17 +6921,25 @@ Cockatrice will now reload the card database. Release Date - - + + Descending Descending - + Ascending Ascending + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + Select a card to view its available printings + + PtMenu @@ -7006,6 +7078,45 @@ Cockatrice will now reload the card database. A .cod version of this deck already exists. Overwrite it? A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + Confirm Change + Confirm Change + QPlatformTheme @@ -7154,37 +7265,37 @@ Cockatrice will now reload the card database. RfgMenu - + &Exile &Exile - + &View exile &View exile - + &Move exile to... &Move exile to... - + &Top of library &Top of library - + &Bottom of library &Bottom of library - + &Hand &Hand - + &Graveyard &Graveyard @@ -7227,6 +7338,14 @@ Cockatrice will now reload the card database. Games + + SayMenu + + + S&ay + S&ay + + SequenceEdit @@ -7291,53 +7410,53 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts Restore all default shortcuts - + Do you really want to restore all default shortcuts? Do you really want to restore all default shortcuts? - + Clear all default shortcuts Clear all default shortcuts - + Do you really want to clear all shortcuts? Do you really want to clear all shortcuts? - + Section: Section: - + Action: Action: - + Shortcut: Shortcut: - + How to set custom shortcuts How to set custom shortcuts - + Clear all shortcuts Clear all shortcuts - + Search by shortcut name Search by shortcut name @@ -7406,27 +7525,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Enable &sounds - + Current sounds theme: Current sounds theme: - + Test system sound engine Test system sound engine - + Sound settings Sound settings - + Master volume Master volume @@ -7642,59 +7761,123 @@ Please check your shortcut settings! TabArchidekt - - + + + Desc. Desc. - + + + AND + AND + + + + + Require ALL selected colors + Require ALL selected colors + + + + + Deck name... + Deck name... + + + + + Owner... + Owner... + + + + + Packages + Packages + + + + + Advanced Filters + Advanced Filters + + + + Bracket: + Bracket: + + + + + Any + Any + + + + + Contains card... + Contains card... + + + + + Commander... + Commander... + + + + + Tag... + Tag... + + + + + Deck Size + Deck Size + + + + Cards: + Cards: + + + + Asc. Asc. - - - Any Bracket - Any Bracket + + Sort by: + Sort by: - - Deck name contains... - Deck name contains... + + Filter by: + Filter by: - - Owner name contains... - Owner name contains... + + Display Settings + Display Settings - - Disabled - Disabled - - - + + Search Search - + + Formats Formats - - Min. # of Cards: - Min. # of Cards: - - - - Page: - Page: - - - + Archidekt: Archidekt: @@ -7702,60 +7885,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info Card Info - + Deck Deck - + Filters Filters - + &View &View - + Card Database Card Database - + Printing Printing - - - - - + Visible Visible - - - - - + Floating Floating - + Reset layout Reset layout - + Deck: %1 Deck: %1 @@ -7763,61 +7938,55 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 Visual Deck: %1 - + &Visual Deck Editor &Visual Deck Editor - - + + Card Info Card Info - - + + Deck Deck - - + + Filters Filters - + &View &View - + Printing Printing - - - - + Visible Visible - - - - + Floating Floating - + Reset layout Reset layout @@ -7882,7 +8051,7 @@ Please check your shortcut settings! - + New folder New folder @@ -7964,18 +8133,18 @@ Please enter a name: Are you sure you want to delete the selected files? - + Delete remote decks Delete remote decks - + Are you sure you want to delete the selected decks? Are you sure you want to delete the selected decks? - + Name of new folder: Name of new folder: @@ -7993,12 +8162,12 @@ Please enter a name: Visual Deck Storage - + Error Error - + Could not open deck at %1 Could not open deck at %1 @@ -8047,197 +8216,191 @@ Please enter a name: TabGame - - - + + + Replay Replay - - + + Game Game - - + + Player List Player List - - + + Card Info Card Info - - + + Messages Messages - - + + Replay Timeline Replay Timeline - + &Phases &Phases - + &Game &Game - + Next &phase Next &phase - + Next phase with &action Next phase with &action - + Next &turn Next &turn - + Reverse turn order Reverse turn order - + &Remove all local arrows &Remove all local arrows - + Rotate View Cl&ockwise Rotate View Cl&ockwise - + Rotate View Co&unterclockwise Rotate View Co&unterclockwise - + Game &information Game &information - + Un&concede Un&concede - - - + + + &Concede &Concede - + &Leave game &Leave game - + C&lose replay C&lose replay - + &Focus Chat &Focus Chat - + &Say: &Say: - + Selected cards Selected cards - + &View &View - - - - + Visible Visible - - - - + Floating Floating - + Reset layout Reset layout - + Concede Concede - + Are you sure you want to concede this game? Are you sure you want to concede this game? - + Unconcede Unconcede - + You have already conceded. Do you want to return to this game? You have already conceded. Do you want to return to this game? - + Leave game Leave game - + Are you sure you want to leave this game? Are you sure you want to leave this game? - + A player has joined game #%1 A player has joined game #%1 - + %1 has joined the game %1 has joined the game - + You have been kicked out of the game. You have been kicked out of the game. @@ -9340,142 +9503,152 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings General interface settings - + &Double-click cards to play them (instead of single-click) &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default &Play all nonlands onto the stack (not the battlefield) by default - + Do not delete &arrows inside of subphases Do not delete &arrows inside of subphases - + Close card view window when last card is removed Close card view window when last card is removed - + Auto focus search bar when card view window is opened Auto focus search bar when card view window is opened - + Annotate card text on tokens Annotate card text on tokens - + + Show selection counter during drag selection + Show selection counter during drag selection + + + + Show total selection counter + Show total selection counter + + + Use tear-off menus, allowing right click menus to persist on screen Use tear-off menus, allowing right click menus to persist on screen - + Notifications settings Notifications settings - + Enable notifications in taskbar Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect Notify in the taskbar when users in your buddy list connect - + Animation settings Animation settings - + &Tap/untap animation &Tap/untap animation - + Deck editor/storage settings Deck editor/storage settings - + Open deck in new tab by default Open deck in new tab by default - + Use visual deck storage in game lobby Use visual deck storage in game lobby - + Use selection animation for Visual Deck Storage Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: When adding a tag in the visual deck storage to a .txt deck: - + do nothing do nothing - + ask to convert to .cod ask to convert to .cod - + always convert to .cod always convert to .cod - + Default deck editor type Default deck editor type - + Classic Deck Editor Classic Deck Editor - + Visual Deck Editor Visual Deck Editor - + Replay settings Replay settings - + Buffer time for backwards skip via shortcut: Buffer time for backwards skip via shortcut: @@ -9539,24 +9712,25 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayColorFilterWidget - - Mode: Exact Match - Mode: Exact Match + + Exact match + Exact match + + + + Includes + Includes - Mode: Includes - Mode: Includes + Include / Exclude + Mode: Includes + Include / Exclude - - Mode: Include/Exclude - Mode: Include/Exclude - - - - Filter mode (AND/OR/NOT conjunctions of filters) - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter + How selected and unselected colors are combined in the filter @@ -9582,25 +9756,108 @@ Please refrain from engaging in this activity or further actions may be taken ag Enter filename... + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + Sort by + + + + Filter by + Filter by + + + + Save and load filters + Save and load filters + + + + Filter by exact card name + Filter by exact card name + + + + Filter by card main-type + Filter by card main-type + + + + Filter by card sub-type + Filter by card sub-type + + + + Filter by set + Filter by set + + + + Filter by format legality + Filter by format legality + + + + Save/Load + Save/Load + + + + Name + Name + + + + Main Type + Main Type + + + + Sub Type + Sub Type + + + + Sets + Sets + + + + Formats + Formats + + VisualDatabaseDisplayFormatLegalityFilterWidget - + + Show formats with at least: + Show formats with at least: + + + + cards + cards + + + Do not display formats with less than this amount of cards in the database Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match Mode: Exact Match - + Mode: Includes Mode: Includes @@ -9608,22 +9865,32 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + Show main types with at least: + + + + cards + cards + + + Do not display card main-types with less than this amount of cards in the database Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match Mode: Exact Match - + Mode: Includes Mode: Includes @@ -9659,7 +9926,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets Filter to most recent sets @@ -9667,19 +9934,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... Search sets... - - + + Mode: Exact Match Mode: Exact Match - - + + Mode: Includes Mode: Includes @@ -9687,27 +9954,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... Search subtypes... - + + Show sub types with at least: + Show sub types with at least: + + + + cards + cards + + + Do not display card sub-types with less than this amount of cards in the database Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match Mode: Exact Match - + Mode: Includes Mode: Includes @@ -9721,52 +9998,22 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Visual Visual - + Loading database ... Loading database ... - + Clear all filters Clear all filters - - Sort by: - Sort by: - - - - Filter by: - Filter by: - - - - Save and load filters - Save and load filters - - - - Filter by exact card name - Filter by exact card name - - - - Filter by card sub-type - Filter by card sub-type - - - - Filter by set - Filter by set - - - + Table Table @@ -9774,56 +10021,64 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: Group by: - + Change how cards are divided into categories/groups. Change how cards are divided into categories/groups. - + Sort by: Sort by: - + Click and drag to change the sort order within the groups Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat Toggle Layout: Flat + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here + Add cards using the search bar or database tab to have them appear here + + VisualDeckEditorSampleHandWidget - + Draw a new sample hand Draw a new sample hand - + Sample hand size Sample hand size @@ -9831,17 +10086,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... Type a card name here for suggestions from the database... - + Quick search and add card Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -9857,47 +10112,52 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders Show Folders - + Show Tag Filter Show Tag Filter - + + Show Color Identity + Show Color Identity + + + Show Tags On Deck Previews Show Tags On Deck Previews - + Show Banner Card Selection Option Show Banner Card Selection Option - + Draw unused Color Identities Draw unused Color Identities - + Unused Color Identities Opacity Unused Color Identities Opacity - + Deck tooltip: Deck tooltip: - + None None - + Filepath Filepath @@ -9998,133 +10258,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top Move selected set to the top - + Move selected set up Move selected set up - + Move selected set down Move selected set down - + Move selected set to the bottom Move selected set to the bottom - + Search by set name, code, or type Search by set name, code, or type - + Default order Default order - + Restore original art priority order Restore original art priority order - + Enable all sets Enable all sets - + Disable all sets Disable all sets - + Enable selected set(s) Enable selected set(s) - + Disable selected set(s) Disable selected set(s) - + Deck Editor Deck Editor - + Use CTRL+A to select all sets in the view. Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) - + Include cards rebalanced for Alchemy [requires restart] Include cards rebalanced for Alchemy [requires restart] - + Card Art Card Art - + How to use custom card art How to use custom card art - + Hints Hints - + Note Note - + Sorting by column allows you to find a set while not changing set priority. Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead Use the current sorting as the set priority instead - + Sorts the set priority using the same column Sorts the set priority using the same column - + Manage sets Manage sets @@ -10132,72 +10392,72 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) Search by card name (or search expressions) - + Ungrouped Ungrouped - + Group by Type Group by Type - + Group by Mana Value Group by Mana Value - + Group by Color Group by Color - + Unsorted Unsorted - + Sort by Name Sort by Name - + Sort by Type Sort by Type - + Sort by Mana Cost Sort by Mana Cost - + Sort by Colors Sort by Colors - + Sort by P/T Sort by P/T - + Sort by Set Sort by Set - + shuffle when closing shuffle when closing - + pile view pile view @@ -10232,7 +10492,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor Deck Editor @@ -10313,7 +10573,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays Replays @@ -10465,7 +10725,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Reset Layout Reset Layout @@ -10886,8 +11146,9 @@ Please refrain from engaging in this activity or further actions may be taken ag - Toggle Untap - Toggle Untap + Toggle Skip Untapping + Toggle Untap + Toggle Skip Untapping @@ -10906,98 +11167,102 @@ Please refrain from engaging in this activity or further actions may be taken ag + Play Card, Face Down + Play Card, Face Down + + + Attach Card... Attach Card... - + Unattach Card Unattach Card - + Clone Card Clone Card - + Create Token... Create Token... - + Create All Related Tokens Create All Related Tokens - + Create Another Token Create Another Token - + Set Annotation... Set Annotation... - + Select All Cards in Zone Select All Cards in Zone - + Select All Cards in Row Select All Cards in Row - + Select All Cards in Column Select All Cards in Column - + Reveal Selected Cards to All Players Reveal Selected Cards to All Players - - + + Bottom of Library Bottom of Library - + - - + + Exile Exile - + - + Graveyard Graveyard - + Hand Hand - - + + Top of Library Top of Library - - + Battlefield, Face Down Battlefield, Face Down @@ -11033,234 +11298,246 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Stack Stack - + Graveyard (Multiple) Graveyard (Multiple) - - + + + Graveyard (Multiple), Face Down + Graveyard (Multiple), Face Down + + + + Exile (Multiple) Exile (Multiple) - + + + Exile (Multiple), Face Down + Exile (Multiple), Face Down + + + Stack Until Found Stack Until Found - + Draw Bottom Card Draw Bottom Card - + Draw Multiple Cards from Bottom... Draw Multiple Cards from Bottom... - + Draw Arrow... Draw Arrow... - + Remove Local Arrows Remove Local Arrows - + Leave Game Leave Game - + Concede Concede - + Roll Dice... Roll Dice... - + Shuffle Library Shuffle Library - + Shuffle Top Cards of Library Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library Shuffle Bottom Cards of Library - + Mulligan Mulligan - + Mulligan (Same hand size) Mulligan (Same hand size) - + Mulligan (Hand size - 1) Mulligan (Hand size - 1) - + Draw a Card Draw a Card - + Draw Multiple Cards... Draw Multiple Cards... - + Undo Draw Undo Draw - + Always Reveal Top Card Always Reveal Top Card - + Always Look At Top Card Always Look At Top Card - + Sort Hand by Name Sort Hand by Name - + Sort Hand by Type Sort Hand by Type - + Sort Hand by Mana Value Sort Hand by Mana Value - + Reveal Hand to All Players Reveal Hand to All Players - + Reveal Random Card to All Players Reveal Random Card to All Players - + Rotate View Clockwise Rotate View Clockwise - + Rotate View Counterclockwise Rotate View Counterclockwise - + Unfocus Text Box Unfocus Text Box - + Focus Chat Focus Chat - + Clear Chat Clear Chat - + Refresh Refresh - + Skip Forward Skip Forward - + Skip Backward Skip Backward - + Skip Forward by a lot Skip Forward by a lot - + Skip Backward by a lot Skip Backward by a lot - + Play/Pause Play/Pause - + Toggle Fast Forward Toggle Fast Forward - + Home Home - + Visual Deck Storage Visual Deck Storage - + Deck Storage Deck Storage - + Server Server - + Account Account - + Administration Administration - + Logs Logs From d2732ac742cd7acf19719bb60ce5007ae3d8d91b Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Fri, 10 Apr 2026 13:42:05 +0200 Subject: [PATCH 298/325] fix prepare cards (#6792) --- oracle/src/oracleimporter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index d585842f6..b5d7b9856 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -360,13 +360,13 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList } // split cards are considered a single card, enqueue for later merging - if (layout == "split" || layout == "aftermath" || layout == "adventure") { + if (layout == "split" || layout == "aftermath" || layout == "adventure" || layout == "prepare") { auto _faceName = getStringPropertyFromMap(card, "faceName"); 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 if (layout == "adventure") { + } else if (layout == "adventure" || layout == "prepare") { found_iter->first.insert(0, split); } else { found_iter->first.append(split); From f9fb03b26b796bdfd1ac298110224598dc506cdc Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Fri, 10 Apr 2026 18:17:43 +0200 Subject: [PATCH 299/325] update all runners to use qt6.11 (#6794) * update all runners to use qt6.11 * use aqt version directly from repo --- .github/workflows/desktop-build.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 7bd84eb21..c93666640 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -270,7 +270,7 @@ jobs: override_target: 13 make_package: 1 package_suffix: "-macOS13_Intel" - qt_version: 6.10.* + qt_version: 6.11.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets cmake_generator: Ninja @@ -284,7 +284,7 @@ jobs: type: Release make_package: 1 package_suffix: "-macOS14" - qt_version: 6.10.* + qt_version: 6.11.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets cmake_generator: Ninja @@ -298,7 +298,7 @@ jobs: type: Release make_package: 1 package_suffix: "-macOS15" - qt_version: 6.10.* + qt_version: 6.11.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets cmake_generator: Ninja @@ -310,7 +310,7 @@ jobs: soc: Apple xcode: "16.4" type: Debug - qt_version: 6.10.* + qt_version: 6.11.* qt_arch: clang_64 qt_modules: qtimageformats qtmultimedia qtwebsockets cmake_generator: Ninja @@ -322,7 +322,7 @@ jobs: type: Release make_package: 1 package_suffix: "-Win10" - qt_version: 6.10.* + qt_version: 6.11.* qt_arch: win64_msvc2022_64 qt_modules: qtimageformats qtmultimedia qtwebsockets cmake_generator: "Visual Studio 17 2022" @@ -409,6 +409,8 @@ jobs: if: matrix.os == 'Windows' uses: jurplel/install-qt-action@v4 with: + # qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released + aqtsource: git+https://github.com/miurahr/aqtinstall.git version: ${{ steps.resolve_qt_version.outputs.version }} arch: ${{matrix.qt_arch}} modules: ${{matrix.qt_modules}} From 36aba81b1b668fcfafcbd4b0966e35d0b43dca26 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Fri, 10 Apr 2026 18:18:08 +0200 Subject: [PATCH 300/325] adds eternal to prioritisation list (#6793) --- oracle/src/oracleimporter.h | 1 + 1 file changed, 1 insertion(+) diff --git a/oracle/src/oracleimporter.h b/oracle/src/oracleimporter.h index e83958d73..5bf352594 100644 --- a/oracle/src/oracleimporter.h +++ b/oracle/src/oracleimporter.h @@ -22,6 +22,7 @@ const QMap setTypePriorities{ {"archenemy", CardSet::PriorityReprint}, {"arsenal", CardSet::PriorityReprint}, {"box", CardSet::PriorityReprint}, + {"eternal", CardSet::PriorityReprint}, {"from_the_vault", CardSet::PriorityReprint}, {"masterpiece", CardSet::PriorityReprint}, {"masters", CardSet::PriorityReprint}, From 29c1d7f3e4eada9981da383e80728df9dff0e5f0 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sat, 11 Apr 2026 18:19:11 +0200 Subject: [PATCH 301/325] add timeout to job matrixes (#6797) --- .github/workflows/desktop-build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index c93666640..653df4ba0 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -156,6 +156,7 @@ jobs: needs: configure runs-on: ubuntu-latest continue-on-error: ${{matrix.allow-failure == 'yes'}} + timeout-minutes: 70 env: NAME: ${{matrix.distro}}${{matrix.version}} CACHE: ${{github.workspace}}/.cache/${{matrix.distro}}${{matrix.version}} # directory for caching docker image and ccache @@ -331,6 +332,7 @@ jobs: name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} needs: configure runs-on: ${{matrix.runner}} + timeout-minutes: 100 env: CCACHE_DIR: ${{github.workspace}}/.cache/ # Cache size over the entire repo is 10Gi: From f56b6723070fedbf3a31c63b195a341d61feff21 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sat, 11 Apr 2026 18:20:35 +0200 Subject: [PATCH 302/325] CI: Allow failing of ccache deletion step (#6799) * Don't fail cache delete step * Use `continue-on-error` * remove leftover --- .github/workflows/desktop-build.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 653df4ba0..8c3fd01de 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -208,9 +208,10 @@ jobs: --ccache "$CCACHE_SIZE" $NO_CLIENT .ci/name_build.sh - # Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342. + # Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342 - name: Delete remote compiler cache (ccache) if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit + continue-on-error: true env: GH_TOKEN: ${{ github.token }} run: gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }} @@ -451,9 +452,10 @@ jobs: TARGET_MACOS_VERSION: ${{ matrix.override_target }} run: .ci/compile.sh --server --test --vcpkg - # Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342. + # Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342 - name: Delete remote compiler cache (ccache) if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 && steps.ccache_restore.outputs.cache-hit + continue-on-error: true env: GH_TOKEN: ${{ github.token }} run: gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }} From e977f123ce47d393cc308b4575fe7444d8a6272c Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Sat, 11 Apr 2026 19:18:12 +0200 Subject: [PATCH 303/325] add ccache eviction for files older than 7 days (#6795) * add ccache eviction for files older than 7 days also clean up some of the scripts to be more internally consistent * move name build into the docker container again * remove one extra empty line [skip ci] * allow canceling concurrent builds on master for both desktop and docker * add ccache eviction age to macos as well --- .ci/compile.sh | 29 +++++++++++++++++++++++- .ci/docker.sh | 29 +++++++++++++++++++----- .github/workflows/desktop-build.yml | 33 ++++++++++++++++++---------- .github/workflows/docker-release.yml | 5 +++++ 4 files changed, 78 insertions(+), 18 deletions(-) diff --git a/.ci/compile.sh b/.ci/compile.sh index 424527f96..7ebdd6e4e 100755 --- a/.ci/compile.sh +++ b/.ci/compile.sh @@ -10,9 +10,11 @@ # --test runs tests # --debug or --release sets the build type ie CMAKE_BUILD_TYPE # --ccache [] uses ccache and shows stats, optionally provide size +# --evict-ccache runs ccache eviction based on given age after build # --dir sets the name of the build dir, default is "build" +# --cmake-generator sets CMAKE_GENERATOR as used by cmake # --target-macos-version sets the min os version - only used for macOS builds -# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_NO_CLIENT MAKE_TEST USE_CCACHE CCACHE_SIZE BUILD_DIR CMAKE_GENERATOR TARGET_MACOS_VERSION +# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_NO_CLIENT MAKE_TEST USE_CCACHE CCACHE_SIZE CCACHE_EVICTION_AGE BUILD_DIR CMAKE_GENERATOR TARGET_MACOS_VERSION # (correspond to args: --debug/--release --install --package --suffix --server --test --ccache --dir ) # exitcode: 1 for failure, 3 for invalid arguments @@ -71,6 +73,15 @@ while [[ $# != 0 ]]; do shift fi ;; + '--evict-ccache') + shift + if [[ $# == 0 ]]; then + echo "::error file=$0::--evict-ccache expects an argument" + exit 3 + fi + CCACHE_EVICTION_AGE=$1 + shift + ;; '--vcpkg') USE_VCPKG=1 shift @@ -84,6 +95,15 @@ while [[ $# != 0 ]]; do BUILD_DIR="$1" shift ;; + '--cmake-generator') + shift + if [[ $# == 0 ]]; then + echo "::error file=$0::--cmake-generator expects an argument" + exit 3 + fi + export CMAKE_GENERATOR=$1 + shift + ;; '--target-macos-version') shift if [[ $# == 0 ]]; then @@ -251,9 +271,16 @@ cmake --build . "${buildflags[@]}" echo "::endgroup::" if [[ $USE_CCACHE ]]; then + if [[ $CCACHE_EVICTION_AGE ]]; then + echo "::group::evict ccache files older than $CCACHE_EVICTION_AGE" + ccache --evict-older-than "$CCACHE_EVICTION_AGE" + echo "::endgroup::" + fi echo "::group::Show ccache stats again" ccachestatsverbose echo "::endgroup::" +elif [[ $CCACHE_EVICTION_AGE ]]; then + echo "::error file=$0::ccache eviction is enabled while ccache is disabled!" fi if [[ $RUNNER_OS == macOS ]]; then diff --git a/.ci/docker.sh b/.ci/docker.sh index 911488ecf..46112daaa 100644 --- a/.ci/docker.sh +++ b/.ci/docker.sh @@ -3,17 +3,28 @@ # This script is to be used by the ci environment from the project root directory, do not use it from somewhere else. # Creates or loads docker images to use in compilation, creates RUN function to start compilation on the docker image. -# sets the name of the docker image, these correspond to directories in .ci +# +# usage: source