diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index fb66a6ebb..08eccf1e4 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -50,13 +50,6 @@ SettingsCache &SettingsCache::instance() return *settingsCache; // returns a QT managed singleton reference } -SettingsCache::~SettingsCache() -{ - for (ReleaseChannel *releaseChannel : releaseChannels) { - delete releaseChannel; - } -} - QString SettingsCache::getDataPath() { if (isPortableBuild) { diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index 472aac44f..5f699247d 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -99,7 +99,6 @@ private: public: SettingsCache(); - ~SettingsCache() override; QString getDataPath(); QString getSettingsPath(); [[nodiscard]] QString getCachePath() const; diff --git a/cockatrice/src/interface/widgets/general/layout_containers/flow_widget.cpp b/cockatrice/src/interface/widgets/general/layout_containers/flow_widget.cpp index 6a6025ec2..caeecad86 100644 --- a/cockatrice/src/interface/widgets/general/layout_containers/flow_widget.cpp +++ b/cockatrice/src/interface/widgets/general/layout_containers/flow_widget.cpp @@ -105,9 +105,9 @@ void FlowWidget::addWidget(QWidget *widget_to_add) * * The widget is filtered for arrow-key events so keyboard navigation between * the flow items keeps working even when the flow sits inside a QScrollArea, - * which swallows arrow keys before they can reach FlowWidget::keyPressEvent. - * Only widgets added through this method are affected; anything that needs its - * own arrow keys should use plain addWidget. + * which swallows arrow keys before they can bubble up. Only widgets added + * through this method are affected; anything that needs its own arrow keys + * should use plain addWidget. * * @param widget_to_add The widget to add to the flow layout. */ @@ -207,17 +207,12 @@ QLayoutItem *FlowWidget::itemAt(int index) const return flowLayout->itemAt(index); } -void FlowWidget::keyPressEvent(QKeyEvent *event) -{ - if (moveFocus(event)) { - event->accept(); - return; - } - QWidget::keyPressEvent(event); -} - bool FlowWidget::eventFilter(QObject *watched, QEvent *event) { + // Arrow-key navigation is deliberately scoped to widgets that opted in via + // addNavigableWidget(). Handling arrow keys in keyPressEvent() as well would + // also catch the events ordinary children (QLineEdit, ...) ignore and move + // focus away from the control the user is typing in. if (event->type() == QEvent::KeyPress && moveFocus(static_cast(event))) { return true; } diff --git a/cockatrice/src/interface/widgets/general/layout_containers/flow_widget.h b/cockatrice/src/interface/widgets/general/layout_containers/flow_widget.h index dbf33d07f..66f3d9bda 100644 --- a/cockatrice/src/interface/widgets/general/layout_containers/flow_widget.h +++ b/cockatrice/src/interface/widgets/general/layout_containers/flow_widget.h @@ -51,7 +51,6 @@ public slots: protected: bool eventFilter(QObject *watched, QEvent *event) override; void resizeEvent(QResizeEvent *event) override; - void keyPressEvent(QKeyEvent *event) override; private: /// @brief Moves keyboard focus to an adjacent flow item for an arrow-key event. diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp b/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp index 2fad5212d..7c41daa3b 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp @@ -133,6 +133,10 @@ ExactCard CardDatabaseQuerier::getCard(const CardRef &cardRef) const ExactCard CardDatabaseQuerier::guessCard(const CardRef &cardRef) const { auto card = lookupCardByName(cardRef.name); + if (!card) { + return {}; + } + auto printing = cardRef.providerId.isEmpty() ? getPreferredPrinting(card) : findPrintingWithId(card, cardRef.providerId); @@ -173,10 +177,14 @@ ExactCard CardDatabaseQuerier::getCardFromSameSet(const QString &cardName, const * * @param cardInfo The CardInfo to search * @param providerId The uuid to look for - * @return The PrintingInfo, or a default-constructed PrintingInfo if not found. + * @return The PrintingInfo, or a default-constructed PrintingInfo if not found or if cardInfo is null. */ PrintingInfo CardDatabaseQuerier::findPrintingWithId(const CardInfoPtr &cardInfo, const QString &providerId) const { + if (!cardInfo) { + return PrintingInfo(); + } + for (const auto &printings : cardInfo->getSets()) { for (const auto &printing : printings) { if (printing.getUuid() == providerId) { diff --git a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp index bc137b0f6..0acc0eca0 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp @@ -43,31 +43,31 @@ SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart QueryPart <- NotQuery / SetQuery / RarityQuery / CMCQuery / FormatQuery / PowerQuery / ToughnessQuery / ColorQuery / TypeQuery / OracleQuery / FieldQuery / GenericQuery NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart -SetQuery <- ('e'/'set') SetQueryValue -SetQueryValue <- ([=:] FlexStringValue) / (<[!][=]?> FlexStringValue) / SetExpression -OracleQuery <- 'o' [:] MatcherString +SetQuery <- ('e'/'set') ws? SetQueryValue +SetQueryValue <- ([=:] ws? FlexStringValue) / (<[!][=]?> ws? FlexStringValue) / SetExpression +OracleQuery <- 'o' ws? [:] ws? MatcherString CMCQuery <- ('cmc'/'mv') ws? NumericExpression PowerQuery <- [Pp] 'ow' 'er'? ws? NumericExpression ToughnessQuery <- [Tt] 'ou' 'ghness'? ws? NumericExpression -RarityQuery <- [rR] ':' Rarity +RarityQuery <- [rR] ws? ':' ws? Rarity Rarity <- [Cc] 'ommon'? / [Uu] 'ncommon'? / [Rr] 'are'? / [Mm] 'ythic'? / [Ss] 'pecial'? / [a-zA-Z] [a-z]* -FormatQuery <- 'f' ':' Format / Legality ':' Format +FormatQuery <- 'f' ws? ':' ws? Format / Legality ws? ':' ws? Format Format <- [a-zA-Z] [a-z]* Legality <- [Ll] 'egal'? / [Bb] 'anned'? / [Rr] 'estricted' -TypeQuery <- [tT] 'ype'? [:] StringValue +TypeQuery <- [tT] 'ype'? ws? [:] ws? StringValue Color <- < [Ww] 'hite'? / [Uu] / [Bb] 'lack'? / [Rr] 'ed'? / [Gg] 'reen'? / [Bb] 'lue'? > ColorEx <- Color / [mc] -ColorQuery <- [cC] 'olor'? <[iI]?> <[:!]> ColorEx* +ColorQuery <- [cC] 'olor'? <[iI]?> ws? <[:!]> ws? ColorEx* -FieldQuery <- String [:] MatcherString / String ws? NumericExpression +FieldQuery <- String ws? [:] ws? MatcherString / String ws? NumericExpression NonDoubleQuoteUnlessEscaped <- '\\\"'. / !["]. NonSingleQuoteUnlessEscaped <- "\\\'". / ![']. diff --git a/tests/carddatabase/carddatabase_test.cpp b/tests/carddatabase/carddatabase_test.cpp index 1b48d934b..2b216d5b6 100644 --- a/tests/carddatabase/carddatabase_test.cpp +++ b/tests/carddatabase/carddatabase_test.cpp @@ -1,6 +1,7 @@ #include "libcockatrice/card/database/card_database.h" #include "libcockatrice/card/database/card_database_loader.h" #include "libcockatrice/card/database/card_database_querier.h" +#include "libcockatrice/card/printing/exact_card.h" #include "libcockatrice/card/set/card_set.h" #include "libcockatrice/card/set/card_set_list.h" #include "test_card_database_path_provider.h" @@ -29,6 +30,7 @@ #include #include #include +#include #include namespace @@ -60,6 +62,31 @@ TEST(CardDatabaseTest, LoadXml) ASSERT_EQ(NotLoaded, db->getLoadStatus()) << "Incorrect status after clear"; } +TEST(CardDatabaseTest, GuessCardWithProviderIdOnUnknownCardReturnsEmpty) +{ + // An unknown name plus a providerId used to dereference the missing card + // inside findPrintingWithId(); the lookup must come back empty instead. + NoopCardPreferenceProvider prefs; + TestCardDatabasePathProvider pathProvider; + NoopCardSetPriorityController controller; + CardDatabase db(nullptr, &prefs, &pathProvider, &controller); + + const ExactCard guessed = db.query()->guessCard({"Not a Card", "card_Not a Card_fixture"}); + + EXPECT_TRUE(guessed.isEmpty()); + EXPECT_FALSE(guessed); +} + +TEST(CardDatabaseTest, FindPrintingWithIdOnNullCardInfoReturnsEmpty) +{ + NoopCardPreferenceProvider prefs; + TestCardDatabasePathProvider pathProvider; + NoopCardSetPriorityController controller; + CardDatabase db(nullptr, &prefs, &pathProvider, &controller); + + EXPECT_TRUE(db.query()->findPrintingWithId(nullptr, "card_Not a Card_fixture").isEmpty()); +} + TEST(CardDatabaseTest, Xml4LocalizedDataRoundTrip) { NoopCardSetPriorityController controller; diff --git a/tests/carddatabase/filter_string_test.cpp b/tests/carddatabase/filter_string_test.cpp index 95225e28c..e561db590 100644 --- a/tests/carddatabase/filter_string_test.cpp +++ b/tests/carddatabase/filter_string_test.cpp @@ -99,6 +99,17 @@ QUERY(SetCodeWrongSet2, doctor, "set:cat", false) QUERY(BracketNextToUnquotedString, cat, "(o:woof OR o:meow)", true) +// Spaces around the colon are optional for all field queries (e.g. "o: flying") +QUERY(OracleSpaceAfterColon, cat, "o: meow", true) +QUERY(OracleSpaceBeforeColon, cat, "o :meow", true) +QUERY(OracleSpacesAroundColon, cat, "o : meow", true) +QUERY(OracleSpaceAfterColonNoMatch, cat, "o: woof", false) +QUERY(TypeSpaceAfterColon, cat, "t: creature", true) +QUERY(FieldSpaceAfterColon, cat, "pt: \"3/3\"", true) +QUERY(ColorSpaceAfterColon, cat, "c: g", true) +QUERY(ColorSpaceBeforeOperator, cat, "c !g", true) +QUERY(SetSpaceAfterColon, cat, "set: cat", true) + CardInfoPtr localizedCat() { CardInfoPtr localized = CardInfo::newInstance("Cat", "Meow!", false, {}, {}, {}, {}, {}); @@ -107,6 +118,22 @@ CardInfoPtr localizedCat() return localized; } +CardInfoPtr greenIdentityCard() +{ + return CardInfo::newInstance("Identity Cat", "", false, {{"colors", "G"}, {"coloridentity", "G"}}, {}, {}, {}, {}); +} + +// Whitespace belongs around the separator, never inside an operator token: "c i: g" must not +// be read as the color-identity operator "ci" (it is a generic "c" AND a field query on "i"). +TEST_F(CardQuery, ColorIdentityOperatorIsNotSplitByWhitespace) +{ + const CardData identityCard = greenIdentityCard(); + ASSERT_TRUE(FilterString("ci:g").check(identityCard)); + ASSERT_TRUE(FilterString("ci: g").check(identityCard)); + ASSERT_TRUE(FilterString("ci : g").check(identityCard)); + ASSERT_FALSE(FilterString("c i: g").check(identityCard)); +} + TEST_F(CardQuery, SearchLanguageEnglishMatchesOnlyEnglish) { const CardData localized = localizedCat(); @@ -136,6 +163,13 @@ TEST_F(CardQuery, SearchLanguageBothMatchesEitherLanguage) ASSERT_TRUE(FilterString("Kater", CardSearchLanguage{"de", SearchLanguageMode::Both}).check(localized)); } +TEST_F(CardQuery, SearchLanguageBothMatchesOracleTextWithSpaceAfterColon) +{ + const CardData localized = localizedCat(); + ASSERT_TRUE(FilterString("o: Meow", CardSearchLanguage{"de", SearchLanguageMode::Both}).check(localized)); + ASSERT_TRUE(FilterString("o: miaut", CardSearchLanguage{"de", SearchLanguageMode::Both}).check(localized)); +} + TEST_F(CardQuery, SearchLanguageIsBoundPerInstance) { const CardData localized = localizedCat();