From 4b7d1ebb59b249d767a96899549db3ac4889bd74 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 13 Mar 2025 17:02:10 -0700 Subject: [PATCH 01/44] Refactor: split card_database into two files (#5715) * make the duplicate * restore original * Refactor: split card_database into two files --- cockatrice/CMakeLists.txt | 1 + cockatrice/resources/config/qtlogging.ini | 1 + cockatrice/src/game/cards/card_database.cpp | 410 +--------------- cockatrice/src/game/cards/card_database.h | 479 +------------------ cockatrice/src/game/cards/card_info.cpp | 418 +++++++++++++++++ cockatrice/src/game/cards/card_info.h | 491 ++++++++++++++++++++ dbconverter/CMakeLists.txt | 1 + oracle/CMakeLists.txt | 1 + tests/carddatabase/CMakeLists.txt | 2 + 9 files changed, 918 insertions(+), 886 deletions(-) create mode 100644 cockatrice/src/game/cards/card_info.cpp create mode 100644 cockatrice/src/game/cards/card_info.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index ca16d3d15..cfe0430e8 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -26,6 +26,7 @@ set(cockatrice_SOURCES src/client/ui/widgets/cards/card_info_text_widget.cpp src/client/ui/widgets/cards/card_info_display_widget.cpp src/client/ui/widgets/cards/card_size_widget.cpp + src/game/cards/card_info.cpp src/game/cards/card_item.cpp src/game/cards/card_list.cpp src/game/zones/card_zone.cpp diff --git a/cockatrice/resources/config/qtlogging.ini b/cockatrice/resources/config/qtlogging.ini index b141cbd00..378b962b7 100644 --- a/cockatrice/resources/config/qtlogging.ini +++ b/cockatrice/resources/config/qtlogging.ini @@ -44,6 +44,7 @@ # cockatrice_xml.* = false # cockatrice_xml.xml_3_parser = false # cockatrice_xml.xml_4_parser = false +# card_info = false # card_list = false # stack_zone = false diff --git a/cockatrice/src/game/cards/card_database.cpp b/cockatrice/src/game/cards/card_database.cpp index c7e2f06f2..b22bc2891 100644 --- a/cockatrice/src/game/cards/card_database.cpp +++ b/cockatrice/src/game/cards/card_database.cpp @@ -1,10 +1,8 @@ #include "card_database.h" -#include "../../client/network/spoiler_background_updater.h" #include "../../client/ui/picture_loader/picture_loader.h" #include "../../settings/cache_settings.h" #include "../../utility/card_set_comparator.h" -#include "../game_specific_terms.h" #include "./card_database_parser/cockatrice_xml_3.h" #include "./card_database_parser/cockatrice_xml_4.h" @@ -20,351 +18,6 @@ const char *CardDatabase::TOKENS_SETNAME = "TK"; -CardSet::CardSet(const QString &_shortName, - const QString &_longName, - const QString &_setType, - const QDate &_releaseDate, - const CardSet::Priority _priority) - : shortName(_shortName), longName(_longName), releaseDate(_releaseDate), setType(_setType), priority(_priority) -{ - loadSetOptions(); -} - -CardSetPtr CardSet::newInstance(const QString &_shortName, - const QString &_longName, - const QString &_setType, - const QDate &_releaseDate, - const Priority _priority) -{ - CardSetPtr ptr(new CardSet(_shortName, _longName, _setType, _releaseDate, _priority)); - // ptr->setSmartPointer(ptr); - return ptr; -} - -QString CardSet::getCorrectedShortName() const -{ - // For Windows machines. - QSet invalidFileNames; - invalidFileNames << "CON" - << "PRN" - << "AUX" - << "NUL" - << "COM1" - << "COM2" - << "COM3" - << "COM4" - << "COM5" - << "COM6" - << "COM7" - << "COM8" - << "COM9" - << "LPT1" - << "LPT2" - << "LPT3" - << "LPT4" - << "LPT5" - << "LPT6" - << "LPT7" - << "LPT8" - << "LPT9"; - - return invalidFileNames.contains(shortName) ? shortName + "_" : shortName; -} - -void CardSet::loadSetOptions() -{ - sortKey = SettingsCache::instance().cardDatabase().getSortKey(shortName); - enabled = SettingsCache::instance().cardDatabase().isEnabled(shortName); - isknown = SettingsCache::instance().cardDatabase().isKnown(shortName); -} - -void CardSet::setSortKey(unsigned int _sortKey) -{ - sortKey = _sortKey; - SettingsCache::instance().cardDatabase().setSortKey(shortName, _sortKey); -} - -void CardSet::setEnabled(bool _enabled) -{ - enabled = _enabled; - SettingsCache::instance().cardDatabase().setEnabled(shortName, _enabled); -} - -void CardSet::setIsKnown(bool _isknown) -{ - isknown = _isknown; - SettingsCache::instance().cardDatabase().setIsKnown(shortName, _isknown); -} - -class SetList::KeyCompareFunctor -{ -public: - inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const - { - if (a.isNull() || b.isNull()) { - qCDebug(CardDatabaseLog) << "SetList::KeyCompareFunctor a or b is null"; - return false; - } - - return a->getSortKey() < b->getSortKey(); - } -}; - -void SetList::sortByKey() -{ - std::sort(begin(), end(), KeyCompareFunctor()); -} - -int SetList::getEnabledSetsNum() -{ - int num = 0; - for (int i = 0; i < size(); ++i) { - CardSetPtr set = at(i); - if (set && set->getEnabled()) { - ++num; - } - } - return num; -} - -int SetList::getUnknownSetsNum() -{ - int num = 0; - for (int i = 0; i < size(); ++i) { - CardSetPtr set = at(i); - if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { - ++num; - } - } - return num; -} - -QStringList SetList::getUnknownSetsNames() -{ - QStringList sets = QStringList(); - for (int i = 0; i < size(); ++i) { - CardSetPtr set = at(i); - if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { - sets << set->getShortName(); - } - } - return sets; -} - -void SetList::enableAllUnknown() -{ - for (int i = 0; i < size(); ++i) { - CardSetPtr set = at(i); - if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { - set->setIsKnown(true); - set->setEnabled(true); - } else if (set && set->getIsKnownIgnored() && !set->getEnabled()) { - set->setEnabled(true); - } - } -} - -void SetList::enableAll() -{ - for (int i = 0; i < size(); ++i) { - CardSetPtr set = at(i); - - if (set == nullptr) { - qCDebug(CardDatabaseLog) << "enabledAll has null"; - continue; - } - - if (!set->getIsKnownIgnored()) { - set->setIsKnown(true); - } - - set->setEnabled(true); - } -} - -void SetList::markAllAsKnown() -{ - for (int i = 0; i < size(); ++i) { - CardSetPtr set = at(i); - if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { - set->setIsKnown(true); - set->setEnabled(false); - } else if (set && set->getIsKnownIgnored() && !set->getEnabled()) { - set->setEnabled(true); - } - } -} - -void SetList::guessSortKeys() -{ - defaultSort(); - for (int i = 0; i < size(); ++i) { - CardSetPtr set = at(i); - if (set.isNull()) { - qCDebug(CardDatabaseLog) << "guessSortKeys set is null"; - continue; - } - set->setSortKey(i); - } -} - -void SetList::defaultSort() -{ - std::sort(begin(), end(), [](const CardSetPtr &a, const CardSetPtr &b) { - // Sort by priority, then by release date, then by short name - if (a->getPriority() != b->getPriority()) { - return a->getPriority() < b->getPriority(); // lowest first - } else if (a->getReleaseDate() != b->getReleaseDate()) { - return a->getReleaseDate() > b->getReleaseDate(); // most recent first - } else { - return a->getShortName() < b->getShortName(); // alphabetically - } - }); -} - -CardInfoPerSet::CardInfoPerSet(const CardSetPtr &_set) : set(_set) -{ -} - -CardInfo::CardInfo(const QString &_name, - const QString &_text, - bool _isToken, - QVariantHash _properties, - const QList &_relatedCards, - const QList &_reverseRelatedCards, - CardInfoPerSetMap _sets, - bool _cipt, - bool _landscapeOrientation, - int _tableRow, - bool _upsideDownArt) - : name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards), - reverseRelatedCards(_reverseRelatedCards), sets(std::move(_sets)), cipt(_cipt), - landscapeOrientation(_landscapeOrientation), tableRow(_tableRow), upsideDownArt(_upsideDownArt) -{ - pixmapCacheKey = QLatin1String("card_") + name; - simpleName = CardInfo::simplifyName(name); - - refreshCachedSetNames(); -} - -CardInfo::~CardInfo() -{ - PictureLoader::clearPixmapCache(smartThis); -} - -CardInfoPtr CardInfo::newInstance(const QString &_name) -{ - return newInstance(_name, QString(), false, QVariantHash(), QList(), QList(), - CardInfoPerSetMap(), false, false, 0, false); -} - -CardInfoPtr CardInfo::newInstance(const QString &_name, - const QString &_text, - bool _isToken, - QVariantHash _properties, - const QList &_relatedCards, - const QList &_reverseRelatedCards, - CardInfoPerSetMap _sets, - bool _cipt, - bool _landscapeOrientation, - int _tableRow, - bool _upsideDownArt) -{ - CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_properties), _relatedCards, _reverseRelatedCards, - _sets, _cipt, _landscapeOrientation, _tableRow, _upsideDownArt)); - ptr->setSmartPointer(ptr); - - for (const auto &cardInfoPerSetList : _sets) { - for (const CardInfoPerSet &set : cardInfoPerSetList) { - set.getPtr()->append(ptr); - break; - } - } - - return ptr; -} - -QString CardInfo::getCorrectedName() const -{ - // remove all the characters reserved in windows file paths, - // other oses only disallow a subset of these so it covers all - static const QRegularExpression rmrx(R"(( // |[*<>:"\\?\x00-\x08\x10-\x1f]))"); - static const QRegularExpression spacerx(R"([/\x09-\x0f])"); - static const QString space(' '); - QString result = name; - // Fire // Ice, Circle of Protection: Red, "Ach! Hans, Run!", Who/What/When/Where/Why, Question Elemental? - return result.remove(rmrx).replace(spacerx, space); -} - -void CardInfo::addToSet(const CardSetPtr &_set, const CardInfoPerSet _info) -{ - _set->append(smartThis); - sets[_set->getShortName()].append(_info); - - refreshCachedSetNames(); -} - -void CardInfo::combineLegalities(const QVariantHash &props) -{ - QHashIterator it(props); - while (it.hasNext()) { - it.next(); - if (it.key().startsWith("format-")) { - smartThis->setProperty(it.key(), it.value().toString()); - } - } -} - -void CardInfo::refreshCachedSetNames() -{ - QStringList setList; - // update the cached list of set names - for (const auto &cardInfoPerSetList : sets) { - for (const auto &set : cardInfoPerSetList) { - if (set.getPtr()->getEnabled()) { - setList << set.getPtr()->getShortName(); - } - break; - } - } - setsNames = setList.join(", "); -} - -QString CardInfo::simplifyName(const QString &name) -{ - static const QRegularExpression spaceOrSplit("(\\s+|\\/\\/.*)"); - static const QRegularExpression nonAlnum("[^a-z0-9]"); - - QString simpleName = name.toLower(); - - // remove spaces and right halves of split cards - simpleName.remove(spaceOrSplit); - - // So Aetherling would work, but not Ætherling since 'Æ' would get replaced - // with nothing. - simpleName.replace("æ", "ae"); - - // Replace Jötun Grunt with Jotun Grunt. - simpleName = simpleName.normalized(QString::NormalizationForm_KD); - - // remove all non alphanumeric characters from the name - simpleName.remove(nonAlnum); - return simpleName; -} - -const QChar CardInfo::getColorChar() const -{ - QString colors = getColors(); - switch (colors.size()) { - case 0: - return QChar(); - case 1: - return colors.at(0); - default: - return QChar('m'); - } -} - CardDatabase::CardDatabase(QObject *parent) : QObject(parent), loadStatus(NotLoaded) { qRegisterMetaType("CardInfoPtr"); @@ -883,65 +536,4 @@ bool CardDatabase::saveCustomTokensToFile() availableParsers.first()->saveToFile(tmpSets, tmpCards, fileName); return true; -} - -CardRelation::CardRelation(const QString &_name, - AttachType _attachType, - bool _isCreateAllExclusion, - bool _isVariableCount, - int _defaultCount, - bool _isPersistent) - : name(_name), attachType(_attachType), isCreateAllExclusion(_isCreateAllExclusion), - isVariableCount(_isVariableCount), defaultCount(_defaultCount), isPersistent(_isPersistent) -{ -} - -void CardInfo::resetReverseRelatedCards2Me() -{ - for (CardRelation *cardRelation : this->getReverseRelatedCards2Me()) { - cardRelation->deleteLater(); - } - reverseRelatedCardsToMe = QList(); -} - -// Back-compatibility methods. Remove ASAP -const QString CardInfo::getCardType() const -{ - return getProperty(Mtg::CardType); -} -void CardInfo::setCardType(const QString &value) -{ - setProperty(Mtg::CardType, value); -} -const QString CardInfo::getCmc() const -{ - return getProperty(Mtg::ConvertedManaCost); -} -const QString CardInfo::getColors() const -{ - return getProperty(Mtg::Colors); -} -void CardInfo::setColors(const QString &value) -{ - setProperty(Mtg::Colors, value); -} -const QString CardInfo::getLoyalty() const -{ - return getProperty(Mtg::Loyalty); -} -const QString CardInfo::getMainCardType() const -{ - return getProperty(Mtg::MainCardType); -} -const QString CardInfo::getManaCost() const -{ - return getProperty(Mtg::ManaCost); -} -const QString CardInfo::getPowTough() const -{ - return getProperty(Mtg::PowTough); -} -void CardInfo::setPowTough(const QString &value) -{ - setProperty(Mtg::PowTough, value); -} +} \ No newline at end of file diff --git a/cockatrice/src/game/cards/card_database.h b/cockatrice/src/game/cards/card_database.h index 12b4a06fe..236278b30 100644 --- a/cockatrice/src/game/cards/card_database.h +++ b/cockatrice/src/game/cards/card_database.h @@ -1,16 +1,14 @@ #ifndef CARDDATABASE_H #define CARDDATABASE_H +#include "card_info.h" + #include #include #include #include #include -#include -#include -#include #include -#include #include #include @@ -18,406 +16,8 @@ inline Q_LOGGING_CATEGORY(CardDatabaseLog, "card_database"); inline Q_LOGGING_CATEGORY(CardDatabaseLoadingLog, "card_database.loading"); inline Q_LOGGING_CATEGORY(CardDatabaseLoadingSuccessOrFailureLog, "card_database.loading.success_or_failure"); -class CardDatabase; -class CardInfo; -class CardInfoPerSet; -class CardSet; -class CardRelation; class ICardDatabaseParser; -typedef QMap QStringMap; -typedef QSharedPointer CardInfoPtr; -typedef QSharedPointer CardSetPtr; -typedef QMap> CardInfoPerSetMap; - -Q_DECLARE_METATYPE(CardInfoPtr) - -class CardSet : public QList -{ -public: - enum Priority - { - PriorityFallback = 0, - PriorityPrimary = 10, - PrioritySecondary = 20, - PriorityReprint = 30, - PriorityOther = 40, - PriorityLowest = 100, - }; - -private: - QString shortName, longName; - unsigned int sortKey; - QDate releaseDate; - QString setType; - Priority priority; - bool enabled, isknown; - -public: - explicit CardSet(const QString &_shortName = QString(), - const QString &_longName = QString(), - const QString &_setType = QString(), - const QDate &_releaseDate = QDate(), - const Priority _priority = PriorityFallback); - static CardSetPtr newInstance(const QString &_shortName = QString(), - const QString &_longName = QString(), - const QString &_setType = QString(), - const QDate &_releaseDate = QDate(), - const Priority _priority = PriorityFallback); - QString getCorrectedShortName() const; - QString getShortName() const - { - return shortName; - } - QString getLongName() const - { - return longName; - } - QString getSetType() const - { - return setType; - } - QDate getReleaseDate() const - { - return releaseDate; - } - Priority getPriority() const - { - return priority; - } - void setLongName(const QString &_longName) - { - longName = _longName; - } - void setSetType(const QString &_setType) - { - setType = _setType; - } - void setReleaseDate(const QDate &_releaseDate) - { - releaseDate = _releaseDate; - } - void setPriority(const Priority _priority) - { - priority = _priority; - } - - void loadSetOptions(); - int getSortKey() const - { - return sortKey; - } - void setSortKey(unsigned int _sortKey); - bool getEnabled() const - { - return enabled; - } - void setEnabled(bool _enabled); - bool getIsKnown() const - { - return isknown; - } - void setIsKnown(bool _isknown); - - // Determine incomplete sets. - bool getIsKnownIgnored() const - { - return longName.length() + setType.length() + releaseDate.toString().length() == 0; - } -}; - -class SetList : public QList -{ -private: - class KeyCompareFunctor; - -public: - void sortByKey(); - void guessSortKeys(); - void enableAllUnknown(); - void enableAll(); - void markAllAsKnown(); - int getEnabledSetsNum(); - int getUnknownSetsNum(); - QStringList getUnknownSetsNames(); - void defaultSort(); -}; - -class CardInfoPerSet -{ -public: - explicit CardInfoPerSet(const CardSetPtr &_set = QSharedPointer(nullptr)); - ~CardInfoPerSet() = default; - - bool operator==(const CardInfoPerSet &other) const - { - return this->set == other.set && this->properties == other.properties; - } - -private: - CardSetPtr set; - // per-set card properties; - QVariantHash properties; - -public: - const CardSetPtr getPtr() const - { - return set; - } - const QStringList getProperties() const - { - return properties.keys(); - } - const QString getProperty(const QString &propertyName) const - { - return properties.value(propertyName).toString(); - } - void setProperty(const QString &_name, const QString &_value) - { - properties.insert(_name, _value); - } -}; - -class CardInfo : public QObject -{ - Q_OBJECT -private: - CardInfoPtr smartThis; - // The card name - QString name; - // The name without punctuation or capitalization, for better card name recognition. - QString simpleName; - // The key used to identify this card in the cache - QString pixmapCacheKey; - // card text - QString text; - // whether this is not a "real" card but a token - bool isToken; - // basic card properties; common for all the sets - QVariantHash properties; - // the cards i'm related to - QList relatedCards; - // the card i'm reverse-related to - QList reverseRelatedCards; - // the cards thare are reverse-related to me - QList reverseRelatedCardsToMe; - // card sets - CardInfoPerSetMap sets; - // cached set names - QString setsNames; - // positioning properties; used by UI - bool cipt; - bool landscapeOrientation; - int tableRow; - bool upsideDownArt; - -public: - explicit CardInfo(const QString &_name, - const QString &_text, - bool _isToken, - QVariantHash _properties, - const QList &_relatedCards, - const QList &_reverseRelatedCards, - CardInfoPerSetMap _sets, - bool _cipt, - bool _landscapeOrientation, - int _tableRow, - bool _upsideDownArt); - CardInfo(const CardInfo &other) - : QObject(other.parent()), name(other.name), simpleName(other.simpleName), pixmapCacheKey(other.pixmapCacheKey), - text(other.text), isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards), - reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe), - sets(other.sets), setsNames(other.setsNames), cipt(other.cipt), - landscapeOrientation(other.landscapeOrientation), tableRow(other.tableRow), upsideDownArt(other.upsideDownArt) - { - } - ~CardInfo() override; - - static CardInfoPtr newInstance(const QString &_name); - - static CardInfoPtr newInstance(const QString &_name, - const QString &_text, - bool _isToken, - QVariantHash _properties, - const QList &_relatedCards, - const QList &_reverseRelatedCards, - CardInfoPerSetMap _sets, - bool _cipt, - bool _landscapeOrientation, - int _tableRow, - bool _upsideDownArt); - - CardInfoPtr clone() const - { - // Use the copy constructor to create a new instance - CardInfoPtr newCardInfo = CardInfoPtr(new CardInfo(*this)); - newCardInfo->setSmartPointer(newCardInfo); // Set the smart pointer for the new instance - return newCardInfo; - } - - void setSmartPointer(CardInfoPtr _ptr) - { - smartThis = std::move(_ptr); - } - - // basic properties - inline const QString &getName() const - { - return name; - } - const QString &getSimpleName() const - { - return simpleName; - } - void setPixmapCacheKey(QString _pixmapCacheKey) - { - pixmapCacheKey = _pixmapCacheKey; - } - const QString &getPixmapCacheKey() const - { - return pixmapCacheKey; - } - - const QString &getText() const - { - return text; - } - void setText(const QString &_text) - { - text = _text; - emit cardInfoChanged(smartThis); - } - - bool getIsToken() const - { - return isToken; - } - const QStringList getProperties() const - { - return properties.keys(); - } - const QString getProperty(const QString &propertyName) const - { - return properties.value(propertyName).toString(); - } - void setProperty(const QString &_name, const QString &_value) - { - properties.insert(_name, _value); - emit cardInfoChanged(smartThis); - } - bool hasProperty(const QString &propertyName) const - { - return properties.contains(propertyName); - } - const CardInfoPerSetMap &getSets() const - { - return sets; - } - const QString &getSetsNames() const - { - return setsNames; - } - const QString getSetProperty(const QString &setName, const QString &propertyName) const - { - if (!sets.contains(setName)) - return ""; - - for (const auto &set : sets[setName]) { - if (QLatin1String("card_") + this->getName() + QString("_") + QString(set.getProperty("uuid")) == - this->getPixmapCacheKey()) { - return set.getProperty(propertyName); - } - } - - return sets[setName][0].getProperty(propertyName); - } - - // related cards - const QList &getRelatedCards() const - { - return relatedCards; - } - const QList &getReverseRelatedCards() const - { - return reverseRelatedCards; - } - const QList &getReverseRelatedCards2Me() const - { - return reverseRelatedCardsToMe; - } - const QList getAllRelatedCards() const - { - QList result; - result.append(getRelatedCards()); - result.append(getReverseRelatedCards2Me()); - return result; - } - void resetReverseRelatedCards2Me(); - void addReverseRelatedCards2Me(CardRelation *cardRelation) - { - reverseRelatedCardsToMe.append(cardRelation); - } - - // positioning - bool getCipt() const - { - return cipt; - } - 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; - - // Back-compatibility methods. Remove ASAP - const QString getCardType() const; - void setCardType(const QString &value); - const QString getCmc() const; - 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; - void setPowTough(const QString &value); - - // methods using per-set properties - QString getCustomPicURL(const QString &set) const - { - return getSetProperty(set, "picurl"); - } - QString getCorrectedName() const; - void addToSet(const CardSetPtr &_set, CardInfoPerSet _info = CardInfoPerSet()); - void combineLegalities(const QVariantHash &props); - void emitPixmapUpdated() - { - emit pixmapUpdated(); - } - void refreshCachedSetNames(); - - /** - * Simplify a name to have no punctuation and lowercase all letters, for - * less strict name-matching. - */ - static QString simplifyName(const QString &name); - -signals: - void pixmapUpdated(); - void cardInfoChanged(CardInfoPtr card); -}; - enum LoadStatus { Ok, @@ -523,79 +123,4 @@ signals: void cardRemoved(CardInfoPtr card); }; -class CardRelation : public QObject -{ - Q_OBJECT -public: - enum AttachType - { - DoesNotAttach = 0, - AttachTo = 1, - TransformInto = 2, - }; - -private: - QString name; - AttachType attachType; - bool isCreateAllExclusion; - bool isVariableCount; - int defaultCount; - bool isPersistent; - -public: - explicit CardRelation(const QString &_name = QString(), - AttachType _attachType = DoesNotAttach, - bool _isCreateAllExclusion = false, - bool _isVariableCount = false, - int _defaultCount = 1, - bool _isPersistent = false); - - inline const QString &getName() const - { - return name; - } - AttachType getAttachType() const - { - return attachType; - } - bool getDoesAttach() const - { - return attachType != DoesNotAttach; - } - bool getDoesTransform() const - { - return attachType == TransformInto; - } - QString getAttachTypeAsString() const - { - switch (attachType) { - case AttachTo: - return "attach"; - case TransformInto: - return "transform"; - default: - return ""; - } - } - bool getCanCreateAnother() const - { - return !getDoesAttach(); - } - bool getIsCreateAllExclusion() const - { - return isCreateAllExclusion; - } - bool getIsVariable() const - { - return isVariableCount; - } - int getDefaultCount() const - { - return defaultCount; - } - bool getIsPersistent() const - { - return isPersistent; - } -}; #endif diff --git a/cockatrice/src/game/cards/card_info.cpp b/cockatrice/src/game/cards/card_info.cpp new file mode 100644 index 000000000..c4e00638b --- /dev/null +++ b/cockatrice/src/game/cards/card_info.cpp @@ -0,0 +1,418 @@ +#include "card_info.h" + +#include "../../client/ui/picture_loader/picture_loader.h" +#include "../../settings/cache_settings.h" +#include "../game_specific_terms.h" + +#include +#include +#include +#include +#include +#include + +CardSet::CardSet(const QString &_shortName, + const QString &_longName, + const QString &_setType, + const QDate &_releaseDate, + const CardSet::Priority _priority) + : shortName(_shortName), longName(_longName), releaseDate(_releaseDate), setType(_setType), priority(_priority) +{ + loadSetOptions(); +} + +CardSetPtr CardSet::newInstance(const QString &_shortName, + const QString &_longName, + const QString &_setType, + const QDate &_releaseDate, + const Priority _priority) +{ + CardSetPtr ptr(new CardSet(_shortName, _longName, _setType, _releaseDate, _priority)); + // ptr->setSmartPointer(ptr); + return ptr; +} + +QString CardSet::getCorrectedShortName() const +{ + // For Windows machines. + QSet invalidFileNames; + invalidFileNames << "CON" + << "PRN" + << "AUX" + << "NUL" + << "COM1" + << "COM2" + << "COM3" + << "COM4" + << "COM5" + << "COM6" + << "COM7" + << "COM8" + << "COM9" + << "LPT1" + << "LPT2" + << "LPT3" + << "LPT4" + << "LPT5" + << "LPT6" + << "LPT7" + << "LPT8" + << "LPT9"; + + return invalidFileNames.contains(shortName) ? shortName + "_" : shortName; +} + +void CardSet::loadSetOptions() +{ + sortKey = SettingsCache::instance().cardDatabase().getSortKey(shortName); + enabled = SettingsCache::instance().cardDatabase().isEnabled(shortName); + isknown = SettingsCache::instance().cardDatabase().isKnown(shortName); +} + +void CardSet::setSortKey(unsigned int _sortKey) +{ + sortKey = _sortKey; + SettingsCache::instance().cardDatabase().setSortKey(shortName, _sortKey); +} + +void CardSet::setEnabled(bool _enabled) +{ + enabled = _enabled; + SettingsCache::instance().cardDatabase().setEnabled(shortName, _enabled); +} + +void CardSet::setIsKnown(bool _isknown) +{ + isknown = _isknown; + SettingsCache::instance().cardDatabase().setIsKnown(shortName, _isknown); +} + +class SetList::KeyCompareFunctor +{ +public: + inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const + { + if (a.isNull() || b.isNull()) { + qCDebug(CardInfoLog) << "SetList::KeyCompareFunctor a or b is null"; + return false; + } + + return a->getSortKey() < b->getSortKey(); + } +}; + +void SetList::sortByKey() +{ + std::sort(begin(), end(), KeyCompareFunctor()); +} + +int SetList::getEnabledSetsNum() +{ + int num = 0; + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set && set->getEnabled()) { + ++num; + } + } + return num; +} + +int SetList::getUnknownSetsNum() +{ + int num = 0; + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { + ++num; + } + } + return num; +} + +QStringList SetList::getUnknownSetsNames() +{ + QStringList sets = QStringList(); + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { + sets << set->getShortName(); + } + } + return sets; +} + +void SetList::enableAllUnknown() +{ + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { + set->setIsKnown(true); + set->setEnabled(true); + } else if (set && set->getIsKnownIgnored() && !set->getEnabled()) { + set->setEnabled(true); + } + } +} + +void SetList::enableAll() +{ + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + + if (set == nullptr) { + qCDebug(CardInfoLog) << "enabledAll has null"; + continue; + } + + if (!set->getIsKnownIgnored()) { + set->setIsKnown(true); + } + + set->setEnabled(true); + } +} + +void SetList::markAllAsKnown() +{ + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { + set->setIsKnown(true); + set->setEnabled(false); + } else if (set && set->getIsKnownIgnored() && !set->getEnabled()) { + set->setEnabled(true); + } + } +} + +void SetList::guessSortKeys() +{ + defaultSort(); + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set.isNull()) { + qCDebug(CardInfoLog) << "guessSortKeys set is null"; + continue; + } + set->setSortKey(i); + } +} + +void SetList::defaultSort() +{ + std::sort(begin(), end(), [](const CardSetPtr &a, const CardSetPtr &b) { + // Sort by priority, then by release date, then by short name + if (a->getPriority() != b->getPriority()) { + return a->getPriority() < b->getPriority(); // lowest first + } else if (a->getReleaseDate() != b->getReleaseDate()) { + return a->getReleaseDate() > b->getReleaseDate(); // most recent first + } else { + return a->getShortName() < b->getShortName(); // alphabetically + } + }); +} + +CardInfoPerSet::CardInfoPerSet(const CardSetPtr &_set) : set(_set) +{ +} + +CardInfo::CardInfo(const QString &_name, + const QString &_text, + bool _isToken, + QVariantHash _properties, + const QList &_relatedCards, + const QList &_reverseRelatedCards, + CardInfoPerSetMap _sets, + bool _cipt, + bool _landscapeOrientation, + int _tableRow, + bool _upsideDownArt) + : name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards), + reverseRelatedCards(_reverseRelatedCards), sets(std::move(_sets)), cipt(_cipt), + landscapeOrientation(_landscapeOrientation), tableRow(_tableRow), upsideDownArt(_upsideDownArt) +{ + pixmapCacheKey = QLatin1String("card_") + name; + simpleName = CardInfo::simplifyName(name); + + refreshCachedSetNames(); +} + +CardInfo::~CardInfo() +{ + PictureLoader::clearPixmapCache(smartThis); +} + +CardInfoPtr CardInfo::newInstance(const QString &_name) +{ + return newInstance(_name, QString(), false, QVariantHash(), QList(), QList(), + CardInfoPerSetMap(), false, false, 0, false); +} + +CardInfoPtr CardInfo::newInstance(const QString &_name, + const QString &_text, + bool _isToken, + QVariantHash _properties, + const QList &_relatedCards, + const QList &_reverseRelatedCards, + CardInfoPerSetMap _sets, + bool _cipt, + bool _landscapeOrientation, + int _tableRow, + bool _upsideDownArt) +{ + CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_properties), _relatedCards, _reverseRelatedCards, + _sets, _cipt, _landscapeOrientation, _tableRow, _upsideDownArt)); + ptr->setSmartPointer(ptr); + + for (const auto &cardInfoPerSetList : _sets) { + for (const CardInfoPerSet &set : cardInfoPerSetList) { + set.getPtr()->append(ptr); + break; + } + } + + return ptr; +} + +QString CardInfo::getCorrectedName() const +{ + // remove all the characters reserved in windows file paths, + // other oses only disallow a subset of these so it covers all + static const QRegularExpression rmrx(R"(( // |[*<>:"\\?\x00-\x08\x10-\x1f]))"); + static const QRegularExpression spacerx(R"([/\x09-\x0f])"); + static const QString space(' '); + QString result = name; + // Fire // Ice, Circle of Protection: Red, "Ach! Hans, Run!", Who/What/When/Where/Why, Question Elemental? + return result.remove(rmrx).replace(spacerx, space); +} + +void CardInfo::addToSet(const CardSetPtr &_set, const CardInfoPerSet _info) +{ + _set->append(smartThis); + sets[_set->getShortName()].append(_info); + + refreshCachedSetNames(); +} + +void CardInfo::combineLegalities(const QVariantHash &props) +{ + QHashIterator it(props); + while (it.hasNext()) { + it.next(); + if (it.key().startsWith("format-")) { + smartThis->setProperty(it.key(), it.value().toString()); + } + } +} + +void CardInfo::refreshCachedSetNames() +{ + QStringList setList; + // update the cached list of set names + for (const auto &cardInfoPerSetList : sets) { + for (const auto &set : cardInfoPerSetList) { + if (set.getPtr()->getEnabled()) { + setList << set.getPtr()->getShortName(); + } + break; + } + } + setsNames = setList.join(", "); +} + +QString CardInfo::simplifyName(const QString &name) +{ + static const QRegularExpression spaceOrSplit("(\\s+|\\/\\/.*)"); + static const QRegularExpression nonAlnum("[^a-z0-9]"); + + QString simpleName = name.toLower(); + + // remove spaces and right halves of split cards + simpleName.remove(spaceOrSplit); + + // So Aetherling would work, but not Ætherling since 'Æ' would get replaced + // with nothing. + simpleName.replace("æ", "ae"); + + // Replace Jötun Grunt with Jotun Grunt. + simpleName = simpleName.normalized(QString::NormalizationForm_KD); + + // remove all non alphanumeric characters from the name + simpleName.remove(nonAlnum); + return simpleName; +} + +const QChar CardInfo::getColorChar() const +{ + QString colors = getColors(); + switch (colors.size()) { + case 0: + return QChar(); + case 1: + return colors.at(0); + default: + return QChar('m'); + } +} + +CardRelation::CardRelation(const QString &_name, + AttachType _attachType, + bool _isCreateAllExclusion, + bool _isVariableCount, + int _defaultCount, + bool _isPersistent) + : name(_name), attachType(_attachType), isCreateAllExclusion(_isCreateAllExclusion), + isVariableCount(_isVariableCount), defaultCount(_defaultCount), isPersistent(_isPersistent) +{ +} + +void CardInfo::resetReverseRelatedCards2Me() +{ + for (CardRelation *cardRelation : this->getReverseRelatedCards2Me()) { + cardRelation->deleteLater(); + } + reverseRelatedCardsToMe = QList(); +} + +// Back-compatibility methods. Remove ASAP +const QString CardInfo::getCardType() const +{ + return getProperty(Mtg::CardType); +} +void CardInfo::setCardType(const QString &value) +{ + setProperty(Mtg::CardType, value); +} +const QString CardInfo::getCmc() const +{ + return getProperty(Mtg::ConvertedManaCost); +} +const QString CardInfo::getColors() const +{ + return getProperty(Mtg::Colors); +} +void CardInfo::setColors(const QString &value) +{ + setProperty(Mtg::Colors, value); +} +const QString CardInfo::getLoyalty() const +{ + return getProperty(Mtg::Loyalty); +} +const QString CardInfo::getMainCardType() const +{ + return getProperty(Mtg::MainCardType); +} +const QString CardInfo::getManaCost() const +{ + return getProperty(Mtg::ManaCost); +} +const QString CardInfo::getPowTough() const +{ + return getProperty(Mtg::PowTough); +} +void CardInfo::setPowTough(const QString &value) +{ + setProperty(Mtg::PowTough, value); +} diff --git a/cockatrice/src/game/cards/card_info.h b/cockatrice/src/game/cards/card_info.h new file mode 100644 index 000000000..9d06bc5f6 --- /dev/null +++ b/cockatrice/src/game/cards/card_info.h @@ -0,0 +1,491 @@ +#ifndef CARD_INFO_H +#define CARD_INFO_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +inline Q_LOGGING_CATEGORY(CardInfoLog, "card_info"); + +class CardInfo; +class CardInfoPerSet; +class CardSet; +class CardRelation; +class ICardDatabaseParser; + +typedef QMap QStringMap; +typedef QSharedPointer CardInfoPtr; +typedef QSharedPointer CardSetPtr; +typedef QMap> CardInfoPerSetMap; + +Q_DECLARE_METATYPE(CardInfoPtr) + +class CardSet : public QList +{ +public: + enum Priority + { + PriorityFallback = 0, + PriorityPrimary = 10, + PrioritySecondary = 20, + PriorityReprint = 30, + PriorityOther = 40, + PriorityLowest = 100, + }; + +private: + QString shortName, longName; + unsigned int sortKey; + QDate releaseDate; + QString setType; + Priority priority; + bool enabled, isknown; + +public: + explicit CardSet(const QString &_shortName = QString(), + const QString &_longName = QString(), + const QString &_setType = QString(), + const QDate &_releaseDate = QDate(), + const Priority _priority = PriorityFallback); + static CardSetPtr newInstance(const QString &_shortName = QString(), + const QString &_longName = QString(), + const QString &_setType = QString(), + const QDate &_releaseDate = QDate(), + const Priority _priority = PriorityFallback); + QString getCorrectedShortName() const; + QString getShortName() const + { + return shortName; + } + QString getLongName() const + { + return longName; + } + QString getSetType() const + { + return setType; + } + QDate getReleaseDate() const + { + return releaseDate; + } + Priority getPriority() const + { + return priority; + } + void setLongName(const QString &_longName) + { + longName = _longName; + } + void setSetType(const QString &_setType) + { + setType = _setType; + } + void setReleaseDate(const QDate &_releaseDate) + { + releaseDate = _releaseDate; + } + void setPriority(const Priority _priority) + { + priority = _priority; + } + + void loadSetOptions(); + int getSortKey() const + { + return sortKey; + } + void setSortKey(unsigned int _sortKey); + bool getEnabled() const + { + return enabled; + } + void setEnabled(bool _enabled); + bool getIsKnown() const + { + return isknown; + } + void setIsKnown(bool _isknown); + + // Determine incomplete sets. + bool getIsKnownIgnored() const + { + return longName.length() + setType.length() + releaseDate.toString().length() == 0; + } +}; + +class SetList : public QList +{ +private: + class KeyCompareFunctor; + +public: + void sortByKey(); + void guessSortKeys(); + void enableAllUnknown(); + void enableAll(); + void markAllAsKnown(); + int getEnabledSetsNum(); + int getUnknownSetsNum(); + QStringList getUnknownSetsNames(); + void defaultSort(); +}; + +class CardInfoPerSet +{ +public: + explicit CardInfoPerSet(const CardSetPtr &_set = QSharedPointer(nullptr)); + ~CardInfoPerSet() = default; + + bool operator==(const CardInfoPerSet &other) const + { + return this->set == other.set && this->properties == other.properties; + } + +private: + CardSetPtr set; + // per-set card properties; + QVariantHash properties; + +public: + const CardSetPtr getPtr() const + { + return set; + } + const QStringList getProperties() const + { + return properties.keys(); + } + const QString getProperty(const QString &propertyName) const + { + return properties.value(propertyName).toString(); + } + void setProperty(const QString &_name, const QString &_value) + { + properties.insert(_name, _value); + } +}; + +class CardInfo : public QObject +{ + Q_OBJECT +private: + CardInfoPtr smartThis; + // The card name + QString name; + // The name without punctuation or capitalization, for better card name recognition. + QString simpleName; + // The key used to identify this card in the cache + QString pixmapCacheKey; + // card text + QString text; + // whether this is not a "real" card but a token + bool isToken; + // basic card properties; common for all the sets + QVariantHash properties; + // the cards i'm related to + QList relatedCards; + // the card i'm reverse-related to + QList reverseRelatedCards; + // the cards thare are reverse-related to me + QList reverseRelatedCardsToMe; + // card sets + CardInfoPerSetMap sets; + // cached set names + QString setsNames; + // positioning properties; used by UI + bool cipt; + bool landscapeOrientation; + int tableRow; + bool upsideDownArt; + +public: + explicit CardInfo(const QString &_name, + const QString &_text, + bool _isToken, + QVariantHash _properties, + const QList &_relatedCards, + const QList &_reverseRelatedCards, + CardInfoPerSetMap _sets, + bool _cipt, + bool _landscapeOrientation, + int _tableRow, + bool _upsideDownArt); + CardInfo(const CardInfo &other) + : QObject(other.parent()), name(other.name), simpleName(other.simpleName), pixmapCacheKey(other.pixmapCacheKey), + text(other.text), isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards), + reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe), + sets(other.sets), setsNames(other.setsNames), cipt(other.cipt), + landscapeOrientation(other.landscapeOrientation), tableRow(other.tableRow), upsideDownArt(other.upsideDownArt) + { + } + ~CardInfo() override; + + static CardInfoPtr newInstance(const QString &_name); + + static CardInfoPtr newInstance(const QString &_name, + const QString &_text, + bool _isToken, + QVariantHash _properties, + const QList &_relatedCards, + const QList &_reverseRelatedCards, + CardInfoPerSetMap _sets, + bool _cipt, + bool _landscapeOrientation, + int _tableRow, + bool _upsideDownArt); + + CardInfoPtr clone() const + { + // Use the copy constructor to create a new instance + CardInfoPtr newCardInfo = CardInfoPtr(new CardInfo(*this)); + newCardInfo->setSmartPointer(newCardInfo); // Set the smart pointer for the new instance + return newCardInfo; + } + + void setSmartPointer(CardInfoPtr _ptr) + { + smartThis = std::move(_ptr); + } + + // basic properties + inline const QString &getName() const + { + return name; + } + const QString &getSimpleName() const + { + return simpleName; + } + void setPixmapCacheKey(QString _pixmapCacheKey) + { + pixmapCacheKey = _pixmapCacheKey; + } + const QString &getPixmapCacheKey() const + { + return pixmapCacheKey; + } + + const QString &getText() const + { + return text; + } + void setText(const QString &_text) + { + text = _text; + emit cardInfoChanged(smartThis); + } + + bool getIsToken() const + { + return isToken; + } + const QStringList getProperties() const + { + return properties.keys(); + } + const QString getProperty(const QString &propertyName) const + { + return properties.value(propertyName).toString(); + } + void setProperty(const QString &_name, const QString &_value) + { + properties.insert(_name, _value); + emit cardInfoChanged(smartThis); + } + bool hasProperty(const QString &propertyName) const + { + return properties.contains(propertyName); + } + const CardInfoPerSetMap &getSets() const + { + return sets; + } + const QString &getSetsNames() const + { + return setsNames; + } + const QString getSetProperty(const QString &setName, const QString &propertyName) const + { + if (!sets.contains(setName)) + return ""; + + for (const auto &set : sets[setName]) { + if (QLatin1String("card_") + this->getName() + QString("_") + QString(set.getProperty("uuid")) == + this->getPixmapCacheKey()) { + return set.getProperty(propertyName); + } + } + + return sets[setName][0].getProperty(propertyName); + } + + // related cards + const QList &getRelatedCards() const + { + return relatedCards; + } + const QList &getReverseRelatedCards() const + { + return reverseRelatedCards; + } + const QList &getReverseRelatedCards2Me() const + { + return reverseRelatedCardsToMe; + } + const QList getAllRelatedCards() const + { + QList result; + result.append(getRelatedCards()); + result.append(getReverseRelatedCards2Me()); + return result; + } + void resetReverseRelatedCards2Me(); + void addReverseRelatedCards2Me(CardRelation *cardRelation) + { + reverseRelatedCardsToMe.append(cardRelation); + } + + // positioning + bool getCipt() const + { + return cipt; + } + 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; + + // Back-compatibility methods. Remove ASAP + const QString getCardType() const; + void setCardType(const QString &value); + const QString getCmc() const; + 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; + void setPowTough(const QString &value); + + // methods using per-set properties + QString getCustomPicURL(const QString &set) const + { + return getSetProperty(set, "picurl"); + } + QString getCorrectedName() const; + void addToSet(const CardSetPtr &_set, CardInfoPerSet _info = CardInfoPerSet()); + void combineLegalities(const QVariantHash &props); + void emitPixmapUpdated() + { + emit pixmapUpdated(); + } + void refreshCachedSetNames(); + + /** + * Simplify a name to have no punctuation and lowercase all letters, for + * less strict name-matching. + */ + static QString simplifyName(const QString &name); + +signals: + void pixmapUpdated(); + void cardInfoChanged(CardInfoPtr card); +}; + +class CardRelation : public QObject +{ + Q_OBJECT +public: + enum AttachType + { + DoesNotAttach = 0, + AttachTo = 1, + TransformInto = 2, + }; + +private: + QString name; + AttachType attachType; + bool isCreateAllExclusion; + bool isVariableCount; + int defaultCount; + bool isPersistent; + +public: + explicit CardRelation(const QString &_name = QString(), + AttachType _attachType = DoesNotAttach, + bool _isCreateAllExclusion = false, + bool _isVariableCount = false, + int _defaultCount = 1, + bool _isPersistent = false); + + inline const QString &getName() const + { + return name; + } + AttachType getAttachType() const + { + return attachType; + } + bool getDoesAttach() const + { + return attachType != DoesNotAttach; + } + bool getDoesTransform() const + { + return attachType == TransformInto; + } + QString getAttachTypeAsString() const + { + switch (attachType) { + case AttachTo: + return "attach"; + case TransformInto: + return "transform"; + default: + return ""; + } + } + bool getCanCreateAnother() const + { + return !getDoesAttach(); + } + bool getIsCreateAllExclusion() const + { + return isCreateAllExclusion; + } + bool getIsVariable() const + { + return isVariableCount; + } + int getDefaultCount() const + { + return defaultCount; + } + bool getIsPersistent() const + { + return isPersistent; + } +}; +#endif diff --git a/dbconverter/CMakeLists.txt b/dbconverter/CMakeLists.txt index 78fe36350..ca661376e 100644 --- a/dbconverter/CMakeLists.txt +++ b/dbconverter/CMakeLists.txt @@ -9,6 +9,7 @@ set(dbconverter_SOURCES ../cockatrice/src/game/cards/card_database_parser/card_database_parser.cpp ../cockatrice/src/game/cards/card_database_parser/cockatrice_xml_3.cpp ../cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp + ../cockatrice/src/game/cards/card_info.cpp ../cockatrice/src/settings/settings_manager.cpp ${VERSION_STRING_CPP} ) diff --git a/oracle/CMakeLists.txt b/oracle/CMakeLists.txt index addfecd78..6f8a7043a 100644 --- a/oracle/CMakeLists.txt +++ b/oracle/CMakeLists.txt @@ -18,6 +18,7 @@ set(oracle_SOURCES src/qt-json/json.cpp ../cockatrice/src/game/cards/card_database.cpp ../cockatrice/src/game/cards/card_database_manager.cpp + ../cockatrice/src/game/cards/card_info.cpp ../cockatrice/src/client/ui/picture_loader/picture_loader.cpp ../cockatrice/src/client/ui/picture_loader/picture_loader_worker.cpp ../cockatrice/src/client/ui/picture_loader/picture_to_load.cpp diff --git a/tests/carddatabase/CMakeLists.txt b/tests/carddatabase/CMakeLists.txt index 3ca812c22..403d14d1d 100644 --- a/tests/carddatabase/CMakeLists.txt +++ b/tests/carddatabase/CMakeLists.txt @@ -22,6 +22,7 @@ add_executable( ../../cockatrice/src/game/cards/card_database_parser/card_database_parser.cpp ../../cockatrice/src/game/cards/card_database_parser/cockatrice_xml_3.cpp ../../cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp + ../../cockatrice/src/game/cards/card_info.cpp ../../cockatrice/src/settings/settings_manager.cpp carddatabase_test.cpp mocks.cpp @@ -35,6 +36,7 @@ add_executable( ../../cockatrice/src/game/cards/card_database_parser/card_database_parser.cpp ../../cockatrice/src/game/cards/card_database_parser/cockatrice_xml_3.cpp ../../cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp + ../../cockatrice/src/game/cards/card_info.cpp ../../cockatrice/src/game/filters/filter_card.cpp ../../cockatrice/src/game/filters/filter_string.cpp ../../cockatrice/src/game/filters/filter_tree.cpp From 4a0e0ed95469833df3227b9c7c2cd8bba01c49f8 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 14 Mar 2025 18:42:56 -0700 Subject: [PATCH 02/44] Automatically find all files for cockatrice_SOURCES (#5716) * Use GLOB_RECURSIVE to find all source files * fix code style --------- Co-authored-by: Zach H --- cockatrice/CMakeLists.txt | 204 +------------------------------------- 1 file changed, 3 insertions(+), 201 deletions(-) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index cfe0430e8..a5ffed4be 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -4,207 +4,9 @@ project(Cockatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") -set(cockatrice_SOURCES - src/game/cards/abstract_card_drag_item.cpp - src/game/cards/abstract_card_item.cpp - src/client/game_logic/abstract_client.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/client/ui/widgets/general/display/banner_widget.cpp - src/game/cards/card_database.cpp - src/game/cards/card_database_manager.cpp - src/game/cards/card_database_model.cpp - src/game/cards/card_database_parser/card_database_parser.cpp - src/game/cards/card_database_parser/cockatrice_xml_3.cpp - src/game/cards/card_database_parser/cockatrice_xml_4.cpp - src/game/cards/card_drag_item.cpp - src/game/filters/filter_card.cpp - src/client/ui/widgets/cards/card_info_frame_widget.cpp - src/client/ui/widgets/cards/card_info_picture_widget.cpp - src/client/ui/widgets/cards/card_info_text_widget.cpp - src/client/ui/widgets/cards/card_info_display_widget.cpp - src/client/ui/widgets/cards/card_size_widget.cpp - src/game/cards/card_info.cpp - src/game/cards/card_item.cpp - src/game/cards/card_list.cpp - src/game/zones/card_zone.cpp - src/server/chat_view/chat_view.cpp - src/game/board/counter_general.cpp - src/deck/custom_line_edit.cpp - src/deck/deck_loader.cpp - src/deck/deck_list_model.cpp - src/deck/deck_stats_interface.cpp - src/dialogs/dlg_connect.cpp - src/dialogs/dlg_convert_deck_to_cod_format.cpp - src/dialogs/dlg_create_token.cpp - src/dialogs/dlg_create_game.cpp - src/dialogs/dlg_edit_avatar.cpp - src/dialogs/dlg_edit_password.cpp - src/dialogs/dlg_edit_tokens.cpp - src/dialogs/dlg_edit_user.cpp - src/dialogs/dlg_filter_games.cpp - src/dialogs/dlg_forgot_password_challenge.cpp - src/dialogs/dlg_forgot_password_request.cpp - src/dialogs/dlg_forgot_password_reset.cpp - src/dialogs/dlg_load_deck_from_clipboard.cpp - src/dialogs/dlg_load_remote_deck.cpp - src/dialogs/dlg_manage_sets.cpp - src/dialogs/dlg_move_top_cards_until.cpp - src/dialogs/dlg_register.cpp - src/dialogs/dlg_roll_dice.cpp - src/dialogs/dlg_settings.cpp - src/dialogs/dlg_tip_of_the_day.cpp - src/dialogs/dlg_update.cpp - src/dialogs/dlg_view_log.cpp - src/dialogs/dlg_load_deck.cpp - src/game/deckview/deck_view.cpp - src/game/deckview/deck_view_container.cpp - src/game/filters/filter_string.cpp - src/game/filters/filter_builder.cpp - src/game/filters/filter_tree.cpp - src/game/filters/filter_tree_model.cpp - src/client/ui/layouts/flow_layout.cpp - src/client/ui/widgets/general/layout_containers/flow_widget.cpp - src/game/game_scene.cpp - src/game/game_selector.cpp - src/game/games_model.cpp - src/game/game_view.cpp - src/client/get_text_with_max.cpp - src/game/hand_counter.cpp - src/server/handle_public_servers.cpp - src/game/zones/hand_zone.cpp - src/client/game_logic/key_signals.cpp - src/client/ui/line_edit_completer.cpp - src/server/local_client.cpp - src/server/local_server.cpp - src/server/local_server_interface.cpp - src/utility/logger.cpp - src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp - src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp - src/client/ui/widgets/cards/additional_info/color_identity_widget.cpp - src/client/ui/widgets/cards/additional_info/mana_cost_widget.cpp - src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp - src/client/ui/widgets/general/display/banner_widget.cpp - src/client/ui/widgets/general/display/labeled_input.cpp - src/client/ui/widgets/general/display/dynamic_font_size_label.cpp - src/client/ui/widgets/general/display/dynamic_font_size_push_button.cpp - src/client/ui/widgets/general/display/shadow_background_label.cpp - src/main.cpp - src/server/message_log_widget.cpp - src/client/ui/layouts/overlap_layout.cpp - src/client/ui/widgets/general/layout_containers/overlap_widget.cpp - src/client/ui/widgets/general/layout_containers/overlap_control_widget.cpp - src/server/pending_command.cpp - src/game/phase.cpp - src/client/ui/phases_toolbar.cpp - src/client/ui/picture_loader/picture_loader.cpp - src/client/ui/picture_loader/picture_loader_worker.cpp - src/client/ui/picture_loader/picture_to_load.cpp - src/game/zones/pile_zone.cpp - src/client/ui/pixel_map_generator.cpp - src/game/player/player.cpp - src/game/player/player_list_widget.cpp - src/game/player/player_target.cpp - src/client/ui/widgets/printing_selector/all_zones_card_amount_widget.cpp - src/client/ui/widgets/printing_selector/card_amount_widget.cpp - src/client/ui/widgets/printing_selector/printing_selector.cpp - src/client/ui/widgets/printing_selector/printing_selector_card_display_widget.cpp - src/client/ui/widgets/printing_selector/printing_selector_card_overlay_widget.cpp - src/client/ui/widgets/printing_selector/printing_selector_card_search_widget.cpp - src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.cpp - src/client/ui/widgets/printing_selector/printing_selector_card_sorting_widget.cpp - src/client/ui/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp - src/client/ui/widgets/quick_settings/settings_button_widget.cpp - src/client/ui/widgets/quick_settings/settings_popup_widget.cpp - src/client/network/release_channel.cpp - src/client/network/client_update_checker.cpp - src/server/remote/remote_client.cpp - src/server/remote/remote_decklist_tree_widget.cpp - src/server/remote/remote_replay_list_tree_widget.cpp - src/client/network/replay_timeline_widget.cpp - src/game/zones/select_zone.cpp - src/utility/sequence_edit.cpp - src/client/network/sets_model.cpp - src/settings/card_database_settings.cpp - src/settings/download_settings.cpp - src/settings/game_filters_settings.cpp - src/settings/layouts_settings.cpp - src/settings/message_settings.cpp - src/settings/recents_settings.cpp - src/settings/servers_settings.cpp - src/settings/settings_manager.cpp - src/settings/cache_settings.cpp - src/settings/shortcuts_settings.cpp - src/settings/shortcut_treeview.cpp - src/settings/card_override_settings.cpp - src/settings/debug_settings.cpp - src/client/sound_engine.cpp - src/client/network/spoiler_background_updater.cpp - src/game/zones/stack_zone.cpp - src/client/tabs/tab.cpp - src/client/tabs/tab_account.cpp - src/client/tabs/tab_admin.cpp - src/client/tabs/abstract_tab_deck_editor.cpp - src/client/tabs/tab_deck_editor.cpp - src/client/tabs/tab_deck_storage.cpp - src/client/tabs/tab_game.cpp - src/client/tabs/tab_logs.cpp - src/client/tabs/tab_message.cpp - src/client/tabs/tab_replays.cpp - src/client/tabs/tab_room.cpp - src/client/tabs/tab_server.cpp - src/client/tabs/tab_supervisor.cpp - src/client/tabs/api/edhrec/tab_edhrec.cpp - src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.cpp - src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.cpp - src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.cpp - src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.cpp - src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.cpp - src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.cpp - src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.cpp - src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.cpp - src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.cpp - src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.cpp - src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.cpp - src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.cpp - src/game/zones/table_zone.cpp - src/client/tapped_out_interface.cpp - src/client/ui/theme_manager.cpp - src/client/ui/tip_of_the_day.cpp - src/client/translate_counter_name.cpp - src/client/update_downloader.cpp - src/server/user/user_context_menu.cpp - src/server/user/user_info_connection.cpp - src/server/user/user_info_box.cpp - src/server/user/user_list_manager.cpp - src/server/user/user_list_widget.cpp - src/client/ui/window_main.cpp - src/game/zones/view_zone_widget.cpp - src/game/zones/view_zone.cpp - src/client/menus/deck_editor/deck_editor_menu.cpp - src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp - src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp - src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp - src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp - src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp - src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp - src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp - src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp - src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp - src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp - src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp - src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp - src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp - src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp - src/client/ui/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp - src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.cpp - src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp - src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.cpp - src/client/ui/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp - ${VERSION_STRING_CPP} -) +file(GLOB_RECURSE cockatrice_CPP_FILES ${CMAKE_SOURCE_DIR}/cockatrice/src/*.cpp) + +set(cockatrice_SOURCES ${cockatrice_CPP_FILES} ${VERSION_STRING_CPP}) add_subdirectory(sounds) add_subdirectory(themes) From 1f0846297f9a3b189678311596bcb1c499910d87 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sat, 15 Mar 2025 02:43:11 +0100 Subject: [PATCH 03/44] websocket is our default port/connection (#5679) --- Dockerfile | 2 +- README.md | 6 +++--- docker-compose.yml | 1 - docker-compose.yml.windows | 1 - servatrice/scripts/register.py | 2 +- servatrice/servatrice.ini.example | 5 +++-- 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 31b84f026..330c51b4a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,6 @@ RUN cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 -DWITH_DBCONVERTER= WORKDIR /home/servatrice -EXPOSE 4747 4748 +EXPOSE 4748 ENTRYPOINT [ "servatrice", "--log-to-console" ] diff --git a/README.md b/README.md index 86c669763..f7fffdedf 100644 --- a/README.md +++ b/README.md @@ -128,9 +128,9 @@ First, create an image from the Dockerfile
`cd /path/to/Cockatrice-Repo/` `docker build -t servatrice .`
And then run it
-`docker run -i -p 4747:4747/tcp -t servatrice:latest`
+`docker run -i -p 4748:4748 -t servatrice:latest`
->Note: Running this command exposes the TCP port 4747 of the docker container
+>Note: Running this command exposes the port 4748 of the docker container
to permit connections to the server. Find more information on how to use Servatrice with Docker in our [wiki](https://github.com/Cockatrice/Cockatrice/wiki/Setting-up-Servatrice#using-docker). @@ -145,7 +145,7 @@ docker-compose build # Build the Servatrice image using the same Dockerfile a docker-compose up # Setup and run both the MySQL server and Servatrice. ``` ->Note: Similar to the above Docker setup, this will expose TCP ports 4747 and 4748. +>Note: Similar to the above Docker setup, this will expose port 4748. >Note: The first time running the docker-compose setup, the MySQL server will take a little time to run the initial setup scripts. Due to this, the Servatrice instance may fail the first few attempts to connect to the database. Servatrice is set to `restart: always` in the docker-compose.yml, which will allow it to continue attempting to start up. Once the MySQL scripts have completed, Servatrice should then connect automatically on the next attempt. diff --git a/docker-compose.yml b/docker-compose.yml index 7cef0ffe0..3d9f9f38f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,6 @@ services: depends_on: - mysql ports: - - "4747:4747" - "4748:4748" entrypoint: "/bin/bash -c 'sleep 10; servatrice --config /tmp/servatrice.ini --log-to-console'" restart: always diff --git a/docker-compose.yml.windows b/docker-compose.yml.windows index 8634e0241..6663c90fd 100644 --- a/docker-compose.yml.windows +++ b/docker-compose.yml.windows @@ -20,7 +20,6 @@ services: depends_on: - mysql ports: - - "4747:4747" - "4748:4748" entrypoint: "/bin/bash -c 'sleep 10; servatrice --config /tmp/servatrice.ini --log-to-console'" restart: always diff --git a/servatrice/scripts/register.py b/servatrice/scripts/register.py index 427c9e72b..d33db2e21 100755 --- a/servatrice/scripts/register.py +++ b/servatrice/scripts/register.py @@ -9,7 +9,7 @@ from pypb.event_server_identification_pb2 import Event_ServerIdentification as S from pypb.response_pb2 import Response HOST = "localhost" -PORT = 4747 +PORT = 4748 CMD_ID = 1 diff --git a/servatrice/servatrice.ini.example b/servatrice/servatrice.ini.example index 8a214f39d..fac743c39 100644 --- a/servatrice/servatrice.ini.example +++ b/servatrice/servatrice.ini.example @@ -20,7 +20,8 @@ id=1 host=any -; The TCP port number servatrice will listen on for clients; default is 4747 +; The TCP port number Servatrice will listen on for clients; default is 4747; +; Will be removed in the future, use websocket connection instead port=4747 ; Servatrice can scale up to serve big number of users using more than one parallel thread of execution; @@ -421,7 +422,7 @@ enable_forgotpassword_audit=true ; "servers" table of the database. Default is 0 (disabled) active=0 -; The TCP port number servatrice will listen on for other servers; default is 14747 +; The TCP port number Servatrice will listen on for other servers; default is 14747 port=14747 ; Server-to-server communication needs a valid certificate in PEM format. Enter its filename in this setting From 068465143bcafe9d74ff3d07e6500f68a88fe1e1 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sat, 15 Mar 2025 02:43:43 +0100 Subject: [PATCH 04/44] Update CONTRIBUTING file (#5701) * Update CONTRIBUTING.md * cleanup * Update CONTRIBUTING.md --- .github/CONTRIBUTING.md | 153 ++++++++++++++++++++-------------------- 1 file changed, 77 insertions(+), 76 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 2db045819..10ffa3493 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -7,32 +7,33 @@
# Contributing to Cockatrice # -First off, thanks for taking the time to contribute to our project! 🎉 ❤ ️✨ -The following is a set of guidelines for contributing to Cockatrice. These are -mostly guidelines, not rules. Use your best judgment, and feel free to propose -changes to this document in a pull request. +First off, thanks for taking the time and considering to lend a helping hand to our project! 🎉 ❤ ️✨ + +> [!NOTE] +> The following is a set of guidelines for contributing to Cockatrice. +> These are mostly guidelines, not rules. Use your best judgment, and feel free to +> propose changes to this document in a pull request. +> +> [![Discord]( +> https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)]( +> https://discord.gg/3Z9yzmA) +> If you'd like to ask questions, get advice, or just want to say "Hi", +> the Cockatrice Development Team uses [Discord](https://discord.gg/ZASRzKu) +> for communications and you can reach out in the `#dev` channel. # Recommended Setups # -For those developers who like the Linux or MacOS environment, many of our +For those developers on **Linux** or **macOS** environment, many of our developers like working with a nifty program called [CLion]( -https://www.jetbrains.com/clion/). The program's a great asset and one of the -best tools you'll find on these systems, but you're welcomed to use any IDE -you most enjoy. +https://www.jetbrains.com/clion/). The program is a great asset and one of the +best tools you'll find on these systems. -Developers who like Windows development tend to find [Visual Studio]( +Developers on **Windows** systems tend to find [Visual Studio]( https://www.visualstudio.com/) the best tool for the job. -[![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white&color=7289da)](https://discord.gg/ZASRzKu) -[![Gitter Chat](https://img.shields.io/gitter/room/Cockatrice/Cockatrice.svg)](https://gitter.im/Cockatrice/Cockatrice) - -If you'd like to ask questions, get advice, or just want to say hi, -the Cockatrice Development Team uses [Discord](https://discord.gg/ZASRzKu) -for communications in the #dev channel. If you're not into Discord, we also -have a [Gitter](https://gitter.im/Cockatrice/Cockatrice) channel available, -albeit slightly less active. +But you're welcomed to use any IDE you enjoy most of course! # Code Style Guide # @@ -54,7 +55,7 @@ The message will look like this: *** Then commit and push those changes to this branch. *** *** Check our CONTRIBUTING.md file for more details. *** *** *** -*** Thank you ❤️ *** +*** Thank you ❤️ *** *** *** *********************************************************** ``` @@ -64,7 +65,7 @@ information on our formatting guidelines. ### Compatibility ### -Cockatrice is currently compiled on all platforms using C++11. +Cockatrice is currently compiled on all platforms using C++20. You'll notice C++03 code throughout the codebase. Please feel free to help convert it over! @@ -78,11 +79,12 @@ or other appropriate conversion. ### Formatting ### The handy tool `clang-format` can format your code for you, it is available for -almost any environment. A special `.clang-format` configuration file is +almost any environment. A special [`.clang-format`]( +https://github.com/Cockatrice/Cockatrice/blob/master/.clang-format) configuration file is included in the project and is used to format your code. We've also included a bash script, `format.sh`, that will use clang-format to -format all files in your pr in one go. Use `./format.sh --help` to show a full +format all files in your PR in one go. Use `./format.sh --help` to show a full help page. To run clang-format on a single source file simply use the command @@ -90,10 +92,10 @@ To run clang-format on a single source file simply use the command clang-format with a specific version number appended, `find /usr/bin -name clang-format*` should find it for you) -See [the clang-format documentation]( +See the [clang-format documentation]( https://clang.llvm.org/docs/ClangFormat.html) for more information about the tool. -#### Header files #### +#### Header Files #### Use header files with the extension `.h` and source files with the extension `.cpp`. @@ -168,10 +170,10 @@ braces around single line statements is preferred. See the following example: ```c++ int main() -{ // function or class: own line - if (someCondition) { // control statement: same line - doSomething(); // single line statement, braces preferred - } else if (someOtherCondition1) { // else goes on the same line as a closing brace +{ // function or class: own line + if (someCondition) { // control statement: same line + doSomething(); // single line statement, braces preferred + } else if (someOtherCondition1) { // else goes on the same line as a closing brace for (int i = 0; i < 100; i++) { doSomethingElse(); } @@ -234,7 +236,7 @@ mutating objects.) When pointers can't be avoided, try to use a smart pointer of some sort, such as `QScopedPointer`, or, less preferably, `QSharedPointer`. -### Database migrations ### +### Database Migrations ### The servatrice database's schema can be found at `servatrice/servatrice.sql`. Everytime the schema gets modified, some other steps are due: @@ -255,7 +257,7 @@ Ensure that the migration produces the expected effects; e.g. if you add a new column, make sure the migration places it in the same order as servatrice.sql. -### Protocol buffer ### +### Protocol Buffer ### Cockatrice and Servatrice exchange data using binary messages. The syntax of these messages is defined in the `proto` files in the `common/pb` folder. These @@ -268,6 +270,7 @@ new clients incompatible to the old server and vice versa. You can find more information on how we use Protobuf on [our wiki!]( https://github.com/Cockatrice/Cockatrice/wiki/Client-server-protocol) + # Reviewing Pull Requests # After you have finished your changes to the project you should put them on a @@ -286,6 +289,7 @@ all changes have been approved your pull request will be squashed into a single commit and merged into the master branch by a team member. Your change will then be included in the next release 👍 + # Translations # Basic workflow for translations: @@ -294,16 +298,16 @@ Basic workflow for translations: 3. Maintainer verifies and merges the change; 4. Transifex picks up the new files from GitHub automatically; 5. Translators translate the new untranslated strings on Transifex; - 6. Before a release, a maintainer fetches the updated translations from Transifex. + 6. Before a release, a maintainer fetches the newest translations from Transifex. ### Using Translations (for developers) ### All user interface strings inside Cockatrice's source code must be written in English (US). Translations to other languages are managed using [Transifex]( -https://www.transifex.com/projects/p/cockatrice/). +https://transifex.com/cockatrice/cockatrice/). -Adding a new string to translate is as easy as adding the string in the +Adding a new string for translation is as easy as adding the string in the `tr("")` function, the string will be picked up as translatable automatically and translated as needed. For example, setting the text of a label in the way that the string @@ -315,9 +319,9 @@ nameLabel.setText(tr("My name is:")); To translate a string that would have plural forms you can add the amount to the tr() call, also you can add an extra string as a hint for translators: ```c++ -QString message = tr("Everyone draws %n cards", "pop up message", amount); +QString message = tr("Everyone draws %n cards", "english hint for translators", amount); ``` -See [QT's wiki on translations]( +See [Qt's wiki on translations]( https://doc.qt.io/qt-5/i18n-source-translation.html#handling-plurals) If you're about to propose a change that adds or modifies any translatable @@ -325,7 +329,7 @@ string in the code, you don't need to take care of adding the new strings to the translation files.
We have an automated process to update our language source files on a schedule and provide the translators on Transifex with the new contents.
-Maintainers can also manually trigger this on demand. +Maintainers can also manually trigger this workflow on demand via GitHub Actions. ### Maintaining Translations (for maintainers) ### @@ -389,27 +393,27 @@ Now you are ready to commit your changes and open a PR. Once the changes get merged, Transifex will pick up the modified files -automatically (checked every few hours) and update their online editor where +automatically (checked every few hours) and update the web editor where translators will be able to translate the new strings right in the browser. ### Releasing Translations (for maintainers) ### -Before rushing out a new release, a maintainer should fetch the most up to date +Before publishing a new release, a maintainer should fetch the most up to date translations from Transifex and commit them into the Cockatrice source code. -This can be done manually from the Transifex web interface, but it's quite time -consuming. - -As an alternative, you can install the Transifex CLI: - - http://docs.transifex.com/developer/client/ +We utilize the official GitHub integration to push all languages that are 100% +translated from Transifex to our GitHub repo automatically. +On top, it runs on a quarterly schedule to update changes for incomplete languages. +A synchronisation/update can also be triggered manually from the Transifex web interface +and a translation treshold can be set. +As an alternative, you can install the [Transifex CLI](https://developers.transifex.com/docs/cli). You'll then be able to use a git-like cli command to push and pull translations from Transifex to the source code and vice versa. ### Adding Translations (for translators) ### -As a translator you can help translate the new strings on [Transifex]( -https://www.transifex.com/projects/p/cockatrice/). +As a translator, you can help to translate new strings on [Transifex]( +https://www.transifex.com/projects/p/cockatrice/) to your native language. Please have a look at the specific [FAQ for translators]( https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ). @@ -419,9 +423,9 @@ https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ). ### Publishing A New Release ### We use [GitHub Releases](https://github.com/Cockatrice/Cockatrice/releases) to -publish new stable versions and betas. -Whenever a git tag is pushed to the repository github will create a draft -release and upload binaries automatically. +publish new stable versions and beta releases. +Whenever a git tag is pushed to the repository, GitHub will create a draft +release and upload binaries from our CI automatically. To create a tag, simply do the following: ```bash @@ -433,18 +437,16 @@ git push $UPSTREAM $TAG_NAME ``` You should define the variables as such: -``` -`$UPSTREAM` - the remote for git@github.com:Cockatrice/Cockatrice.git -`$TAG_NAME` should be formatted as: - - `YYYY-MM-DD-Release-MAJ.MIN.PATCH` for **stable releases** - - `YYYY-MM-DD-Development-MAJ.MIN.PATCH-beta.X` for **beta releases**
- With *MAJ.MIN.PATCH* being the NEXT release version! -``` + - `$UPSTREAM`: the remote for `git@github.com:Cockatrice/Cockatrice.git` + - `$TAG_NAME` should be formatted as: + - `YYYY-MM-DD-Release-MAJ.MIN.PATCH` for **stable releases** + - `YYYY-MM-DD-Development-MAJ.MIN.PATCH-beta.X` for **beta releases**
+ With MAJ.MIN.PATCH being the NEXT release version! This will cause a tagged release to be established on the GitHub repository, -with the binaries being added to the release whenever they are ready. -The release is initially a draft, where the path notes can be edited and after -all is good and ready it can be published on GitHub. +with the binaries being added to the release whenever they are done building in CI. +The release is initially a draft, where the release notes can be edited and after +all is checked and ready, it can be published as GitHub release. If you use a SemVer tag including "beta", the release that will be created at GitHub will be marked as a "Pre-release" automatically. The target of the `.../latest` URL will not be changed in that case, it always @@ -457,7 +459,7 @@ 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 new release. +You can also do this on GitHub, you'll also want to delete the false release. In the first lines of [CMakeLists.txt]( https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt) @@ -468,25 +470,24 @@ coming from the tag title, it's good practice to increment the ones at CMake after every full release to stress that master is ahead of the last stable release. The preferred flow of operation is: - * Just before a release, make sure the version number in CMakeLists.txt is set - to the same release version you are about to tag. - * This is also the time to change the pretty name in CMakeLists.txt called - GIT_TAG_RELEASENAME and commit and push these changes. - * Tag the release following the previously described syntax in order to get it - correctly built and deployed by CI. - * Wait for the configure step to create the release and update the patch - notes. - * Check on the github actions page for build progress which should be the top - listed [here]( + - Just before a release, make sure the version number in CMakeLists.txt is set + to the same release version you are about to tag. + - This is also the time to change the pretty name in CMakeLists.txt called + `GIT_TAG_RELEASENAME` and commit and push these changes. + - Tag the release following the previously described syntax in order to get it + correctly built and deployed by CI. + - Wait for the configuration step to create the release and update the patch + notes. + - Check on the GitHub Actions page for build progress which should be the top + listed [here]( https://github.com/Cockatrice/Cockatrice/actions?query=event%3Apush+-branch%3Amaster ). - * When the build has been completed you can verify all uploaded files on the - release are in order and hit the publish button. - * After the release is complete, update the CMake version number again to the - next targeted beta version, typically increasing `PROJECT_VERSION_PATCH` by - one. + - When the build has been completed, you can verify if all uploaded files on the + draft release are included and hit the publish button. + - After the release is complete, update the CMake version number again to the + next targeted beta version, typically increasing `PROJECT_VERSION_PATCH` by + one. When releasing a new stable version, all previous beta releases (and tags) -should be deleted. This is needed for Cockatrice to update users of the "Beta" -release channel correctly to the latest stable version as well. +should be deleted as well. This can be done the same way as revoking tags, mentioned above. From 3a11ccb85480158428e8e271f596d4f44c95bdcf Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 14 Mar 2025 18:44:13 -0700 Subject: [PATCH 05/44] Update cipt parsing (#5712) * refactor * move thing out * write unit tests * get thing to work * optimization? * fix build failure --- oracle/CMakeLists.txt | 1 + oracle/src/oracleimporter.cpp | 5 +- oracle/src/parsehelpers.cpp | 63 +++++++++ oracle/src/parsehelpers.h | 8 ++ tests/CMakeLists.txt | 1 + tests/oracle/CMakeLists.txt | 11 ++ tests/oracle/parse_cipt_test.cpp | 219 +++++++++++++++++++++++++++++++ 7 files changed, 305 insertions(+), 3 deletions(-) create mode 100644 oracle/src/parsehelpers.cpp create mode 100644 oracle/src/parsehelpers.h create mode 100644 tests/oracle/CMakeLists.txt create mode 100644 tests/oracle/parse_cipt_test.cpp diff --git a/oracle/CMakeLists.txt b/oracle/CMakeLists.txt index 6f8a7043a..130dfdaab 100644 --- a/oracle/CMakeLists.txt +++ b/oracle/CMakeLists.txt @@ -15,6 +15,7 @@ set(oracle_SOURCES src/oraclewizard.cpp src/oracleimporter.cpp src/pagetemplates.cpp + src/parsehelpers.cpp src/qt-json/json.cpp ../cockatrice/src/game/cards/card_database.cpp ../cockatrice/src/game/cards/card_database_manager.cpp diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 07aad804d..31cf0e9a5 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -1,6 +1,7 @@ #include "oracleimporter.h" #include "game/cards/card_database_parser/cockatrice_xml_4.h" +#include "parsehelpers.h" #include "qt-json/json.h" #include @@ -157,9 +158,7 @@ CardInfoPtr OracleImporter::addCard(QString name, // DETECT CARD POSITIONING INFO // cards that enter the field tapped - QRegularExpression ciptRegex("( it|" + QRegularExpression::escape(name) + - ") enters( the battlefield)? tapped(?! unless)"); - bool cipt = ciptRegex.match(text).hasMatch(); + bool cipt = parseCipt(name, text); bool landscapeOrientation = properties.value("maintype").toString() == "Battle" || properties.value("layout").toString() == "split" || diff --git a/oracle/src/parsehelpers.cpp b/oracle/src/parsehelpers.cpp new file mode 100644 index 000000000..c7f340139 --- /dev/null +++ b/oracle/src/parsehelpers.cpp @@ -0,0 +1,63 @@ +#include "parsehelpers.h" + +#include +#include + +/** + * Parses the card text to determine if the card should have the cipt tag + * + * The parsing logic is able to handle the following cases: + * - " enters tapped" + * - " enters tapped", if the card name starts with the shortname + * - "This enters tapped" + * - "..., it enters tapped" + * - Any naming scheme that appends a non-alphanumeric character plus extra text to the end of the name. + * (e.g. name is "Card Name_SET" or "Card Name (Set)" and text contains "Card Name enters tapped") + * + * However, it will still miss on certain cases: + * - shortnames that aren't the at the beginning of the card name + * + * Note that "...enters tapped unless..." returns false. + * + * @param name The name of the card + * @param text The oracle text of the card + */ +bool parseCipt(const QString &name, const QString &text) +{ + // Try to split shortname on most non-alphanumeric characters (including _) + auto isShortnameDivider = [](const QChar &c) { + return c == '_' || (!c.isLetterOrNumber() && c != '\'' && c != '\"'); + }; + + // Try all possible shortnames. + // This also handles the case of extra text appended at end. + QStringList possibleNames; + bool inAlphanumericPart = true; + for (int i = 0; i < name.length(); ++i) { + if (isShortnameDivider(name.at(i))) { + if (inAlphanumericPart) { + // only add to names on a "falling edge", in order to reduce the amount of redundant splits + possibleNames.append(QRegularExpression::escape(name.left(i))); + inAlphanumericPart = false; + } + } else { + inAlphanumericPart = true; + } + } + + // and the full name + possibleNames.append(QRegularExpression::escape(name)); + + QString subject = "(it|" // "..., it enters tapped" + "(T|t)his [^ ]+|" // "This enters tapped" + + possibleNames.join("|") + ")"; + + auto ciptPattern = QRegularExpression( + // cipt phrase is either first sentence of line, or is after a punctuation mark + "(^|(, |\\. ))" + subject + + // support old wording, and exclude the "unless" case + " enters( the battlefield)? tapped(?! unless)", + QRegularExpression::MultilineOption); + + return ciptPattern.match(text).hasMatch(); +} diff --git a/oracle/src/parsehelpers.h b/oracle/src/parsehelpers.h new file mode 100644 index 000000000..40a36c753 --- /dev/null +++ b/oracle/src/parsehelpers.h @@ -0,0 +1,8 @@ +#ifndef PARSEHELPERS_H +#define PARSEHELPERS_H + +#include + +bool parseCipt(const QString &name, const QString &text); + +#endif // PARSEHELPERS_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d9e6b6345..f6b2d12d2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -51,3 +51,4 @@ target_link_libraries(password_hash_test cockatrice_common Threads::Threads ${GT add_subdirectory(carddatabase) add_subdirectory(loading_from_clipboard) +add_subdirectory(oracle) diff --git a/tests/oracle/CMakeLists.txt b/tests/oracle/CMakeLists.txt new file mode 100644 index 000000000..0c11eb751 --- /dev/null +++ b/tests/oracle/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(parse_cipt_test ../../oracle/src/parsehelpers.cpp parse_cipt_test.cpp) + +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 cockatrice_common Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}) + +add_test(NAME parse_cipt_test COMMAND parse_cipt_test) diff --git a/tests/oracle/parse_cipt_test.cpp b/tests/oracle/parse_cipt_test.cpp new file mode 100644 index 000000000..f5036f962 --- /dev/null +++ b/tests/oracle/parse_cipt_test.cpp @@ -0,0 +1,219 @@ +#include "../../oracle/src/parsehelpers.h" + +#include "gtest/gtest.h" + +TEST(ParseCiptTest, parsesThisEntersTapped) +{ + auto name = "Boring Fields"; + auto text = "This land enters tapped."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, parsesThisEntersTheBattlefieldTapped) +{ + auto name = "Boring Fields"; + auto text = "This land enters the battlefield tapped.\n" + "{T}: Add {G}."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, parsesItEntersTappedAtEndOfSentence) +{ + auto name = "Shocking Fields"; + auto text = "As this land enters, you may pay 2 life. If you don't, it enters tapped.\n" + "{T}: Add {G}."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, parsesThisEntersTappedWhenNotOnFirstLine) +{ + auto name = "Boring Fields"; + auto text = "Flying\n" + "This land enters tapped.\n" + "{T}: Add {G}."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, parsesFullNameWithUnderscoreAppendedText) +{ + auto name = "Boring Fields_SL50"; + auto text = "Boring Fields enters tapped.\n" + "{T}: Add {G}."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, parsesFullNameWithBracketsAppendedText) +{ + auto name = "Boring Fields (SL50)"; + auto text = "Boring Fields enters tapped.\n" + "{T}: Add {G}."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, parsesFullNameWithComma) +{ + auto name = "Bob, the Legend"; + auto text = "Bob, the Legend enters tapped.\n" + "Whenever Bob attacks, you win the game."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, parsesFullNameWithCommaAtEndOfSentence) +{ + auto name = "Bob, the Legend"; + auto text = "As Bob, the Legend enters, you may pay 2 life. If you don't, Bob, the Legend enters tapped.\n" + "Whenever Bob attacks, you win the game."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, parsesFullNameWithApostropheAtEndOfSentence) +{ + auto name = "Bob's Bobber"; + auto text = "As Bob's Bobber enters, you may pay 2 life. If you don't, Bob's Bobber enters tapped.\n" + "Whenever Bob attacks, you win the game."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, parsesShortnameEndingWithComma) +{ + auto name = "Bob, the Legend"; + auto text = "Bob enters tapped.\n" + "Whenever Bob attacks, you win the game."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, parsesShortnameEndingWithSpace) +{ + auto name = "Bob the Legend"; + auto text = "Bob enters tapped.\n" + "Whenever Bob attacks, you win the game."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, parsesMultiWordShortnameEndingWithComma) +{ + auto name = "Bob Dod, the Legend"; + auto text = "Bob Dod enters tapped.\n" + "Whenever Bob Dod attacks, you win the game."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, parsesMultiWordShortnameEndingWithSpace) +{ + auto name = "Bob Dod the Legend"; + auto text = "Bob Dod enters tapped.\n" + "Whenever Bob Dod attacks, you win the game."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, parsesShortnameEndingWithSpaceWithUnderscoreAppendedText) +{ + auto name = "Bob the Legend_SL50"; + auto text = "Bob enters tapped.\n" + "Whenever Bob attacks, you win the game."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, parsesShortnameEndingWithSpaceWithBracketsAppendedText) +{ + auto name = "Bob the Legend (SL50)"; + auto text = "Bob enters tapped.\n" + "Whenever Bob attacks, you win the game."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, parsesMultiWordShortnameEndingWithSpaceWithUnderscoreAppendedText) +{ + auto name = "Bob Dod the Legend_SL50"; + auto text = "Bob Dod enters tapped.\n" + "Whenever Bob Dod attacks, you win the game."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, parsesMultiWordShortnameEndingWithSpaceWithBracketsAppendedText) +{ + auto name = "Bob Dod the Legend (SL50)"; + auto text = "Bob Dod enters tapped.\n" + "Whenever Bob Dod attacks, you win the game."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, rejectsEmptyText) +{ + auto name = "Vanilla Dude"; + auto text = ""; + + ASSERT_FALSE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, rejectsEntersTappedUnless) +{ + auto name = "Fast Fields"; + auto text = "This land enters tapped unless you control another land."; + + ASSERT_FALSE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, rejectsWhenNameIsDifferent) +{ + auto name = "Boring Fields"; + auto text = "Fast Fields enters tapped."; + + ASSERT_FALSE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, rejectsOtherCreaturesEnterTapped) +{ + auto name = "Imposing Guy"; + auto text = "Other creatures enter tapped."; + + ASSERT_FALSE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, rejectsAbilityGrantingEntersTapped) +{ + auto name = "Imposing Guy"; + auto text = "Other creatures have \"This creature enters tapped\"."; + + ASSERT_FALSE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, parsesEntersTappedAndAbilityGrantingEntersTappedOnSameCard) +{ + auto name = "Imposing Guy"; + auto text = "This creature enters tapped." + "Other creatures have \"This creature enters tapped\"."; + + ASSERT_TRUE(parseCipt(name, text)); +} + +TEST(ParseCiptTest, rejectsItEntersTappedAndAttacking) +{ + auto name = "Token Maker"; + auto text = "When Token Maker attacks, create a token. It enters tapped and attacking."; + + ASSERT_FALSE(parseCipt(name, text)); +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From 087f88146d9d0ace7a51136d16ffd1553e871cdc Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Fri, 14 Mar 2025 22:19:07 -0700 Subject: [PATCH 06/44] Make internal updater failure message more user-friendly (#5718) --- cockatrice/src/dialogs/dlg_update.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cockatrice/src/dialogs/dlg_update.cpp b/cockatrice/src/dialogs/dlg_update.cpp index 3e3ec2039..ae60fc0b7 100644 --- a/cockatrice/src/dialogs/dlg_update.cpp +++ b/cockatrice/src/dialogs/dlg_update.cpp @@ -165,10 +165,12 @@ void DlgUpdate::finishedUpdateCheck(bool needToUpdate, bool isCompatible, Releas QString(": %1
").arg(release->getName()) + "" + tr("Released") + QString(": %1 (").arg(publishDate, release->getDescriptionUrl()) + tr("Changelog") + ")

" + - tr("Unfortunately there are no download packages available for your operating system. \nYou may have " - "to build from source yourself.") + + tr("Unfortunately, the automatic updater failed to find a compatible download. \nYou may have to " + "manually download the new version.") + "

" + - tr("Please check the download page manually and visit the wiki for instructions on compiling.")); + tr("Please check the releases page on our Github and download the build for your " + "system.") + .arg(release->getDescriptionUrl())); } } From eb4b1c2a072bd91c69099147ff95f99c555024bd Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 15 Mar 2025 11:44:03 -0700 Subject: [PATCH 07/44] Fix extra .cod in "save deck as" default name (#5720) --- cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp b/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp index 326e722c9..92e63230b 100644 --- a/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp @@ -356,7 +356,7 @@ bool AbstractTabDeckEditor::actSaveDeckAs() dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setDefaultSuffix("cod"); dialog.setNameFilters(DeckLoader::fileNameFilters); - dialog.selectFile(getDeckList()->getName().trimmed() + ".cod"); + dialog.selectFile(getDeckList()->getName().trimmed()); if (!dialog.exec()) return false; From 7d558edb3eb518e8d98206b17e09bc768830932d Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 15 Mar 2025 11:44:51 -0700 Subject: [PATCH 08/44] Fix banner and tags not resetting on blank new deck (#5721) * Fix bannerWidget not resetting when opening new blank deck * also reset tags --- .../ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp index 1d6ac86bb..d24182167 100644 --- a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp @@ -314,12 +314,17 @@ DeckLoader *DeckEditorDeckDockWidget::getDeckList() return deckModel->getDeckList(); } +/** + * Resets the tab to the state for a blank new tab. + */ void DeckEditorDeckDockWidget::cleanDeck() { deckModel->cleanList(); nameEdit->setText(QString()); commentsEdit->setText(QString()); hashLabel->setText(QString()); + updateBannerCardComboBox(); + deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList()); } void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index) From b9900e67a6b32e9baece02c3ad06cc2687fbdf0d Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Sat, 15 Mar 2025 20:03:26 +0100 Subject: [PATCH 09/44] nix: Add development utilities to shell.nix (#5725) - Remove hardening flags to allow debug builds (otherwise GCC complains because nix adds the FORTIFY_SOURCE flag, which is not compatible with -O0) - Allow ninja as build system - Add clang-tools dependency for LSP support --- shell.nix | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/shell.nix b/shell.nix index 5315170d0..404e9e1b6 100644 --- a/shell.nix +++ b/shell.nix @@ -4,6 +4,7 @@ # Build tools cmake cmake-format + ninja bash curl git @@ -12,6 +13,7 @@ # Debug / Test valgrind gdb + clang-tools # Compiler gcc @@ -23,4 +25,8 @@ qt6.full qt6.wrapQtAppsHook ]; + + # Make debug builds work + # https://github.com/NixOS/nixpkgs/issues/18995 + hardeningDisable = [ "fortify" ]; } From e4f40a82a2f26ac22c27d1d08365624613d118e2 Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Sat, 15 Mar 2025 20:07:51 +0100 Subject: [PATCH 10/44] Use native hover events (#5722) * Use native hover events * Update cockatrice/src/game/cards/abstract_card_item.cpp * Reorder --------- Co-authored-by: Zach H --- .../src/game/cards/abstract_card_item.cpp | 38 +++++++++------- .../src/game/cards/abstract_card_item.h | 6 +-- cockatrice/src/game/cards/card_item.cpp | 1 - cockatrice/src/game/deckview/deck_view.cpp | 6 --- cockatrice/src/game/deckview/deck_view.h | 1 - cockatrice/src/game/game_scene.cpp | 45 ------------------- cockatrice/src/game/game_scene.h | 3 -- cockatrice/src/game/player/player.cpp | 1 - cockatrice/src/game/zones/pile_zone.cpp | 3 +- 9 files changed, 27 insertions(+), 77 deletions(-) diff --git a/cockatrice/src/game/cards/abstract_card_item.cpp b/cockatrice/src/game/cards/abstract_card_item.cpp index 15dcc7e71..b071caca9 100644 --- a/cockatrice/src/game/cards/abstract_card_item.cpp +++ b/cockatrice/src/game/cards/abstract_card_item.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, @@ -18,7 +19,7 @@ AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, Player *_owner, int _id) : ArrowTarget(_owner, parent), id(_id), name(_name), providerId(_providerId), tapped(false), facedown(false), - tapAngle(0), bgColor(Qt::transparent), isHovered(false), realZValue(0) + tapAngle(0), bgColor(Qt::transparent), realZValue(0) { setCursor(Qt::OpenHandCursor); setFlag(ItemIsSelectable); @@ -26,6 +27,8 @@ AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, connect(&SettingsCache::instance(), &SettingsCache::displayCardNamesChanged, this, [this] { update(); }); refreshCardInfo(); + + setAcceptHoverEvents(true); } AbstractCardItem::~AbstractCardItem() @@ -157,7 +160,7 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS painter->restore(); } -void AbstractCardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) +void AbstractCardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget * /*widget*/) { painter->save(); @@ -166,6 +169,7 @@ void AbstractCardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * painter->setRenderHint(QPainter::Antialiasing, false); + bool isHovered = option->state.testFlag(QStyle::State_MouseOver); if (isSelected() || isHovered) { QPen pen; if (isHovered) @@ -208,17 +212,24 @@ void AbstractCardItem::setProviderId(const QString &_providerId) refreshCardInfo(); } -void AbstractCardItem::setHovered(bool _hovered) +void AbstractCardItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { - if (isHovered == _hovered) - return; + Q_UNUSED(event); - if (_hovered) - processHoverEvent(); - isHovered = _hovered; - setZValue(_hovered ? 2000000004 : realZValue); - setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1); - setTransformOriginPoint(_hovered ? CARD_WIDTH / 2 : 0, _hovered ? CARD_HEIGHT / 2 : 0); + emit hovered(this); + setZValue(2000000004); + setScale(SettingsCache::instance().getScaleCards() ? 1.1 : 1); + setTransformOriginPoint(CARD_WIDTH / 2, CARD_HEIGHT / 2); + update(); +} + +void AbstractCardItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); + + setZValue(realZValue); + setScale(1); + setTransformOriginPoint(0, 0); update(); } @@ -314,11 +325,6 @@ void AbstractCardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) event->accept(); } -void AbstractCardItem::processHoverEvent() -{ - emit hovered(this); -} - QVariant AbstractCardItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) { if (change == ItemSelectedHasChanged) { diff --git a/cockatrice/src/game/cards/abstract_card_item.h b/cockatrice/src/game/cards/abstract_card_item.h index 51b345267..cc13fe993 100644 --- a/cockatrice/src/game/cards/abstract_card_item.h +++ b/cockatrice/src/game/cards/abstract_card_item.h @@ -24,7 +24,6 @@ protected: QColor bgColor; private: - bool isHovered; qreal realZValue; private slots: void pixmapUpdated(); @@ -86,7 +85,6 @@ public: return realZValue; } void setRealZValue(qreal _zValue); - void setHovered(bool _hovered); QString getColor() const { return color; @@ -102,7 +100,6 @@ public: return facedown; } void setFaceDown(bool _facedown); - void processHoverEvent(); void deleteCardInfoPopup() { emit deleteCardInfoPopup(name); @@ -114,6 +111,9 @@ protected: void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; void cacheBgColor(); + + void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; }; #endif diff --git a/cockatrice/src/game/cards/card_item.cpp b/cockatrice/src/game/cards/card_item.cpp index 19cff1df1..67515e978 100644 --- a/cockatrice/src/game/cards/card_item.cpp +++ b/cockatrice/src/game/cards/card_item.cpp @@ -470,7 +470,6 @@ bool CardItem::animationEvent() .translate(CARD_WIDTH_HALF, CARD_HEIGHT_HALF) .rotate(tapAngle) .translate(-CARD_WIDTH_HALF, -CARD_HEIGHT_HALF)); - setHovered(false); update(); return animationIncomplete; diff --git a/cockatrice/src/game/deckview/deck_view.cpp b/cockatrice/src/game/deckview/deck_view.cpp index 920703d63..2f01361db 100644 --- a/cockatrice/src/game/deckview/deck_view.cpp +++ b/cockatrice/src/game/deckview/deck_view.cpp @@ -158,12 +158,6 @@ void DeckView::mouseDoubleClickEvent(QMouseEvent *event) } } -void DeckViewCard::hoverEnterEvent(QGraphicsSceneHoverEvent *event) -{ - event->accept(); - processHoverEvent(); -} - DeckViewCardContainer::DeckViewCardContainer(const QString &_name) : QGraphicsItem(), name(_name), width(0), height(0) { setCacheMode(DeviceCoordinateCache); diff --git a/cockatrice/src/game/deckview/deck_view.h b/cockatrice/src/game/deckview/deck_view.h index 777298714..14c8f9da1 100644 --- a/cockatrice/src/game/deckview/deck_view.h +++ b/cockatrice/src/game/deckview/deck_view.h @@ -37,7 +37,6 @@ public: protected: void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; - void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; }; class DeckViewCardDragItem : public AbstractCardDragItem diff --git a/cockatrice/src/game/game_scene.cpp b/cockatrice/src/game/game_scene.cpp index e649bbb2a..53851e31a 100644 --- a/cockatrice/src/game/game_scene.cpp +++ b/cockatrice/src/game/game_scene.cpp @@ -253,51 +253,6 @@ void GameScene::processViewSizeChange(const QSize &newSize) } } -void GameScene::updateHover(const QPointF &scenePos) -{ - QList itemList = - items(scenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, getViewTransform()); - - // Search for the topmost zone and ignore all cards not belonging to that zone. - CardZone *zone = 0; - for (int i = 0; i < itemList.size(); ++i) - if ((zone = qgraphicsitem_cast(itemList[i]))) - break; - - CardItem *maxZCard = 0; - if (zone) { - qreal maxZ = -1; - for (int i = 0; i < itemList.size(); ++i) { - CardItem *card = qgraphicsitem_cast(itemList[i]); - if (!card) - continue; - if (card->getAttachedTo()) { - if (card->getAttachedTo()->getZone() != zone) - continue; - } else if (card->getZone() != zone) - continue; - - if (card->getRealZValue() > maxZ) { - maxZ = card->getRealZValue(); - maxZCard = card; - } - } - } - if (hoveredCard && (maxZCard != hoveredCard)) - hoveredCard->setHovered(false); - if (maxZCard && (maxZCard != hoveredCard)) - maxZCard->setHovered(true); - hoveredCard = maxZCard; -} - -bool GameScene::event(QEvent *event) -{ - if (event->type() == QEvent::GraphicsSceneMouseMove) - updateHover(static_cast(event)->scenePos()); - - return QGraphicsScene::event(event); -} - void GameScene::timerEvent(QTimerEvent * /*event*/) { QMutableSetIterator i(cardsToAnimate); diff --git a/cockatrice/src/game/game_scene.h b/cockatrice/src/game/game_scene.h index 801acacee..e580f1e33 100644 --- a/cockatrice/src/game/game_scene.h +++ b/cockatrice/src/game/game_scene.h @@ -30,11 +30,9 @@ private: QList> playersByColumn; QList zoneViews; QSize viewSize; - QPointer hoveredCard; QBasicTimer *animationTimer; QSet cardsToAnimate; int playerRotation; - void updateHover(const QPointF &scenePos); public: explicit GameScene(PhasesToolbar *_phasesToolbar, QObject *parent = nullptr); @@ -65,7 +63,6 @@ public slots: void rearrange(); protected: - bool event(QEvent *event) override; void timerEvent(QTimerEvent *event) override; signals: void sigStartRubberBand(const QPointF &selectionOrigin); diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index bcd18d23a..e94342bd2 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -2392,7 +2392,6 @@ void Player::eventMoveCard(const Event_MoveCard &event, const GameEventContext & card->setFaceDown(event.face_down()); if (startZone != targetZone) { card->setBeingPointedAt(false); - card->setHovered(false); const QList &attachedCards = card->getAttachedCards(); for (auto attachedCard : attachedCards) { diff --git a/cockatrice/src/game/zones/pile_zone.cpp b/cockatrice/src/game/zones/pile_zone.cpp index 08ee3d28e..b2645bd6a 100644 --- a/cockatrice/src/game/zones/pile_zone.cpp +++ b/cockatrice/src/game/zones/pile_zone.cpp @@ -131,6 +131,7 @@ void PileZone::mouseReleaseEvent(QGraphicsSceneMouseEvent * /*event*/) void PileZone::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { if (!cards.isEmpty()) - cards[0]->processHoverEvent(); + emit cards[0]->hovered(cards[0]); + QGraphicsItem::hoverEnterEvent(event); } From 1851f71850ff461942ed00f65910acd07de4a7e9 Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Sat, 15 Mar 2025 20:09:14 +0100 Subject: [PATCH 11/44] Remove revealedCard flag from CardItem (#5723) It is no longer used since #5254. --- cockatrice/src/game/cards/card_item.cpp | 5 ++--- cockatrice/src/game/cards/card_item.h | 6 ------ cockatrice/src/game/zones/view_zone.cpp | 7 +++---- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/cockatrice/src/game/cards/card_item.cpp b/cockatrice/src/game/cards/card_item.cpp index 67515e978..a16fd71b1 100644 --- a/cockatrice/src/game/cards/card_item.cpp +++ b/cockatrice/src/game/cards/card_item.cpp @@ -22,10 +22,9 @@ CardItem::CardItem(Player *_owner, const QString &_name, const QString &_providerId, int _cardid, - bool _revealedCard, CardZone *_zone) - : AbstractCardItem(parent, _name, _providerId, _owner, _cardid), zone(_zone), revealedCard(_revealedCard), - attacking(false), destroyOnZoneChange(false), doesntUntap(false), dragItem(nullptr), attachedTo(nullptr) + : AbstractCardItem(parent, _name, _providerId, _owner, _cardid), zone(_zone), attacking(false), + destroyOnZoneChange(false), doesntUntap(false), dragItem(nullptr), attachedTo(nullptr) { owner->addCard(this); diff --git a/cockatrice/src/game/cards/card_item.h b/cockatrice/src/game/cards/card_item.h index 2c77af20e..5b4b325ba 100644 --- a/cockatrice/src/game/cards/card_item.h +++ b/cockatrice/src/game/cards/card_item.h @@ -22,7 +22,6 @@ class CardItem : public AbstractCardItem Q_OBJECT private: CardZone *zone; - bool revealedCard; bool attacking; QMap counters; QString annotation; @@ -55,7 +54,6 @@ public: const QString &_name = QString(), const QString &_providerId = QString(), int _cardid = -1, - bool revealedCard = false, CardZone *_zone = nullptr); ~CardItem() override; void retranslateUi(); @@ -85,10 +83,6 @@ public: { owner = _owner; } - bool getRevealedCard() const - { - return revealedCard; - } bool getAttacking() const { return attacking; diff --git a/cockatrice/src/game/zones/view_zone.cpp b/cockatrice/src/game/zones/view_zone.cpp index d86e71f90..9e9ba6e81 100644 --- a/cockatrice/src/game/zones/view_zone.cpp +++ b/cockatrice/src/game/zones/view_zone.cpp @@ -70,7 +70,7 @@ void ZoneViewZone::initializeCards(const QList &cardLis if (!cardList.isEmpty()) { for (int i = 0; i < cardList.size(); ++i) addCard(new CardItem(player, this, QString::fromStdString(cardList[i]->name()), - QString::fromStdString(cardList[i]->provider_id()), cardList[i]->id(), revealZone), + QString::fromStdString(cardList[i]->provider_id()), cardList[i]->id()), false, i); reorganizeCards(); } else if (!origZone->contentsKnown()) { @@ -88,8 +88,7 @@ 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); - addCard(new CardItem(player, this, card->getName(), card->getProviderId(), card->getId(), revealZone), - false, i); + addCard(new CardItem(player, this, card->getName(), card->getProviderId(), card->getId()), false, i); } reorganizeCards(); } @@ -103,7 +102,7 @@ void ZoneViewZone::zoneDumpReceived(const Response &r) const ServerInfo_Card &cardInfo = resp.zone_info().card_list(i); auto cardName = QString::fromStdString(cardInfo.name()); auto cardProviderId = QString::fromStdString(cardInfo.provider_id()); - auto *card = new CardItem(player, this, cardName, cardProviderId, cardInfo.id(), revealZone, this); + auto *card = new CardItem(player, this, cardName, cardProviderId, cardInfo.id(), this); cards.insert(i, card); } From a407c8b956ab5bc0c5d308b761fca151947e929e Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Sat, 15 Mar 2025 20:11:46 +0100 Subject: [PATCH 12/44] Reintroduce ability to display unused mana symbol widgets. (#5726) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Lukas Brübach --- .../additional_info/color_identity_widget.cpp | 51 +++++++++++++++---- .../additional_info/color_identity_widget.h | 4 ++ 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.cpp b/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.cpp index c39d6656d..59388bfc3 100644 --- a/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.cpp @@ -1,5 +1,6 @@ #include "color_identity_widget.h" +#include "../../../../../settings/cache_settings.h" #include "mana_symbol_widget.h" #include @@ -17,18 +18,21 @@ ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, CardInfoPtr _card) : Q 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) { - QString manaCost = card->getColors(); // Get mana cost string + manaCost = card->getColors(); // Get mana cost string QStringList symbols = parseColorIdentity(manaCost); // Parse mana cost string - for (const QString &symbol : symbols) { - ManaSymbolWidget *manaSymbol = new ManaSymbolWidget(this, symbol, true); - layout->addWidget(manaSymbol); - } + populateManaSymbolWidgets(); } + connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageDrawUnusedColorIdentitiesChanged, this, + &ColorIdentityWidget::toggleUnusedVisibility); } -ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, QString manaCost) : QWidget(parent), card(nullptr) +ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, QString _manaCost) + : QWidget(parent), card(nullptr), manaCost(_manaCost) { layout = new QHBoxLayout(this); layout->setSpacing(5); // Small spacing between icons @@ -36,14 +40,43 @@ ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, QString manaCost) : QW layout->setAlignment(Qt::AlignCenter); // Ensure icons are centered setLayout(layout); + populateManaSymbolWidgets(); + + connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageDrawUnusedColorIdentitiesChanged, this, + &ColorIdentityWidget::toggleUnusedVisibility); +} + +void ColorIdentityWidget::populateManaSymbolWidgets() +{ + // Define the full WUBRG set (White, Blue, Black, Red, Green) + QString fullColorIdentity = "WUBRG"; QStringList symbols = parseColorIdentity(manaCost); // Parse mana cost string - for (const QString &symbol : symbols) { - ManaSymbolWidget *manaSymbol = new ManaSymbolWidget(this, symbol); - layout->addWidget(manaSymbol); + if (SettingsCache::instance().getVisualDeckStorageDrawUnusedColorIdentities()) { + for (const QString symbol : fullColorIdentity) { + auto *manaSymbol = new ManaSymbolWidget(this, symbol, symbols.contains(symbol)); + layout->addWidget(manaSymbol); + } + } else { + for (const QString &symbol : symbols) { + auto *manaSymbol = new ManaSymbolWidget(this, symbol, symbols.contains(symbol)); + layout->addWidget(manaSymbol); + } } } +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(); +} + void ColorIdentityWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); diff --git a/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.h b/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.h index 1e81e7c26..8518aa1a5 100644 --- a/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.h @@ -12,13 +12,17 @@ class ColorIdentityWidget : public QWidget public: explicit ColorIdentityWidget(QWidget *parent, CardInfoPtr card); explicit ColorIdentityWidget(QWidget *parent, QString manaCost); + void populateManaSymbolWidgets(); QStringList parseColorIdentity(const QString &manaString); + public slots: void resizeEvent(QResizeEvent *event) override; + void toggleUnusedVisibility(); private: CardInfoPtr card; + QString manaCost; QHBoxLayout *layout; }; From b58b85dc0f01ab1ec91650d8d0517d5d7cad4907 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 15 Mar 2025 12:13:13 -0700 Subject: [PATCH 13/44] Re-add old names for mana value property to oracle (#5711) --- oracle/src/oracleimporter.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 31cf0e9a5..360423394 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -206,8 +206,9 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList { // mtgjson name => xml name static const QMap cardProperties{ - {"manaCost", "manacost"}, {"manaValue", "cmc"}, {"type", "type"}, - {"loyalty", "loyalty"}, {"layout", "layout"}, {"side", "side"}, + {"manaCost", "manacost"}, {"manaValue", "cmc"}, {"type", "type"}, + {"loyalty", "loyalty"}, {"layout", "layout"}, {"side", "side"}, + {"convertedManaCost", "cmc"}, // old name for manaValue, for backwards compatibility }; // mtgjson name => xml name @@ -349,7 +350,13 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList // add other face for split cards as card relation if (!getStringPropertyFromMap(card, "side").isEmpty()) { - properties["cmc"] = getStringPropertyFromMap(card, "faceManaValue"); + auto faceManaValue = getStringPropertyFromMap(card, "faceManaValue"); + if (faceManaValue.isEmpty()) { + // check the old name for the property, for backwards compatibility purposes + faceManaValue = getStringPropertyFromMap(card, "faceConvertedManaCost"); + } + properties["cmc"] = faceManaValue; + if (layout == "meld") { // meld cards don't work static const QRegularExpression meldNameRegex{"then meld them into ([^\\.]*)"}; QString additionalName = meldNameRegex.match(text).captured(1); From c99afe7956945f6fcee7a5d782a6ce2dc3457578 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 15 Mar 2025 14:23:41 -0700 Subject: [PATCH 14/44] Optimize cipt parsing by early returning (#5727) --- oracle/src/parsehelpers.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/oracle/src/parsehelpers.cpp b/oracle/src/parsehelpers.cpp index c7f340139..a97bf67c9 100644 --- a/oracle/src/parsehelpers.cpp +++ b/oracle/src/parsehelpers.cpp @@ -24,6 +24,12 @@ */ bool parseCipt(const QString &name, const QString &text) { + // Use precompiled regex to check if text is a possible candidate, and early return if not + static auto prelimCheck = QRegularExpression(" enters( the battlefield)? tapped(?! unless)"); + if (!prelimCheck.match(text).hasMatch()) { + return false; + } + // Try to split shortname on most non-alphanumeric characters (including _) auto isShortnameDivider = [](const QChar &c) { return c == '_' || (!c.isLetterOrNumber() && c != '\'' && c != '\"'); From 4ada011632e114e2d2d69faa9d85f72c8a427f1f Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Sun, 16 Mar 2025 23:58:06 +0100 Subject: [PATCH 15/44] game: Automatic update of arrow position (#5729) Currently, zones must keep track of which cards they move in order to manually call `updatePath` on arrows. This patch sets the `ItemSendsScenePositionChanges` flag on `ArrowTarget`s to automatically update arrow positions without requiring zones to keep track of that information. --- cockatrice/src/game/board/arrow_target.cpp | 14 ++++++++++++++ cockatrice/src/game/board/arrow_target.h | 3 +++ .../src/game/cards/abstract_card_item.cpp | 2 +- cockatrice/src/game/cards/card_item.cpp | 2 +- cockatrice/src/game/zones/stack_zone.cpp | 12 ------------ cockatrice/src/game/zones/table_zone.cpp | 18 ------------------ 6 files changed, 19 insertions(+), 32 deletions(-) diff --git a/cockatrice/src/game/board/arrow_target.cpp b/cockatrice/src/game/board/arrow_target.cpp index e844757dd..2dbd913fa 100644 --- a/cockatrice/src/game/board/arrow_target.cpp +++ b/cockatrice/src/game/board/arrow_target.cpp @@ -6,6 +6,7 @@ ArrowTarget::ArrowTarget(Player *_owner, QGraphicsItem *parent) : AbstractGraphicsItem(parent), owner(_owner), beingPointedAt(false) { + setFlag(ItemSendsScenePositionChanges); } ArrowTarget::~ArrowTarget() @@ -25,3 +26,16 @@ void ArrowTarget::setBeingPointedAt(bool _beingPointedAt) beingPointedAt = _beingPointedAt; update(); } + +QVariant ArrowTarget::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemScenePositionHasChanged && scene()) { + for (auto *arrow : arrowsFrom) + arrow->updatePath(); + + for (auto *arrow : arrowsTo) + arrow->updatePath(); + } + + return QGraphicsItem::itemChange(change, value); +} diff --git a/cockatrice/src/game/board/arrow_target.h b/cockatrice/src/game/board/arrow_target.h index 98a80091f..a4ef6895d 100644 --- a/cockatrice/src/game/board/arrow_target.h +++ b/cockatrice/src/game/board/arrow_target.h @@ -57,5 +57,8 @@ public: { arrowsTo.removeOne(arrow); } + +protected: + QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; }; #endif diff --git a/cockatrice/src/game/cards/abstract_card_item.cpp b/cockatrice/src/game/cards/abstract_card_item.cpp index b071caca9..28ed493e7 100644 --- a/cockatrice/src/game/cards/abstract_card_item.cpp +++ b/cockatrice/src/game/cards/abstract_card_item.cpp @@ -331,5 +331,5 @@ QVariant AbstractCardItem::itemChange(QGraphicsItem::GraphicsItemChange change, update(); return value; } else - return QGraphicsItem::itemChange(change, value); + return ArrowTarget::itemChange(change, value); } diff --git a/cockatrice/src/game/cards/card_item.cpp b/cockatrice/src/game/cards/card_item.cpp index a16fd71b1..44a7c0880 100644 --- a/cockatrice/src/game/cards/card_item.cpp +++ b/cockatrice/src/game/cards/card_item.cpp @@ -487,5 +487,5 @@ QVariant CardItem::itemChange(GraphicsItemChange change, const QVariant &value) owner->getGame()->setActiveCard(nullptr); } } - return QGraphicsItem::itemChange(change, value); + return AbstractCardItem::itemChange(change, value); } diff --git a/cockatrice/src/game/zones/stack_zone.cpp b/cockatrice/src/game/zones/stack_zone.cpp index 189a0a551..010f85b96 100644 --- a/cockatrice/src/game/zones/stack_zone.cpp +++ b/cockatrice/src/game/zones/stack_zone.cpp @@ -109,8 +109,6 @@ void StackZone::handleDropEvent(const QList &dragItems, CardZone void StackZone::reorganizeCards() { if (!cards.isEmpty()) { - QSet arrowsToUpdate; - const auto cardCount = static_cast(cards.size()); qreal totalWidth = boundingRect().width(); qreal cardWidth = cards.at(0)->boundingRect().width(); @@ -125,16 +123,6 @@ void StackZone::reorganizeCards() divideCardSpaceInZone(i, cardCount, boundingRect().height(), cards.at(0)->boundingRect().height()); card->setPos(x, y); card->setRealZValue(i); - - for (ArrowItem *item : card->getArrowsFrom()) { - arrowsToUpdate.insert(item); - } - for (ArrowItem *item : card->getArrowsTo()) { - arrowsToUpdate.insert(item); - } - } - for (ArrowItem *item : arrowsToUpdate) { - item->updatePath(); } } update(); diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index 686cb3588..d64acdcd7 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -157,8 +157,6 @@ void TableZone::handleDropEventByGrid(const QList &dragItems, void TableZone::reorganizeCards() { - QSet arrowsToUpdate; - // Calculate card stack widths so mapping functions work properly computeCardStackWidths(); @@ -189,23 +187,7 @@ void TableZone::reorganizeCards() qreal childY = y + 5; attachedCard->setPos(childX, childY); attachedCard->setRealZValue((childY + CARD_HEIGHT) * 100000 + (childX + 1) * 100); - for (ArrowItem *item : attachedCard->getArrowsFrom()) { - arrowsToUpdate.insert(item); - } - for (ArrowItem *item : attachedCard->getArrowsTo()) { - arrowsToUpdate.insert(item); - } } - - for (ArrowItem *item : cards[i]->getArrowsFrom()) { - arrowsToUpdate.insert(item); - } - for (ArrowItem *item : cards[i]->getArrowsTo()) { - arrowsToUpdate.insert(item); - } - } - for (ArrowItem *item : arrowsToUpdate) { - item->updatePath(); } resizeToContents(); From 273955008716956d9b201d9cb120843952338df9 Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Mon, 17 Mar 2025 00:01:25 +0100 Subject: [PATCH 16/44] Use enum for ThemeManager brushes (#5730) * Use enum for ThemeManager brushes This patch introduces an enum to distinguish the different brushes that can be set by the theme (hand, stack, etc.) and generic functions taking the enum rather than having one copy of each function for each brush. This is preliminary work before merging StackZone and HandZone to simplify #4974. * Include header * Header spacing --- cockatrice/src/client/ui/theme_manager.cpp | 89 +++++++++------------- cockatrice/src/client/ui/theme_manager.h | 38 ++++----- cockatrice/src/game/deckview/deck_view.cpp | 2 +- cockatrice/src/game/player/player.cpp | 7 +- cockatrice/src/game/zones/hand_zone.cpp | 7 +- cockatrice/src/game/zones/stack_zone.cpp | 7 +- cockatrice/src/game/zones/table_zone.cpp | 7 +- 7 files changed, 58 insertions(+), 99 deletions(-) diff --git a/cockatrice/src/client/ui/theme_manager.cpp b/cockatrice/src/client/ui/theme_manager.cpp index 4decf3f40..3dff1b7ac 100644 --- a/cockatrice/src/client/ui/theme_manager.cpp +++ b/cockatrice/src/client/ui/theme_manager.cpp @@ -120,10 +120,10 @@ void ThemeManager::themeChangedSlot() if (dirPath.isEmpty()) { // set default values QDir::setSearchPaths("theme", DEFAULT_RESOURCE_PATHS); - handBgBrush = HANDZONE_BG_DEFAULT; - tableBgBrush = TABLEZONE_BG_DEFAULT; - playerBgBrush = PLAYERZONE_BG_DEFAULT; - stackBgBrush = STACKZONE_BG_DEFAULT; + brushes[Role::Hand] = HANDZONE_BG_DEFAULT; + brushes[Role::Table] = TABLEZONE_BG_DEFAULT; + brushes[Role::Player] = PLAYERZONE_BG_DEFAULT; + brushes[Role::Stack] = STACKZONE_BG_DEFAULT; } else { // resources QStringList resources; @@ -132,73 +132,58 @@ void ThemeManager::themeChangedSlot() // zones bg dir.cd("zones"); - handBgBrush = loadBrush(HANDZONE_BG_NAME, HANDZONE_BG_DEFAULT); - tableBgBrush = loadBrush(TABLEZONE_BG_NAME, TABLEZONE_BG_DEFAULT); - playerBgBrush = loadBrush(PLAYERZONE_BG_NAME, PLAYERZONE_BG_DEFAULT); - stackBgBrush = loadBrush(STACKZONE_BG_NAME, STACKZONE_BG_DEFAULT); + brushes[Role::Hand] = loadBrush(HANDZONE_BG_NAME, HANDZONE_BG_DEFAULT); + brushes[Role::Table] = loadBrush(TABLEZONE_BG_NAME, TABLEZONE_BG_DEFAULT); + brushes[Role::Player] = loadBrush(PLAYERZONE_BG_NAME, PLAYERZONE_BG_DEFAULT); + brushes[Role::Stack] = loadBrush(STACKZONE_BG_NAME, STACKZONE_BG_DEFAULT); + } + for (auto &brushCache : brushesCache) { + brushCache.clear(); } - tableBgBrushesCache.clear(); - stackBgBrushesCache.clear(); - playerBgBrushesCache.clear(); - handBgBrushesCache.clear(); QPixmapCache::clear(); emit themeChanged(); } -QBrush ThemeManager::getExtraTableBgBrush(QString extraNumber, QBrush &fallbackBrush) +static QString roleBgName(ThemeManager::Role role) { - QBrush returnBrush; + switch (role) { + case ThemeManager::Hand: + return HANDZONE_BG_NAME; - if (!tableBgBrushesCache.contains(extraNumber.toInt())) { - returnBrush = loadExtraBrush(TABLEZONE_BG_NAME + extraNumber, fallbackBrush); - tableBgBrushesCache.insert(extraNumber.toInt(), returnBrush); - } else { - returnBrush = tableBgBrushesCache.value(extraNumber.toInt()); + case ThemeManager::Player: + return PLAYERZONE_BG_NAME; + + case ThemeManager::Stack: + return STACKZONE_BG_NAME; + + case ThemeManager::Table: + return TABLEZONE_BG_NAME; + + default: + Q_ASSERT(false); } - - return returnBrush; } -QBrush ThemeManager::getExtraStackBgBrush(QString extraNumber, QBrush &fallbackBrush) +QBrush &ThemeManager::getBgBrush(Role role) { - QBrush returnBrush; - - if (!stackBgBrushesCache.contains(extraNumber.toInt())) { - returnBrush = loadExtraBrush(STACKZONE_BG_NAME + extraNumber, fallbackBrush); - stackBgBrushesCache.insert(extraNumber.toInt(), returnBrush); - } else { - returnBrush = stackBgBrushesCache.value(extraNumber.toInt()); - } - - return returnBrush; + return brushes[role]; } -QBrush ThemeManager::getExtraPlayerBgBrush(QString extraNumber, QBrush &fallbackBrush) +QBrush ThemeManager::getExtraBgBrush(Role role, int zoneId) { - QBrush returnBrush; - - if (!playerBgBrushesCache.contains(extraNumber.toInt())) { - returnBrush = loadExtraBrush(PLAYERZONE_BG_NAME + extraNumber, fallbackBrush); - playerBgBrushesCache.insert(extraNumber.toInt(), returnBrush); - } else { - returnBrush = playerBgBrushesCache.value(extraNumber.toInt()); + if (zoneId <= 0) { + return getBgBrush(role); } - return returnBrush; -} + QBrushMap &brushCache = brushesCache[role]; -QBrush ThemeManager::getExtraHandBgBrush(QString extraNumber, QBrush &fallbackBrush) -{ - QBrush returnBrush; - - if (!handBgBrushesCache.contains(extraNumber.toInt())) { - returnBrush = loadExtraBrush(HANDZONE_BG_NAME + extraNumber, fallbackBrush); - handBgBrushesCache.insert(extraNumber.toInt(), returnBrush); - } else { - returnBrush = handBgBrushesCache.value(extraNumber.toInt()); + if (!brushCache.contains(zoneId)) { + QBrush brush = loadExtraBrush(roleBgName(role) + QString::number(zoneId), getBgBrush(role)); + brushCache.insert(zoneId, brush); + return brush; } - return returnBrush; + return brushCache.value(zoneId); } diff --git a/cockatrice/src/client/ui/theme_manager.h b/cockatrice/src/client/ui/theme_manager.h index 2385f5c23..20854b961 100644 --- a/cockatrice/src/client/ui/theme_manager.h +++ b/cockatrice/src/client/ui/theme_manager.h @@ -8,6 +8,7 @@ #include #include #include +#include inline Q_LOGGING_CATEGORY(ThemeManagerLog, "theme_manager"); @@ -22,13 +23,23 @@ class ThemeManager : public QObject public: ThemeManager(QObject *parent = nullptr); + enum Role + { + MinRole = 0, + Hand = MinRole, + Stack, + Table, + Player, + MaxRole = Player, + }; + private: - QBrush handBgBrush, stackBgBrush, tableBgBrush, playerBgBrush; + std::array brushes; QStringMap availableThemes; /* Internal cache for multiple backgrounds */ - QBrushMap tableBgBrushesCache, stackBgBrushesCache, playerBgBrushesCache, handBgBrushesCache; + std::array brushesCache; protected: void ensureThemeDirectoryExists(); @@ -36,27 +47,10 @@ protected: QBrush loadExtraBrush(QString fileName, QBrush &fallbackBrush); public: - QBrush &getHandBgBrush() - { - return handBgBrush; - } - QBrush &getStackBgBrush() - { - return stackBgBrush; - } - QBrush &getTableBgBrush() - { - return tableBgBrush; - } - QBrush &getPlayerBgBrush() - { - return playerBgBrush; - } QStringMap &getAvailableThemes(); - QBrush getExtraTableBgBrush(QString extraNumber, QBrush &fallbackBrush); - QBrush getExtraStackBgBrush(QString extraNumber, QBrush &fallbackBrush); - QBrush getExtraPlayerBgBrush(QString extraNumber, QBrush &fallbackBrush); - QBrush getExtraHandBgBrush(QString extraNumber, QBrush &fallbackBrush); + + QBrush &getBgBrush(Role zone); + QBrush getExtraBgBrush(Role zone, int zoneId = 0); protected slots: void themeChangedSlot(); signals: diff --git a/cockatrice/src/game/deckview/deck_view.cpp b/cockatrice/src/game/deckview/deck_view.cpp index 2f01361db..bbc25ef6a 100644 --- a/cockatrice/src/game/deckview/deck_view.cpp +++ b/cockatrice/src/game/deckview/deck_view.cpp @@ -172,7 +172,7 @@ void DeckViewCardContainer::paint(QPainter *painter, const QStyleOptionGraphicsI { qreal totalTextWidth = getCardTypeTextWidth(); - painter->fillRect(boundingRect(), themeManager->getTableBgBrush()); + painter->fillRect(boundingRect(), themeManager->getBgBrush(ThemeManager::Table)); painter->setPen(QColor(255, 255, 255, 100)); painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY)); diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index e94342bd2..2e63bba4a 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -93,12 +93,7 @@ void PlayerArea::updateBg() void PlayerArea::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - QBrush brush = themeManager->getPlayerBgBrush(); - - if (playerZoneId > 0) { - // If the extra image is not found, load the default one - brush = themeManager->getExtraPlayerBgBrush(QString::number(playerZoneId), brush); - } + QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Player, playerZoneId); painter->fillRect(boundingRect(), brush); } diff --git a/cockatrice/src/game/zones/hand_zone.cpp b/cockatrice/src/game/zones/hand_zone.cpp index 896e9b60f..31f0515d4 100644 --- a/cockatrice/src/game/zones/hand_zone.cpp +++ b/cockatrice/src/game/zones/hand_zone.cpp @@ -78,12 +78,7 @@ QRectF HandZone::boundingRect() const void HandZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - QBrush brush = themeManager->getHandBgBrush(); - - if (player->getZoneId() > 0) { - // If the extra image is not found, load the default one - brush = themeManager->getExtraHandBgBrush(QString::number(player->getZoneId()), brush); - } + QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Hand, player->getZoneId()); painter->fillRect(boundingRect(), brush); } diff --git a/cockatrice/src/game/zones/stack_zone.cpp b/cockatrice/src/game/zones/stack_zone.cpp index 010f85b96..2f307085c 100644 --- a/cockatrice/src/game/zones/stack_zone.cpp +++ b/cockatrice/src/game/zones/stack_zone.cpp @@ -49,12 +49,7 @@ QRectF StackZone::boundingRect() const void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - QBrush brush = themeManager->getStackBgBrush(); - - if (player->getZoneId() > 0) { - // If the extra image is not found, load the default one - brush = themeManager->getExtraStackBgBrush(QString::number(player->getZoneId()), brush); - } + QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Stack, player->getZoneId()); painter->fillRect(boundingRect(), brush); } diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index d64acdcd7..71327b9ec 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -54,12 +54,7 @@ bool TableZone::isInverted() const void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - QBrush brush = themeManager->getTableBgBrush(); - - if (player->getZoneId() > 0) { - // If the extra image is not found, load the default one - brush = themeManager->getExtraTableBgBrush(QString::number(player->getZoneId()), brush); - } + QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Table, player->getZoneId()); painter->fillRect(boundingRect(), brush); if (active) { From 6b4ae8308ac4a2506c99910310d40549926bbe72 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 16 Mar 2025 16:02:06 -0700 Subject: [PATCH 17/44] Reduce tag display widget spacing (#5731) * Reduce tag display widget spacing * Reduce bottom margin in deck dock --- .../ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp | 2 ++ .../deck_preview/deck_preview_deck_tags_display_widget.cpp | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp index d24182167..a89f73aed 100644 --- a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp @@ -105,6 +105,8 @@ void DeckEditorDeckDockWidget::createDeckDock() auto *upperLayout = new QGridLayout; upperLayout->setObjectName("upperLayout"); + upperLayout->setContentsMargins(11, 11, 11, 0); + upperLayout->addWidget(nameLabel, 0, 0); upperLayout->addWidget(nameEdit, 0, 1); diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp index 60b4d5b26..ae09305d1 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp @@ -21,8 +21,7 @@ DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_par // Create layout auto *layout = new QHBoxLayout(this); - layout->setContentsMargins(5, 5, 5, 5); - layout->setSpacing(5); + layout->setContentsMargins(0, 0, 0, 0); setFixedHeight(100); From 57a896084147fa07374d3a54f3068d3e849b1338 Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Mon, 17 Mar 2025 00:02:31 +0100 Subject: [PATCH 18/44] Add declaration for setAttrRecur (#5734) --- cockatrice/src/client/ui/pixel_map_generator.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cockatrice/src/client/ui/pixel_map_generator.cpp b/cockatrice/src/client/ui/pixel_map_generator.cpp index c695e1093..25bd67264 100644 --- a/cockatrice/src/client/ui/pixel_map_generator.cpp +++ b/cockatrice/src/client/ui/pixel_map_generator.cpp @@ -180,6 +180,12 @@ QMap CountryPixmapGenerator::pmCache; * @param idName id that the tag has to match * @param attrValue the value to update the attribute to */ +static void setAttrRecur(QDomElement &elem, + const QString &tagName, + const QString &attrName, + const QString &idName, + const QString &attrValue); + void setAttrRecur(QDomElement &elem, const QString &tagName, const QString &attrName, From 37382dea44ed4d0664e76b0ed9b3e468ec2b433b Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Mon, 17 Mar 2025 00:05:04 +0100 Subject: [PATCH 19/44] Close the `TabGame`s when closing the `TabSupervisor` (#5735) * Close the `TabGame`s when closing the `TabSupervisor` This ensures that we go through the same code path (in terms of Qt events) when closing the whole supervisor as when closing a single tab. Also, use the `close` event instead of the `hide` event to detect when we are closing a game. Fixes #5697 * Compat with old Qt versions * Old Qt, reloaded * Review: use hideEvent and call super --- cockatrice/src/client/tabs/tab_game.cpp | 40 ++++++++++--------- cockatrice/src/client/tabs/tab_game.h | 1 + cockatrice/src/client/tabs/tab_supervisor.cpp | 27 ++++++++++--- cockatrice/src/client/tabs/tab_supervisor.h | 4 +- cockatrice/src/client/ui/window_main.cpp | 2 +- 5 files changed, 47 insertions(+), 27 deletions(-) diff --git a/cockatrice/src/client/tabs/tab_game.cpp b/cockatrice/src/client/tabs/tab_game.cpp index e6c794a6a..1b2dc0327 100644 --- a/cockatrice/src/client/tabs/tab_game.cpp +++ b/cockatrice/src/client/tabs/tab_game.cpp @@ -119,7 +119,6 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay) refreshShortcuts(); messageLog->logReplayStarted(gameInfo.game_id()); - this->installEventFilter(this); QTimer::singleShot(0, this, SLOT(loadLayout())); } @@ -164,7 +163,6 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, for (int i = gameInfo.game_types_size() - 1; i >= 0; i--) gameTypes.append(roomGameTypes.find(gameInfo.game_types(i)).value()); - this->installEventFilter(this); QTimer::singleShot(0, this, SLOT(loadLayout())); } @@ -1753,6 +1751,27 @@ void TabGame::createMessageDock(bool bReplay) connect(messageLayoutDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool))); } +void TabGame::hideEvent(QHideEvent *event) +{ + LayoutsSettings &layouts = SettingsCache::instance().layouts(); + if (replay) { + layouts.setReplayPlayAreaState(saveState()); + layouts.setReplayPlayAreaGeometry(saveGeometry()); + layouts.setReplayCardInfoSize(cardInfoDock->size()); + layouts.setReplayMessageLayoutSize(messageLayoutDock->size()); + layouts.setReplayPlayerListSize(playerListDock->size()); + layouts.setReplayReplaySize(replayDock->size()); + } else { + layouts.setGamePlayAreaState(saveState()); + layouts.setGamePlayAreaGeometry(saveGeometry()); + layouts.setGameCardInfoSize(cardInfoDock->size()); + layouts.setGameMessageLayoutSize(messageLayoutDock->size()); + layouts.setGamePlayerListSize(playerListDock->size()); + } + + Tab::hideEvent(event); +} + // Method uses to sync docks state with menu items state bool TabGame::eventFilter(QObject *o, QEvent *e) { @@ -1772,23 +1791,6 @@ bool TabGame::eventFilter(QObject *o, QEvent *e) } } - if (o == this && e->type() == QEvent::Hide) { - LayoutsSettings &layouts = SettingsCache::instance().layouts(); - if (replay) { - layouts.setReplayPlayAreaState(saveState()); - layouts.setReplayPlayAreaGeometry(saveGeometry()); - layouts.setReplayCardInfoSize(cardInfoDock->size()); - layouts.setReplayMessageLayoutSize(messageLayoutDock->size()); - layouts.setReplayPlayerListSize(playerListDock->size()); - layouts.setReplayReplaySize(replayDock->size()); - } else { - layouts.setGamePlayAreaState(saveState()); - layouts.setGamePlayAreaGeometry(saveGeometry()); - layouts.setGameCardInfoSize(cardInfoDock->size()); - layouts.setGameMessageLayoutSize(messageLayoutDock->size()); - layouts.setGamePlayerListSize(playerListDock->size()); - } - } return false; } diff --git a/cockatrice/src/client/tabs/tab_game.h b/cockatrice/src/client/tabs/tab_game.h index 984ed4b0d..096998728 100644 --- a/cockatrice/src/client/tabs/tab_game.h +++ b/cockatrice/src/client/tabs/tab_game.h @@ -208,6 +208,7 @@ private slots: void actResetLayout(); void freeDocksSize(); + void hideEvent(QHideEvent *event) override; bool eventFilter(QObject *o, QEvent *e) override; void dockVisibleTriggered(); void dockFloatingTriggered(); diff --git a/cockatrice/src/client/tabs/tab_supervisor.cpp b/cockatrice/src/client/tabs/tab_supervisor.cpp index e2fb234d7..8ba5d791a 100644 --- a/cockatrice/src/client/tabs/tab_supervisor.cpp +++ b/cockatrice/src/client/tabs/tab_supervisor.cpp @@ -232,22 +232,39 @@ void TabSupervisor::refreshShortcuts() aTabLog->setShortcuts(shortcuts.getShortcut("Tabs/aTabLog")); } -bool TabSupervisor::closeRequest() +void TabSupervisor::closeEvent(QCloseEvent *event) { + // This will accept the event, which we may then override. + QTabWidget::closeEvent(event); + if (getGameCount()) { if (QMessageBox::question(this, tr("Are you sure?"), tr("There are still open games. Are you sure you want to quit?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) { - return false; + event->ignore(); + return; } } for (AbstractTabDeckEditor *tab : deckEditorTabs) { - if (!tab->confirmClose()) - return false; + if (!tab->confirmClose()) { + event->ignore(); + } } - return true; + // Close the game tabs in order to make sure they store their layout. + QSet gameTabsToRemove; + for (auto it = gameTabs.begin(), end = gameTabs.end(); it != end; ++it) { + if (it.value()->close()) { + gameTabsToRemove.insert(it.key()); + } else { + event->ignore(); + } + } + + for (auto tabId : gameTabsToRemove) { + gameTabs.remove(tabId); + } } AbstractClient *TabSupervisor::getClient() const diff --git a/cockatrice/src/client/tabs/tab_supervisor.h b/cockatrice/src/client/tabs/tab_supervisor.h index 068128793..c8ae109d8 100644 --- a/cockatrice/src/client/tabs/tab_supervisor.h +++ b/cockatrice/src/client/tabs/tab_supervisor.h @@ -138,7 +138,7 @@ public: return deckEditorTabs; } bool getAdminLocked() const; - bool closeRequest(); + void closeEvent(QCloseEvent *event) override; bool switchToGameTabIfAlreadyExists(const int gameId); static void actShowPopup(const QString &message); signals: @@ -192,4 +192,4 @@ private slots: void processNotifyUserEvent(const Event_NotifyUser &event); }; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/client/ui/window_main.cpp b/cockatrice/src/client/ui/window_main.cpp index 9e5129bf1..cf0ee6c27 100644 --- a/cockatrice/src/client/ui/window_main.cpp +++ b/cockatrice/src/client/ui/window_main.cpp @@ -1018,7 +1018,7 @@ void MainWindow::closeEvent(QCloseEvent *event) return; bClosingDown = true; - if (!tabSupervisor->closeRequest()) { + if (!tabSupervisor->close()) { event->ignore(); bClosingDown = false; return; From bd28e04635c7c4d342b244f541f262cee6de2aa4 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Mon, 17 Mar 2025 00:05:38 +0100 Subject: [PATCH 20/44] Reintroduce unused color identity opacity (#5733) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Lukas Brübach --- .../cards/additional_info/mana_symbol_widget.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp index a66c314a5..8226d998c 100644 --- a/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp @@ -1,5 +1,7 @@ #include "mana_symbol_widget.h" +#include "../../../../../settings/cache_settings.h" + #include ManaSymbolWidget::ManaSymbolWidget(QWidget *parent, QString _symbol, bool _isActive, bool _mayBeToggled) @@ -26,7 +28,14 @@ void ManaSymbolWidget::setColorActive(bool active) void ManaSymbolWidget::updateOpacity() { - qreal opacity = isActive ? 1.0 : 0.5; + qreal opacity; + if (mayBeToggled) { + // UI elements that users can click on shouldn't be transparent. + opacity = isActive ? 1.0 : 0.5; + } else { + // It's just for display, they can do whatever they want. + opacity = isActive ? 1.0 : SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity() / 100.0; + } opacityEffect->setOpacity(opacity); } From a7641a571fa297061e9909fa84685792b004aeeb Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Mon, 17 Mar 2025 00:19:39 +0100 Subject: [PATCH 21/44] Display visual feedback of where cards will go (#5737) This is part of the code from #4974, including an improved drag-and-drop API and its use to display visual feedback of card destination on the board. It does not include the improved logic for pushing cards around as I still need to figure out edge cases there - the logic for choosing where cards go is not changed, so some of the artifacts described in #4817 and #4975 (particularly around multi-card) are still present. --- cockatrice/src/game/cards/card_drag_item.cpp | 148 ++++++++++--------- cockatrice/src/game/cards/card_drag_item.h | 15 +- cockatrice/src/game/zones/card_zone.cpp | 28 +++- cockatrice/src/game/zones/card_zone.h | 21 ++- cockatrice/src/game/zones/table_zone.cpp | 99 +++++++++++-- cockatrice/src/game/zones/table_zone.h | 19 ++- 6 files changed, 230 insertions(+), 100 deletions(-) diff --git a/cockatrice/src/game/cards/card_drag_item.cpp b/cockatrice/src/game/cards/card_drag_item.cpp index d21e9168b..318aceefa 100644 --- a/cockatrice/src/game/cards/card_drag_item.cpp +++ b/cockatrice/src/game/cards/card_drag_item.cpp @@ -2,8 +2,6 @@ #include "../game_scene.h" #include "../zones/card_zone.h" -#include "../zones/table_zone.h" -#include "../zones/view_zone.h" #include "card_item.h" #include @@ -15,104 +13,112 @@ CardDragItem::CardDragItem(CardItem *_item, const QPointF &_hotSpot, bool _faceDown, AbstractCardDragItem *parentDrag) - : AbstractCardDragItem(_item, _hotSpot, parentDrag), id(_id), faceDown(_faceDown), occupied(false), currentZone(0) + : AbstractCardDragItem(_item, _hotSpot, parentDrag), id(_id), faceDown(_faceDown), currentZone(0) { } +CardDragItem::~CardDragItem() +{ + if (currentZone) { + currentZone->dragLeave(this); + currentZone = nullptr; + } +} + void CardDragItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { AbstractCardDragItem::paint(painter, option, widget); - if (occupied) + if (!isValid) { painter->fillPath(shape(), QColor(200, 0, 0, 100)); + } } void CardDragItem::updatePosition(const QPointF &cursorScenePos) { + QPointF topLeftScenePos = cursorScenePos - hotSpot; + QPointF centerScenePos = topLeftScenePos + QPointF(CARD_WIDTH_HALF, CARD_HEIGHT_HALF); + + // Use center of the card for intersection. QList colliding = - scene()->items(cursorScenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, + scene()->items(centerScenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, static_cast(scene())->getViewTransform()); - CardZone *cardZone = 0; - ZoneViewZone *zoneViewZone = 0; - for (int i = colliding.size() - 1; i >= 0; i--) { - CardZone *temp = qgraphicsitem_cast(colliding.at(i)); - if (!cardZone) - cardZone = temp; - if (!zoneViewZone) - zoneViewZone = qobject_cast(temp); - } - CardZone *cursorZone = 0; - if (zoneViewZone) - cursorZone = zoneViewZone; - else if (cardZone) - cursorZone = cardZone; - if (!cursorZone) - return; - currentZone = cursorZone; + CardZone *cardZone = nullptr; + for (auto *item : colliding) { + cardZone = qgraphicsitem_cast(item); - QPointF zonePos = currentZone->scenePos(); - QPointF cursorPosInZone = cursorScenePos - zonePos; - - // If we are on a Table, we center the card around the cursor, because we - // snap it into place and no longer see it being dragged. - // - // For other zones (where we do display the card under the cursor), we use - // the hotspot to feel like the card was dragged at the corresponding - // position. - TableZone *tableZone = qobject_cast(cursorZone); - QPointF closestGridPoint; - if (tableZone) - closestGridPoint = tableZone->closestGridPoint(cursorPosInZone); - else - closestGridPoint = cursorPosInZone - hotSpot; - - QPointF newPos = zonePos + closestGridPoint; - - if (newPos != pos()) { - for (int i = 0; i < childDrags.size(); i++) - childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot()); - setPos(newPos); - - bool newOccupied = false; - TableZone *table = qobject_cast(cursorZone); - if (table) - if (table->getCardFromCoords(closestGridPoint)) - newOccupied = true; - if (newOccupied != occupied) { - occupied = newOccupied; - update(); + if (cardZone) { + break; } } + + if (cardZone != currentZone) { + if (currentZone) { + currentZone->dragLeave(this); + currentZone = nullptr; + } + + if (cardZone && cardZone->dragEnter(this, centerScenePos - cardZone->scenePos())) { + setValid(true); + currentZone = cardZone; + } else { + setValid(false); + } + } + + if (currentZone) { + currentZone->dragMove(this, centerScenePos - currentZone->scenePos()); + } + + if (topLeftScenePos != pos()) { + for (int i = 0; i < childDrags.size(); i++) + childDrags[i]->setPos(topLeftScenePos + childDrags[i]->getHotSpot()); + setPos(topLeftScenePos); + } } void CardDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { setCursor(Qt::OpenHandCursor); QGraphicsScene *sc = scene(); - QPointF sp = pos(); sc->removeItem(this); - QList dragItemList; - CardZone *startZone = static_cast(item)->getZone(); - if (currentZone && !(static_cast(item)->getAttachedTo() && (startZone == currentZone))) { - if (!occupied) { - dragItemList.append(this); - } - - for (int i = 0; i < childDrags.size(); i++) { - CardDragItem *c = static_cast(childDrags[i]); - if (!occupied && !(static_cast(c->item)->getAttachedTo() && (startZone == currentZone)) && - !c->occupied) { - dragItemList.append(c); - } - sc->removeItem(c); - } + for (auto *childDrag : childDrags) { + sc->removeItem(childDrag); } if (currentZone) { - currentZone->handleDropEvent(dragItemList, startZone, (sp - currentZone->scenePos()).toPoint()); + QPointF cursorScenePos = event->scenePos(); + QPointF topLeftScenePos = cursorScenePos - hotSpot; + QPointF centerScenePos = topLeftScenePos + QPointF(CARD_WIDTH_HALF, CARD_HEIGHT_HALF); + currentZone->dragAccept(this, centerScenePos - currentZone->scenePos()); + currentZone = nullptr; } - event->accept(); + static_cast(item)->deleteDragItem(); + AbstractCardDragItem::mouseReleaseEvent(event); +} + +CardZone *CardDragItem::getStartZone() const +{ + return static_cast(item)->getZone(); +} + +QList CardDragItem::getValidItems() +{ + QList dragItemList; + CardZone *startZone = getStartZone(); + if (!(static_cast(item)->getAttachedTo() && startZone == currentZone)) { + dragItemList.append(this); + + for (int i = 0; i < childDrags.size(); i++) { + CardDragItem *c = static_cast(childDrags[i]); + if (!(static_cast(c->item)->getAttachedTo() && startZone == currentZone)) { + dragItemList.append(c); + } + } + } + + return dragItemList; } diff --git a/cockatrice/src/game/cards/card_drag_item.h b/cockatrice/src/game/cards/card_drag_item.h index 794fd6ed3..19db2757f 100644 --- a/cockatrice/src/game/cards/card_drag_item.h +++ b/cockatrice/src/game/cards/card_drag_item.h @@ -11,7 +11,7 @@ class CardDragItem : public AbstractCardDragItem private: int id; bool faceDown; - bool occupied; + bool isValid = true; CardZone *currentZone; public: @@ -20,6 +20,7 @@ public: const QPointF &_hotSpot, bool _faceDown, AbstractCardDragItem *parentDrag = 0); + virtual ~CardDragItem(); int getId() const { return id; @@ -31,6 +32,18 @@ public: void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; void updatePosition(const QPointF &cursorScenePos) override; + void setValid(bool _valid) + { + if (_valid != isValid) { + isValid = _valid; + update(); + } + } + + CardZone *getStartZone() const; + + QList getValidItems(); + protected: void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; }; diff --git a/cockatrice/src/game/zones/card_zone.cpp b/cockatrice/src/game/zones/card_zone.cpp index f3ebbd214..b4806a010 100644 --- a/cockatrice/src/game/zones/card_zone.cpp +++ b/cockatrice/src/game/zones/card_zone.cpp @@ -1,11 +1,10 @@ #include "card_zone.h" #include "../cards/card_database_manager.h" +#include "../cards/card_drag_item.h" #include "../cards/card_item.h" #include "../player/player.h" #include "pb/command_move_card.pb.h" -#include "pb/serverinfo_user.pb.h" -#include "pile_zone.h" #include "view_zone.h" #include @@ -233,7 +232,26 @@ void CardZone::moveAllToZone() player->sendGameCommand(cmd); } -QPointF CardZone::closestGridPoint(const QPointF &point) +bool CardZone::dragEnter(CardDragItem *dragItem, const QPointF &pos) { - return point; -} \ No newline at end of file + Q_UNUSED(dragItem); + Q_UNUSED(pos); + + return true; +} + +void CardZone::dragMove(CardDragItem *dragItem, const QPointF &pos) +{ + Q_UNUSED(dragItem); + Q_UNUSED(pos); +} + +void CardZone::dragAccept(CardDragItem *dragItem, const QPointF &pos) +{ + handleDropEvent(dragItem->getValidItems(), dragItem->getStartZone(), pos.toPoint()); +} + +void CardZone::dragLeave(CardDragItem *dragItem) +{ + Q_UNUSED(dragItem); +} diff --git a/cockatrice/src/game/zones/card_zone.h b/cockatrice/src/game/zones/card_zone.h index 91cad179c..4bb67435f 100644 --- a/cockatrice/src/game/zones/card_zone.h +++ b/cockatrice/src/game/zones/card_zone.h @@ -58,6 +58,26 @@ public: { return Type; } + + /* Called when a card is dragged on top of the zone. + + Return `true` to accept the drag, `false` otherwise. + */ + virtual bool dragEnter(CardDragItem *dragItem, const QPointF &pos); + + /* Called when a card that has been accepted by `dragEnter` is moved over + the zone. + */ + virtual void dragMove(CardDragItem *dragItem, const QPointF &pos); + + /* Called when a card that has been accepted by `dragEnter` is dropped onto + the zone. */ + virtual void dragAccept(CardDragItem *dragItem, const QPointF &pos); + + /* Called when a card that has been accepted by `dragEnter` leaves the zone. + */ + virtual void dragLeave(CardDragItem *dragItem); + virtual void handleDropEvent(const QList &dragItem, CardZone *startZone, const QPoint &dropPoint) = 0; CardZone(Player *_player, @@ -114,7 +134,6 @@ public: return views; } virtual void reorganizeCards() = 0; - virtual QPointF closestGridPoint(const QPointF &point); bool getIsView() const { return isView; diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index 71327b9ec..1c02bb077 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -118,9 +118,66 @@ void TableZone::addCardImpl(CardItem *card, int _x, int _y) card->update(); } +bool TableZone::dragEnter(CardDragItem *dragItem, const QPointF &pos) +{ + if (!SelectZone::dragEnter(dragItem, pos)) { + return false; + } + + if (m_feedbackItem) { + scene()->removeItem(m_feedbackItem); + delete m_feedbackItem; + } + + m_feedbackItem = new QGraphicsPathItem(dragItem->getItem()->shape(), this); + m_feedbackItem->setBrush(QColor(176, 196, 222)); // lightsteelblue + m_feedbackItem->setPen(QColor(119, 136, 153)); // lightslategray + m_feedbackItem->setZValue(2000000005); + m_feedbackItem->setTransform(dragItem->getItem()->transform()); + updateFeedback(dragItem, pos); + + return true; +} + +void TableZone::dragMove(CardDragItem *dragItem, const QPointF &pos) +{ + if (m_feedbackItem) { + updateFeedback(dragItem, pos); + } + + SelectZone::dragMove(dragItem, pos); +} + +void TableZone::dragAccept(CardDragItem *dragItem, const QPointF &pos) +{ + SelectZone::dragAccept(dragItem, pos); + + if (m_feedbackItem) { + scene()->removeItem(m_feedbackItem); + delete m_feedbackItem; + m_feedbackItem = nullptr; + } +} + +void TableZone::dragLeave(CardDragItem *dragItem) +{ + SelectZone::dragLeave(dragItem); + + if (m_feedbackItem) { + scene()->removeItem(m_feedbackItem); + delete m_feedbackItem; + m_feedbackItem = nullptr; + } +} + void TableZone::handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint) { - handleDropEventByGrid(dragItems, startZone, mapToGrid(dropPoint)); + Q_UNUSED(dropPoint); + + // Always use the position of the feedback item for consistency + if (m_feedbackItem && m_feedbackItem->isVisible()) { + handleDropEventByGrid(dragItems, startZone, mapToGrid(m_feedbackItem->pos())); + } } void TableZone::handleDropEventByGrid(const QList &dragItems, @@ -260,12 +317,6 @@ CardItem *TableZone::getCardFromGrid(const QPoint &gridPoint) const return 0; } -CardItem *TableZone::getCardFromCoords(const QPointF &point) const -{ - QPoint gridPoint = mapToGrid(point); - return getCardFromGrid(gridPoint); -} - void TableZone::computeCardStackWidths() { // Each card stack is three grid points worth of card locations. @@ -366,18 +417,36 @@ QPoint TableZone::mapToGrid(const QPointF &mapPoint) const int xDiff = x - xStack; int gridPointX = stackCol * 3 + qMin(xDiff / STACKED_CARD_OFFSET_X, 2); - return QPoint(gridPointX, gridPointY); + return QPoint((gridPointX / 3) * 3, gridPointY); } -QPointF TableZone::closestGridPoint(const QPointF &point) +void TableZone::updateFeedback(CardDragItem *dragItem, const QPointF &point) { QPoint gridPoint = mapToGrid(point); - gridPoint.setX((gridPoint.x() / 3) * 3); - if (getCardFromGrid(gridPoint)) - gridPoint.setX(gridPoint.x() + 1); - if (getCardFromGrid(gridPoint)) - gridPoint.setX(gridPoint.x() + 1); - return mapFromGrid(gridPoint); + + for (int i = 0; i < 3; ++i) { + if (CardItem *existingCard = getCardFromGrid(gridPoint)) { + if (existingCard == dragItem->getItem()) { + // Dropping the card onto its existing position is a no-op, and + // we don't want to display the feedback item, but we don't want + // to display an invalid state either, because that'd be + // confusing. + dragItem->setValid(true); + m_feedbackItem->hide(); + return; + } + + gridPoint.setX(gridPoint.x() + 1); + } else { + dragItem->setValid(true); + m_feedbackItem->show(); + m_feedbackItem->setPos(mapFromGrid(gridPoint)); + return; + } + } + + dragItem->setValid(false); + m_feedbackItem->hide(); } int TableZone::clampValidTableRow(const int row) diff --git a/cockatrice/src/game/zones/table_zone.h b/cockatrice/src/game/zones/table_zone.h index 271276967..a10a05306 100644 --- a/cockatrice/src/game/zones/table_zone.h +++ b/cockatrice/src/game/zones/table_zone.h @@ -77,6 +77,8 @@ private: */ bool active; + QGraphicsPathItem *m_feedbackItem = nullptr; + bool isInverted() const; private slots: @@ -118,6 +120,14 @@ public: */ void toggleTapped(); + bool dragEnter(CardDragItem *dragItem, const QPointF &pos) override; + + void dragMove(CardDragItem *dragItem, const QPointF &pos) override; + + void dragAccept(CardDragItem *dragItem, const QPointF &pos) override; + + void dragLeave(CardDragItem *dragItem) override; + /** See HandleDropEventByGrid */ @@ -133,13 +143,6 @@ public: */ CardItem *getCardFromGrid(const QPoint &gridPoint) const; - /** - @return CardItem from coordinate location - */ - CardItem *getCardFromCoords(const QPointF &point) const; - - QPointF closestGridPoint(const QPointF &point) override; - static int clampValidTableRow(const int row); /** @@ -184,6 +187,8 @@ private: void paintZoneOutline(QPainter *painter); void paintLandDivider(QPainter *painter); + void updateFeedback(CardDragItem *dragItem, const QPointF &point); + /* Calculates card stack widths so mapping functions work properly */ From 4d8a124822465c7f5b42da2e2eb98ae4de561b0a Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 16 Mar 2025 16:19:57 -0700 Subject: [PATCH 22/44] Rename save to clipboard actions in DeckPreviewWidget (#5738) --- .../visual_deck_storage/deck_preview/deck_preview_widget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp index 3564a05a9..35f434bdf 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp @@ -298,11 +298,11 @@ QMenu *DeckPreviewWidget::createRightClickMenu() connect(saveToClipboardMenu->addAction(tr("Annotated")), &QAction::triggered, this, [this] { deckLoader->saveToClipboard(true, true); }); - connect(saveToClipboardMenu->addAction(tr("Annotated (No set name or number)")), &QAction::triggered, this, + connect(saveToClipboardMenu->addAction(tr("Annotated (No set info)")), &QAction::triggered, this, [this] { deckLoader->saveToClipboard(true, false); }); connect(saveToClipboardMenu->addAction(tr("Not Annotated")), &QAction::triggered, this, [this] { deckLoader->saveToClipboard(false, true); }); - connect(saveToClipboardMenu->addAction(tr("Not Annotated (No set name or number)")), &QAction::triggered, this, + connect(saveToClipboardMenu->addAction(tr("Not Annotated (No set info)")), &QAction::triggered, this, [this] { deckLoader->saveToClipboard(false, false); }); menu->addSeparator(); From 0d2061365c4d6e04fba62c0ff69c8272b1bf9dcf Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 16 Mar 2025 16:30:12 -0700 Subject: [PATCH 23/44] Fix edit deck in clipboard clearing values (#5732) * Fix edit deck in clipboard clearing values * fix build failures --- .../dialogs/dlg_load_deck_from_clipboard.cpp | 2 +- common/decklist.cpp | 28 ++++++++++++++----- common/decklist.h | 4 +-- .../clipboard_testing.cpp | 4 +-- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/cockatrice/src/dialogs/dlg_load_deck_from_clipboard.cpp b/cockatrice/src/dialogs/dlg_load_deck_from_clipboard.cpp index 6cecc10bf..26f92f4e9 100644 --- a/cockatrice/src/dialogs/dlg_load_deck_from_clipboard.cpp +++ b/cockatrice/src/dialogs/dlg_load_deck_from_clipboard.cpp @@ -80,7 +80,7 @@ bool AbstractDlgDeckTextEdit::loadIntoDeck(DeckLoader *deckLoader) const QTextStream stream(&buffer); - if (deckLoader->loadFromStream_Plain(stream)) { + if (deckLoader->loadFromStream_Plain(stream, true)) { if (loadSetNameAndNumberCheckBox->isChecked()) { deckLoader->resolveSetNameAndNumberToProviderID(); } else { diff --git a/common/decklist.cpp b/common/decklist.cpp index cf1240d8a..789f57910 100644 --- a/common/decklist.cpp +++ b/common/decklist.cpp @@ -548,7 +548,14 @@ bool DeckList::saveToFile_Native(QIODevice *device) return true; } -bool DeckList::loadFromStream_Plain(QTextStream &in) +/** + * Clears the decklist and loads in a new deck from text + * + * @param in The text to load + * @param preserveMetadata If true, don't clear the existing metadata + * @return False if the input was empty, true otherwise. + */ +bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata) { const QRegularExpression reCardLine(R"(^\s*[\w\[\(\{].*$)", QRegularExpression::UseUnicodePropertiesOption); const QRegularExpression reEmpty("^\\s*$"); @@ -577,7 +584,7 @@ bool DeckList::loadFromStream_Plain(QTextStream &in) {QRegularExpression("æ"), QString("ae")}, {QRegularExpression(" ?[|/]+ ?"), QString(" // ")}}; - cleanList(); + cleanList(preserveMetadata); auto inputs = in.readAll().trimmed().split('\n'); auto max_line = inputs.size(); @@ -752,7 +759,7 @@ InnerDecklistNode *DeckList::getZoneObjFromName(const QString &zoneName) bool DeckList::loadFromFile_Plain(QIODevice *device) { QTextStream in(device); - return loadFromStream_Plain(in); + return loadFromStream_Plain(in, false); } struct WriteToStream @@ -801,12 +808,19 @@ QString DeckList::writeToString_Plain(bool prefixSideboardCards, bool slashTappe return result; } -void DeckList::cleanList() +/** + * Clears all cards and other data from the decklist + * + * @param preserveMetadata If true, only clear the cards + */ +void DeckList::cleanList(bool preserveMetadata) { root->clearTree(); - setName(); - setComments(); - setTags(); + if (!preserveMetadata) { + setName(); + setComments(); + setTags(); + } refreshDeckHash(); } diff --git a/common/decklist.h b/common/decklist.h index 8acbd4945..de839ad23 100644 --- a/common/decklist.h +++ b/common/decklist.h @@ -351,13 +351,13 @@ public: QString writeToString_Native(); bool loadFromFile_Native(QIODevice *device); bool saveToFile_Native(QIODevice *device); - bool loadFromStream_Plain(QTextStream &stream); + 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); - void cleanList(); + void cleanList(bool preserveMetadata = false); bool isEmpty() const { return root->isEmpty() && name.isEmpty() && comments.isEmpty() && sideboardPlans.isEmpty(); diff --git a/tests/loading_from_clipboard/clipboard_testing.cpp b/tests/loading_from_clipboard/clipboard_testing.cpp index b9093364a..ad8db3dbf 100644 --- a/tests/loading_from_clipboard/clipboard_testing.cpp +++ b/tests/loading_from_clipboard/clipboard_testing.cpp @@ -18,7 +18,7 @@ void testEmpty(const QString &clipboard) QString cp(clipboard); DeckList deckList; QTextStream stream(&cp); // text stream requires local copy - deckList.loadFromStream_Plain(stream); + deckList.loadFromStream_Plain(stream, false); ASSERT_TRUE(deckList.getCardList().isEmpty()); } @@ -28,7 +28,7 @@ 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); + deckList.loadFromStream_Plain(stream, false); ASSERT_EQ(result.name, deckList.getName().toStdString()); ASSERT_EQ(result.comments, deckList.getComments().toStdString()); From 57b9f0e54c79b0d3dfe19fdea96fae8942d4e821 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 16 Mar 2025 19:43:11 -0700 Subject: [PATCH 24/44] Add CONFIGURE_DEPENDS to the cmake (#5739) --- cockatrice/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index a5ffed4be..9b50c9469 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -4,7 +4,7 @@ project(Cockatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") -file(GLOB_RECURSE cockatrice_CPP_FILES ${CMAKE_SOURCE_DIR}/cockatrice/src/*.cpp) +file(GLOB_RECURSE cockatrice_CPP_FILES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/cockatrice/src/*.cpp) set(cockatrice_SOURCES ${cockatrice_CPP_FILES} ${VERSION_STRING_CPP}) From 4812508afce1dc20c5a893b13e0c9ee197c69e3d Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Tue, 18 Mar 2025 02:43:14 +0100 Subject: [PATCH 25/44] DeckEditor: Initialize the `modified` flag (#5743) C++ does not require compilers to zero-initialize value types, so depending on the platform (here: Linux), the deck editor starts up with an uninitialized value in the `modified` flag, which is usually not zero. --- cockatrice/src/client/tabs/abstract_tab_deck_editor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cockatrice/src/client/tabs/abstract_tab_deck_editor.h b/cockatrice/src/client/tabs/abstract_tab_deck_editor.h index 47e6fc78c..9f3cca0e1 100644 --- a/cockatrice/src/client/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/client/tabs/abstract_tab_deck_editor.h @@ -147,7 +147,7 @@ protected: QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aDeckDockVisible, *aDeckDockFloating; QAction *aFilterDockVisible, *aFilterDockFloating, *aPrintingSelectorDockVisible, *aPrintingSelectorDockFloating; - bool modified; + bool modified = false; }; #endif // TAB_GENERIC_DECK_EDITOR_H From c219d8bdbbb2b65d715175e83cd8b8f569f6c521 Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Tue, 18 Mar 2025 03:54:16 +0100 Subject: [PATCH 26/44] hotfix: Remove menus when closing game (#5744) Version of #5740 that doesn't leave freed `QMenu`s lying around. --- cockatrice/src/client/tabs/tab_supervisor.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cockatrice/src/client/tabs/tab_supervisor.cpp b/cockatrice/src/client/tabs/tab_supervisor.cpp index 8ba5d791a..632eae06d 100644 --- a/cockatrice/src/client/tabs/tab_supervisor.cpp +++ b/cockatrice/src/client/tabs/tab_supervisor.cpp @@ -256,6 +256,12 @@ void TabSupervisor::closeEvent(QCloseEvent *event) QSet gameTabsToRemove; for (auto it = gameTabs.begin(), end = gameTabs.end(); it != end; ++it) { if (it.value()->close()) { + // Hotfix: the tab owns the `QMenu`s so they need to be cleared, + // otherwise we end up with use-after-free bugs. + if (it.value() == currentWidget()) { + emit setMenu(); + } + gameTabsToRemove.insert(it.key()); } else { event->ignore(); From b5c5d221c43c25456d73adc1d2ca3c759aaff3bc Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:21:28 -0700 Subject: [PATCH 27/44] Remove redundant "show unused color identities" settings (#5745) * move setting to vds settings menu * emit signal on change * rename setting --- .../additional_info/mana_symbol_widget.cpp | 3 ++ .../visual_deck_storage_widget.cpp | 32 +++++++++++++++++-- .../visual_deck_storage_widget.h | 3 ++ cockatrice/src/dialogs/dlg_settings.cpp | 21 ------------ cockatrice/src/dialogs/dlg_settings.h | 3 -- cockatrice/src/settings/cache_settings.cpp | 1 + cockatrice/src/settings/cache_settings.h | 1 + 7 files changed, 38 insertions(+), 26 deletions(-) diff --git a/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp index 8226d998c..dd4d1c1f6 100644 --- a/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp @@ -15,6 +15,9 @@ ManaSymbolWidget::ManaSymbolWidget(QWidget *parent, QString _symbol, bool _isAct opacityEffect = new QGraphicsOpacityEffect(this); setGraphicsEffect(opacityEffect); updateOpacity(); + + connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageUnusedColorIdentitiesOpacityChanged, this, + &ManaSymbolWidget::updateOpacity); } void ManaSymbolWidget::setColorActive(bool active) diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp index 8ac228410..a1c75644c 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(parent), folderWidget(nullptr) @@ -40,6 +41,7 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare refreshButton->setFixedSize(32, 32); connect(refreshButton, &QPushButton::clicked, this, &VisualDeckStorageWidget::refreshIfPossible); + // quick settings menu showFoldersCheckBox = new QCheckBox(this); showFoldersCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowFolders()); connect(showFoldersCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &VisualDeckStorageWidget::updateShowFolders); @@ -77,6 +79,29 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare connect(searchFolderNamesCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setVisualDeckStorageSearchFolderNames); + // color identity opacity selector + auto unusedColorIdentityOpacityWidget = new QWidget(this); + + unusedColorIdentitiesOpacityLabel = new QLabel(unusedColorIdentityOpacityWidget); + unusedColorIdentitiesOpacitySpinBox = new QSpinBox(unusedColorIdentityOpacityWidget); + + unusedColorIdentitiesOpacitySpinBox->setMinimum(0); + unusedColorIdentitiesOpacitySpinBox->setMaximum(100); + unusedColorIdentitiesOpacitySpinBox->setValue( + SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity()); + connect(unusedColorIdentitiesOpacitySpinBox, QOverload::of(&QSpinBox::valueChanged), + &SettingsCache::instance(), &SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity); + + unusedColorIdentitiesOpacityLabel->setBuddy(unusedColorIdentitiesOpacitySpinBox); + + unusedColorIdentitiesOpacitySpinBox->setValue( + SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity()); + + auto unusedColorIdentityOpacityLayout = new QHBoxLayout(unusedColorIdentityOpacityWidget); + unusedColorIdentityOpacityLayout->setContentsMargins(11, 0, 11, 0); + unusedColorIdentityOpacityLayout->addWidget(unusedColorIdentitiesOpacityLabel); + unusedColorIdentityOpacityLayout->addWidget(unusedColorIdentitiesOpacitySpinBox); + // card size slider cardSizeWidget = new CardSizeWidget(this, nullptr, SettingsCache::instance().getVisualDeckStorageCardSize()); @@ -84,9 +109,10 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare quickSettingsWidget->addSettingsWidget(showFoldersCheckBox); quickSettingsWidget->addSettingsWidget(tagFilterVisibilityCheckBox); quickSettingsWidget->addSettingsWidget(tagsOnWidgetsVisibilityCheckBox); - quickSettingsWidget->addSettingsWidget(drawUnusedColorIdentitiesCheckBox); quickSettingsWidget->addSettingsWidget(bannerCardComboBoxVisibilityCheckBox); quickSettingsWidget->addSettingsWidget(searchFolderNamesCheckBox); + quickSettingsWidget->addSettingsWidget(drawUnusedColorIdentitiesCheckBox); + quickSettingsWidget->addSettingsWidget(unusedColorIdentityOpacityWidget); quickSettingsWidget->addSettingsWidget(cardSizeWidget); searchAndSortLayout->addWidget(deckPreviewColorIdentityFilterWidget); @@ -159,9 +185,11 @@ void VisualDeckStorageWidget::retranslateUi() showFoldersCheckBox->setText(tr("Show Folders")); tagFilterVisibilityCheckBox->setText(tr("Show Tag Filter")); tagsOnWidgetsVisibilityCheckBox->setText(tr("Show Tags On Deck Previews")); - drawUnusedColorIdentitiesCheckBox->setText(tr("Draw not contained Color Identities")); bannerCardComboBoxVisibilityCheckBox->setText(tr("Show Banner Card Selection Option")); searchFolderNamesCheckBox->setText(tr("Include Folder Names in Search")); + drawUnusedColorIdentitiesCheckBox->setText(tr("Draw unused Color Identities")); + unusedColorIdentitiesOpacityLabel->setText(tr("Unused Color Identities Opacity")); + unusedColorIdentitiesOpacitySpinBox->setSuffix("%"); } /** diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h index adfff06cb..acab16710 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h @@ -15,6 +15,7 @@ #include #include +class QSpinBox; class VisualDeckStorageSearchWidget; class VisualDeckStorageSortWidget; class VisualDeckStorageTagFilterWidget; @@ -64,6 +65,8 @@ private: QCheckBox *tagFilterVisibilityCheckBox; QCheckBox *tagsOnWidgetsVisibilityCheckBox; QCheckBox *searchFolderNamesCheckBox; + QLabel *unusedColorIdentitiesOpacityLabel; + QSpinBox *unusedColorIdentitiesOpacitySpinBox; QScrollArea *scrollArea; VisualDeckStorageFolderDisplayWidget *folderWidget; diff --git a/cockatrice/src/dialogs/dlg_settings.cpp b/cockatrice/src/dialogs/dlg_settings.cpp index 058455069..b97155d2a 100644 --- a/cockatrice/src/dialogs/dlg_settings.cpp +++ b/cockatrice/src/dialogs/dlg_settings.cpp @@ -358,25 +358,8 @@ AppearanceSettingsPage::AppearanceSettingsPage() showShortcutsCheckBox.setChecked(settings.getShowShortcuts()); connect(&showShortcutsCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &AppearanceSettingsPage::showShortcutsChanged); - visualDeckStorageDrawUnusedColorIdentitiesCheckBox.setChecked( - settings.getVisualDeckStorageDrawUnusedColorIdentities()); - connect(&visualDeckStorageDrawUnusedColorIdentitiesCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, - &SettingsCache::setVisualDeckStorageDrawUnusedColorIdentities); - - visualDeckStorageUnusedColorIdentitiesOpacitySpinBox.setMinimum(0); - visualDeckStorageUnusedColorIdentitiesOpacitySpinBox.setMaximum(100); - visualDeckStorageUnusedColorIdentitiesOpacitySpinBox.setValue( - settings.getVisualDeckStorageUnusedColorIdentitiesOpacity()); - connect(&visualDeckStorageUnusedColorIdentitiesOpacitySpinBox, QOverload::of(&QSpinBox::valueChanged), - &settings, &SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity); - - visualDeckStorageUnusedColorIdentitiesOpacityLabel.setBuddy(&visualDeckStorageUnusedColorIdentitiesOpacitySpinBox); - auto *menuGrid = new QGridLayout; menuGrid->addWidget(&showShortcutsCheckBox, 0, 0); - menuGrid->addWidget(&visualDeckStorageDrawUnusedColorIdentitiesCheckBox, 1, 0); - menuGrid->addWidget(&visualDeckStorageUnusedColorIdentitiesOpacityLabel, 2, 0); - menuGrid->addWidget(&visualDeckStorageUnusedColorIdentitiesOpacitySpinBox, 2, 1); menuGroupBox = new QGroupBox; menuGroupBox->setLayout(menuGrid); @@ -547,10 +530,6 @@ void AppearanceSettingsPage::retranslateUi() menuGroupBox->setTitle(tr("Menu settings")); showShortcutsCheckBox.setText(tr("Show keyboard shortcuts in right-click menus")); - visualDeckStorageDrawUnusedColorIdentitiesCheckBox.setText( - tr("Draw missing color identities in visual deck storage without color label")); - visualDeckStorageUnusedColorIdentitiesOpacityLabel.setText(tr("Missing color identity opacity")); - visualDeckStorageUnusedColorIdentitiesOpacitySpinBox.setSuffix("%"); cardsGroupBox->setTitle(tr("Card rendering")); displayCardNamesCheckBox.setText(tr("Display card names on cards having a picture")); diff --git a/cockatrice/src/dialogs/dlg_settings.h b/cockatrice/src/dialogs/dlg_settings.h index 6ebe47307..ec295a618 100644 --- a/cockatrice/src/dialogs/dlg_settings.h +++ b/cockatrice/src/dialogs/dlg_settings.h @@ -102,9 +102,6 @@ private: QLabel minPlayersForMultiColumnLayoutLabel; QLabel maxFontSizeForCardsLabel; QCheckBox showShortcutsCheckBox; - QCheckBox visualDeckStorageDrawUnusedColorIdentitiesCheckBox; - QLabel visualDeckStorageUnusedColorIdentitiesOpacityLabel; - QSpinBox visualDeckStorageUnusedColorIdentitiesOpacitySpinBox; QCheckBox displayCardNamesCheckBox; QCheckBox autoRotateSidewaysLayoutCardsCheckBox; QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox; diff --git a/cockatrice/src/settings/cache_settings.cpp b/cockatrice/src/settings/cache_settings.cpp index 0dc082f0d..bd88a8a3f 100644 --- a/cockatrice/src/settings/cache_settings.cpp +++ b/cockatrice/src/settings/cache_settings.cpp @@ -721,6 +721,7 @@ void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity(int _visual visualDeckStorageUnusedColorIdentitiesOpacity = _visualDeckStorageUnusedColorIdentitiesOpacity; settings->setValue("interface/visualdeckstorageunusedcoloridentitiesopacity", visualDeckStorageUnusedColorIdentitiesOpacity); + emit visualDeckStorageUnusedColorIdentitiesOpacityChanged(visualDeckStorageUnusedColorIdentitiesOpacity); } void SettingsCache::setVisualDeckStoragePromptForConversion(QT_STATE_CHANGED_T _visualDeckStoragePromptForConversion) diff --git a/cockatrice/src/settings/cache_settings.h b/cockatrice/src/settings/cache_settings.h index eaecdf73c..771b2663c 100644 --- a/cockatrice/src/settings/cache_settings.h +++ b/cockatrice/src/settings/cache_settings.h @@ -64,6 +64,7 @@ signals: void visualDeckStorageShowTagsOnDeckPreviewsChanged(bool _visible); void visualDeckStorageCardSizeChanged(); void visualDeckStorageDrawUnusedColorIdentitiesChanged(bool _visible); + void visualDeckStorageUnusedColorIdentitiesOpacityChanged(bool value); void visualDeckStorageInGameChanged(bool enabled); void horizontalHandChanged(); void handJustificationChanged(); From 6c19254abd72cd59d4689a57de75258aea123b9f Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:22:16 -0700 Subject: [PATCH 28/44] Fix AttachTo tokens not having card info (#5747) --- cockatrice/src/game/player/player.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index 2e63bba4a..3c31f1f83 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -2064,7 +2064,6 @@ void Player::createCard(const CardItem *sourceCard, cmd.set_target_zone("table"); // We currently only support creating tokens on the table cmd.set_target_card_id(sourceCard->getId()); cmd.set_target_mode(Command_CreateToken::ATTACH_TO); - cmd.set_card_provider_id(sourceCard->getProviderId().toStdString()); break; case CardRelation::TransformInto: From 42301d4f1a6f76c6455083a379dcd0af852ceebe Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:22:36 -0700 Subject: [PATCH 29/44] Filter out non-deck files when building VDS (#5748) --- .../visual_deck_storage_folder_display_widget.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp index b198d06bd..ab64b0d60 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp @@ -58,6 +58,12 @@ void VisualDeckStorageFolderDisplayWidget::refreshUi() header->setText(bannerText); } +/** + * Gets all files in the directory that have a .txt or .cod extension + * + * @param filePath The directory to search through + * @param recursive Whether to search through subdirectories + */ static QStringList getAllFiles(const QString &filePath, bool recursive) { QStringList allFiles; @@ -65,7 +71,7 @@ static QStringList getAllFiles(const QString &filePath, bool recursive) // QDirIterator with QDir::Files ensures only files are listed (no directories) auto flags = recursive ? QDirIterator::Subdirectories | QDirIterator::FollowSymlinks : QDirIterator::NoIteratorFlags; - QDirIterator it(filePath, QDir::Files, flags); + QDirIterator it(filePath, {"*.txt", "*.cod"}, QDir::Files, flags); while (it.hasNext()) { allFiles << it.next(); // Add each file path to the list From 0fa744f6ec7b18604f0e38a1f279f4199ea72023 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Tue, 18 Mar 2025 18:53:14 -0700 Subject: [PATCH 30/44] Consolidate accepted decklist file extensions (#5749) * Consolidate accepted decklist file extensions * rename the other const --- .../src/client/tabs/abstract_tab_deck_editor.cpp | 2 +- .../visual_deck_storage_folder_display_widget.cpp | 2 +- cockatrice/src/deck/deck_loader.cpp | 7 ++++--- cockatrice/src/deck/deck_loader.h | 11 ++++++++++- cockatrice/src/dialogs/dlg_load_deck.cpp | 2 +- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp b/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp index 92e63230b..734ed1298 100644 --- a/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp @@ -355,7 +355,7 @@ bool AbstractTabDeckEditor::actSaveDeckAs() dialog.setDirectory(SettingsCache::instance().getDeckPath()); dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setDefaultSuffix("cod"); - dialog.setNameFilters(DeckLoader::fileNameFilters); + dialog.setNameFilters(DeckLoader::FILE_NAME_FILTERS); dialog.selectFile(getDeckList()->getName().trimmed()); if (!dialog.exec()) return false; diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp index ab64b0d60..b4a07386c 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp @@ -71,7 +71,7 @@ static QStringList getAllFiles(const QString &filePath, bool recursive) // QDirIterator with QDir::Files ensures only files are listed (no directories) auto flags = recursive ? QDirIterator::Subdirectories | QDirIterator::FollowSymlinks : QDirIterator::NoIteratorFlags; - QDirIterator it(filePath, {"*.txt", "*.cod"}, QDir::Files, flags); + QDirIterator it(filePath, DeckLoader::ACCEPTED_FILE_EXTENSIONS, QDir::Files, flags); while (it.hasNext()) { allFiles << it.next(); // Add each file path to the list diff --git a/cockatrice/src/deck/deck_loader.cpp b/cockatrice/src/deck/deck_loader.cpp index 697905d0e..16132c571 100644 --- a/cockatrice/src/deck/deck_loader.cpp +++ b/cockatrice/src/deck/deck_loader.cpp @@ -16,9 +16,10 @@ #include #include -const QStringList DeckLoader::fileNameFilters = QStringList() - << QObject::tr("Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck)") - << QObject::tr("All files (*.*)"); +const QStringList DeckLoader::ACCEPTED_FILE_EXTENSIONS = {"*.cod", "*.dec", "*.dek", "*.txt", "*.mwDeck"}; + +const QStringList DeckLoader::FILE_NAME_FILTERS = { + tr("Common deck formats (%1)").arg(ACCEPTED_FILE_EXTENSIONS.join(" ")), tr("All files (*.*)")}; DeckLoader::DeckLoader() : DeckList(), lastFileName(QString()), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1) { diff --git a/cockatrice/src/deck/deck_loader.h b/cockatrice/src/deck/deck_loader.h index 035830e0a..c62fd45c9 100644 --- a/cockatrice/src/deck/deck_loader.h +++ b/cockatrice/src/deck/deck_loader.h @@ -20,7 +20,16 @@ public: PlainTextFormat, CockatriceFormat }; - static const QStringList fileNameFilters; + + /** + * Supported file extensions for decklist files + */ + static const QStringList ACCEPTED_FILE_EXTENSIONS; + + /** + * For use with `QFileDialog::setNameFilters` + */ + static const QStringList FILE_NAME_FILTERS; private: QString lastFileName; diff --git a/cockatrice/src/dialogs/dlg_load_deck.cpp b/cockatrice/src/dialogs/dlg_load_deck.cpp index 0fabdaaa3..91252a768 100644 --- a/cockatrice/src/dialogs/dlg_load_deck.cpp +++ b/cockatrice/src/dialogs/dlg_load_deck.cpp @@ -11,7 +11,7 @@ DlgLoadDeck::DlgLoadDeck(QWidget *parent) : QFileDialog(parent, tr("Load Deck")) } setDirectory(startingDir); - setNameFilters(DeckLoader::fileNameFilters); + setNameFilters(DeckLoader::FILE_NAME_FILTERS); connect(this, &DlgLoadDeck::accepted, this, &DlgLoadDeck::actAccepted); } From 48123c8822adde4ed204bf12a787f12ac120650f Mon Sep 17 00:00:00 2001 From: Zach H Date: Tue, 18 Mar 2025 21:53:35 -0400 Subject: [PATCH 31/44] Revert "Display visual feedback of where cards will go (#5737)" (#5750) This reverts commit a7641a571fa297061e9909fa84685792b004aeeb. --- cockatrice/src/game/cards/card_drag_item.cpp | 132 +++++++++---------- cockatrice/src/game/cards/card_drag_item.h | 15 +-- cockatrice/src/game/zones/card_zone.cpp | 28 +--- cockatrice/src/game/zones/card_zone.h | 21 +-- cockatrice/src/game/zones/table_zone.cpp | 99 +++----------- cockatrice/src/game/zones/table_zone.h | 19 +-- 6 files changed, 92 insertions(+), 222 deletions(-) diff --git a/cockatrice/src/game/cards/card_drag_item.cpp b/cockatrice/src/game/cards/card_drag_item.cpp index 318aceefa..d21e9168b 100644 --- a/cockatrice/src/game/cards/card_drag_item.cpp +++ b/cockatrice/src/game/cards/card_drag_item.cpp @@ -2,6 +2,8 @@ #include "../game_scene.h" #include "../zones/card_zone.h" +#include "../zones/table_zone.h" +#include "../zones/view_zone.h" #include "card_item.h" #include @@ -13,68 +15,74 @@ CardDragItem::CardDragItem(CardItem *_item, const QPointF &_hotSpot, bool _faceDown, AbstractCardDragItem *parentDrag) - : AbstractCardDragItem(_item, _hotSpot, parentDrag), id(_id), faceDown(_faceDown), currentZone(0) + : AbstractCardDragItem(_item, _hotSpot, parentDrag), id(_id), faceDown(_faceDown), occupied(false), currentZone(0) { } -CardDragItem::~CardDragItem() -{ - if (currentZone) { - currentZone->dragLeave(this); - currentZone = nullptr; - } -} - void CardDragItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { AbstractCardDragItem::paint(painter, option, widget); - if (!isValid) { + if (occupied) painter->fillPath(shape(), QColor(200, 0, 0, 100)); - } } void CardDragItem::updatePosition(const QPointF &cursorScenePos) { - QPointF topLeftScenePos = cursorScenePos - hotSpot; - QPointF centerScenePos = topLeftScenePos + QPointF(CARD_WIDTH_HALF, CARD_HEIGHT_HALF); - - // Use center of the card for intersection. QList colliding = - scene()->items(centerScenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, + scene()->items(cursorScenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, static_cast(scene())->getViewTransform()); - CardZone *cardZone = nullptr; - for (auto *item : colliding) { - cardZone = qgraphicsitem_cast(item); - - if (cardZone) { - break; - } + CardZone *cardZone = 0; + ZoneViewZone *zoneViewZone = 0; + for (int i = colliding.size() - 1; i >= 0; i--) { + CardZone *temp = qgraphicsitem_cast(colliding.at(i)); + if (!cardZone) + cardZone = temp; + if (!zoneViewZone) + zoneViewZone = qobject_cast(temp); } + CardZone *cursorZone = 0; + if (zoneViewZone) + cursorZone = zoneViewZone; + else if (cardZone) + cursorZone = cardZone; + if (!cursorZone) + return; + currentZone = cursorZone; - if (cardZone != currentZone) { - if (currentZone) { - currentZone->dragLeave(this); - currentZone = nullptr; - } + QPointF zonePos = currentZone->scenePos(); + QPointF cursorPosInZone = cursorScenePos - zonePos; - if (cardZone && cardZone->dragEnter(this, centerScenePos - cardZone->scenePos())) { - setValid(true); - currentZone = cardZone; - } else { - setValid(false); - } - } + // If we are on a Table, we center the card around the cursor, because we + // snap it into place and no longer see it being dragged. + // + // For other zones (where we do display the card under the cursor), we use + // the hotspot to feel like the card was dragged at the corresponding + // position. + TableZone *tableZone = qobject_cast(cursorZone); + QPointF closestGridPoint; + if (tableZone) + closestGridPoint = tableZone->closestGridPoint(cursorPosInZone); + else + closestGridPoint = cursorPosInZone - hotSpot; - if (currentZone) { - currentZone->dragMove(this, centerScenePos - currentZone->scenePos()); - } + QPointF newPos = zonePos + closestGridPoint; - if (topLeftScenePos != pos()) { + if (newPos != pos()) { for (int i = 0; i < childDrags.size(); i++) - childDrags[i]->setPos(topLeftScenePos + childDrags[i]->getHotSpot()); - setPos(topLeftScenePos); + childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot()); + setPos(newPos); + + bool newOccupied = false; + TableZone *table = qobject_cast(cursorZone); + if (table) + if (table->getCardFromCoords(closestGridPoint)) + newOccupied = true; + if (newOccupied != occupied) { + occupied = newOccupied; + update(); + } } } @@ -82,43 +90,29 @@ void CardDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { setCursor(Qt::OpenHandCursor); QGraphicsScene *sc = scene(); + QPointF sp = pos(); sc->removeItem(this); - for (auto *childDrag : childDrags) { - sc->removeItem(childDrag); - } - - if (currentZone) { - QPointF cursorScenePos = event->scenePos(); - QPointF topLeftScenePos = cursorScenePos - hotSpot; - QPointF centerScenePos = topLeftScenePos + QPointF(CARD_WIDTH_HALF, CARD_HEIGHT_HALF); - currentZone->dragAccept(this, centerScenePos - currentZone->scenePos()); - currentZone = nullptr; - } - - static_cast(item)->deleteDragItem(); - AbstractCardDragItem::mouseReleaseEvent(event); -} - -CardZone *CardDragItem::getStartZone() const -{ - return static_cast(item)->getZone(); -} - -QList CardDragItem::getValidItems() -{ QList dragItemList; - CardZone *startZone = getStartZone(); - if (!(static_cast(item)->getAttachedTo() && startZone == currentZone)) { - dragItemList.append(this); + CardZone *startZone = static_cast(item)->getZone(); + if (currentZone && !(static_cast(item)->getAttachedTo() && (startZone == currentZone))) { + if (!occupied) { + dragItemList.append(this); + } for (int i = 0; i < childDrags.size(); i++) { CardDragItem *c = static_cast(childDrags[i]); - if (!(static_cast(c->item)->getAttachedTo() && startZone == currentZone)) { + if (!occupied && !(static_cast(c->item)->getAttachedTo() && (startZone == currentZone)) && + !c->occupied) { dragItemList.append(c); } + sc->removeItem(c); } } - return dragItemList; + if (currentZone) { + currentZone->handleDropEvent(dragItemList, startZone, (sp - currentZone->scenePos()).toPoint()); + } + + event->accept(); } diff --git a/cockatrice/src/game/cards/card_drag_item.h b/cockatrice/src/game/cards/card_drag_item.h index 19db2757f..794fd6ed3 100644 --- a/cockatrice/src/game/cards/card_drag_item.h +++ b/cockatrice/src/game/cards/card_drag_item.h @@ -11,7 +11,7 @@ class CardDragItem : public AbstractCardDragItem private: int id; bool faceDown; - bool isValid = true; + bool occupied; CardZone *currentZone; public: @@ -20,7 +20,6 @@ public: const QPointF &_hotSpot, bool _faceDown, AbstractCardDragItem *parentDrag = 0); - virtual ~CardDragItem(); int getId() const { return id; @@ -32,18 +31,6 @@ public: void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; void updatePosition(const QPointF &cursorScenePos) override; - void setValid(bool _valid) - { - if (_valid != isValid) { - isValid = _valid; - update(); - } - } - - CardZone *getStartZone() const; - - QList getValidItems(); - protected: void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; }; diff --git a/cockatrice/src/game/zones/card_zone.cpp b/cockatrice/src/game/zones/card_zone.cpp index b4806a010..f3ebbd214 100644 --- a/cockatrice/src/game/zones/card_zone.cpp +++ b/cockatrice/src/game/zones/card_zone.cpp @@ -1,10 +1,11 @@ #include "card_zone.h" #include "../cards/card_database_manager.h" -#include "../cards/card_drag_item.h" #include "../cards/card_item.h" #include "../player/player.h" #include "pb/command_move_card.pb.h" +#include "pb/serverinfo_user.pb.h" +#include "pile_zone.h" #include "view_zone.h" #include @@ -232,26 +233,7 @@ void CardZone::moveAllToZone() player->sendGameCommand(cmd); } -bool CardZone::dragEnter(CardDragItem *dragItem, const QPointF &pos) +QPointF CardZone::closestGridPoint(const QPointF &point) { - Q_UNUSED(dragItem); - Q_UNUSED(pos); - - return true; -} - -void CardZone::dragMove(CardDragItem *dragItem, const QPointF &pos) -{ - Q_UNUSED(dragItem); - Q_UNUSED(pos); -} - -void CardZone::dragAccept(CardDragItem *dragItem, const QPointF &pos) -{ - handleDropEvent(dragItem->getValidItems(), dragItem->getStartZone(), pos.toPoint()); -} - -void CardZone::dragLeave(CardDragItem *dragItem) -{ - Q_UNUSED(dragItem); -} + return point; +} \ No newline at end of file diff --git a/cockatrice/src/game/zones/card_zone.h b/cockatrice/src/game/zones/card_zone.h index 4bb67435f..91cad179c 100644 --- a/cockatrice/src/game/zones/card_zone.h +++ b/cockatrice/src/game/zones/card_zone.h @@ -58,26 +58,6 @@ public: { return Type; } - - /* Called when a card is dragged on top of the zone. - - Return `true` to accept the drag, `false` otherwise. - */ - virtual bool dragEnter(CardDragItem *dragItem, const QPointF &pos); - - /* Called when a card that has been accepted by `dragEnter` is moved over - the zone. - */ - virtual void dragMove(CardDragItem *dragItem, const QPointF &pos); - - /* Called when a card that has been accepted by `dragEnter` is dropped onto - the zone. */ - virtual void dragAccept(CardDragItem *dragItem, const QPointF &pos); - - /* Called when a card that has been accepted by `dragEnter` leaves the zone. - */ - virtual void dragLeave(CardDragItem *dragItem); - virtual void handleDropEvent(const QList &dragItem, CardZone *startZone, const QPoint &dropPoint) = 0; CardZone(Player *_player, @@ -134,6 +114,7 @@ public: return views; } virtual void reorganizeCards() = 0; + virtual QPointF closestGridPoint(const QPointF &point); bool getIsView() const { return isView; diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index 1c02bb077..71327b9ec 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -118,66 +118,9 @@ void TableZone::addCardImpl(CardItem *card, int _x, int _y) card->update(); } -bool TableZone::dragEnter(CardDragItem *dragItem, const QPointF &pos) -{ - if (!SelectZone::dragEnter(dragItem, pos)) { - return false; - } - - if (m_feedbackItem) { - scene()->removeItem(m_feedbackItem); - delete m_feedbackItem; - } - - m_feedbackItem = new QGraphicsPathItem(dragItem->getItem()->shape(), this); - m_feedbackItem->setBrush(QColor(176, 196, 222)); // lightsteelblue - m_feedbackItem->setPen(QColor(119, 136, 153)); // lightslategray - m_feedbackItem->setZValue(2000000005); - m_feedbackItem->setTransform(dragItem->getItem()->transform()); - updateFeedback(dragItem, pos); - - return true; -} - -void TableZone::dragMove(CardDragItem *dragItem, const QPointF &pos) -{ - if (m_feedbackItem) { - updateFeedback(dragItem, pos); - } - - SelectZone::dragMove(dragItem, pos); -} - -void TableZone::dragAccept(CardDragItem *dragItem, const QPointF &pos) -{ - SelectZone::dragAccept(dragItem, pos); - - if (m_feedbackItem) { - scene()->removeItem(m_feedbackItem); - delete m_feedbackItem; - m_feedbackItem = nullptr; - } -} - -void TableZone::dragLeave(CardDragItem *dragItem) -{ - SelectZone::dragLeave(dragItem); - - if (m_feedbackItem) { - scene()->removeItem(m_feedbackItem); - delete m_feedbackItem; - m_feedbackItem = nullptr; - } -} - void TableZone::handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint) { - Q_UNUSED(dropPoint); - - // Always use the position of the feedback item for consistency - if (m_feedbackItem && m_feedbackItem->isVisible()) { - handleDropEventByGrid(dragItems, startZone, mapToGrid(m_feedbackItem->pos())); - } + handleDropEventByGrid(dragItems, startZone, mapToGrid(dropPoint)); } void TableZone::handleDropEventByGrid(const QList &dragItems, @@ -317,6 +260,12 @@ CardItem *TableZone::getCardFromGrid(const QPoint &gridPoint) const return 0; } +CardItem *TableZone::getCardFromCoords(const QPointF &point) const +{ + QPoint gridPoint = mapToGrid(point); + return getCardFromGrid(gridPoint); +} + void TableZone::computeCardStackWidths() { // Each card stack is three grid points worth of card locations. @@ -417,36 +366,18 @@ QPoint TableZone::mapToGrid(const QPointF &mapPoint) const int xDiff = x - xStack; int gridPointX = stackCol * 3 + qMin(xDiff / STACKED_CARD_OFFSET_X, 2); - return QPoint((gridPointX / 3) * 3, gridPointY); + return QPoint(gridPointX, gridPointY); } -void TableZone::updateFeedback(CardDragItem *dragItem, const QPointF &point) +QPointF TableZone::closestGridPoint(const QPointF &point) { QPoint gridPoint = mapToGrid(point); - - for (int i = 0; i < 3; ++i) { - if (CardItem *existingCard = getCardFromGrid(gridPoint)) { - if (existingCard == dragItem->getItem()) { - // Dropping the card onto its existing position is a no-op, and - // we don't want to display the feedback item, but we don't want - // to display an invalid state either, because that'd be - // confusing. - dragItem->setValid(true); - m_feedbackItem->hide(); - return; - } - - gridPoint.setX(gridPoint.x() + 1); - } else { - dragItem->setValid(true); - m_feedbackItem->show(); - m_feedbackItem->setPos(mapFromGrid(gridPoint)); - return; - } - } - - dragItem->setValid(false); - m_feedbackItem->hide(); + gridPoint.setX((gridPoint.x() / 3) * 3); + if (getCardFromGrid(gridPoint)) + gridPoint.setX(gridPoint.x() + 1); + if (getCardFromGrid(gridPoint)) + gridPoint.setX(gridPoint.x() + 1); + return mapFromGrid(gridPoint); } int TableZone::clampValidTableRow(const int row) diff --git a/cockatrice/src/game/zones/table_zone.h b/cockatrice/src/game/zones/table_zone.h index a10a05306..271276967 100644 --- a/cockatrice/src/game/zones/table_zone.h +++ b/cockatrice/src/game/zones/table_zone.h @@ -77,8 +77,6 @@ private: */ bool active; - QGraphicsPathItem *m_feedbackItem = nullptr; - bool isInverted() const; private slots: @@ -120,14 +118,6 @@ public: */ void toggleTapped(); - bool dragEnter(CardDragItem *dragItem, const QPointF &pos) override; - - void dragMove(CardDragItem *dragItem, const QPointF &pos) override; - - void dragAccept(CardDragItem *dragItem, const QPointF &pos) override; - - void dragLeave(CardDragItem *dragItem) override; - /** See HandleDropEventByGrid */ @@ -143,6 +133,13 @@ public: */ CardItem *getCardFromGrid(const QPoint &gridPoint) const; + /** + @return CardItem from coordinate location + */ + CardItem *getCardFromCoords(const QPointF &point) const; + + QPointF closestGridPoint(const QPointF &point) override; + static int clampValidTableRow(const int row); /** @@ -187,8 +184,6 @@ private: void paintZoneOutline(QPainter *painter); void paintLandDivider(QPainter *painter); - void updateFeedback(CardDragItem *dragItem, const QPointF &point); - /* Calculates card stack widths so mapping functions work properly */ From d03f5388d486b1b1b0de9945ddac8ae25a6d699f Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Fri, 21 Mar 2025 01:23:26 +0100 Subject: [PATCH 32/44] Update translations (#5758) --- cockatrice/cockatrice_en@source.ts | 2356 ++++++++++++++++------------ oracle/oracle_en@source.ts | 81 +- 2 files changed, 1392 insertions(+), 1045 deletions(-) diff --git a/cockatrice/cockatrice_en@source.ts b/cockatrice/cockatrice_en@source.ts index 82e0da237..8be7525f4 100644 --- a/cockatrice/cockatrice_en@source.ts +++ b/cockatrice/cockatrice_en@source.ts @@ -22,6 +22,86 @@ + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + 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 + + + + + No deck was selected to be saved. + + + AdminNotesDialog @@ -51,127 +131,123 @@ AppearanceSettingsPage - + Error - + Could not create themes directory at '%1'. - + Theme settings - + Current theme: - + Open themes folder - + Menu settings - + Show keyboard shortcuts in right-click menus - - Draw missing color identities in visual deck storage without color label - - - - - Missing color identity opacity - - - - + 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) [Requires Client restart] - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over - + 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: + + + + 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: @@ -291,22 +367,22 @@ This is only saved for moderators and cannot be seen by the banned person. BetaReleaseChannel - + Beta - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the file update server. @@ -457,22 +533,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 @@ -498,12 +574,12 @@ This is only saved for moderators and cannot be seen by the banned person. CardItem - + &Move to - + &Power / toughness @@ -639,169 +715,409 @@ This is only saved for moderators and cannot be seen by the banned person. + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + 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 + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + 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... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + 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 @@ -834,15 +1150,28 @@ This is only saved for moderators and cannot be seen by the banned person. + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes @@ -850,8 +1179,8 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewDeckTagsDisplayWidget - - + + Edit tags ... @@ -914,6 +1243,101 @@ 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 + + + DeckStatsInterface @@ -931,48 +1355,48 @@ This is only saved for moderators and cannot be seen by the banned person. DeckViewContainer - + Load deck... - + Load remote deck... - + Unload deck Unload deck... - + Ready to start - + Force start - + Sideboard unlocked - + Sideboard locked - + Error - + The selected file could not be loaded. @@ -1392,6 +1816,24 @@ To remove your current avatar, confirm without choosing a new image. + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1876,29 +2318,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - - &Refresh - - - - - Parse Set Name and Number (if available) - - - - + Load deck from clipboard - - + Error - - + Invalid deck list. @@ -1906,7 +2336,7 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadRemoteDeck - + Load deck @@ -2062,12 +2492,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 @@ -2078,7 +2508,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 @@ -2089,7 +2519,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 @@ -2098,21 +2528,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 @@ -2121,59 +2551,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 @@ -2225,9 +2655,9 @@ Would you like to change your database location setting? - - - + + + Error @@ -2239,7 +2669,8 @@ Please visit the download page to update manually. - Downloading update... + Downloading update: %1 + Downloading update... @@ -2314,49 +2745,52 @@ Please visit the download page to update manually. - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. - Please check the download page manually and visit the wiki for instructions on compiling. + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. - - - + + + 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 @@ -2621,97 +3055,102 @@ You may have to build from source yourself. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - + + + + + + Choose path - + Personal settings - + Language: - + Paths (editing disabled in portable mode) - + Paths - - - Decks directory: - - - - - Replays directory: - - - - - Pictures directory: - - - - - Card database: - - - - - Custom database directory: - - - Token database: + How to help with translations - Update channel + Decks directory: - Check for client updates on startup + Replays directory: - Notify if a feature supported by the server is missing in my client + Pictures directory: - Automatically run Oracle when running a new version of Cockatrice + Card database: + Custom database directory: + + + + + Token database: + + + + + Update channel + + + + + Check for client updates on startup + + + + + 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 @@ -2919,7 +3358,7 @@ Will now login. - + Error @@ -3389,7 +3828,7 @@ Read more about changing the set order or disabling specific sets and consequent - + Information @@ -3405,41 +3844,42 @@ Read more about changing the set order or disabling specific sets and consequent - failed to start. + Failed to start. The file might be missing, or permissions might be incorrect. - crashed. + The process crashed some time after starting successfully. - - timed out. + + Timed out. The process took too long to respond. The last waitFor...() function timed out. - - write error. + + 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. - - read error. + + An error occurred when attempting to read from the process. For example, the process may not be running. - - unknown error. + + 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. @@ -3447,54 +3887,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. @@ -4110,110 +4550,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 @@ -4422,639 +4862,669 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library - - + - - - - - - - + + + + + + + + + + Number of cards: (max. %1) - + &View graveyard - + &View exile - + Player "%1" - - - - + + + + &Graveyard - - + - + + &Exile - + &Move hand to... - + &Top of library - + &Bottom of library - + &Move graveyard to... - - - + + + &Hand - + &Move exile to... - + &View library - + &View hand - + View &top cards of library... - + Reveal &library to... - + &Always reveal top card - + &View sideboard - + &Draw card - + D&raw cards... - + &Undo last draw - + Take &mulligan - - &Shuffle - - - - + Play top card &face down - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + Move top cards to &exile... - + Put top card on &bottom - + View bottom cards of library... - + Lend library to... - + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... - - &Reveal hand to... + + Shuffle top cards... - - Reveal r&andom card to... - - - - - Reveal random card to... - - - - - &Sideboard - - - - - &Library - - - - - &Counters - - - - - &Untap all permanents - - - - - R&oll die... + + Shuffle bottom cards... - &Create token... + &Reveal hand to... - C&reate another token + Reveal r&andom card to... - Cr&eate predefined token + Reveal random card to... + + + + + &Sideboard + + + + + &Library + + + + + &Counters + + + + + &Untap all permanents + + + + + R&oll die... + + + + + &Create token... - + C&reate another token + + + + + Cr&eate predefined token + + + + + &All players - + S&ay - + &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... - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness - + &Set annotation... - + Red - + Yellow - + Green - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + View top cards of library - + View bottom cards of library - + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: - + (max. %1) - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - - + + Number: - + Move top cards to grave - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always look at top card - + &Open deck in deck editor - + &Play top card - + &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 &exile... - + Put bottom card on &top - + Selec&ted cards - + 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 - + Place card X cards from top of library - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters - + Re&veal to... - + View related cards - + Token: - + All tokens + + PrintingSelector + + + Display Navigation Buttons + + + PrintingSelectorCardOverlayWidget @@ -5117,48 +5587,17 @@ Cockatrice will now reload the card database. - - + + Descending - + Ascending - - PrintingSelectorViewOptionsToolbarWidget - - - Display Options - - - - - PrintingSelectorViewOptionsWidget - - - Display Sorting Options - - - - - Display Search Bar - - - - - Display Card Size Slider - - - - - Display Navigation Buttons - - - QMenuBar @@ -5206,7 +5645,6 @@ Cockatrice will now reload the card database. - All files (*.*) @@ -5215,11 +5653,6 @@ Cockatrice will now reload the card database. Cockatrice replays (*.cor) - - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - - Maindeck @@ -5482,53 +5915,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 @@ -5582,27 +6015,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -5659,27 +6092,27 @@ Please check your shortcut settings! StableReleaseChannel - + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. @@ -5818,430 +6251,192 @@ Please check your shortcut settings! TabDeckEditor - - Banner Card - - - - - Search by card name (or search expressions) - Search by card name - - - - - Add to Deck - - - - - Add to Sideboard - - - - - - Select Printing - - - - - Show on EDHREC (Commander) - - - - - Show on EDHREC (Card) - - - - - Show Related cards - - - - - &Clear all filters - - - - - Delete selected - - - - - Deck &name: - - - - - &Comments: - - - - - Hash: - - - - - &New deck - - - - - &Load deck... - - - - - Load recent deck... - - - - - Clear - - - - - &Save deck - - - - - Save deck &as... - - - - - Load deck from cl&ipboard... - - - - - Save deck to clipboard - - - - - Annotated - - - - - Annotated (No set name or number) - - - - - Not Annotated - - - - - Not Annotated (No set name or number) - - - - - &Print deck... - - - - - &Send deck to online service - - - - - Create decklist (decklist.org) - - - - - Analyze deck (deckstats.net) - - - - - Analyze deck (tappedout.net) - - - - - &Close - - - - - Add card to &maindeck - - - - - Add card to &sideboard - - - - - &Remove row - - - - - &Increment number - - - - - &Decrement number - - - - - Swap card to/from sideboard - - - - - &Deck Editor - - - - - + Card Info - - + Deck - - + Filters - - Printing Selector - - - - + &View - + Printing - - - - + + + + Visible - - - - + + + + Floating - + Reset layout - + Deck: %1 - - - Could not open deck at %1 - - - - - Open in new tab - - - - - Are you sure? - - - - - The decklist has been modified. -Do you want to save the changes? - - - - - - - - - - - Error - - - - - The deck could not be saved. - - - - - Could not save remote deck - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - - - - - Save deck - - - - - There are no cards in your deck to be exported - - - - - No deck was selected to be saved. - - TabDeckStorage - + Local file system - + Server deck storage - - + + Open in deck editor - - Upload deck - - - - - Download deck - - - - - - - New folder + Rename deck or folder - - Delete + 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: - + Deck Storage @@ -6253,6 +6448,16 @@ Please enter a name: Visual Deck Storage + + + Error + + + + + Could not open deck at %1 + + TabEdhRec @@ -6265,175 +6470,175 @@ 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: - + &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? @@ -6443,37 +6648,37 @@ Please enter a name: - + A player has joined game #%1 - + %1 has joined the game - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown - + You have been kicked out of the game. @@ -6907,32 +7112,32 @@ The more information you put in, the more specific your results will be. - + 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. @@ -6940,38 +7145,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) @@ -7425,102 +7630,102 @@ 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 - + 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 - + Prompt before converting .txt decks to .cod format - + Always convert if not prompted - + Use visual deck storage in game lobby - + Replay settings - + Buffer time for backwards skip via shortcut: @@ -7528,22 +7733,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 @@ -7551,7 +7756,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -7559,7 +7764,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename @@ -7590,10 +7795,45 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + + WarningDialog @@ -7847,12 +8087,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -7866,7 +8106,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor @@ -7947,7 +8187,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays @@ -8057,668 +8297,698 @@ Please refrain from engaging in this activity or further actions may be taken ag - + + 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 - - - Load Local Deck... - - - - - Load Remote Deck... - - - - - Set Ready to Start - - - - - Toggle Sideboard Lock - - - - Add Green Counter + Save Deck to Clipboard (No Set Info) - - Remove Green Counter + Load Local Deck... - - Set Green Counters... + Load Remote Deck... + Set Ready to Start + + + + + Toggle Sideboard Lock + + + + + + Add Green Counter + + + + + + Remove Green Counter + + + + + + Set Green Counters... + + + + Add Yellow Counter - + Remove Yellow Counter - + Set Yellow Counters... - - + + Add Red Counter - - + + Remove Red Counter - - + + Set Red Counters... - + Add Life Counter - + 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... - + 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 - - + + Bottom of Library - - - - + + + + Exile - - - - + + + + Graveyard - - + + Hand - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - + 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 - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + 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 - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/oracle/oracle_en@source.ts b/oracle/oracle_en@source.ts index 433176c57..7c085344a 100644 --- a/oracle/oracle_en@source.ts +++ b/oracle/oracle_en@source.ts @@ -1,6 +1,29 @@ + + BetaReleaseChannel + + + Beta + + + + + No reply received from the release update server. + + + + + Invalid reply received from the release update server. + + + + + No reply received from the file update server. + + + IntroPage @@ -63,7 +86,8 @@ - Sets JSON file (%1) + Sets file (%1) + Sets JSON file (%1) @@ -247,7 +271,7 @@ OracleImporter - + Dummy set containing tokens @@ -283,6 +307,15 @@ + + PictureLoader + + + en + code for scryfall's language property, not available for all languages + + + SaveSetsPage @@ -357,6 +390,21 @@ + + ShortcutsSettings + + + Your configuration file contained invalid shortcuts. +Please check your shortcut settings! + + + + + The following shortcuts have been set to default: + + + + SimpleDownloadFilePage @@ -392,6 +440,34 @@ + + StableReleaseChannel + + + Default + + + + + No reply received from the release update server. + + + + + Invalid reply received from the release update server. + + + + + No reply received from the tag update server. + + + + + Invalid reply received from the tag update server. + + + UnZip @@ -537,6 +613,7 @@ i18n + English From be28d50997ebb3ae78a7e81ab54d7af80c6230d5 Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Fri, 21 Mar 2025 01:25:20 +0100 Subject: [PATCH 33/44] Revert "Use native hover events (#5722)" (#5757) This reverts commit e4f40a82a2f26ac22c27d1d08365624613d118e2. This change had unintended consequences in the hover behavior, reverting for now. --- .../src/game/cards/abstract_card_item.cpp | 38 +++++++--------- .../src/game/cards/abstract_card_item.h | 6 +-- cockatrice/src/game/cards/card_item.cpp | 1 + cockatrice/src/game/deckview/deck_view.cpp | 6 +++ cockatrice/src/game/deckview/deck_view.h | 1 + cockatrice/src/game/game_scene.cpp | 45 +++++++++++++++++++ cockatrice/src/game/game_scene.h | 3 ++ cockatrice/src/game/player/player.cpp | 1 + cockatrice/src/game/zones/pile_zone.cpp | 3 +- 9 files changed, 77 insertions(+), 27 deletions(-) diff --git a/cockatrice/src/game/cards/abstract_card_item.cpp b/cockatrice/src/game/cards/abstract_card_item.cpp index 28ed493e7..05e19d3b3 100644 --- a/cockatrice/src/game/cards/abstract_card_item.cpp +++ b/cockatrice/src/game/cards/abstract_card_item.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, @@ -19,7 +18,7 @@ AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, Player *_owner, int _id) : ArrowTarget(_owner, parent), id(_id), name(_name), providerId(_providerId), tapped(false), facedown(false), - tapAngle(0), bgColor(Qt::transparent), realZValue(0) + tapAngle(0), bgColor(Qt::transparent), isHovered(false), realZValue(0) { setCursor(Qt::OpenHandCursor); setFlag(ItemIsSelectable); @@ -27,8 +26,6 @@ AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, connect(&SettingsCache::instance(), &SettingsCache::displayCardNamesChanged, this, [this] { update(); }); refreshCardInfo(); - - setAcceptHoverEvents(true); } AbstractCardItem::~AbstractCardItem() @@ -160,7 +157,7 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS painter->restore(); } -void AbstractCardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget * /*widget*/) +void AbstractCardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { painter->save(); @@ -169,7 +166,6 @@ void AbstractCardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * painter->setRenderHint(QPainter::Antialiasing, false); - bool isHovered = option->state.testFlag(QStyle::State_MouseOver); if (isSelected() || isHovered) { QPen pen; if (isHovered) @@ -212,24 +208,17 @@ void AbstractCardItem::setProviderId(const QString &_providerId) refreshCardInfo(); } -void AbstractCardItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +void AbstractCardItem::setHovered(bool _hovered) { - Q_UNUSED(event); + if (isHovered == _hovered) + return; - emit hovered(this); - setZValue(2000000004); - setScale(SettingsCache::instance().getScaleCards() ? 1.1 : 1); - setTransformOriginPoint(CARD_WIDTH / 2, CARD_HEIGHT / 2); - update(); -} - -void AbstractCardItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) -{ - Q_UNUSED(event); - - setZValue(realZValue); - setScale(1); - setTransformOriginPoint(0, 0); + if (_hovered) + processHoverEvent(); + isHovered = _hovered; + setZValue(_hovered ? 2000000004 : realZValue); + setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1); + setTransformOriginPoint(_hovered ? CARD_WIDTH / 2 : 0, _hovered ? CARD_HEIGHT / 2 : 0); update(); } @@ -325,6 +314,11 @@ void AbstractCardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) event->accept(); } +void AbstractCardItem::processHoverEvent() +{ + emit hovered(this); +} + QVariant AbstractCardItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) { if (change == ItemSelectedHasChanged) { diff --git a/cockatrice/src/game/cards/abstract_card_item.h b/cockatrice/src/game/cards/abstract_card_item.h index cc13fe993..51b345267 100644 --- a/cockatrice/src/game/cards/abstract_card_item.h +++ b/cockatrice/src/game/cards/abstract_card_item.h @@ -24,6 +24,7 @@ protected: QColor bgColor; private: + bool isHovered; qreal realZValue; private slots: void pixmapUpdated(); @@ -85,6 +86,7 @@ public: return realZValue; } void setRealZValue(qreal _zValue); + void setHovered(bool _hovered); QString getColor() const { return color; @@ -100,6 +102,7 @@ public: return facedown; } void setFaceDown(bool _facedown); + void processHoverEvent(); void deleteCardInfoPopup() { emit deleteCardInfoPopup(name); @@ -111,9 +114,6 @@ protected: void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; void cacheBgColor(); - - void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; - void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; }; #endif diff --git a/cockatrice/src/game/cards/card_item.cpp b/cockatrice/src/game/cards/card_item.cpp index 44a7c0880..f5426a60f 100644 --- a/cockatrice/src/game/cards/card_item.cpp +++ b/cockatrice/src/game/cards/card_item.cpp @@ -469,6 +469,7 @@ bool CardItem::animationEvent() .translate(CARD_WIDTH_HALF, CARD_HEIGHT_HALF) .rotate(tapAngle) .translate(-CARD_WIDTH_HALF, -CARD_HEIGHT_HALF)); + setHovered(false); update(); return animationIncomplete; diff --git a/cockatrice/src/game/deckview/deck_view.cpp b/cockatrice/src/game/deckview/deck_view.cpp index bbc25ef6a..a9bd08051 100644 --- a/cockatrice/src/game/deckview/deck_view.cpp +++ b/cockatrice/src/game/deckview/deck_view.cpp @@ -158,6 +158,12 @@ void DeckView::mouseDoubleClickEvent(QMouseEvent *event) } } +void DeckViewCard::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + event->accept(); + processHoverEvent(); +} + DeckViewCardContainer::DeckViewCardContainer(const QString &_name) : QGraphicsItem(), name(_name), width(0), height(0) { setCacheMode(DeviceCoordinateCache); diff --git a/cockatrice/src/game/deckview/deck_view.h b/cockatrice/src/game/deckview/deck_view.h index 14c8f9da1..777298714 100644 --- a/cockatrice/src/game/deckview/deck_view.h +++ b/cockatrice/src/game/deckview/deck_view.h @@ -37,6 +37,7 @@ public: protected: void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; }; class DeckViewCardDragItem : public AbstractCardDragItem diff --git a/cockatrice/src/game/game_scene.cpp b/cockatrice/src/game/game_scene.cpp index 53851e31a..e649bbb2a 100644 --- a/cockatrice/src/game/game_scene.cpp +++ b/cockatrice/src/game/game_scene.cpp @@ -253,6 +253,51 @@ void GameScene::processViewSizeChange(const QSize &newSize) } } +void GameScene::updateHover(const QPointF &scenePos) +{ + QList itemList = + items(scenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, getViewTransform()); + + // Search for the topmost zone and ignore all cards not belonging to that zone. + CardZone *zone = 0; + for (int i = 0; i < itemList.size(); ++i) + if ((zone = qgraphicsitem_cast(itemList[i]))) + break; + + CardItem *maxZCard = 0; + if (zone) { + qreal maxZ = -1; + for (int i = 0; i < itemList.size(); ++i) { + CardItem *card = qgraphicsitem_cast(itemList[i]); + if (!card) + continue; + if (card->getAttachedTo()) { + if (card->getAttachedTo()->getZone() != zone) + continue; + } else if (card->getZone() != zone) + continue; + + if (card->getRealZValue() > maxZ) { + maxZ = card->getRealZValue(); + maxZCard = card; + } + } + } + if (hoveredCard && (maxZCard != hoveredCard)) + hoveredCard->setHovered(false); + if (maxZCard && (maxZCard != hoveredCard)) + maxZCard->setHovered(true); + hoveredCard = maxZCard; +} + +bool GameScene::event(QEvent *event) +{ + if (event->type() == QEvent::GraphicsSceneMouseMove) + updateHover(static_cast(event)->scenePos()); + + return QGraphicsScene::event(event); +} + void GameScene::timerEvent(QTimerEvent * /*event*/) { QMutableSetIterator i(cardsToAnimate); diff --git a/cockatrice/src/game/game_scene.h b/cockatrice/src/game/game_scene.h index e580f1e33..801acacee 100644 --- a/cockatrice/src/game/game_scene.h +++ b/cockatrice/src/game/game_scene.h @@ -30,9 +30,11 @@ private: QList> playersByColumn; QList zoneViews; QSize viewSize; + QPointer hoveredCard; QBasicTimer *animationTimer; QSet cardsToAnimate; int playerRotation; + void updateHover(const QPointF &scenePos); public: explicit GameScene(PhasesToolbar *_phasesToolbar, QObject *parent = nullptr); @@ -63,6 +65,7 @@ public slots: void rearrange(); protected: + bool event(QEvent *event) override; void timerEvent(QTimerEvent *event) override; signals: void sigStartRubberBand(const QPointF &selectionOrigin); diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index 3c31f1f83..629074dc8 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -2386,6 +2386,7 @@ void Player::eventMoveCard(const Event_MoveCard &event, const GameEventContext & card->setFaceDown(event.face_down()); if (startZone != targetZone) { card->setBeingPointedAt(false); + card->setHovered(false); const QList &attachedCards = card->getAttachedCards(); for (auto attachedCard : attachedCards) { diff --git a/cockatrice/src/game/zones/pile_zone.cpp b/cockatrice/src/game/zones/pile_zone.cpp index b2645bd6a..08ee3d28e 100644 --- a/cockatrice/src/game/zones/pile_zone.cpp +++ b/cockatrice/src/game/zones/pile_zone.cpp @@ -131,7 +131,6 @@ void PileZone::mouseReleaseEvent(QGraphicsSceneMouseEvent * /*event*/) void PileZone::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { if (!cards.isEmpty()) - emit cards[0]->hovered(cards[0]); - + cards[0]->processHoverEvent(); QGraphicsItem::hoverEnterEvent(event); } From 99376e75d6b6ab610d5a3448edddab73fc481753 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 20 Mar 2025 17:28:15 -0700 Subject: [PATCH 34/44] Support exporting to decklist.xyz website (#5756) * Support exporting to decklist.xyz * fix typo --- .../menus/deck_editor/deck_editor_menu.cpp | 6 ++++ .../menus/deck_editor/deck_editor_menu.h | 2 +- .../client/tabs/abstract_tab_deck_editor.cpp | 29 +++++++++++++------ .../client/tabs/abstract_tab_deck_editor.h | 2 ++ cockatrice/src/deck/deck_loader.cpp | 22 ++++++++++++-- cockatrice/src/deck/deck_loader.h | 8 ++++- 6 files changed, 55 insertions(+), 14 deletions(-) diff --git a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp index 73c76c807..306e63e32 100644 --- a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp +++ b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp @@ -55,6 +55,9 @@ DeckEditorMenu::DeckEditorMenu(QWidget *parent, AbstractTabDeckEditor *_deckEdit aExportDeckDecklist = new QAction(QString(), this); connect(aExportDeckDecklist, SIGNAL(triggered()), deckEditor, SLOT(actExportDeckDecklist())); + aExportDeckDecklistXyz = new QAction(QString(), this); + connect(aExportDeckDecklistXyz, SIGNAL(triggered()), deckEditor, SLOT(actExportDeckDecklistXyz())); + aAnalyzeDeckDeckstats = new QAction(QString(), this); connect(aAnalyzeDeckDeckstats, SIGNAL(triggered()), deckEditor, SLOT(actAnalyzeDeckDeckstats())); @@ -63,6 +66,8 @@ DeckEditorMenu::DeckEditorMenu(QWidget *parent, AbstractTabDeckEditor *_deckEdit analyzeDeckMenu = new QMenu(this); analyzeDeckMenu->addAction(aExportDeckDecklist); + analyzeDeckMenu->addAction(aExportDeckDecklistXyz); + analyzeDeckMenu->addSeparator(); analyzeDeckMenu->addAction(aAnalyzeDeckDeckstats); analyzeDeckMenu->addAction(aAnalyzeDeckTappedout); @@ -160,6 +165,7 @@ void DeckEditorMenu::retranslateUi() analyzeDeckMenu->setTitle(tr("&Send deck to online service")); aExportDeckDecklist->setText(tr("Create decklist (decklist.org)")); + aExportDeckDecklistXyz->setText(tr("Create decklist (decklist.xyz)")); aAnalyzeDeckDeckstats->setText(tr("Analyze deck (deckstats.net)")); aAnalyzeDeckTappedout->setText(tr("Analyze deck (tappedout.net)")); diff --git a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.h b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.h index 50ddc795a..a713b009f 100644 --- a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.h +++ b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.h @@ -17,7 +17,7 @@ public: QAction *aNewDeck, *aLoadDeck, *aClearRecents, *aSaveDeck, *aSaveDeckAs, *aLoadDeckFromClipboard, *aEditDeckInClipboard, *aEditDeckInClipboardRaw, *aSaveDeckToClipboard, *aSaveDeckToClipboardNoSetInfo, *aSaveDeckToClipboardRaw, *aSaveDeckToClipboardRawNoSetInfo, *aPrintDeck, *aExportDeckDecklist, - *aAnalyzeDeckDeckstats, *aAnalyzeDeckTappedout, *aClose; + *aExportDeckDecklistXyz, *aAnalyzeDeckDeckstats, *aAnalyzeDeckTappedout, *aClose; QMenu *loadRecentDeckMenu, *analyzeDeckMenu, *editDeckInClipboardMenu, *saveDeckToClipboardMenu; void setSaveStatus(bool newStatus); diff --git a/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp b/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp index 734ed1298..fe2148293 100644 --- a/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp @@ -455,17 +455,12 @@ void AbstractTabDeckEditor::actPrintDeck() dlg->exec(); } -// Action called when export deck to decklist menu item is pressed. -void AbstractTabDeckEditor::actExportDeckDecklist() +void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website) { - // Get the decklist class for the deck. - DeckLoader *const deck = getDeckList(); - // create a string to load the decklist url into. - QString decklistUrlString; // check if deck is not null - if (deck) { + if (DeckLoader *const deck = getDeckList()) { // Get the decklist url string from the deck loader class. - decklistUrlString = deck->exportDeckToDecklist(); + QString decklistUrlString = deck->exportDeckToDecklist(website); // Check to make sure the string isn't empty. if (QString::compare(decklistUrlString, "", Qt::CaseInsensitive) == 0) { // Show an error if the deck is empty, and return. @@ -481,10 +476,26 @@ void AbstractTabDeckEditor::actExportDeckDecklist() 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 saved.")); + QMessageBox::critical(this, tr("Error"), tr("No deck was selected to be exported.")); } } +/** + * Exports the deck to www.decklist.org (the old website) + */ +void AbstractTabDeckEditor::actExportDeckDecklist() +{ + exportToDecklistWebsite(DeckLoader::DecklistOrg); +} + +/** + * Exports the deck to www.decklist.xyz (the new website) + */ +void AbstractTabDeckEditor::actExportDeckDecklistXyz() +{ + exportToDecklistWebsite(DeckLoader::DecklistXyz); +} + void AbstractTabDeckEditor::actAnalyzeDeckDeckstats() { auto *interface = new DeckStatsInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(), diff --git a/cockatrice/src/client/tabs/abstract_tab_deck_editor.h b/cockatrice/src/client/tabs/abstract_tab_deck_editor.h index 9f3cca0e1..0a5f97f56 100644 --- a/cockatrice/src/client/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/client/tabs/abstract_tab_deck_editor.h @@ -100,6 +100,7 @@ protected slots: void actSaveDeckToClipboardRawNoSetInfo(); void actPrintDeck(); void actExportDeckDecklist(); + void actExportDeckDecklistXyz(); void actAnalyzeDeckDeckstats(); void actAnalyzeDeckTappedout(); @@ -119,6 +120,7 @@ protected slots: private: virtual void setDeck(DeckLoader *_deck); void editDeckInClipboard(bool annotated); + void exportToDecklistWebsite(DeckLoader::DecklistWebsite website); protected: /** diff --git a/cockatrice/src/deck/deck_loader.cpp b/cockatrice/src/deck/deck_loader.cpp index 16132c571..8130752b7 100644 --- a/cockatrice/src/deck/deck_loader.cpp +++ b/cockatrice/src/deck/deck_loader.cpp @@ -226,6 +226,19 @@ bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, FileFormat f return result; } +static QString getDomainForWebsite(DeckLoader::DecklistWebsite website) +{ + switch (website) { + case DeckLoader::DecklistOrg: + return "www.decklist.org"; + case DeckLoader::DecklistXyz: + return "www.decklist.xyz"; + default: + qCWarning(DeckLoaderLog) << "Invalid decklist website enum:" << website; + return ""; + } +} + // This struct is here to support the forEachCard function call, defined in decklist. It // requires a function to be called for each card, and passes an inner node and a card for // each card in the decklist. @@ -275,11 +288,14 @@ struct FormatDeckListForExport } }; -// Export deck to decklist function, called to format the deck in a way to be sent to a server -QString DeckLoader::exportDeckToDecklist() +/** + * Export deck to decklist function, called to format the deck in a way to be sent to a server + * @param website The website we're sending the deck to + */ +QString DeckLoader::exportDeckToDecklist(DecklistWebsite website) { // Add the base url - QString deckString = "https://www.decklist.org/?"; + QString deckString = "https://" + getDomainForWebsite(website) + "/?"; // Create two strings to pass to function QString mainBoardCards, sideBoardCards; // Set up the struct to call. diff --git a/cockatrice/src/deck/deck_loader.h b/cockatrice/src/deck/deck_loader.h index c62fd45c9..53dd1f711 100644 --- a/cockatrice/src/deck/deck_loader.h +++ b/cockatrice/src/deck/deck_loader.h @@ -31,6 +31,12 @@ public: */ static const QStringList FILE_NAME_FILTERS; + enum DecklistWebsite + { + DecklistOrg, + DecklistXyz + }; + private: QString lastFileName; FileFormat lastFileFormat; @@ -71,7 +77,7 @@ public: bool loadFromRemote(const QString &nativeString, int remoteDeckId); bool saveToFile(const QString &fileName, FileFormat fmt); bool updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt); - QString exportDeckToDecklist(); + QString exportDeckToDecklist(DecklistWebsite website); void resolveSetNameAndNumberToProviderID(); From 2e01dfd23a272df0427d25ea03a5f5ae8a8063d9 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 20 Mar 2025 17:29:59 -0700 Subject: [PATCH 35/44] Remember past entries in "reveal card until X" window (#5755) --- .../src/dialogs/dlg_move_top_cards_until.cpp | 36 ++++++++++++++----- .../src/dialogs/dlg_move_top_cards_until.h | 7 ++-- cockatrice/src/game/player/player.cpp | 7 ++-- cockatrice/src/game/player/player.h | 2 +- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/cockatrice/src/dialogs/dlg_move_top_cards_until.cpp b/cockatrice/src/dialogs/dlg_move_top_cards_until.cpp index 4f0207155..edf593571 100644 --- a/cockatrice/src/dialogs/dlg_move_top_cards_until.cpp +++ b/cockatrice/src/dialogs/dlg_move_top_cards_until.cpp @@ -3,7 +3,6 @@ #include "../game/cards/card_database.h" #include "../game/cards/card_database_manager.h" #include "../game/filters/filter_string.h" -#include "trice_limits.h" #include #include @@ -14,15 +13,17 @@ #include #include -DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, QString _expr, uint _numberOfHits, bool autoPlay) +DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, QStringList exprs, uint _numberOfHits, bool autoPlay) : QDialog(parent) { exprLabel = new QLabel(tr("Card name (or search expressions):")); - exprEdit = new QLineEdit(this); - exprEdit->setFocus(); - exprEdit->setText(_expr); - exprLabel->setBuddy(exprEdit); + exprComboBox = new QComboBox(this); + exprComboBox->setFocus(); + exprComboBox->setEditable(true); + exprComboBox->setInsertPolicy(QComboBox::InsertAtTop); + exprComboBox->insertItems(0, exprs); + exprLabel->setBuddy(exprComboBox); numberOfHitsLabel = new QLabel(tr("Number of hits:")); numberOfHitsEdit = new QSpinBox(this); @@ -43,7 +44,7 @@ DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, QString _expr, uint auto *mainLayout = new QVBoxLayout; mainLayout->addWidget(exprLabel); - mainLayout->addWidget(exprEdit); + mainLayout->addWidget(exprComboBox); mainLayout->addItem(grid); mainLayout->addWidget(autoPlayCheckBox); mainLayout->addWidget(buttonBox); @@ -92,7 +93,7 @@ bool DlgMoveTopCardsUntil::validateMatchExists(const FilterString &filterString) void DlgMoveTopCardsUntil::validateAndAccept() { - auto movingCardsUntilFilter = FilterString(exprEdit->text()); + auto movingCardsUntilFilter = FilterString(exprComboBox->currentText()); if (!movingCardsUntilFilter.valid()) { QMessageBox::warning(this, tr("Invalid filter"), movingCardsUntilFilter.error(), QMessageBox::Ok); return; @@ -102,12 +103,29 @@ void DlgMoveTopCardsUntil::validateAndAccept() return; } + // move currently selected text to top of history list + if (exprComboBox->currentIndex() != 0) { + QString currentExpr = exprComboBox->currentText(); + exprComboBox->removeItem(exprComboBox->currentIndex()); + exprComboBox->insertItem(0, currentExpr); + exprComboBox->setCurrentIndex(0); + } + accept(); } QString DlgMoveTopCardsUntil::getExpr() const { - return exprEdit->text(); + return exprComboBox->currentText(); +} + +QStringList DlgMoveTopCardsUntil::getExprs() const +{ + QStringList exprs; + for (int i = 0; i < exprComboBox->count(); ++i) { + exprs.append(exprComboBox->itemText(i)); + } + return exprs; } uint DlgMoveTopCardsUntil::getNumberOfHits() const diff --git a/cockatrice/src/dialogs/dlg_move_top_cards_until.h b/cockatrice/src/dialogs/dlg_move_top_cards_until.h index c4cc2e0cb..d41d029d6 100644 --- a/cockatrice/src/dialogs/dlg_move_top_cards_until.h +++ b/cockatrice/src/dialogs/dlg_move_top_cards_until.h @@ -2,10 +2,10 @@ #define DLG_MOVE_TOP_CARDS_UNTIL_H #include +#include #include #include #include -#include #include class FilterString; @@ -15,7 +15,7 @@ class DlgMoveTopCardsUntil : public QDialog Q_OBJECT QLabel *exprLabel, *numberOfHitsLabel; - QLineEdit *exprEdit; + QComboBox *exprComboBox; QSpinBox *numberOfHitsEdit; QDialogButtonBox *buttonBox; QCheckBox *autoPlayCheckBox; @@ -25,10 +25,11 @@ class DlgMoveTopCardsUntil : public QDialog public: explicit DlgMoveTopCardsUntil(QWidget *parent = nullptr, - QString expr = QString(), + QStringList exprs = QStringList(), uint numberOfHits = 1, bool autoPlay = false); QString getExpr() const; + QStringList getExprs() const; uint getNumberOfHits() const; bool isAutoPlay() const; }; diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index 629074dc8..8244f35c0 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -1443,19 +1443,20 @@ void Player::actMoveTopCardsUntil() { stopMoveTopCardsUntil(); - DlgMoveTopCardsUntil dlg(game, movingCardsUntilExpr, movingCardsUntilNumberOfHits, movingCardsUntilAutoPlay); + DlgMoveTopCardsUntil dlg(game, movingCardsUntilExprs, movingCardsUntilNumberOfHits, movingCardsUntilAutoPlay); if (!dlg.exec()) { return; } - movingCardsUntilExpr = dlg.getExpr(); + auto expr = dlg.getExpr(); + movingCardsUntilExprs = dlg.getExprs(); movingCardsUntilNumberOfHits = dlg.getNumberOfHits(); movingCardsUntilAutoPlay = dlg.isAutoPlay(); if (zones.value("deck")->getCards().empty()) { stopMoveTopCardsUntil(); } else { - movingCardsUntilFilter = FilterString(movingCardsUntilExpr); + movingCardsUntilFilter = FilterString(expr); movingCardsUntilCounter = movingCardsUntilNumberOfHits; movingCardsUntil = true; actMoveTopCardToPlay(); diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h index 66f3089ed..26df3ae50 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player.h @@ -279,7 +279,7 @@ private: bool movingCardsUntil; QTimer *moveTopCardTimer; - QString movingCardsUntilExpr = {}; + QStringList movingCardsUntilExprs = {}; int movingCardsUntilNumberOfHits = 1; bool movingCardsUntilAutoPlay = false; FilterString movingCardsUntilFilter; From 76fa87c63ef2659d45dc2533e5c3f46269c5429b Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Fri, 21 Mar 2025 01:30:46 +0100 Subject: [PATCH 36/44] Fix StackZone crash when divideCardSpaceInZone overflows (#5751) The divideCardSpaceInZone function introduced in #4930 is buggy and sometimes returns an index that is too large for the current zone, which causes us to call `cards.at(index)` with an `index` that's bigger than the amount of cards. This is the bug that #5609 intended to fix but was improperly diagnosed. Remove part of #5609 as the cases it is guarding against (e.g. null card pointer) cannot actually happen. --- cockatrice/resources/config/qtlogging.ini | 4 +--- cockatrice/src/game/zones/stack_zone.cpp | 27 +++++++++++------------ cockatrice/src/game/zones/stack_zone.h | 2 -- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/cockatrice/resources/config/qtlogging.ini b/cockatrice/resources/config/qtlogging.ini index 378b962b7..60a123d5e 100644 --- a/cockatrice/resources/config/qtlogging.ini +++ b/cockatrice/resources/config/qtlogging.ini @@ -47,12 +47,10 @@ # card_info = false # card_list = false -# stack_zone = false - flow_layout.debug = false flow_widget.debug = false flow_widget.size.debug = false # pixel_map_generator = false -# filter_string = false \ No newline at end of file +# filter_string = false diff --git a/cockatrice/src/game/zones/stack_zone.cpp b/cockatrice/src/game/zones/stack_zone.cpp index 2f307085c..b597a9e81 100644 --- a/cockatrice/src/game/zones/stack_zone.cpp +++ b/cockatrice/src/game/zones/stack_zone.cpp @@ -66,26 +66,25 @@ void StackZone::handleDropEvent(const QList &dragItems, CardZone cmd.set_target_zone(getName().toStdString()); int index = 0; - if (cards.isEmpty()) { - index = 0; - } else { + + if (!cards.isEmpty()) { const auto cardCount = static_cast(cards.size()); const auto &card = cards.at(0); - if (card == nullptr) { - qCWarning(StackZoneLog) << "Attempted to move card from" << startZone->getName() << ", but was null"; - return; - } - index = qRound(divideCardSpaceInZone(dropPoint.y(), cardCount, boundingRect().height(), card->boundingRect().height(), true)); - } - if (startZone == this) { - const auto &dragItem = dragItems.at(0); - const auto &card = cards.at(index); - if (card != nullptr && dragItem != nullptr && card->getId() == dragItem->getId()) { - return; + // divideCardSpaceInZone is not guaranteed to return a valid index + // currently, so clamp it to avoid crashes. + index = qBound(0, index, cardCount - 1); + + if (startZone == this) { + const auto &dragItem = dragItems.at(0); + const auto &card = cards.at(index); + + if (card->getId() == dragItem->getId()) { + return; + } } } diff --git a/cockatrice/src/game/zones/stack_zone.h b/cockatrice/src/game/zones/stack_zone.h index 4ef06ea9a..38dee0aca 100644 --- a/cockatrice/src/game/zones/stack_zone.h +++ b/cockatrice/src/game/zones/stack_zone.h @@ -3,8 +3,6 @@ #include "select_zone.h" -inline Q_LOGGING_CATEGORY(StackZoneLog, "stack_zone"); - class StackZone : public SelectZone { Q_OBJECT From a6f2e69e1abaa8f44419f2d8871bb555637b6897 Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Fri, 21 Mar 2025 01:31:25 +0100 Subject: [PATCH 37/44] vds: Allow editing tags more than once (#5752) `refreshTags` is not connecting the signal to open the dialog to edit the tags, so tags can only be edited once for a given deck. Fix by only having the logic for creating the "Edit tags" button once and call it from `connectDeckList`. --- .../deck_preview_deck_tags_display_widget.cpp | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp index ae09305d1..97825ff21 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp @@ -14,7 +14,7 @@ #include DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList) - : QWidget(_parent), deckList(_deckList) + : QWidget(_parent), deckList(nullptr) { setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); @@ -27,8 +27,8 @@ DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_par flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); - if (deckList) { - connectDeckList(deckList); + if (_deckList) { + connectDeckList(_deckList); } layout->addWidget(flowWidget); @@ -36,10 +36,20 @@ DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_par void DeckPreviewDeckTagsDisplayWidget::connectDeckList(DeckList *_deckList) { - flowWidget->clearLayout(); + if (deckList) { + disconnect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags); + } + deckList = _deckList; connect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags); + refreshTags(); +} + +void DeckPreviewDeckTagsDisplayWidget::refreshTags() +{ + flowWidget->clearLayout(); + for (const QString &tag : deckList->getTags()) { flowWidget->addWidget(new DeckPreviewTagDisplayWidget(this, tag)); } @@ -50,16 +60,6 @@ void DeckPreviewDeckTagsDisplayWidget::connectDeckList(DeckList *_deckList) flowWidget->addWidget(tagAdditionWidget); } -void DeckPreviewDeckTagsDisplayWidget::refreshTags() -{ - flowWidget->clearLayout(); - QStringList tags = deckList->getTags(); - for (const QString &tag : tags) { - flowWidget->addWidget(new DeckPreviewTagDisplayWidget(this, tag)); - } - flowWidget->addWidget(new DeckPreviewTagAdditionWidget(this, tr("Edit tags ..."))); -} - /** * Gets the filepath of all files (no directories) in target directory and all subdirectories */ @@ -160,4 +160,4 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() } } } -} \ No newline at end of file +} From 9decf78d2d2d813d342e4bb35a2862faf25e7e49 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 20 Mar 2025 17:31:38 -0700 Subject: [PATCH 38/44] Fix typo in comment about accepted decklist file formats (#5754) --- .../visual_deck_storage_folder_display_widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp index b4a07386c..319244f85 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp @@ -59,7 +59,7 @@ void VisualDeckStorageFolderDisplayWidget::refreshUi() } /** - * Gets all files in the directory that have a .txt or .cod extension + * Gets all files in the directory that have an accepted decklist file extension * * @param filePath The directory to search through * @param recursive Whether to search through subdirectories From 345606846ffe838b9c6583c93cfa9f7c8ab40a1b Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Thu, 20 Mar 2025 19:49:02 -0700 Subject: [PATCH 39/44] Enable shortcuts for the remaining export deck actions (#5761) --- .../menus/deck_editor/deck_editor_menu.cpp | 6 +++++- cockatrice/src/settings/shortcuts_settings.h | 17 +++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp index 306e63e32..2d8c613a5 100644 --- a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp +++ b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp @@ -178,13 +178,17 @@ void DeckEditorMenu::refreshShortcuts() aNewDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aNewDeck")); aLoadDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aLoadDeck")); aSaveDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeck")); - aExportDeckDecklist->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aExportDeckDecklist")); aSaveDeckAs->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckAs")); aLoadDeckFromClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aLoadDeckFromClipboard")); aEditDeckInClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aEditDeckInClipboard")); aEditDeckInClipboardRaw->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aEditDeckInClipboardRaw")); aPrintDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aPrintDeck")); + + aExportDeckDecklist->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aExportDeckDecklist")); + aExportDeckDecklistXyz->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aExportDeckDecklistXyz")); aAnalyzeDeckDeckstats->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aAnalyzeDeck")); + aAnalyzeDeckTappedout->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aAnalyzeDeckTappedout")); + aClose->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClose")); aSaveDeckToClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckToClipboard")); diff --git a/cockatrice/src/settings/shortcuts_settings.h b/cockatrice/src/settings/shortcuts_settings.h index 0dcbcbad0..595932623 100644 --- a/cockatrice/src/settings/shortcuts_settings.h +++ b/cockatrice/src/settings/shortcuts_settings.h @@ -173,9 +173,13 @@ private: {"MainWindow/aWatchReplay", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Watch Replay..."), parseSequenceString(""), ShortcutGroup::Main_Window)}, - {"TabDeckEditor/aAnalyzeDeck", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Analyze Deck"), + {"TabDeckEditor/aAnalyzeDeck", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Analyze Deck (deckstats.net)"), parseSequenceString(""), ShortcutGroup::Deck_Editor)}, + {"TabDeckEditor/aAnalyzeDeckTappedout", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Analyze Deck (tappedout.net)"), + parseSequenceString(""), + ShortcutGroup::Deck_Editor)}, {"TabDeckEditor/aClearFilterAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Clear All Filters"), parseSequenceString(""), ShortcutGroup::Deck_Editor)}, @@ -193,9 +197,14 @@ private: {"TabDeckEditor/aEditTokens", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Edit Custom Tokens..."), parseSequenceString(""), ShortcutGroup::Deck_Editor)}, - {"TabDeckEditor/aExportDeckDecklist", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Export Deck"), - parseSequenceString(""), - ShortcutGroup::Deck_Editor)}, + {"TabDeckEditor/aExportDeckDecklist", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Export Deck (decklist.org)"), + parseSequenceString(""), + ShortcutGroup::Deck_Editor)}, + {"TabDeckEditor/aExportDeckDecklistXyz", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Export Deck (decklist.xyz)"), + parseSequenceString(""), + ShortcutGroup::Deck_Editor)}, {"TabDeckEditor/aIncrement", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Card"), parseSequenceString("+"), ShortcutGroup::Deck_Editor)}, From 0ae7d01234d1d797d9427bb5644b9077b5f2a50a Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Sat, 22 Mar 2025 06:07:42 +0100 Subject: [PATCH 40/44] Hide arena only cards (#5759) * Add settings (default: true) to ignore online-only cards * Use QAbstractButton::toggled Also, fix dbconverter build * Mocks mocks mocks * Update dlg_manage_sets.cpp * translations * Update dlg_manage_sets.cpp --------- Co-authored-by: Zach H --- cockatrice/cockatrice_en@source.ts | 526 +++++++++--------- cockatrice/src/dialogs/dlg_manage_sets.cpp | 17 +- cockatrice/src/dialogs/dlg_manage_sets.h | 2 + cockatrice/src/game/cards/card_database.cpp | 2 +- .../card_database_parser/cockatrice_xml_4.cpp | 15 +- cockatrice/src/settings/cache_settings.cpp | 8 + cockatrice/src/settings/cache_settings.h | 7 + dbconverter/src/mocks.cpp | 3 + doc/carddatabase_v4/cards.xsd | 1 + oracle/oracle_en@source.ts | 2 +- oracle/src/oracleimporter.cpp | 3 +- tests/carddatabase/mocks.cpp | 3 + 12 files changed, 330 insertions(+), 259 deletions(-) diff --git a/cockatrice/cockatrice_en@source.ts b/cockatrice/cockatrice_en@source.ts index 8be7525f4..be95b2f58 100644 --- a/cockatrice/cockatrice_en@source.ts +++ b/cockatrice/cockatrice_en@source.ts @@ -59,8 +59,8 @@ Do you want to save the changes? - - + + Error @@ -92,13 +92,13 @@ Please check that the directory is writable and try again. - + There are no cards in your deck to be exported - - No deck was selected to be saved. + + No deck was selected to be exported. @@ -845,104 +845,109 @@ 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... - + &Send deck to online service - + Create decklist (decklist.org) - + + Create decklist (decklist.xyz) + + + + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1179,8 +1184,7 @@ This is only saved for moderators and cannot be seen by the banned person. DeckPreviewDeckTagsDisplayWidget - - + Edit tags ... @@ -2344,37 +2348,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia 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 @@ -4876,9 +4880,9 @@ Cockatrice will now reload the card database. - - - + + + Number of cards: (max. %1) @@ -5320,12 +5324,12 @@ Cockatrice will now reload the card database. - + Which position should this card be placed: - + (max. %1) @@ -5345,8 +5349,8 @@ Cockatrice will now reload the card database. - - + + Number: @@ -5441,78 +5445,78 @@ Cockatrice will now reload the card database. - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - + Place card X cards from top of library - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters - + Re&veal to... - + View related cards - + Token: - + All tokens @@ -7882,128 +7886,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 - + 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 - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets @@ -8106,7 +8115,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor @@ -8187,7 +8196,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays @@ -8243,752 +8252,763 @@ Please refrain from engaging in this activity or further actions may be taken ag - Analyze Deck + Analyze Deck (deckstats.net) + Analyze Deck - + + Analyze Deck (tappedout.net) + + + + Clear All Filters - + Clear Selected Filter - + Close - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - - Export Deck + + 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 Yellow Counter - + Remove Yellow Counter - + Set Yellow Counters... - - + + Add Red Counter - - + + Remove Red Counter - - + + Set Red Counters... - + Add Life Counter - + 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... - + 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 - - + + Bottom of Library - - - - + + + + Exile - - - - + + + + Graveyard - - + + Hand - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - + 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 - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + 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 - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/src/dialogs/dlg_manage_sets.cpp b/cockatrice/src/dialogs/dlg_manage_sets.cpp index ab82b9b65..626abd43f 100644 --- a/cockatrice/src/dialogs/dlg_manage_sets.cpp +++ b/cockatrice/src/dialogs/dlg_manage_sets.cpp @@ -8,6 +8,7 @@ #include "../settings/cache_settings.h" #include +#include #include #include #include @@ -162,6 +163,11 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) sortWarning->setLayout(sortWarningLayout); sortWarning->setVisible(false); + includeOnlineOnlyCards = SettingsCache::instance().getIncludeOnlineOnlyCards(); + QCheckBox *onlineOnly = new QCheckBox(tr("Include online-only (Arena) cards [requires restart]")); + onlineOnly->setChecked(includeOnlineOnlyCards); + connect(onlineOnly, &QAbstractButton::toggled, this, &WndSets::includeOnlineOnlyCardsChanged); + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, SIGNAL(accepted()), this, SLOT(actSave())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(actRestore())); @@ -175,8 +181,9 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) mainLayout->addWidget(enableSomeButton, 2, 1); mainLayout->addWidget(disableSomeButton, 2, 2); mainLayout->addWidget(sortWarning, 3, 1, 1, 2); - mainLayout->addWidget(hintsGroupBox, 4, 1, 1, 2); - mainLayout->addWidget(buttonBox, 5, 1, 1, 2); + mainLayout->addWidget(onlineOnly, 4, 1, 1, 2); + mainLayout->addWidget(hintsGroupBox, 5, 1, 1, 2); + mainLayout->addWidget(buttonBox, 6, 1, 1, 2); mainLayout->setColumnStretch(1, 1); mainLayout->setColumnStretch(2, 1); @@ -239,9 +246,15 @@ void WndSets::rebuildMainLayout(int actionToTake) } } +void WndSets::includeOnlineOnlyCardsChanged(bool _includeOnlineOnlyCards) +{ + includeOnlineOnlyCards = _includeOnlineOnlyCards; +} + void WndSets::actSave() { model->save(CardDatabaseManager::getInstance()); + SettingsCache::instance().setIncludeOnlineOnlyCards(includeOnlineOnlyCards); PictureLoader::clearPixmapCache(); close(); } diff --git a/cockatrice/src/dialogs/dlg_manage_sets.h b/cockatrice/src/dialogs/dlg_manage_sets.h index e974f67f9..13dea7fef 100644 --- a/cockatrice/src/dialogs/dlg_manage_sets.h +++ b/cockatrice/src/dialogs/dlg_manage_sets.h @@ -44,6 +44,7 @@ private: void saveHeaderState(); void rebuildMainLayout(int actionToTake); bool setOrderIsSorted; + bool includeOnlineOnlyCards; enum { NO_SETS_SELECTED, @@ -73,6 +74,7 @@ private slots: void actDisableResetButton(const QString &filterText); void actSort(int index); void actIgnoreWarning(); + void includeOnlineOnlyCardsChanged(bool _includeOnlineOnlyCardsChanged); }; #endif diff --git a/cockatrice/src/game/cards/card_database.cpp b/cockatrice/src/game/cards/card_database.cpp index b22bc2891..0fe43e20a 100644 --- a/cockatrice/src/game/cards/card_database.cpp +++ b/cockatrice/src/game/cards/card_database.cpp @@ -536,4 +536,4 @@ bool CardDatabase::saveCustomTokensToFile() availableParsers.first()->saveToFile(tmpSets, tmpCards, fileName); return true; -} \ No newline at end of file +} diff --git a/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp b/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp index e7f657b96..827b70fc3 100644 --- a/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp +++ b/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp @@ -1,5 +1,7 @@ #include "cockatrice_xml_4.h" +#include "../../../settings/cache_settings.h" + #include #include #include @@ -124,6 +126,7 @@ QVariantHash CockatriceXml4Parser::loadCardPropertiesFromXml(QXmlStreamReader &x void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) { + bool includeOnlineOnlyCards = SettingsCache::instance().getIncludeOnlineOnlyCards(); while (!xml.atEnd()) { if (xml.readNext() == QXmlStreamReader::EndElement) { break; @@ -183,7 +186,17 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) attrName = "picurl"; setInfo.setProperty(attrName, attr.value().toString()); } - _sets[setName].append(setInfo); + + // This is very much a hack and not the right place to + // put this check, as it requires a reload of Cockatrice + // to be apply. + // + // However, this is also true of the `set->getEnabled()` + // check above (which is currently bugged as well), so + // we'll fix both at the same time. + if (includeOnlineOnlyCards || setInfo.getProperty("isOnlineOnly") != "true") { + _sets[setName].append(setInfo); + } } // related cards } else if (xmlName == "related" || xmlName == "reverse-related") { diff --git a/cockatrice/src/settings/cache_settings.cpp b/cockatrice/src/settings/cache_settings.cpp index bd88a8a3f..52e6af104 100644 --- a/cockatrice/src/settings/cache_settings.cpp +++ b/cockatrice/src/settings/cache_settings.cpp @@ -259,6 +259,7 @@ SettingsCache::SettingsCache() bumpSetsWithCardsInDeckToTop = settings->value("cards/bumpsetswithcardsindecktotop", true).toBool(); printingSelectorSortOrder = settings->value("cards/printingselectorsortorder", 1).toInt(); printingSelectorCardSize = settings->value("cards/printingselectorcardsize", 100).toInt(); + includeOnlineOnlyCards = settings->value("cards/includeonlineonlycards", false).toBool(); printingSelectorNavigationButtonsVisible = settings->value("cards/printingselectornavigationbuttonsvisible", true).toBool(); visualDeckStorageCardSize = settings->value("interface/visualdeckstoragecardsize", 100).toInt(); @@ -654,6 +655,13 @@ void SettingsCache::setPrintingSelectorCardSize(int _printingSelectorCardSize) emit printingSelectorCardSizeChanged(); } +void SettingsCache::setIncludeOnlineOnlyCards(bool _includeOnlineOnlyCards) +{ + includeOnlineOnlyCards = _includeOnlineOnlyCards; + settings->setValue("cards/includeonlineonlycards", includeOnlineOnlyCards); + emit includeOnlineOnlyCardsChanged(includeOnlineOnlyCards); +} + void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T _navigationButtonsVisible) { printingSelectorNavigationButtonsVisible = _navigationButtonsVisible; diff --git a/cockatrice/src/settings/cache_settings.h b/cockatrice/src/settings/cache_settings.h index 771b2663c..5986b07a7 100644 --- a/cockatrice/src/settings/cache_settings.h +++ b/cockatrice/src/settings/cache_settings.h @@ -58,6 +58,7 @@ signals: void bumpSetsWithCardsInDeckToTopChanged(); void printingSelectorSortOrderChanged(); void printingSelectorCardSizeChanged(); + void includeOnlineOnlyCardsChanged(bool _includeOnlineOnlyCards); void printingSelectorNavigationButtonsVisibleChanged(); void visualDeckStorageShowTagFilterChanged(bool _visible); void visualDeckStorageShowBannerCardComboBoxChanged(bool _visible); @@ -128,6 +129,7 @@ private: bool bumpSetsWithCardsInDeckToTop; int printingSelectorSortOrder; int printingSelectorCardSize; + bool includeOnlineOnlyCards; bool printingSelectorNavigationButtonsVisible; int visualDeckStorageSortingOrder; bool visualDeckStorageShowFolders; @@ -401,6 +403,10 @@ public: { return printingSelectorCardSize; } + bool getIncludeOnlineOnlyCards() const + { + return includeOnlineOnlyCards; + } bool getPrintingSelectorNavigationButtonsVisible() const { return printingSelectorNavigationButtonsVisible; @@ -774,6 +780,7 @@ public slots: void setBumpSetsWithCardsInDeckToTop(QT_STATE_CHANGED_T _bumpSetsWithCardsInDeckToTop); void setPrintingSelectorSortOrder(int _printingSelectorSortOrder); void setPrintingSelectorCardSize(int _printingSelectorCardSize); + void setIncludeOnlineOnlyCards(bool _includeOnlineOnlyCards); void setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T _navigationButtonsVisible); void setVisualDeckStorageSortingOrder(int _visualDeckStorageSortingOrder); void setVisualDeckStorageShowFolders(QT_STATE_CHANGED_T value); diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp index e2f6ed4df..06889d2b3 100644 --- a/dbconverter/src/mocks.cpp +++ b/dbconverter/src/mocks.cpp @@ -199,6 +199,9 @@ void SettingsCache::setPrintingSelectorSortOrder(int /* _printingSelectorSortOrd void SettingsCache::setPrintingSelectorCardSize(int /* _printingSelectorCardSize */) { } +void SettingsCache::setIncludeOnlineOnlyCards(bool /* _includeOnlineOnlyCards */) +{ +} void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T /* _navigationButtonsVisible */) { } diff --git a/doc/carddatabase_v4/cards.xsd b/doc/carddatabase_v4/cards.xsd index 00ed72555..6c5eb9bda 100644 --- a/doc/carddatabase_v4/cards.xsd +++ b/doc/carddatabase_v4/cards.xsd @@ -28,6 +28,7 @@ + diff --git a/oracle/oracle_en@source.ts b/oracle/oracle_en@source.ts index 7c085344a..301de42e3 100644 --- a/oracle/oracle_en@source.ts +++ b/oracle/oracle_en@source.ts @@ -271,7 +271,7 @@ OracleImporter - + Dummy set containing tokens diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 360423394..4187c4c9f 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -212,7 +212,8 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList }; // mtgjson name => xml name - static const QMap setInfoProperties{{"number", "num"}, {"rarity", "rarity"}}; + static const QMap setInfoProperties{ + {"number", "num"}, {"rarity", "rarity"}, {"isOnlineOnly", "isOnlineOnly"}}; // mtgjson name => xml name static const QMap identifierProperties{{"multiverseId", "muid"}, {"scryfallId", "uuid"}}; diff --git a/tests/carddatabase/mocks.cpp b/tests/carddatabase/mocks.cpp index f399179c3..78b95cc1c 100644 --- a/tests/carddatabase/mocks.cpp +++ b/tests/carddatabase/mocks.cpp @@ -203,6 +203,9 @@ void SettingsCache::setPrintingSelectorSortOrder(int /* _printingSelectorSortOrd void SettingsCache::setPrintingSelectorCardSize(int /* _printingSelectorCardSize */) { } +void SettingsCache::setIncludeOnlineOnlyCards(bool /* _includeOnlineOnlyCards */) +{ +} void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T /* _navigationButtonsVisible */) { } From c71685b261bfed2a55109bec22afee11ed1b64e0 Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Sat, 22 Mar 2025 06:07:52 +0100 Subject: [PATCH 41/44] Add option to disable card rounding (#5760) * Add option to disable card rounding * Effing mocks * format * Get rid of cardCornerRadius property --- .../card_info_picture_enlarged_widget.cpp | 10 ++++++++- .../cards/card_info_picture_widget.cpp | 10 +++++++-- cockatrice/src/dialogs/dlg_settings.cpp | 22 ++++++++++++------- cockatrice/src/dialogs/dlg_settings.h | 1 + .../game/cards/abstract_card_drag_item.cpp | 12 ++++++++-- .../src/game/cards/abstract_card_item.cpp | 10 ++++++++- cockatrice/src/game/deckview/deck_view.cpp | 10 +++++++-- cockatrice/src/game/zones/pile_zone.cpp | 10 ++++++++- cockatrice/src/settings/cache_settings.cpp | 11 ++++++++++ cockatrice/src/settings/cache_settings.h | 8 +++++++ dbconverter/src/mocks.cpp | 3 +++ tests/carddatabase/mocks.cpp | 3 +++ 12 files changed, 93 insertions(+), 17 deletions(-) diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp index 31089e7c4..775390182 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp @@ -1,5 +1,6 @@ #include "card_info_picture_enlarged_widget.h" +#include "../../../../settings/cache_settings.h" #include "../../picture_loader/picture_loader.h" #include @@ -17,6 +18,12 @@ CardInfoPictureEnlargedWidget::CardInfoPictureEnlargedWidget(QWidget *parent) { setWindowFlags(Qt::ToolTip); // Keeps this widget on top of everything setAttribute(Qt::WA_TranslucentBackground); + + connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) { + Q_UNUSED(_roundCardCorners); + + update(); + }); } /** @@ -79,7 +86,8 @@ void CardInfoPictureEnlargedWidget::paintEvent(QPaintEvent *event) QPoint topLeft{(width() - scaledSize.width()) / 2, (height() - scaledSize.height()) / 2}; // Define the radius for rounded corners - qreal radius = 0.05 * scaledSize.width(); // Adjust the radius as needed for rounded corners + // Adjust the radius as needed for rounded corners + qreal radius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * scaledSize.width() : 0.; QStylePainter painter(this); // Fill the background with transparent color to ensure rounded corners are rendered properly diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp index 592766939..546568a73 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp @@ -3,7 +3,6 @@ #include "../../../../game/cards/card_database_manager.h" #include "../../../../game/cards/card_item.h" #include "../../../../settings/cache_settings.h" -#include "../../../tabs/tab_deck_editor.h" #include "../../../tabs/tab_supervisor.h" #include "../../picture_loader/picture_loader.h" #include "../../window_main.h" @@ -44,6 +43,12 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool hoverTo hoverTimer = new QTimer(this); hoverTimer->setSingleShot(true); connect(hoverTimer, &QTimer::timeout, this, &CardInfoPictureWidget::showEnlargedPixmap); + + connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) { + Q_UNUSED(_roundCardCorners); + + update(); + }); } /** @@ -186,7 +191,8 @@ void CardInfoPictureWidget::paintEvent(QPaintEvent *event) QRect targetRect{targetX, targetY, targetW, targetH}; // Compute rounded corner radius - qreal radius = 0.05 * static_cast(targetRect.width()); // Ensure consistent rounding + // Ensure consistent rounding + qreal radius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * static_cast(targetRect.width()) : 0.; // Draw the pixmap with rounded corners QStylePainter painter(this); diff --git a/cockatrice/src/dialogs/dlg_settings.cpp b/cockatrice/src/dialogs/dlg_settings.cpp index b97155d2a..4989b4a3f 100644 --- a/cockatrice/src/dialogs/dlg_settings.cpp +++ b/cockatrice/src/dialogs/dlg_settings.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #define WIKI_CUSTOM_PIC_URL "https://github.com/Cockatrice/Cockatrice/wiki/Custom-Picture-Download-URLs" #define WIKI_CUSTOM_SHORTCUTS "https://github.com/Cockatrice/Cockatrice/wiki/Custom-Keyboard-Shortcuts" @@ -383,6 +384,9 @@ AppearanceSettingsPage::AppearanceSettingsPage() cardScalingCheckBox.setChecked(settings.getScaleCards()); connect(&cardScalingCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setCardScaling); + roundCardCornersCheckBox.setChecked(settings.getRoundCardCorners()); + connect(&roundCardCornersCheckBox, &QAbstractButton::toggled, &settings, &SettingsCache::setRoundCardCorners); + verticalCardOverlapPercentBox.setValue(settings.getStackCardOverlapPercent()); verticalCardOverlapPercentBox.setRange(0, 80); connect(&verticalCardOverlapPercentBox, SIGNAL(valueChanged(int)), &settings, @@ -402,14 +406,15 @@ AppearanceSettingsPage::AppearanceSettingsPage() cardsGrid->addWidget(&displayCardNamesCheckBox, 0, 0, 1, 2); cardsGrid->addWidget(&autoRotateSidewaysLayoutCardsCheckBox, 1, 0, 1, 2); cardsGrid->addWidget(&cardScalingCheckBox, 2, 0, 1, 2); - cardsGrid->addWidget(&overrideAllCardArtWithPersonalPreferenceCheckBox, 3, 0, 1, 2); - cardsGrid->addWidget(&bumpSetsWithCardsInDeckToTopCheckBox, 4, 0, 1, 2); - cardsGrid->addWidget(&verticalCardOverlapPercentLabel, 5, 0, 1, 1); - cardsGrid->addWidget(&verticalCardOverlapPercentBox, 5, 1, 1, 1); - cardsGrid->addWidget(&cardViewInitialRowsMaxLabel, 6, 0); - cardsGrid->addWidget(&cardViewInitialRowsMaxBox, 6, 1); - cardsGrid->addWidget(&cardViewExpandedRowsMaxLabel, 7, 0); - cardsGrid->addWidget(&cardViewExpandedRowsMaxBox, 7, 1); + cardsGrid->addWidget(&roundCardCornersCheckBox, 3, 0, 1, 2); + cardsGrid->addWidget(&overrideAllCardArtWithPersonalPreferenceCheckBox, 4, 0, 1, 2); + cardsGrid->addWidget(&bumpSetsWithCardsInDeckToTopCheckBox, 5, 0, 1, 2); + cardsGrid->addWidget(&verticalCardOverlapPercentLabel, 6, 0, 1, 1); + cardsGrid->addWidget(&verticalCardOverlapPercentBox, 6, 1, 1, 1); + cardsGrid->addWidget(&cardViewInitialRowsMaxLabel, 7, 0); + cardsGrid->addWidget(&cardViewInitialRowsMaxBox, 7, 1); + cardsGrid->addWidget(&cardViewExpandedRowsMaxLabel, 8, 0); + cardsGrid->addWidget(&cardViewExpandedRowsMaxBox, 8, 1); cardsGroupBox = new QGroupBox; cardsGroupBox->setLayout(cardsGrid); @@ -540,6 +545,7 @@ void AppearanceSettingsPage::retranslateUi() bumpSetsWithCardsInDeckToTopCheckBox.setText( tr("Bump sets that the deck contains cards from to the top in the printing selector")); cardScalingCheckBox.setText(tr("Scale cards on mouse over")); + roundCardCornersCheckBox.setText(tr("Use rounded card corners")); verticalCardOverlapPercentLabel.setText( tr("Minimum overlap percentage of cards on the stack and in vertical hand")); cardViewInitialRowsMaxLabel.setText(tr("Maximum initial height for card view window:")); diff --git a/cockatrice/src/dialogs/dlg_settings.h b/cockatrice/src/dialogs/dlg_settings.h index ec295a618..95ae17979 100644 --- a/cockatrice/src/dialogs/dlg_settings.h +++ b/cockatrice/src/dialogs/dlg_settings.h @@ -107,6 +107,7 @@ private: QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox; QCheckBox bumpSetsWithCardsInDeckToTopCheckBox; QCheckBox cardScalingCheckBox; + QCheckBox roundCardCornersCheckBox; QLabel verticalCardOverlapPercentLabel; QSpinBox verticalCardOverlapPercentBox; QLabel cardViewInitialRowsMaxLabel; diff --git a/cockatrice/src/game/cards/abstract_card_drag_item.cpp b/cockatrice/src/game/cards/abstract_card_drag_item.cpp index e9fe30c50..0c7acd917 100644 --- a/cockatrice/src/game/cards/abstract_card_drag_item.cpp +++ b/cockatrice/src/game/cards/abstract_card_drag_item.cpp @@ -1,6 +1,6 @@ #include "abstract_card_drag_item.h" -#include "card_database.h" +#include "../../settings/cache_settings.h" #include #include @@ -32,6 +32,13 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item, .translate(-CARD_WIDTH_HALF, -CARD_HEIGHT_HALF)); setCacheMode(DeviceCoordinateCache); + + connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) { + Q_UNUSED(_roundCardCorners); + + prepareGeometryChange(); + update(); + }); } AbstractCardDragItem::~AbstractCardDragItem() @@ -43,7 +50,8 @@ AbstractCardDragItem::~AbstractCardDragItem() QPainterPath AbstractCardDragItem::shape() const { QPainterPath shape; - shape.addRoundedRect(boundingRect(), 0.05 * CARD_WIDTH, 0.05 * CARD_WIDTH); + qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0; + shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius); return shape; } diff --git a/cockatrice/src/game/cards/abstract_card_item.cpp b/cockatrice/src/game/cards/abstract_card_item.cpp index 05e19d3b3..dc00aae66 100644 --- a/cockatrice/src/game/cards/abstract_card_item.cpp +++ b/cockatrice/src/game/cards/abstract_card_item.cpp @@ -26,6 +26,13 @@ AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, connect(&SettingsCache::instance(), &SettingsCache::displayCardNamesChanged, this, [this] { update(); }); refreshCardInfo(); + + connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) { + Q_UNUSED(_roundCardCorners); + + prepareGeometryChange(); + update(); + }); } AbstractCardItem::~AbstractCardItem() @@ -41,7 +48,8 @@ QRectF AbstractCardItem::boundingRect() const QPainterPath AbstractCardItem::shape() const { QPainterPath shape; - shape.addRoundedRect(boundingRect(), 0.05 * CARD_WIDTH, 0.05 * CARD_WIDTH); + qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0; + shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius); return shape; } diff --git a/cockatrice/src/game/deckview/deck_view.cpp b/cockatrice/src/game/deckview/deck_view.cpp index a9bd08051..41c6ed45a 100644 --- a/cockatrice/src/game/deckview/deck_view.cpp +++ b/cockatrice/src/game/deckview/deck_view.cpp @@ -74,6 +74,12 @@ DeckViewCard::DeckViewCard(QGraphicsItem *parent, : AbstractCardItem(parent, _name, _providerId, 0, -1), originZone(_originZone), dragItem(0) { setAcceptHoverEvents(true); + + connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) { + Q_UNUSED(_roundCardCorners); + + update(); + }); } DeckViewCard::~DeckViewCard() @@ -91,7 +97,7 @@ 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 = 0.05 * (CARD_WIDTH - 3); + 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); painter->restore(); } @@ -525,4 +531,4 @@ void DeckView::clearDeck() void DeckView::resetSideboardPlan() { deckViewScene->resetSideboardPlan(); -} \ No newline at end of file +} diff --git a/cockatrice/src/game/zones/pile_zone.cpp b/cockatrice/src/game/zones/pile_zone.cpp index 08ee3d28e..ba23560c9 100644 --- a/cockatrice/src/game/zones/pile_zone.cpp +++ b/cockatrice/src/game/zones/pile_zone.cpp @@ -21,6 +21,13 @@ PileZone::PileZone(Player *_p, const QString &_name, bool _isShufflable, bool _c .translate((float)CARD_WIDTH / 2, (float)CARD_HEIGHT / 2) .rotate(90) .translate((float)-CARD_WIDTH / 2, (float)-CARD_HEIGHT / 2)); + + connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) { + Q_UNUSED(_roundCardCorners); + + prepareGeometryChange(); + update(); + }); } QRectF PileZone::boundingRect() const @@ -31,7 +38,8 @@ QRectF PileZone::boundingRect() const QPainterPath PileZone::shape() const { QPainterPath shape; - shape.addRoundedRect(boundingRect(), 0.05 * CARD_WIDTH, 0.05 * CARD_WIDTH); + qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0; + shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius); return shape; } diff --git a/cockatrice/src/settings/cache_settings.cpp b/cockatrice/src/settings/cache_settings.cpp index 52e6af104..87215aefb 100644 --- a/cockatrice/src/settings/cache_settings.cpp +++ b/cockatrice/src/settings/cache_settings.cpp @@ -254,6 +254,7 @@ SettingsCache::SettingsCache() showShortcuts = settings->value("menu/showshortcuts", true).toBool(); displayCardNames = settings->value("cards/displaycardnames", true).toBool(); + roundCardCorners = settings->value("cards/roundcardcorners", true).toBool(); overrideAllCardArtWithPersonalPreference = settings->value("cards/overrideallcardartwithpersonalpreference", false).toBool(); bumpSetsWithCardsInDeckToTop = settings->value("cards/bumpsetswithcardsindecktotop", true).toBool(); @@ -1294,6 +1295,16 @@ void SettingsCache::setMaxFontSize(int _max) settings->setValue("game/maxfontsize", maxFontSize); } +void SettingsCache::setRoundCardCorners(bool _roundCardCorners) +{ + if (_roundCardCorners == roundCardCorners) + return; + + roundCardCorners = _roundCardCorners; + settings->setValue("cards/roundcardcorners", _roundCardCorners); + emit roundCardCornersChanged(roundCardCorners); +} + void SettingsCache::loadPaths() { QString dataPath = getDataPath(); diff --git a/cockatrice/src/settings/cache_settings.h b/cockatrice/src/settings/cache_settings.h index 5986b07a7..c33efad6d 100644 --- a/cockatrice/src/settings/cache_settings.h +++ b/cockatrice/src/settings/cache_settings.h @@ -47,6 +47,7 @@ class QSettings; class SettingsCache : public QObject { Q_OBJECT + signals: void langChanged(); void picsPathChanged(); @@ -83,6 +84,7 @@ signals: void downloadSpoilerTimeIndexChanged(); void downloadSpoilerStatusChanged(); void useTearOffMenusChanged(bool state); + void roundCardCornersChanged(bool roundCardCorners); private: QSettings *settings; @@ -203,6 +205,7 @@ private: bool rememberGameSettings; QList releaseChannels; bool isPortableBuild; + bool roundCardCorners; public: SettingsCache(); @@ -733,6 +736,10 @@ public: { return mbDownloadSpoilers; } + bool getRoundCardCorners() const + { + return roundCardCorners; + } static SettingsCache &instance(); void resetPaths(); @@ -841,6 +848,7 @@ public slots: void setNotifyAboutNewVersion(QT_STATE_CHANGED_T _notifyaboutnewversion); void setUpdateReleaseChannelIndex(int value); void setMaxFontSize(int _max); + void setRoundCardCorners(bool _roundCardCorners); }; #endif diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp index 06889d2b3..f2d4b7566 100644 --- a/dbconverter/src/mocks.cpp +++ b/dbconverter/src/mocks.cpp @@ -387,6 +387,9 @@ void SettingsCache::setUpdateReleaseChannelIndex(int /* value */) void SettingsCache::setMaxFontSize(int /* _max */) { } +void SettingsCache::setRoundCardCorners(bool /* _roundCardCorners */) +{ +} void PictureLoader::clearPixmapCache(CardInfoPtr /* card */) { diff --git a/tests/carddatabase/mocks.cpp b/tests/carddatabase/mocks.cpp index 78b95cc1c..944254d74 100644 --- a/tests/carddatabase/mocks.cpp +++ b/tests/carddatabase/mocks.cpp @@ -391,6 +391,9 @@ void SettingsCache::setUpdateReleaseChannelIndex(int /* value */) void SettingsCache::setMaxFontSize(int /* _max */) { } +void SettingsCache::setRoundCardCorners(bool /* _roundCardCorners */) +{ +} void PictureLoader::clearPixmapCache(CardInfoPtr /* card */) { From 9bc6ae1567b0e32aeae7299e1e072b4ae496c213 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sun, 23 Mar 2025 09:03:56 -0700 Subject: [PATCH 42/44] Fix delete action in filters not working (#5765) * Fix delete action in filters not working * move filterRemove under slots --- .../ui/widgets/deck_editor/deck_editor_filter_dock_widget.cpp | 4 ++-- .../ui/widgets/deck_editor/deck_editor_filter_dock_widget.h | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.cpp b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.cpp index 2f03ad656..c60b1733b 100644 --- a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.cpp +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.cpp @@ -96,11 +96,11 @@ void DeckEditorFilterDockWidget::filterViewCustomContextMenu(const QPoint &point action = menu.addAction(QString("delete")); action->setData(point); - connect(&menu, SIGNAL(triggered(QAction *)), this, SLOT(filterRemove(QAction *))); + connect(&menu, &QMenu::triggered, this, &DeckEditorFilterDockWidget::filterRemove); menu.exec(filterView->mapToGlobal(point)); } -void DeckEditorFilterDockWidget::filterRemove(QAction *action) +void DeckEditorFilterDockWidget::filterRemove(const QAction *action) { QPoint point; QModelIndex idx; diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.h b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.h index 3113b2e24..9cd8d8d6f 100644 --- a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.h +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.h @@ -28,10 +28,9 @@ private: KeySignals filterViewKeySignals; QWidget *filterBox; - void filterRemove(QAction *action); - private slots: void filterViewCustomContextMenu(const QPoint &point); + void filterRemove(const QAction *action); void actClearFilterAll(); void actClearFilterOne(); void refreshShortcuts(); From a4b0cddcf81a539d3b36babaaabe50f1b97f6830 Mon Sep 17 00:00:00 2001 From: Zach H Date: Sun, 23 Mar 2025 19:04:24 -0400 Subject: [PATCH 43/44] Revert "Disable CardMenu iff no items selected (#5376)" (#5768) This reverts commit b4036c867187e319530d181bc95d903c45228f78. --- cockatrice/src/game/cards/card_item.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cockatrice/src/game/cards/card_item.cpp b/cockatrice/src/game/cards/card_item.cpp index f5426a60f..c088a4ed1 100644 --- a/cockatrice/src/game/cards/card_item.cpp +++ b/cockatrice/src/game/cards/card_item.cpp @@ -482,9 +482,7 @@ QVariant CardItem::itemChange(GraphicsItemChange change, const QVariant &value) owner->setCardMenu(cardMenu); owner->getGame()->setActiveCard(this); } else if (owner->getCardMenu() == cardMenu) { - if (scene() && scene()->selectedItems().isEmpty()) { - owner->setCardMenu(nullptr); - } + owner->setCardMenu(nullptr); owner->getGame()->setActiveCard(nullptr); } } From 91ee6097d29eb98c19135c3f215eee54d6ee7d5b Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 22:11:47 +0000 Subject: [PATCH 44/44] Translate oracle/oracle_en@source.ts in it (#5770) 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 | 85 ++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/oracle/translations/oracle_it.ts b/oracle/translations/oracle_it.ts index 9bb09707d..728067573 100644 --- a/oracle/translations/oracle_it.ts +++ b/oracle/translations/oracle_it.ts @@ -1,4 +1,27 @@ + + BetaReleaseChannel + + + Beta + Beta + + + + No reply received from the release update server. + Nessuna risposta ricevuta dal server degli aggiornamenti di versione. + + + + Invalid reply received from the release update server. + Ricevuta risposta non valida dal server degli aggiornamenti. + + + + No reply received from the file update server. + Nessuna risposta ricevuta dal server degli aggiornamenti. + + IntroPage @@ -62,8 +85,9 @@ e pedine che verranno usate da Cockatrice. - Sets JSON file (%1) - File dei set JSON (%1) + Sets file (%1) + Sets JSON file (%1) + File dei set (%1) @@ -246,7 +270,7 @@ e pedine che verranno usate da Cockatrice. OracleImporter - + Dummy set containing tokens Set finto contenente i token @@ -282,6 +306,15 @@ e pedine che verranno usate da Cockatrice. Se il database delle carte non si ricarica in automatico, riavvia il programma Cockatrice. + + PictureLoader + + + en + code for scryfall's language property, not available for all languages + it + + SaveSetsPage @@ -356,6 +389,23 @@ e pedine che verranno usate da Cockatrice. Impossibile salvare il file su %1 + + ShortcutsSettings + + + Your configuration file contained invalid shortcuts. +Please check your shortcut settings! + Il file di configurazione conteneva combinazioni di tasti non valide. +Controlla le impostazioni delle combinazioni! + + + + The following shortcuts have been set to default: + + Le seguenti combinazioni di tasti sono state reimpostate al valore predefinito: + + + SimpleDownloadFilePage @@ -391,6 +441,34 @@ e pedine che verranno usate da Cockatrice. Impossibile salvare il file su %1 + + StableReleaseChannel + + + Default + Predefinito + + + + No reply received from the release update server. + Nessuna risposta ricevuta dal server degli aggiornamenti di versione. + + + + Invalid reply received from the release update server. + Ricevuta risposta non valida dal server degli aggiornamenti. + + + + No reply received from the tag update server. + Nessuna risposta ricevuta dal server dei tag di aggiornamento. + + + + Invalid reply received from the tag update server. + Ricevuta risposta non valida dal server dei tag di aggiornamento. + + UnZip @@ -536,6 +614,7 @@ e pedine che verranno usate da Cockatrice. i18n + English Italiano (Italian)