Turn Card, Settings and Utility into libraries.

Took 4 hours 3 minutes

Took 2 minutes

Took 38 seconds

Took 5 minutes

Took 5 minutes

Took 9 minutes


Took 15 minutes
This commit is contained in:
Lukas Brübach 2025-10-02 17:41:07 +02:00
parent 30e6b52783
commit 53d80efab8
262 changed files with 1025 additions and 918 deletions

View file

@ -0,0 +1,67 @@
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
set(HEADERS
include/card/card_info.h
include/card/card_info_comparator.h
include/card/card_database/card_database.h
include/card/card_database/card_database_loader.h
include/card/card_database/card_database_manager.h
include/card/card_database/card_database_querier.h
include/card/card_database/model/card_database_model.h
include/card/card_database/model/card_database_display_model.h
include/card/card_database/model/card/card_completer_proxy_model.h
include/card/card_database/model/card/card_search_model.h
include/card/card_database/model/token/token_display_model.h
include/card/card_database/model/token/token_edit_model.h
include/card/card_database/parser/card_database_parser.h
include/card/card_database/parser/cockatrice_xml_3.h
include/card/card_database/parser/cockatrice_xml_4.h
include/card/card_printing/exact_card.h
include/card/card_printing/printing_info.h
include/card/card_set/card_set.h
include/card/card_relation/card_relation.h
)
if (Qt6_FOUND)
qt6_wrap_cpp(MOC_SOURCES ${HEADERS})
elseif (Qt5_FOUND)
qt5_wrap_cpp(MOC_SOURCES ${HEADERS})
endif ()
qt_add_library(cardlib STATIC
src/card_info.cpp
src/card_info_comparator.cpp
src/card_database/card_database.cpp
src/card_database/card_database_loader.cpp
src/card_database/card_database_manager.cpp
src/card_database/card_database_querier.cpp
src/card_database/model/card_database_model.cpp
src/card_database/model/card_database_display_model.cpp
src/card_database/model/card/card_completer_proxy_model.cpp
src/card_database/model/card/card_search_model.cpp
src/card_database/model/token/token_display_model.cpp
src/card_database/model/token/token_edit_model.cpp
src/card_database/parser/card_database_parser.cpp
src/card_database/parser/cockatrice_xml_3.cpp
src/card_database/parser/cockatrice_xml_4.cpp
src/card_printing/exact_card.cpp
src/card_printing/printing_info.cpp
src/card_set/card_set.cpp
src/card_set/card_set_list.cpp
src/card_relation/card_relation.cpp
${MOC_SOURCES}
)
target_include_directories(cardlib
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
PUBLIC ${CMAKE_SOURCE_DIR}/common
PUBLIC ${CMAKE_SOURCE_DIR}/cockatrice/libs/settings/include
PUBLIC ${CMAKE_SOURCE_DIR}/cockatrice/src/filters
)
target_link_libraries(cardlib
PUBLIC settingslib
PUBLIC ${COCKATRICE_QT_MODULES}
)

View file

@ -0,0 +1,102 @@
/**
* @file card_database.h
* @ingroup CardDatabase
* @brief The CardDatabase is responsible for holding the card and set maps.
*/
#ifndef CARDDATABASE_H
#define CARDDATABASE_H
#include "../../common/card_ref.h"
#include "card/card_database/card_database_loader.h"
#include "card/card_set/card_set_list.h"
#include "card_database_querier.h"
#include <QBasicMutex>
#include <QDate>
#include <QHash>
#include <QList>
#include <QLoggingCategory>
#include <QStringList>
#include <QVector>
#include <utility>
inline Q_LOGGING_CATEGORY(CardDatabaseLog, "card_database");
class CardDatabase : public QObject
{
Q_OBJECT
protected:
/*
* The cards, indexed by name.
*/
CardNameMap cards;
/**
* The cards, indexed by their simple name.
*/
CardNameMap simpleNameCards;
/*
* The sets, indexed by short name.
*/
SetNameMap sets;
// loader responsible for file discovery & parsing
CardDatabaseLoader *loader;
LoadStatus loadStatus;
CardDatabaseQuerier *querier;
private:
void checkUnknownSets();
void refreshCachedReverseRelatedCards();
QBasicMutex *clearDatabaseMutex = new QBasicMutex(), *addCardMutex = new QBasicMutex(),
*removeCardMutex = new QBasicMutex();
public:
explicit CardDatabase(QObject *parent = nullptr);
~CardDatabase() override;
void removeCard(CardInfoPtr card);
void clear();
const CardNameMap &getCardList() const
{
return cards;
}
CardSetPtr getSet(const QString &setName);
CardSetList getSetList() const;
LoadStatus getLoadStatus() const
{
return loadStatus;
}
CardDatabaseQuerier *query() const
{
return querier;
}
void enableAllUnknownSets();
void markAllSetsAsKnown();
void notifyEnabledSetsChanged();
public slots:
void addCard(CardInfoPtr card);
void addSet(CardSetPtr set);
void loadCardDatabases();
bool saveCustomTokensToFile();
signals:
void cardDatabaseLoadingFinished();
void cardDatabaseLoadingFailed();
void cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames);
void cardDatabaseAllNewSetsEnabled();
void cardDatabaseEnabledSetsChanged();
void cardAdded(CardInfoPtr card);
void cardRemoved(CardInfoPtr card);
friend class CardDatabaseLoader;
friend class CardDatabaseQuerier;
};
#endif

View file

@ -0,0 +1,63 @@
/**
* @file card_database_loader.h
* @ingroup CardDatabase
* @brief The CardDatabaseLoader is responsible for populating the card database from files on disk.
*/
#ifndef COCKATRICE_CARD_DATABASE_LOADER_H
#define COCKATRICE_CARD_DATABASE_LOADER_H
#include <QBasicMutex>
#include <QList>
#include <QLoggingCategory>
#include <QObject>
inline Q_LOGGING_CATEGORY(CardDatabaseLoadingLog, "card_database.loading");
inline Q_LOGGING_CATEGORY(CardDatabaseLoadingSuccessOrFailureLog, "card_database.loading.success_or_failure");
class CardDatabase;
class ICardDatabaseParser;
enum LoadStatus
{
Ok,
VersionTooOld,
Invalid,
NotLoaded,
FileError,
NoCards
};
class CardDatabaseLoader : public QObject
{
Q_OBJECT
public:
explicit CardDatabaseLoader(QObject *parent, CardDatabase *db);
~CardDatabaseLoader() override;
public slots:
LoadStatus loadCardDatabases(); // discover & load the configured databases
LoadStatus loadCardDatabase(const QString &path); // load a single file
bool saveCustomTokensToFile(); // write tokens to custom DB path
signals:
void loadingStarted();
void loadingFinished();
void loadingFailed();
void newSetsFound(int numSets, const QStringList &setNames);
void allNewSetsEnabled();
private:
LoadStatus loadFromFile(const QString &fileName); // internal helper
QStringList collectCustomDatabasePaths() const;
CardDatabase *database; // non-owning pointer to the container
// parsers
QList<ICardDatabaseParser *> availableParsers;
QBasicMutex *loadFromFileMutex = new QBasicMutex();
QBasicMutex *reloadDatabaseMutex = new QBasicMutex();
};
#endif // COCKATRICE_CARD_DATABASE_LOADER_H

View file

@ -0,0 +1,29 @@
/**
* @file card_database_manager.h
* @ingroup CardDatabase
* @brief The CardDatabaseManager is responsible for managing the global database singleton.
*/
#ifndef CARD_DATABASE_ACCESSOR_H
#define CARD_DATABASE_ACCESSOR_H
#pragma once
#include "card_database.h"
class CardDatabaseManager
{
public:
// Delete copy constructor and assignment operator to enforce singleton
CardDatabaseManager(const CardDatabaseManager &) = delete;
CardDatabaseManager &operator=(const CardDatabaseManager &) = delete;
// Static method to access the singleton instance
static CardDatabase *getInstance();
static CardDatabaseQuerier *query();
private:
CardDatabaseManager() = default; // Private constructor
~CardDatabaseManager() = default;
};
#endif // CARD_DATABASE_ACCESSOR_H

View file

@ -0,0 +1,61 @@
/**
* @file card_database_querier.h
* @ingroup CardDatabase
* @brief The CardDatabaseQuerier is responsible for querying the database and returning data.
*/
#ifndef COCKATRICE_CARD_DATABASE_QUERIER_H
#define COCKATRICE_CARD_DATABASE_QUERIER_H
#include "../../common/card_ref.h"
#include "card/card_info.h"
#include "card/card_printing/exact_card.h"
#include <QObject>
class CardDatabase;
class CardDatabaseQuerier : public QObject
{
Q_OBJECT
public:
explicit CardDatabaseQuerier(QObject *parent, const CardDatabase *db);
[[nodiscard]] CardInfoPtr getCardInfo(const QString &cardName) const;
[[nodiscard]] QList<CardInfoPtr> getCardInfos(const QStringList &cardNames) const;
/*
* Get a card by its simple name. The name will be simplified in this
* function, so you don't need to simplify it beforehand.
*/
[[nodiscard]] CardInfoPtr getCardBySimpleName(const QString &cardName) const;
[[nodiscard]] ExactCard guessCard(const CardRef &cardRef) const;
[[nodiscard]] ExactCard getCard(const CardRef &cardRef) const;
[[nodiscard]] QList<ExactCard> getCards(const QList<CardRef> &cardRefs) const;
[[nodiscard]] ExactCard getRandomCard() const;
[[nodiscard]] ExactCard getCardFromSameSet(const QString &cardName, const PrintingInfo &otherPrinting) const;
[[nodiscard]] ExactCard getPreferredCard(const CardInfoPtr &card) const;
[[nodiscard]] bool isPreferredPrinting(const CardRef &cardRef) const;
[[nodiscard]] PrintingInfo getPreferredPrinting(const CardInfoPtr &card) const;
[[nodiscard]] PrintingInfo getPreferredPrinting(const QString &cardName) const;
[[nodiscard]] QString getPreferredPrintingProviderId(const QString &cardName) const;
[[nodiscard]] PrintingInfo getSpecificPrinting(const CardRef &cardRef) const;
[[nodiscard]] PrintingInfo
getSpecificPrinting(const QString &cardName, const QString &setCode, const QString &collectorNumber) const;
[[nodiscard]] PrintingInfo findPrintingWithId(const CardInfoPtr &card, const QString &providerId) const;
[[nodiscard]] QStringList getAllMainCardTypes() const;
[[nodiscard]] QMap<QString, int> getAllMainCardTypesWithCount() const;
[[nodiscard]] QMap<QString, int> getAllSubCardTypesWithCount() const;
private:
const CardDatabase *db;
CardInfoPtr lookupCardByName(const QString &name) const;
};
#endif // COCKATRICE_CARD_DATABASE_QUERIER_H

View file

@ -0,0 +1,22 @@
/**
* @file card_completer_proxy_model.h
* @ingroup CardDatabaseModels
* @brief TODO: Document this.
*/
#ifndef CARD_COMPLETER_PROXY_MODEL_H
#define CARD_COMPLETER_PROXY_MODEL_H
#include <QSortFilterProxyModel>
class CardCompleterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit CardCompleterProxyModel(QObject *parent = nullptr);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
};
#endif // CARD_COMPLETER_PROXY_MODEL_H

View file

@ -0,0 +1,36 @@
/**
* @file card_search_model.h
* @ingroup CardDatabaseModels
* @brief TODO: Document this.
*/
#ifndef CARD_SEARCH_MODEL_H
#define CARD_SEARCH_MODEL_H
#include "card/card_database/model/card_database_display_model.h"
#include <QAbstractListModel>
class CardSearchModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void updateSearchResults(const QString &query); // Update results based on input
private:
struct SearchResult
{
CardInfoPtr card;
int distance;
};
CardDatabaseDisplayModel *sourceModel;
QList<SearchResult> searchResults;
};
#endif // CARD_SEARCH_MODEL_H

View file

@ -0,0 +1,99 @@
/**
* @file card_database_display_model.h
* @ingroup CardDatabaseModels
* @brief The CardDatabaseDisplayModel is a QSortFilterProxyModel that allows applying filters and sorting to a
* CardDatabaseModel.
*/
#ifndef COCKATRICE_CARD_DATABASE_DISPLAY_MODEL_H
#define COCKATRICE_CARD_DATABASE_DISPLAY_MODEL_H
#include "filter_string.h"
#include <QList>
#include <QSet>
#include <QSortFilterProxyModel>
#include <QTimer>
class FilterTree;
class CardDatabaseDisplayModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
enum FilterBool
{
ShowTrue,
ShowFalse,
ShowAll
};
private:
FilterBool isToken;
QString cardName, cardText;
QSet<QString> cardNameSet, cardTypes, cardColors;
FilterTree *filterTree;
FilterString *filterString;
int loadedRowCount;
QTimer dirtyTimer;
/** The translation table that will be used for sanitizeCardName. */
static QMap<wchar_t, wchar_t> characterTranslation;
public:
explicit CardDatabaseDisplayModel(QObject *parent = nullptr);
void setFilterTree(FilterTree *_filterTree);
void setIsToken(FilterBool _isToken)
{
isToken = _isToken;
emit modelDirty();
dirty();
}
void setCardName(const QString &_cardName)
{
if (filterString != nullptr) {
delete filterString;
filterString = nullptr;
}
cardName = sanitizeCardName(_cardName, characterTranslation);
emit modelDirty();
dirty();
}
void setStringFilter(const QString &_src)
{
delete filterString;
filterString = new FilterString(_src);
emit modelDirty();
dirty();
}
void setCardNameSet(const QSet<QString> &_cardNameSet)
{
cardNameSet = _cardNameSet;
emit modelDirty();
dirty();
}
void dirty()
{
dirtyTimer.start(20);
}
void clearFilterAll();
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
bool canFetchMore(const QModelIndex &parent) const override;
void fetchMore(const QModelIndex &parent) override;
signals:
void modelDirty();
protected:
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
static int lessThanNumerically(const QString &left, const QString &right);
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
bool rowMatchesCardName(CardInfoPtr info) const;
private slots:
void filterTreeChanged();
/** Will translate all undesirable characters in DIRTYNAME according to the TABLE. */
const QString sanitizeCardName(const QString &dirtyName, const QMap<wchar_t, wchar_t> &table);
};
#endif // COCKATRICE_CARD_DATABASE_DISPLAY_MODEL_H

View file

@ -0,0 +1,62 @@
/**
* @file card_database_model.h
* @ingroup CardDatabaseModels
* @brief The CardDatabaseModel maps the cardList contained in the CardDatabase as a QAbstractListModel.
*/
#ifndef CARDDATABASEMODEL_H
#define CARDDATABASEMODEL_H
#include "card/card_database/card_database.h"
#include <QAbstractListModel>
#include <QList>
#include <QSet>
class CardDatabaseModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Columns
{
NameColumn,
SetListColumn,
ManaCostColumn,
PTColumn,
CardTypeColumn,
ColorColumn
};
enum Role
{
SortRole = Qt::UserRole
};
CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent = nullptr);
~CardDatabaseModel() override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
CardDatabase *getDatabase() const
{
return db;
}
CardInfoPtr getCard(int index) const
{
return cardList[index];
}
private:
QList<CardInfoPtr> cardList;
QSet<CardInfoPtr> cardListSet; // Supports faster lookups in cardDatabaseEnabledSetsChanged()
CardDatabase *db;
bool showOnlyCardsFromEnabledSets;
inline bool checkCardHasAtLeastOneEnabledSet(CardInfoPtr card);
private slots:
void cardAdded(CardInfoPtr card);
void cardRemoved(CardInfoPtr card);
void cardInfoChanged(CardInfoPtr card);
void cardDatabaseEnabledSetsChanged();
};
#endif

View file

@ -0,0 +1,23 @@
/**
* @file token_display_model.h
* @ingroup CardDatabaseModels
* @brief TODO: Document this.
*/
#ifndef COCKATRICE_TOKEN_DISPLAY_MODEL_H
#define COCKATRICE_TOKEN_DISPLAY_MODEL_H
#include "card/card_database/model/card_database_display_model.h"
class TokenDisplayModel : public CardDatabaseDisplayModel
{
Q_OBJECT
public:
explicit TokenDisplayModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
};
#endif // COCKATRICE_TOKEN_DISPLAY_MODEL_H

View file

@ -0,0 +1,23 @@
/**
* @file token_edit_model.h
* @ingroup CardDatabaseModels
* @brief TODO: Document this.
*/
#ifndef COCKATRICE_TOKEN_EDIT_MODEL_H
#define COCKATRICE_TOKEN_EDIT_MODEL_H
#include "card/card_database/model/card_database_display_model.h"
class TokenEditModel : public CardDatabaseDisplayModel
{
Q_OBJECT
public:
explicit TokenEditModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
};
#endif // COCKATRICE_TOKEN_EDIT_MODEL_H

View file

@ -0,0 +1,51 @@
/**
* @file card_database_parser.h
* @ingroup CardDatabaseParsers
* @brief The ICardDatabaseParser defines the base interface for parser sub-classes.
*/
#ifndef CARDDATABASE_PARSER_H
#define CARDDATABASE_PARSER_H
#include "card/card_info.h"
#include <QIODevice>
#include <QString>
#define COCKATRICE_XML_XSI_NAMESPACE "http://www.w3.org/2001/XMLSchema-instance"
class ICardDatabaseParser : public QObject
{
Q_OBJECT
public:
~ICardDatabaseParser() override = default;
virtual bool getCanParseFile(const QString &name, QIODevice &device) = 0;
virtual void parseFile(QIODevice &device) = 0;
virtual bool saveToFile(SetNameMap sets,
CardNameMap cards,
const QString &fileName,
const QString &sourceUrl = "unknown",
const QString &sourceVersion = "unknown") = 0;
static void clearSetlist();
protected:
/*
* A cached list of the available sets, needed to cross-reference sets from cards.
* Shared between all parsers
*/
static SetNameMap sets;
CardSetPtr internalAddSet(const QString &setName,
const QString &longName = "",
const QString &setType = "",
const QDate &releaseDate = QDate(),
const CardSet::Priority priority = CardSet::PriorityFallback);
signals:
void addCard(CardInfoPtr card);
void addSet(CardSetPtr set);
};
Q_DECLARE_INTERFACE(ICardDatabaseParser, "ICardDatabaseParser")
#endif

View file

@ -0,0 +1,37 @@
/**
* @file cockatrice_xml_3.h
* @ingroup CardDatabaseParsers
* @brief The CockatriceXml3Parser is capable of parsing version 3 of the Cockatrice XML Schema.
*/
#ifndef COCKATRICE_XML3_H
#define COCKATRICE_XML3_H
#include "card/card_database/parser/card_database_parser.h"
#include <QLoggingCategory>
#include <QXmlStreamReader>
inline Q_LOGGING_CATEGORY(CockatriceXml3Log, "cockatrice_xml.xml_3_parser");
class CockatriceXml3Parser : public ICardDatabaseParser
{
Q_OBJECT
public:
CockatriceXml3Parser() = default;
~CockatriceXml3Parser() override = default;
bool getCanParseFile(const QString &name, QIODevice &device) override;
void parseFile(QIODevice &device) override;
bool saveToFile(SetNameMap _sets,
CardNameMap cards,
const QString &fileName,
const QString &sourceUrl = "unknown",
const QString &sourceVersion = "unknown") override;
private:
void loadCardsFromXml(QXmlStreamReader &xml);
void loadSetsFromXml(QXmlStreamReader &xml);
QString getMainCardType(QString &type);
};
#endif

View file

@ -0,0 +1,37 @@
/**
* @file cockatrice_xml_4.h
* @ingroup CardDatabaseParsers
* @brief The CockatriceXml4Parser is capable of parsing version 4 of the Cockatrice XML Schema.
*/
#ifndef COCKATRICE_XML4_H
#define COCKATRICE_XML4_H
#include "card/card_database/parser/card_database_parser.h"
#include <QLoggingCategory>
#include <QXmlStreamReader>
inline Q_LOGGING_CATEGORY(CockatriceXml4Log, "cockatrice_xml.xml_4_parser");
class CockatriceXml4Parser : public ICardDatabaseParser
{
Q_OBJECT
public:
CockatriceXml4Parser() = default;
~CockatriceXml4Parser() override = default;
bool getCanParseFile(const QString &name, QIODevice &device) override;
void parseFile(QIODevice &device) override;
bool saveToFile(SetNameMap _sets,
CardNameMap cards,
const QString &fileName,
const QString &sourceUrl = "unknown",
const QString &sourceVersion = "unknown") override;
private:
QVariantHash loadCardPropertiesFromXml(QXmlStreamReader &xml);
void loadCardsFromXml(QXmlStreamReader &xml);
void loadSetsFromXml(QXmlStreamReader &xml);
};
#endif

View file

@ -0,0 +1,354 @@
/**
* @file card_info.h
* @ingroup Cards
* @brief TODO: Document this.
*/
#ifndef CARD_INFO_H
#define CARD_INFO_H
#include "card_printing/printing_info.h"
#include <QDate>
#include <QHash>
#include <QList>
#include <QLoggingCategory>
#include <QMap>
#include <QMetaType>
#include <QSharedPointer>
#include <QStringList>
#include <QVariant>
#include <utility>
inline Q_LOGGING_CATEGORY(CardInfoLog, "card_info");
class CardInfo;
class CardSet;
class CardRelation;
class ICardDatabaseParser;
typedef QSharedPointer<CardInfo> CardInfoPtr;
typedef QSharedPointer<CardSet> CardSetPtr;
typedef QMap<QString, QList<PrintingInfo>> SetToPrintingsMap;
typedef QHash<QString, CardInfoPtr> CardNameMap;
typedef QHash<QString, CardSetPtr> SetNameMap;
Q_DECLARE_METATYPE(CardInfoPtr)
/**
* @class CardInfo
* @ingroup Cards
*
* @brief Represents a card and its associated metadata, properties, and relationships.
*
* CardInfo holds both static information (name, text, flags) and dynamic data
* (properties, set memberships, relationships). It also integrates with
* signals/slots, allowing observers to react to property or visual updates.
*
* Each CardInfo may belong to multiple sets through its printings, and can
* be related to other cards through defined relationships.
*/
class CardInfo : public QObject
{
Q_OBJECT
private:
CardInfoPtr smartThis; ///< Smart pointer to self for safe cross-references.
QString name; ///< Full name of the card.
QString simpleName; ///< Simplified name for fuzzy matching.
QString text; ///< Text description or rules text of the card.
bool isToken; ///< Whether this card is a token or not.
QVariantHash properties; ///< Key-value store of dynamic card properties.
QList<CardRelation *> relatedCards; ///< Forward references to related cards.
QList<CardRelation *> reverseRelatedCards; ///< Cards that refer back to this card.
QList<CardRelation *> reverseRelatedCardsToMe; ///< Cards that consider this card as related.
SetToPrintingsMap setsToPrintings; ///< Mapping from set names to printing variations.
QString setsNames; ///< Cached, human-readable list of set names.
bool cipt; ///< Positioning flag used by UI.
bool landscapeOrientation; ///< Orientation flag for rendering.
int tableRow; ///< Row index in a table or visual representation.
bool upsideDownArt; ///< Whether artwork is flipped for visual purposes.
public:
/**
* @brief Constructs a CardInfo with full initialization.
*
* @param _name The name of the card.
* @param _text Rules text or description of the card.
* @param _isToken Flag indicating whether the card is a token.
* @param _properties Arbitrary key-value properties.
* @param _relatedCards Forward references to related cards.
* @param _reverseRelatedCards Backward references to related cards.
* @param _sets Map of set names to printing information.
* @param _cipt UI positioning flag.
* @param _landscapeOrientation UI rendering orientation.
* @param _tableRow Row index for table placement.
* @param _upsideDownArt Whether the artwork should be displayed upside down.
*/
explicit CardInfo(const QString &_name,
const QString &_text,
bool _isToken,
QVariantHash _properties,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
SetToPrintingsMap _sets,
bool _cipt,
bool _landscapeOrientation,
int _tableRow,
bool _upsideDownArt);
/**
* @brief Copy constructor for CardInfo.
*
* Performs a deep copy of properties, sets, and related card lists.
*
* @param other Another CardInfo to copy.
*/
CardInfo(const CardInfo &other)
: QObject(other.parent()), name(other.name), simpleName(other.simpleName), text(other.text),
isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards),
reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe),
setsToPrintings(other.setsToPrintings), setsNames(other.setsNames), cipt(other.cipt),
landscapeOrientation(other.landscapeOrientation), tableRow(other.tableRow), upsideDownArt(other.upsideDownArt)
{
}
/**
* @brief Creates a new instance with only the card name.
*
* All other fields are set to defaults.
*
* @param _name The card name.
* @return Shared pointer to the new CardInfo instance.
*/
static CardInfoPtr newInstance(const QString &_name);
/**
* @brief Creates a new instance with full initialization.
*
* @param _name Name of the card.
* @param _text Rules text or description.
* @param _isToken Token flag.
* @param _properties Arbitrary properties.
* @param _relatedCards Forward relationships.
* @param _reverseRelatedCards Reverse relationships.
* @param _sets Printing information per set.
* @param _cipt UI positioning flag.
* @param _landscapeOrientation UI rendering orientation.
* @param _tableRow Row index for table placement.
* @param _upsideDownArt Artwork orientation flag.
* @return Shared pointer to the new CardInfo instance.
*/
static CardInfoPtr newInstance(const QString &_name,
const QString &_text,
bool _isToken,
QVariantHash _properties,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
SetToPrintingsMap _sets,
bool _cipt,
bool _landscapeOrientation,
int _tableRow,
bool _upsideDownArt);
/**
* @brief Clones the current CardInfo instance.
*
* Uses the copy constructor and ensures the smart pointer is properly set.
*
* @return Shared pointer to the cloned CardInfo.
*/
CardInfoPtr clone() const
{
CardInfoPtr newCardInfo = CardInfoPtr(new CardInfo(*this));
newCardInfo->setSmartPointer(newCardInfo); // Set the smart pointer for the new instance
return newCardInfo;
}
/**
* @brief Sets the internal smart pointer to self.
*
* Used internally to allow safe cross-references among CardInfo and CardSet.
*
* @param _ptr Shared pointer pointing to this instance.
*/
void setSmartPointer(CardInfoPtr _ptr)
{
smartThis = std::move(_ptr);
}
/** @name Basic Properties Accessors */ //@{
inline const QString &getName() const
{
return name;
}
const QString &getSimpleName() const
{
return simpleName;
}
const QString &getText() const
{
return text;
}
void setText(const QString &_text)
{
text = _text;
emit cardInfoChanged(smartThis);
}
bool getIsToken() const
{
return isToken;
}
QStringList getProperties() const
{
return properties.keys();
}
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 SetToPrintingsMap &getSets() const
{
return setsToPrintings;
}
const QString &getSetsNames() const
{
return setsNames;
}
//@}
/** @name Related Cards Accessors */ //@{
const QList<CardRelation *> &getRelatedCards() const
{
return relatedCards;
}
const QList<CardRelation *> &getReverseRelatedCards() const
{
return reverseRelatedCards;
}
const QList<CardRelation *> &getReverseRelatedCards2Me() const
{
return reverseRelatedCardsToMe;
}
QList<CardRelation *> getAllRelatedCards() const
{
QList<CardRelation *> result;
result.append(getRelatedCards());
result.append(getReverseRelatedCards2Me());
return result;
}
void resetReverseRelatedCards2Me();
void addReverseRelatedCards2Me(CardRelation *cardRelation)
{
reverseRelatedCardsToMe.append(cardRelation);
}
//@}
/** @name UI 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;
//@}
/** @name Legacy/Convenience Property Accessors */ //@{
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);
//@}
/**
* @brief Returns a version of the card name safe for file storage or fuzzy matching.
*
* Removes invalid characters, replaces spacing markers, and normalizes diacritics.
*
* @return Corrected card name as a QString.
*/
QString getCorrectedName() const;
/**
* @brief Adds a printing to a specific set.
*
* Updates the mapping and refreshes the cached list of set names.
*
* @param _set The set to which the card should be added.
* @param _info Optional printing information.
*/
void addToSet(const CardSetPtr &_set, PrintingInfo _info = PrintingInfo());
/**
* @brief Combines legality properties from a provided map.
*
* Useful for merging format legality flags from multiple sources.
*
* @param props Key-value mapping of format legalities.
*/
void combineLegalities(const QVariantHash &props);
/**
* @brief Refreshes the cached, human-readable list of set names.
*
* Typically called after adding or modifying set memberships.
*/
void refreshCachedSetNames();
/**
* @brief Simplifies a name for fuzzy matching.
*
* Converts to lowercase, removes punctuation/spacing.
*
* @param name Original name string.
* @return Simplified name string.
*/
static QString simplifyName(const QString &name);
signals:
/**
* @brief Emitted when a pixmap for this card has been updated or finished loading.
*
* @param printing Specific printing for which the pixmap has updated.
*/
void pixmapUpdated(const PrintingInfo &printing);
/**
* @brief Emitted when card properties or state have changed.
*
* @param card Shared pointer to the CardInfo instance that changed.
*/
void cardInfoChanged(CardInfoPtr card);
};
#endif

View file

@ -0,0 +1,30 @@
/**
* @file card_info_comparator.h
* @ingroup Cards
* @brief TODO: Document this.
*/
#ifndef CARD_INFO_COMPARATOR_H
#define CARD_INFO_COMPARATOR_H
#include "card_info.h"
#include <QStringList>
#include <QVariant>
#include <Qt>
class CardInfoComparator
{
public:
explicit CardInfoComparator(const QStringList &properties, Qt::SortOrder order = Qt::AscendingOrder);
bool operator()(const CardInfoPtr &a, const CardInfoPtr &b) const;
private:
QStringList m_properties; // List of properties to sort by
Qt::SortOrder m_order;
QVariant getProperty(const CardInfoPtr &card, const QString &property) const;
bool compareVariants(const QVariant &a, const QVariant &b) const;
};
#endif // CARD_INFO_COMPARATOR_H

View file

@ -0,0 +1,48 @@
#ifndef EXACT_CARD_H
#define EXACT_CARD_H
#include "card/card_info.h"
/**
* @class ExactCard
* @ingroup Cards
* @brief Identifies the card by its CardInfoPtr along with its exact printing by its PrintingInfo.
*/
class ExactCard
{
CardInfoPtr card;
PrintingInfo printing;
public:
ExactCard();
explicit ExactCard(const CardInfoPtr &_card, const PrintingInfo &_printing = PrintingInfo());
/**
* Gets the CardInfoPtr. Can be null.
*/
CardInfoPtr getCardPtr() const
{
return card;
}
/**
* Gets the PrintingInfo. Can be empty.
*/
PrintingInfo getPrinting() const
{
return printing;
}
bool operator==(const ExactCard &other) const;
QString getName() const;
const CardInfo &getInfo() const;
QString getPixmapCacheKey() const;
bool isEmpty() const;
explicit operator bool() const;
void emitPixmapUpdated() const;
};
#endif // EXACT_CARD_H

View file

@ -0,0 +1,56 @@
#ifndef COCKATRICE_PRINTING_INFO_H
#define COCKATRICE_PRINTING_INFO_H
#include "card/card_set/card_set.h"
#include <QList>
#include <QMap>
#include <QStringList>
#include <QVariant>
class PrintingInfo;
using SetToPrintingsMap = QMap<QString, QList<PrintingInfo>>;
/**
* Info relating to a specific printing for a card.
*/
class PrintingInfo
{
public:
explicit PrintingInfo(const CardSetPtr &_set = nullptr);
~PrintingInfo() = default;
bool operator==(const PrintingInfo &other) const
{
return this->set == other.set && this->properties == other.properties;
}
private:
CardSetPtr set;
// per-printing card properties;
QVariantHash properties;
public:
CardSetPtr getSet() const
{
return set;
}
QStringList getProperties() const
{
return properties.keys();
}
QString getProperty(const QString &propertyName) const
{
return properties.value(propertyName).toString();
}
void setProperty(const QString &_name, const QString &_value)
{
properties.insert(_name, _value);
}
QString getUuid() const;
};
#endif // COCKATRICE_PRINTING_INFO_H

View file

@ -0,0 +1,73 @@
#ifndef COCKATRICE_CARD_RELATION_H
#define COCKATRICE_CARD_RELATION_H
#include "card/card_relation/card_relation_type.h"
#include <QObject>
#include <QString>
class CardRelation : public QObject
{
Q_OBJECT
private:
QString name;
CardRelationType attachType;
bool isCreateAllExclusion;
bool isVariableCount;
int defaultCount;
bool isPersistent;
public:
explicit CardRelation(const QString &_name = QString(),
CardRelationType _attachType = CardRelationType::DoesNotAttach,
bool _isCreateAllExclusion = false,
bool _isVariableCount = false,
int _defaultCount = 1,
bool _isPersistent = false);
const QString &getName() const
{
return name;
}
CardRelationType getAttachType() const
{
return attachType;
}
bool getDoesAttach() const
{
return attachType != CardRelationType::DoesNotAttach;
}
bool getDoesTransform() const
{
return attachType == CardRelationType::TransformInto;
}
QString getAttachTypeAsString() const
{
return cardAttachTypeToString(attachType);
}
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 // COCKATRICE_CARD_RELATION_H

View file

@ -0,0 +1,29 @@
#ifndef COCKATRICE_CARD_RELATION_TYPE_H
#define COCKATRICE_CARD_RELATION_TYPE_H
#include <QString>
/**
* Represents how a card relates to another (attach, transform, etc.).
*/
enum class CardRelationType
{
DoesNotAttach = 0,
AttachTo = 1,
TransformInto = 2,
};
// Optional helper
inline QString cardAttachTypeToString(CardRelationType type)
{
switch (type) {
case CardRelationType::AttachTo:
return "attach";
case CardRelationType::TransformInto:
return "transform";
default:
return "";
}
}
#endif // COCKATRICE_CARD_RELATION_TYPE_H

View file

@ -0,0 +1,116 @@
#ifndef COCKATRICE_CARD_SET_H
#define COCKATRICE_CARD_SET_H
#include <QDate>
#include <QList>
#include <QSharedPointer>
#include <QString>
class CardInfo;
using CardInfoPtr = QSharedPointer<CardInfo>;
class CardSet;
using CardSetPtr = QSharedPointer<CardSet>;
class CardSet : public QList<CardInfoPtr>
{
public:
enum Priority
{
PriorityFallback = 0,
PriorityPrimary = 10,
PrioritySecondary = 20,
PriorityReprint = 30,
PriorityOther = 40,
PriorityLowest = 100,
};
static const char *TOKENS_SETNAME;
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);
bool getIsKnownIgnored() const
{
return longName.length() + setType.length() + releaseDate.toString().length() == 0;
}
};
#endif // COCKATRICE_CARD_SET_H

View file

@ -0,0 +1,66 @@
/**
* @file card_set_comparator.h
* @ingroup Cards
* @brief TODO: Document this.
*/
#ifndef SET_PRIORITY_COMPARATOR_H
#define SET_PRIORITY_COMPARATOR_H
#include "card/card_info.h"
class SetPriorityComparator
{
public:
/*
* Returns true if a has higher download priority than b
* Enabled sets have priority over disabled sets
* Both groups follow the user-defined order
*/
inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const
{
if (a->getEnabled()) {
return !b->getEnabled() || a->getSortKey() < b->getSortKey();
} else {
return !b->getEnabled() && a->getSortKey() < b->getSortKey();
}
}
};
class SetReleaseDateComparator
{
public:
/*
* Returns true if a has higher download priority than b
* Enabled sets have priority over disabled sets
* Both groups follow the user-defined order
*/
inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const
{
if (a->getEnabled()) {
return !b->getEnabled() || a->getReleaseDate() < b->getReleaseDate();
} else {
return !b->getEnabled() && a->getReleaseDate() < b->getReleaseDate();
}
}
};
class CardSetPriorityComparator
{
public:
/*
* Returns true if a has higher download priority than b
* Enabled sets have priority over disabled sets
* Both groups follow the user-defined order
*/
inline bool operator()(const PrintingInfo &a, const PrintingInfo &b) const
{
if (a.getSet()->getEnabled()) {
return !b.getSet()->getEnabled() || a.getSet()->getSortKey() < b.getSet()->getSortKey();
} else {
return !b.getSet()->getEnabled() && a.getSet()->getSortKey() < b.getSet()->getSortKey();
}
}
};
#endif // SET_PRIORITY_COMPARATOR_H

View file

@ -0,0 +1,26 @@
#ifndef COCKATRICE_CARD_SET_LIST_H
#define COCKATRICE_CARD_SET_LIST_H
#include "card_set.h"
#include <QList>
#include <QStringList>
class CardSetList : public QList<CardSetPtr>
{
private:
class KeyCompareFunctor;
public:
void sortByKey();
void guessSortKeys();
void enableAllUnknown();
void enableAll();
void markAllAsKnown();
int getEnabledSetsNum();
int getUnknownSetsNum();
QStringList getUnknownSetsNames();
void defaultSort();
};
#endif // COCKATRICE_CARD_SET_LIST_H

View file

@ -0,0 +1,58 @@
/**
* @file game_specific_terms.h
* @ingroup Cards
* @brief TODO: Document this.
*/
#ifndef GAME_SPECIFIC_TERMS_H
#define GAME_SPECIFIC_TERMS_H
#include <QCoreApplication>
#include <QString>
/*
* Collection of traslatable property names used in games,
* so we can use Game::Property instead of hardcoding strings.
* Note: Mtg = "Maybe that game"
*/
namespace Mtg
{
QString const CardType("type");
QString const ConvertedManaCost("cmc");
QString const Colors("colors");
QString const Loyalty("loyalty");
QString const MainCardType("maintype");
QString const ManaCost("manacost");
QString const PowTough("pt");
QString const Side("side");
QString const Layout("layout");
QString const ColorIdentity("coloridentity");
inline static const QString getNicePropertyName(QString key)
{
if (key == CardType)
return QCoreApplication::translate("Mtg", "Card Type");
if (key == ConvertedManaCost)
return QCoreApplication::translate("Mtg", "Mana Value");
if (key == Colors)
return QCoreApplication::translate("Mtg", "Color(s)");
if (key == Loyalty)
return QCoreApplication::translate("Mtg", "Loyalty");
if (key == MainCardType)
return QCoreApplication::translate("Mtg", "Main Card Type");
if (key == ManaCost)
return QCoreApplication::translate("Mtg", "Mana Cost");
if (key == PowTough)
return QCoreApplication::translate("Mtg", "P/T");
if (key == Side)
return QCoreApplication::translate("Mtg", "Side");
if (key == Layout)
return QCoreApplication::translate("Mtg", "Layout");
if (key == ColorIdentity)
return QCoreApplication::translate("Mtg", "Color Identity");
return key;
}
}; // namespace Mtg
#endif

View file

@ -0,0 +1,200 @@
#include "card/card_database/card_database.h"
#include "card/card_database/parser/cockatrice_xml_3.h"
#include "card/card_database/parser/cockatrice_xml_4.h"
#include "card/card_relation/card_relation.h"
#include "settings/cache_settings.h"
#include <QCryptographicHash>
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QMessageBox>
#include <QRegularExpression>
#include <algorithm>
#include <utility>
CardDatabase::CardDatabase(QObject *parent) : QObject(parent), loadStatus(NotLoaded)
{
qRegisterMetaType<CardInfoPtr>("CardInfoPtr");
qRegisterMetaType<CardInfoPtr>("CardSetPtr");
// create loader and wire it up
loader = new CardDatabaseLoader(this, this);
// re-emit loader signals (so other code doesn't need to know about internals)
connect(loader, &CardDatabaseLoader::loadingFinished, this, &CardDatabase::cardDatabaseLoadingFinished);
connect(loader, &CardDatabaseLoader::loadingFailed, this, &CardDatabase::cardDatabaseLoadingFailed);
connect(loader, &CardDatabaseLoader::newSetsFound, this, &CardDatabase::cardDatabaseNewSetsFound);
connect(loader, &CardDatabaseLoader::allNewSetsEnabled, this, &CardDatabase::cardDatabaseAllNewSetsEnabled);
querier = new CardDatabaseQuerier(this, this);
}
CardDatabase::~CardDatabase()
{
clear();
}
void CardDatabase::clear()
{
QMutexLocker locker(clearDatabaseMutex);
for (const auto &card : cards.values()) {
if (card) {
removeCard(card);
}
}
cards.clear();
simpleNameCards.clear();
sets.clear();
ICardDatabaseParser::clearSetlist();
loadStatus = NotLoaded;
}
void CardDatabase::loadCardDatabases()
{
loadStatus = loader->loadCardDatabases();
}
bool CardDatabase::saveCustomTokensToFile()
{
return loader->saveCustomTokensToFile();
}
void CardDatabase::refreshCachedReverseRelatedCards()
{
for (const auto &card : cards) {
card->resetReverseRelatedCards2Me();
}
for (const auto &card : cards) {
for (auto *rel : card->getReverseRelatedCards()) {
if (auto target = cards.value(rel->getName())) {
auto *newRel = new CardRelation(card->getName(), rel->getAttachType(), rel->getIsCreateAllExclusion(),
rel->getIsVariable(), rel->getDefaultCount(), rel->getIsPersistent());
target->addReverseRelatedCards2Me(newRel);
}
}
}
}
void CardDatabase::addCard(CardInfoPtr card)
{
if (card == nullptr) {
qCWarning(CardDatabaseLog) << "CardDatabase::addCard(nullptr)";
return;
}
auto name = card->getName();
// If a card already exists, just add the new set property.
if (auto existing = cards.value(name)) {
for (const auto &printings : card->getSets())
for (const auto &printing : printings)
existing->addToSet(printing.getSet(), printing);
return;
}
QMutexLocker locker(addCardMutex);
cards.insert(name, card);
simpleNameCards.insert(card->getSimpleName(), card);
emit cardAdded(card);
}
void CardDatabase::removeCard(CardInfoPtr card)
{
if (card.isNull()) {
qCWarning(CardDatabaseLog) << "CardDatabase::removeCard(nullptr)";
return;
}
for (auto *cardRelation : card->getRelatedCards())
cardRelation->deleteLater();
for (auto *cardRelation : card->getReverseRelatedCards())
cardRelation->deleteLater();
for (auto *cardRelation : card->getReverseRelatedCards2Me())
cardRelation->deleteLater();
QMutexLocker locker(removeCardMutex);
cards.remove(card->getName());
simpleNameCards.remove(card->getSimpleName());
emit cardRemoved(card);
}
void CardDatabase::addSet(CardSetPtr set)
{
sets.insert(set->getShortName(), set);
}
CardSetPtr CardDatabase::getSet(const QString &setName)
{
if (sets.contains(setName)) {
return sets.value(setName);
} else {
CardSetPtr newSet = CardSet::newInstance(setName);
sets.insert(setName, newSet);
return newSet;
}
}
CardSetList CardDatabase::getSetList() const
{
CardSetList result;
for (auto set : sets.values()) {
result << set;
}
return result;
}
void CardDatabase::checkUnknownSets()
{
auto _sets = getSetList();
if (_sets.getEnabledSetsNum()) {
// if some sets are first found on this run, ask the user
int numUnknownSets = _sets.getUnknownSetsNum();
QStringList unknownSetNames = _sets.getUnknownSetsNames();
if (numUnknownSets > 0) {
emit cardDatabaseNewSetsFound(numUnknownSets, unknownSetNames);
} else {
_sets.markAllAsKnown();
}
} else {
// No set enabled. Probably this is the first time running trice
_sets.guessSortKeys();
_sets.sortByKey();
_sets.enableAll();
notifyEnabledSetsChanged();
emit cardDatabaseAllNewSetsEnabled();
}
}
void CardDatabase::enableAllUnknownSets()
{
auto _sets = getSetList();
_sets.enableAllUnknown();
}
void CardDatabase::markAllSetsAsKnown()
{
auto _sets = getSetList();
_sets.markAllAsKnown();
}
void CardDatabase::notifyEnabledSetsChanged()
{
// refresh the list of cached set names
for (const CardInfoPtr &card : cards)
card->refreshCachedSetNames();
// inform the carddatabasemodels that they need to re-check their list of cards
emit cardDatabaseEnabledSetsChanged();
}

View file

@ -0,0 +1,153 @@
#include "card/card_database/card_database_loader.h"
#include "card/card_database/card_database.h"
#include "card/card_database/parser/cockatrice_xml_3.h"
#include "card/card_database/parser/cockatrice_xml_4.h"
#include "settings/cache_settings.h"
#include <QDebug>
#include <QDirIterator>
#include <QFile>
#include <QTime>
CardDatabaseLoader::CardDatabaseLoader(QObject *parent, CardDatabase *db) : QObject(parent), database(db)
{
// instantiate available parsers here and connect them to the database
availableParsers << new CockatriceXml4Parser;
availableParsers << new CockatriceXml3Parser;
for (auto *p : availableParsers) {
// connect parser outputs to the database adders
connect(p, &ICardDatabaseParser::addCard, database, &CardDatabase::addCard, Qt::DirectConnection);
connect(p, &ICardDatabaseParser::addSet, database, &CardDatabase::addSet, Qt::DirectConnection);
}
// when SettingsCache's path changes, trigger reloads
connect(&SettingsCache::instance(), &SettingsCache::cardDatabasePathChanged, this,
&CardDatabaseLoader::loadCardDatabases);
}
CardDatabaseLoader::~CardDatabaseLoader()
{
qDeleteAll(availableParsers);
availableParsers.clear();
}
LoadStatus CardDatabaseLoader::loadFromFile(const QString &fileName)
{
QFile file(fileName);
file.open(QIODevice::ReadOnly);
if (!file.isOpen()) {
return FileError;
}
for (auto parser : availableParsers) {
file.reset();
if (parser->getCanParseFile(fileName, file)) {
file.reset();
parser->parseFile(file);
return Ok;
}
}
return Invalid;
}
LoadStatus CardDatabaseLoader::loadCardDatabase(const QString &path)
{
auto startTime = QTime::currentTime();
LoadStatus tempLoadStatus = NotLoaded;
if (!path.isEmpty()) {
QMutexLocker locker(loadFromFileMutex);
tempLoadStatus = loadFromFile(path);
}
int msecs = startTime.msecsTo(QTime::currentTime());
qCInfo(CardDatabaseLoadingLog) << "Loaded card database: Path =" << path << "Status =" << tempLoadStatus
<< "Cards =" << (database ? database->cards.size() : 0)
<< "Sets =" << (database ? database->sets.size() : 0) << QString("%1ms").arg(msecs);
return tempLoadStatus;
}
LoadStatus CardDatabaseLoader::loadCardDatabases()
{
QMutexLocker locker(reloadDatabaseMutex);
if (!database) {
qCWarning(CardDatabaseLoadingLog) << "Loader has no database pointer";
emit loadingFailed();
return FileError;
}
emit loadingStarted();
qCInfo(CardDatabaseLoadingLog) << "Card Database Loading Started";
database->clear(); // remove old db
LoadStatus loadStatus =
loadCardDatabase(SettingsCache::instance().getCardDatabasePath()); // load main card database
loadCardDatabase(SettingsCache::instance().getTokenDatabasePath()); // load tokens database
loadCardDatabase(SettingsCache::instance().getSpoilerCardDatabasePath()); // load spoilers database
// find all custom card databases, recursively & following symlinks
// then load them alphabetically
const QStringList customPaths = collectCustomDatabasePaths();
for (int i = 0; i < customPaths.size(); ++i) {
const auto &p = customPaths.at(i);
qCInfo(CardDatabaseLoadingLog) << "Loading Custom Set" << i << "(" << p << ")";
loadCardDatabase(p);
}
// AFTER all the cards have been loaded
// resolve the reverse-related tags
database->refreshCachedReverseRelatedCards();
if (loadStatus == Ok) {
database->checkUnknownSets(); // update deck editors, etc
qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Success";
emit loadingFinished();
} else {
qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Failed";
emit loadingFailed(); // bring up the settings dialog
}
return loadStatus;
}
QStringList CardDatabaseLoader::collectCustomDatabasePaths() const
{
QDirIterator it(SettingsCache::instance().getCustomCardDatabasePath(), {"*.xml"}, QDir::Files,
QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
QStringList paths;
while (it.hasNext())
paths << it.next();
paths.sort();
return paths;
}
bool CardDatabaseLoader::saveCustomTokensToFile()
{
if (!database) {
qCWarning(CardDatabaseLog) << "saveCustomTokensToFile: database pointer missing";
return false;
}
QString fileName = SettingsCache::instance().getCustomCardDatabasePath() + "/" + CardSet::TOKENS_SETNAME + ".xml";
SetNameMap tmpSets;
CardSetPtr customTokensSet = database->getSet(CardSet::TOKENS_SETNAME);
tmpSets.insert(CardSet::TOKENS_SETNAME, customTokensSet);
CardNameMap tmpCards;
for (const CardInfoPtr &card : database->cards) {
if (card->getSets().contains(CardSet::TOKENS_SETNAME)) {
tmpCards.insert(card->getName(), card);
}
}
availableParsers.first()->saveToFile(tmpSets, tmpCards, fileName);
return true;
}

View file

@ -0,0 +1,12 @@
#include "card/card_database/card_database_manager.h"
CardDatabase *CardDatabaseManager::getInstance()
{
static CardDatabase instance; // Created only once, on first access
return &instance;
}
CardDatabaseQuerier *CardDatabaseManager::query()
{
return getInstance()->query();
}

View file

@ -0,0 +1,324 @@
#include "card/card_database/card_database_querier.h"
#include "card/card_database/card_database.h"
#include "card/card_info.h"
#include "card/card_printing/exact_card.h"
#include "card/card_set/card_set_comparator.h"
#include <qrandom.h>
CardDatabaseQuerier::CardDatabaseQuerier(QObject *_parent, const CardDatabase *_db) : QObject(_parent), db(_db)
{
}
/**
* Looks up the cardInfo corresponding to the cardName.
*
* @param cardName The card name to look up
* @return A CardInfoPtr, or null if not corresponding CardInfo is found.
*/
CardInfoPtr CardDatabaseQuerier::getCardInfo(const QString &cardName) const
{
return db->cards.value(cardName);
}
/**
* Looks up the cardInfos for a list of card names.
*
* @param cardNames The card names to look up
* @return A List of CardInfoPtr. Any failed lookups will be ignored and dropped from the resulting list
*/
QList<CardInfoPtr> CardDatabaseQuerier::getCardInfos(const QStringList &cardNames) const
{
QList<CardInfoPtr> cardInfos;
for (const QString &cardName : cardNames) {
CardInfoPtr ptr = db->cards.value(cardName);
if (ptr)
cardInfos.append(ptr);
}
return cardInfos;
}
CardInfoPtr CardDatabaseQuerier::getCardBySimpleName(const QString &cardName) const
{
return db->simpleNameCards.value(CardInfo::simplifyName(cardName));
}
CardInfoPtr CardDatabaseQuerier::lookupCardByName(const QString &name) const
{
if (auto info = getCardInfo(name))
return info;
if (auto info = getCardBySimpleName(name))
return info;
return getCardBySimpleName(CardInfo::simplifyName(name));
}
/**
* Looks up the cards corresponding to the CardRefs.
* If the providerId is empty, will default to the preferred printing.
* If providerId is given but not found, the PrintingInfo will be empty.
*
* @param cardRefs The cards to look up. If providerId is empty for an entry, will default to the preferred printing for
* that entry. If providerId is given but not found, the PrintingInfo will be empty for that entry.
* @return A list of cards. Any failed lookups will be ignored and dropped from the resulting list.
*/
QList<ExactCard> CardDatabaseQuerier::getCards(const QList<CardRef> &cardRefs) const
{
QList<ExactCard> cards;
for (const auto &cardRef : cardRefs) {
ExactCard card = getCard(cardRef);
if (card)
cards.append(card);
}
return cards;
}
/**
* Looks up the card corresponding to the CardRef.
* If the providerId is empty, will default to the preferred printing.
* If providerId is given but not found, the PrintingInfo will be empty.
*
* @param cardRef The card to look up.
* @return A specific printing of a card, or empty if not found.
*/
ExactCard CardDatabaseQuerier::getCard(const CardRef &cardRef) const
{
auto info = getCardInfo(cardRef.name);
if (info.isNull()) {
return {};
}
if (cardRef.providerId.isEmpty() || cardRef.providerId.isNull()) {
return ExactCard(info, getPreferredPrinting(info));
}
return ExactCard(info, findPrintingWithId(info, cardRef.providerId));
}
/**
* Looks up the card by CardRef, simplifying the name if required.
* If the providerId is empty, will default to the preferred printing.
* If providerId is given but not found, the PrintingInfo will be empty.
*
* @param cardRef The card to look up.
* @return A specific printing of a card, or empty if not found.
*/
ExactCard CardDatabaseQuerier::guessCard(const CardRef &cardRef) const
{
auto card = lookupCardByName(cardRef.name);
auto printing =
cardRef.providerId.isEmpty() ? getPreferredPrinting(card) : findPrintingWithId(card, cardRef.providerId);
return ExactCard(card, printing);
}
ExactCard CardDatabaseQuerier::getRandomCard() const
{
if (db->cards.isEmpty())
return {};
const auto keys = db->cards.keys();
int randomIndex = QRandomGenerator::global()->bounded(keys.size());
const QString &randomKey = keys.at(randomIndex);
CardInfoPtr randomCard = getCardInfo(randomKey);
return ExactCard{randomCard, getPreferredPrinting(randomCard)};
}
ExactCard CardDatabaseQuerier::getCardFromSameSet(const QString &cardName, const PrintingInfo &otherPrinting) const
{
// The source card does not have a printing defined, which means we can't get a card from the same set.
if (otherPrinting == PrintingInfo()) {
return getCard({cardName});
}
// The source card does have a printing defined, which means we can attempt to get a card from the same set.
PrintingInfo relatedPrinting = getSpecificPrinting(cardName, otherPrinting.getSet()->getCorrectedShortName(), "");
ExactCard relatedCard(guessCard({cardName}).getCardPtr(), relatedPrinting);
// If we didn't find a card from the same set, just try to find any card with the same name.
return relatedCard ? relatedCard : getCard({cardName});
}
/**
* Finds the PrintingInfo in the cardInfo that has the given uuid field.
*
* @param cardInfo The CardInfo to search
* @param providerId The uuid to look for
* @return The PrintingInfo, or a default-constructed PrintingInfo if not found.
*/
PrintingInfo CardDatabaseQuerier::findPrintingWithId(const CardInfoPtr &cardInfo, const QString &providerId) const
{
for (const auto &printings : cardInfo->getSets()) {
for (const auto &printing : printings) {
if (printing.getUuid() == providerId) {
return printing;
}
}
}
return PrintingInfo();
}
PrintingInfo CardDatabaseQuerier::getSpecificPrinting(const CardRef &cardRef) const
{
CardInfoPtr cardInfo = getCardInfo(cardRef.name);
if (!cardInfo) {
return PrintingInfo(nullptr);
}
return findPrintingWithId(cardInfo, cardRef.providerId);
}
PrintingInfo CardDatabaseQuerier::getSpecificPrinting(const QString &cardName,
const QString &setShortName,
const QString &collectorNumber) const
{
CardInfoPtr cardInfo = getCardInfo(cardName);
if (!cardInfo) {
return PrintingInfo(nullptr);
}
SetToPrintingsMap setMap = cardInfo->getSets();
if (setMap.empty()) {
return PrintingInfo(nullptr);
}
for (const auto &printings : setMap) {
for (auto &cardInfoForSet : printings) {
if (!collectorNumber.isEmpty()) {
if (cardInfoForSet.getSet()->getShortName() == setShortName &&
cardInfoForSet.getProperty("num") == collectorNumber) {
return cardInfoForSet;
}
} else {
if (cardInfoForSet.getSet()->getShortName() == setShortName) {
return cardInfoForSet;
}
}
}
}
return PrintingInfo(nullptr);
}
/**
* Gets the card representing the preferred printing of the cardInfo
*
* @param cardInfo The cardInfo to find the preferred printing for
* @return A specific printing of a card
*/
ExactCard CardDatabaseQuerier::getPreferredCard(const CardInfoPtr &cardInfo) const
{
return ExactCard(cardInfo, getPreferredPrinting(cardInfo));
}
bool CardDatabaseQuerier::isPreferredPrinting(const CardRef &cardRef) const
{
if (cardRef.providerId.startsWith("card_")) {
return cardRef.providerId ==
QLatin1String("card_") + cardRef.name + QString("_") + getPreferredPrintingProviderId(cardRef.name);
}
return cardRef.providerId == getPreferredPrintingProviderId(cardRef.name);
}
PrintingInfo CardDatabaseQuerier::getPreferredPrinting(const QString &cardName) const
{
CardInfoPtr cardInfo = getCardInfo(cardName);
return getPreferredPrinting(cardInfo);
}
PrintingInfo CardDatabaseQuerier::getPreferredPrinting(const CardInfoPtr &cardInfo) const
{
if (!cardInfo) {
return PrintingInfo(nullptr);
}
SetToPrintingsMap setMap = cardInfo->getSets();
if (setMap.empty()) {
return PrintingInfo(nullptr);
}
CardSetPtr preferredSet = nullptr;
PrintingInfo preferredPrinting;
SetPriorityComparator comparator;
for (const auto &printings : setMap) {
for (auto &printing : printings) {
CardSetPtr currentSet = printing.getSet();
if (!preferredSet || comparator(currentSet, preferredSet)) {
preferredSet = currentSet;
preferredPrinting = printing;
}
}
}
if (preferredSet) {
return preferredPrinting;
}
return PrintingInfo(nullptr);
}
QString CardDatabaseQuerier::getPreferredPrintingProviderId(const QString &cardName) const
{
PrintingInfo preferredPrinting = getPreferredPrinting(cardName);
QString uuid = preferredPrinting.getUuid();
if (!uuid.isEmpty()) {
return uuid;
}
CardInfoPtr defaultCardInfo = getCardInfo(cardName);
if (defaultCardInfo.isNull()) {
return cardName;
}
return defaultCardInfo->getName();
}
QStringList CardDatabaseQuerier::getAllMainCardTypes() const
{
QSet<QString> types;
for (const auto &card : db->cards.values()) {
types.insert(card->getMainCardType());
}
return types.values();
}
QMap<QString, int> CardDatabaseQuerier::getAllMainCardTypesWithCount() const
{
QMap<QString, int> typeCounts;
for (const auto &card : db->cards.values()) {
QString type = card->getMainCardType();
typeCounts[type]++;
}
return typeCounts;
}
QMap<QString, int> CardDatabaseQuerier::getAllSubCardTypesWithCount() const
{
QMap<QString, int> typeCounts;
for (const auto &card : db->cards.values()) {
QString type = card->getCardType();
QStringList parts = type.split("");
if (parts.size() > 1) { // Ensure there are subtypes
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
QStringList subtypes = parts[1].split(" ", Qt::SkipEmptyParts);
#else
QStringList subtypes = parts[1].split(" ", QString::SkipEmptyParts);
#endif
for (const QString &subtype : subtypes) {
typeCounts[subtype]++;
}
}
}
return typeCounts;
}

View file

@ -0,0 +1,18 @@
#include "card/card_database/model/card/card_completer_proxy_model.h"
CardCompleterProxyModel::CardCompleterProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
{
}
bool CardCompleterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
if (filterRegularExpression().pattern().isEmpty()) {
return true;
}
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
QString data = index.data(Qt::DisplayRole).toString();
// Ensure substring matching
return data.contains(filterRegularExpression());
}

View file

@ -0,0 +1,73 @@
#include "card/card_database/model/card/card_search_model.h"
#include "card/card_database/model/card_database_display_model.h"
#include "card/card_database/model/card_database_model.h"
#include "utility/levenshtein.h"
#include <algorithm>
CardSearchModel::CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent)
: QAbstractListModel(parent), sourceModel(sourceModel)
{
}
int CardSearchModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return searchResults.size();
}
QVariant CardSearchModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= searchResults.size())
return QVariant();
if (role == Qt::DisplayRole) {
return searchResults.at(index.row()).card->getName();
}
return QVariant();
}
void CardSearchModel::updateSearchResults(const QString &query)
{
beginResetModel();
searchResults.clear();
if (query.isEmpty() || !sourceModel)
return;
// Set the filter for the display model
sourceModel->setCardName(query);
// Collect matching cards and compute Levenshtein distance
for (int i = 0; i < sourceModel->rowCount(); ++i) {
QModelIndex modelIndex = sourceModel->index(i, 0);
QModelIndex sourceIndex = sourceModel->mapToSource(modelIndex);
CardDatabaseModel *sourceDbModel = qobject_cast<CardDatabaseModel *>(sourceModel->sourceModel());
if (!sourceDbModel || !sourceIndex.isValid())
return;
CardInfoPtr card = sourceDbModel->getCard(sourceIndex.row());
if (!card)
continue;
int distance = levenshteinDistance(query.toLower(), card->getName().toLower());
searchResults.append({card, distance});
}
// Sort by Levenshtein distance (lower distance = better match)
std::sort(searchResults.begin(), searchResults.end(),
[](const SearchResult &a, const SearchResult &b) { return a.distance < b.distance; });
// Keep only the top 5 results
if (searchResults.size() > 10)
searchResults = searchResults.mid(0, 10);
emit dataChanged(index(0, 0), index(rowCount() - 1, 0));
emit layoutChanged();
endResetModel();
}

View file

@ -0,0 +1,220 @@
#include "card/card_database/model/card_database_display_model.h"
#include "card/card_database/model/card_database_model.h"
CardDatabaseDisplayModel::CardDatabaseDisplayModel(QObject *parent)
: QSortFilterProxyModel(parent), isToken(ShowAll), filterString(nullptr)
{
filterTree = nullptr;
setFilterCaseSensitivity(Qt::CaseInsensitive);
setSortCaseSensitivity(Qt::CaseInsensitive);
dirtyTimer.setSingleShot(true);
connect(&dirtyTimer, &QTimer::timeout, this, &CardDatabaseDisplayModel::invalidate);
loadedRowCount = 0;
}
QMap<wchar_t, wchar_t> CardDatabaseDisplayModel::characterTranslation = {{L'', L'\"'},
{L'', L'\"'},
{L'', L'\''},
{L'', L'\''}};
bool CardDatabaseDisplayModel::canFetchMore(const QModelIndex &index) const
{
return loadedRowCount < sourceModel()->rowCount(index);
}
void CardDatabaseDisplayModel::fetchMore(const QModelIndex &index)
{
int remainder = sourceModel()->rowCount(index) - loadedRowCount;
int itemsToFetch = qMin(100, remainder);
if (itemsToFetch == 0) {
return;
}
const auto startIndex = qMin(rowCount(QModelIndex()), loadedRowCount);
beginInsertRows(QModelIndex(), startIndex, startIndex + itemsToFetch - 1);
loadedRowCount += itemsToFetch;
endInsertRows();
}
int CardDatabaseDisplayModel::rowCount(const QModelIndex &parent) const
{
return QSortFilterProxyModel::rowCount(parent);
}
bool CardDatabaseDisplayModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
QString leftString = sourceModel()->data(left, CardDatabaseModel::SortRole).toString();
QString rightString = sourceModel()->data(right, CardDatabaseModel::SortRole).toString();
if (!cardName.isEmpty() && left.column() == CardDatabaseModel::NameColumn) {
bool isLeftType = leftString.startsWith(cardName, Qt::CaseInsensitive);
bool isRightType = rightString.startsWith(cardName, Qt::CaseInsensitive);
// test for an exact match: isLeftType && leftString.size() == cardName.size()
// or an exclusive start match: isLeftType && !isRightType
if (isLeftType && (!isRightType || leftString.size() == cardName.size()))
return true;
// same checks for the right string
if (isRightType && (!isLeftType || rightString.size() == cardName.size()))
return false;
} else if (right.column() == CardDatabaseModel::PTColumn && left.column() == CardDatabaseModel::PTColumn) {
QStringList leftList = leftString.split("/");
QStringList rightList = rightString.split("/");
if (leftList.size() == 2 && rightList.size() == 2) {
// cool, have both P/T in list now
int lessThanNum = lessThanNumerically(leftList.at(0), rightList.at(0));
if (lessThanNum != 0) {
return lessThanNum < 0;
} else {
// power equal, check toughness
return lessThanNumerically(leftList.at(1), rightList.at(1)) < 0;
}
}
}
return QString::localeAwareCompare(leftString, rightString) < 0;
}
int CardDatabaseDisplayModel::lessThanNumerically(const QString &left, const QString &right)
{
if (left == right) {
return 0;
}
bool okLeft, okRight;
float leftNum = left.toFloat(&okLeft);
float rightNum = right.toFloat(&okRight);
if (okLeft && okRight) {
if (leftNum < rightNum) {
return -1;
} else if (leftNum > rightNum) {
return 1;
} else {
return 0;
}
}
// try and parsing again, for weird ones like "1+*"
QString leftAfterNum = "";
QString rightAfterNum = "";
if (!okLeft) {
int leftNumIndex = 0;
for (; leftNumIndex < left.length(); leftNumIndex++) {
if (!left.at(leftNumIndex).isDigit()) {
break;
}
}
if (leftNumIndex != 0) {
leftNum = left.left(leftNumIndex).toFloat(&okLeft);
leftAfterNum = left.right(leftNumIndex);
}
}
if (!okRight) {
int rightNumIndex = 0;
for (; rightNumIndex < right.length(); rightNumIndex++) {
if (!right.at(rightNumIndex).isDigit()) {
break;
}
}
if (rightNumIndex != 0) {
rightNum = right.left(rightNumIndex).toFloat(&okRight);
rightAfterNum = right.right(rightNumIndex);
}
}
if (okLeft && okRight) {
if (leftNum != rightNum) {
// both parsed as numbers, but different number
if (leftNum < rightNum) {
return -1;
} else {
return 1;
}
} else {
// both parsed, same number, but at least one has something else
// so compare the part after the number - prefer nothing
return QString::localeAwareCompare(leftAfterNum, rightAfterNum);
}
} else if (okLeft) {
return -1;
} else if (okRight) {
return 1;
}
// couldn't parse it, just return String comparison
return QString::localeAwareCompare(left, right);
}
bool CardDatabaseDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
{
CardInfoPtr info = static_cast<CardDatabaseModel *>(sourceModel())->getCard(sourceRow);
if (((isToken == ShowTrue) && !info->getIsToken()) || ((isToken == ShowFalse) && info->getIsToken()))
return false;
if (filterString != nullptr) {
if (filterTree != nullptr && !filterTree->acceptsCard(info)) {
return false;
}
return filterString->check(info);
}
return rowMatchesCardName(info);
}
bool CardDatabaseDisplayModel::rowMatchesCardName(CardInfoPtr info) const
{
if (!cardName.isEmpty() && !info->getName().contains(cardName, Qt::CaseInsensitive))
return false;
if (!cardNameSet.isEmpty() && !cardNameSet.contains(info->getName()))
return false;
if (filterTree != nullptr)
return filterTree->acceptsCard(info);
return true;
}
void CardDatabaseDisplayModel::clearFilterAll()
{
cardName.clear();
cardText.clear();
cardTypes.clear();
cardColors.clear();
if (filterTree != nullptr)
filterTree->clear();
invalidateFilter();
}
void CardDatabaseDisplayModel::setFilterTree(FilterTree *_filterTree)
{
if (this->filterTree != nullptr)
disconnect(this->filterTree, nullptr, this, nullptr);
this->filterTree = _filterTree;
connect(this->filterTree, &FilterTree::changed, this, &CardDatabaseDisplayModel::filterTreeChanged);
invalidate();
}
void CardDatabaseDisplayModel::filterTreeChanged()
{
invalidate();
}
const QString CardDatabaseDisplayModel::sanitizeCardName(const QString &dirtyName, const QMap<wchar_t, wchar_t> &table)
{
std::wstring toReturn = dirtyName.toStdWString();
for (wchar_t &ch : toReturn) {
if (table.contains(ch)) {
ch = table.value(ch);
}
}
return QString::fromStdWString(toReturn);
}

View file

@ -0,0 +1,148 @@
#include "card/card_database/model/card_database_model.h"
#include "card/card_database/card_database.h"
#include <QMap>
#define CARDDBMODEL_COLUMNS 6
CardDatabaseModel::CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent)
: QAbstractListModel(parent), db(_db), showOnlyCardsFromEnabledSets(_showOnlyCardsFromEnabledSets)
{
connect(db, &CardDatabase::cardAdded, this, &CardDatabaseModel::cardAdded);
connect(db, &CardDatabase::cardRemoved, this, &CardDatabaseModel::cardRemoved);
connect(db, &CardDatabase::cardDatabaseEnabledSetsChanged, this,
&CardDatabaseModel::cardDatabaseEnabledSetsChanged);
cardDatabaseEnabledSetsChanged();
}
CardDatabaseModel::~CardDatabaseModel() = default;
int CardDatabaseModel::rowCount(const QModelIndex & /*parent*/) const
{
return cardList.size();
}
int CardDatabaseModel::columnCount(const QModelIndex & /*parent*/) const
{
return CARDDBMODEL_COLUMNS;
}
QVariant CardDatabaseModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= cardList.size() || index.column() >= CARDDBMODEL_COLUMNS ||
(role != Qt::DisplayRole && role != SortRole))
return QVariant();
CardInfoPtr card = cardList.at(index.row());
switch (index.column()) {
case NameColumn:
return card->getName();
case SetListColumn:
return card->getSetsNames();
case ManaCostColumn:
return role == SortRole ? QString("%1%2").arg(card->getCmc(), 4, QChar('0')).arg(card->getManaCost())
: card->getManaCost();
case CardTypeColumn:
return card->getCardType();
case PTColumn:
return card->getPowTough();
case ColorColumn:
return card->getColors();
default:
return QVariant();
}
}
QVariant CardDatabaseModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation != Qt::Horizontal)
return QVariant();
switch (section) {
case NameColumn:
return QString(tr("Name"));
case SetListColumn:
return QString(tr("Sets"));
case ManaCostColumn:
return QString(tr("Mana cost"));
case CardTypeColumn:
return QString(tr("Card type"));
case PTColumn:
return QString(tr("P/T"));
case ColorColumn:
return QString(tr("Color(s)"));
default:
return QVariant();
}
}
void CardDatabaseModel::cardInfoChanged(CardInfoPtr card)
{
const int row = cardList.indexOf(card);
if (row == -1)
return;
emit dataChanged(index(row, 0), index(row, CARDDBMODEL_COLUMNS - 1));
}
bool CardDatabaseModel::checkCardHasAtLeastOneEnabledSet(CardInfoPtr card)
{
if (!showOnlyCardsFromEnabledSets)
return true;
for (const auto &printings : card->getSets()) {
for (const auto &printing : printings) {
if (printing.getSet()->getEnabled())
return true;
}
}
return false;
}
void CardDatabaseModel::cardDatabaseEnabledSetsChanged()
{
// remove all the cards no more present in at least one enabled set
for (const CardInfoPtr &card : cardList) {
if (!checkCardHasAtLeastOneEnabledSet(card)) {
cardRemoved(card);
}
}
// re-check all the card currently not shown, maybe their part of a newly-enabled set
for (const CardInfoPtr &card : db->getCardList()) {
if (!cardListSet.contains(card)) {
cardAdded(card);
}
}
}
void CardDatabaseModel::cardAdded(CardInfoPtr card)
{
if (checkCardHasAtLeastOneEnabledSet(card)) {
// add the card if it's present in at least one enabled set
beginInsertRows(QModelIndex(), cardList.size(), cardList.size());
cardList.append(card);
cardListSet.insert(card);
connect(card.data(), &CardInfo::cardInfoChanged, this, &CardDatabaseModel::cardInfoChanged);
endInsertRows();
}
}
void CardDatabaseModel::cardRemoved(CardInfoPtr card)
{
const int row = cardList.indexOf(card);
if (row == -1) {
return;
}
beginRemoveRows(QModelIndex(), row, row);
disconnect(card.data(), nullptr, this, nullptr);
cardListSet.remove(card);
card.clear();
cardList.removeAt(row);
endRemoveRows();
}

View file

@ -0,0 +1,19 @@
#include "card/card_database/model/token/token_display_model.h"
#include "card/card_database/model/card_database_model.h"
TokenDisplayModel::TokenDisplayModel(QObject *parent) : CardDatabaseDisplayModel(parent)
{
}
bool TokenDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
{
CardInfoPtr info = static_cast<CardDatabaseModel *>(sourceModel())->getCard(sourceRow);
return info->getIsToken() && rowMatchesCardName(info);
}
int TokenDisplayModel::rowCount(const QModelIndex &parent) const
{
// always load all tokens at start
return QSortFilterProxyModel::rowCount(parent);
}

View file

@ -0,0 +1,21 @@
#include "card/card_database/model/token/token_edit_model.h"
#include "card/card_database/model/card_database_display_model.h"
#include "card/card_database/model/card_database_model.h"
#include "card/card_info.h"
TokenEditModel::TokenEditModel(QObject *parent) : CardDatabaseDisplayModel(parent)
{
}
bool TokenEditModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
{
CardInfoPtr info = static_cast<CardDatabaseModel *>(sourceModel())->getCard(sourceRow);
return info->getIsToken() && info->getSets().contains(CardSet::TOKENS_SETNAME) && rowMatchesCardName(info);
}
int TokenEditModel::rowCount(const QModelIndex &parent) const
{
// always load all tokens at start
return QSortFilterProxyModel::rowCount(parent);
}

View file

@ -0,0 +1,29 @@
#include "card/card_database/parser/card_database_parser.h"
SetNameMap ICardDatabaseParser::sets;
void ICardDatabaseParser::clearSetlist()
{
sets.clear();
}
CardSetPtr ICardDatabaseParser::internalAddSet(const QString &setName,
const QString &longName,
const QString &setType,
const QDate &releaseDate,
const CardSet::Priority priority)
{
if (sets.contains(setName)) {
return sets.value(setName);
}
CardSetPtr newSet = CardSet::newInstance(setName);
newSet->setLongName(longName);
newSet->setSetType(setType);
newSet->setReleaseDate(releaseDate);
newSet->setPriority(priority);
sets.insert(setName, newSet);
emit addSet(newSet);
return newSet;
}

View file

@ -0,0 +1,483 @@
#include "card/card_database/parser/cockatrice_xml_3.h"
#include "card/card_relation/card_relation.h"
#include "card/card_relation/card_relation_type.h"
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
#include <QXmlStreamReader>
#include <version_string.h>
#define COCKATRICE_XML3_TAGNAME "cockatrice_carddatabase"
#define COCKATRICE_XML3_TAGVER 3
#define COCKATRICE_XML3_SCHEMALOCATION \
"https://raw.githubusercontent.com/Cockatrice/Cockatrice/master/doc/carddatabase_v3/cards.xsd"
bool CockatriceXml3Parser::getCanParseFile(const QString &fileName, QIODevice &device)
{
qCInfo(CockatriceXml3Log) << "Trying to parse: " << fileName;
if (!fileName.endsWith(".xml", Qt::CaseInsensitive)) {
qCInfo(CockatriceXml3Log) << "Parsing failed: wrong extension";
return false;
}
QXmlStreamReader xml(&device);
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::StartElement) {
if (xml.name().toString() == COCKATRICE_XML3_TAGNAME) {
int version = xml.attributes().value("version").toString().toInt();
if (version == COCKATRICE_XML3_TAGVER) {
return true;
} else {
qCInfo(CockatriceXml3Log) << "Parsing failed: wrong version" << version;
return false;
}
} else {
qCInfo(CockatriceXml3Log) << "Parsing failed: wrong element tag" << xml.name();
return false;
}
}
}
return true;
}
void CockatriceXml3Parser::parseFile(QIODevice &device)
{
QXmlStreamReader xml(&device);
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::StartElement) {
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement) {
break;
}
auto name = xml.name().toString();
if (name == "sets") {
loadSetsFromXml(xml);
} else if (name == "cards") {
loadCardsFromXml(xml);
} else if (!name.isEmpty()) {
qCInfo(CockatriceXml3Log) << "Unknown item" << name << ", trying to continue anyway";
xml.skipCurrentElement();
}
}
}
}
if (xml.hasError()) {
QString preamble = tr("Parse error at line %1 col %2:").arg(xml.lineNumber()).arg(xml.columnNumber());
qCWarning(CockatriceXml3Log).noquote() << preamble << xml.errorString();
}
}
void CockatriceXml3Parser::loadSetsFromXml(QXmlStreamReader &xml)
{
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement) {
break;
}
auto name = xml.name().toString();
if (name == "set") {
QString shortName, longName, setType;
QDate releaseDate;
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement) {
break;
}
name = xml.name().toString();
if (name == "name") {
shortName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
} else if (name == "longname") {
longName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
} else if (name == "settype") {
setType = xml.readElementText(QXmlStreamReader::IncludeChildElements);
} else if (name == "releasedate") {
releaseDate =
QDate::fromString(xml.readElementText(QXmlStreamReader::IncludeChildElements), Qt::ISODate);
} else if (!name.isEmpty()) {
qCInfo(CockatriceXml3Log) << "Unknown set property" << name << ", trying to continue anyway";
xml.skipCurrentElement();
}
}
internalAddSet(shortName, longName, setType, releaseDate);
}
}
}
QString CockatriceXml3Parser::getMainCardType(QString &type)
{
QString result = type;
/*
Legendary Artifact Creature - Golem
Instant // Instant
*/
int pos;
if ((pos = result.indexOf('-')) != -1) {
result.remove(pos, result.length());
}
if ((pos = result.indexOf("")) != -1) {
result.remove(pos, result.length());
}
if ((pos = result.indexOf("//")) != -1) {
result.remove(pos, result.length());
}
result = result.simplified();
/*
Legendary Artifact Creature
Instant
*/
if ((pos = result.lastIndexOf(' ')) != -1) {
result = result.mid(pos + 1);
}
/*
Creature
Instant
*/
return result;
}
void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml)
{
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement) {
break;
}
auto xmlName = xml.name().toString();
if (xmlName == "card") {
QString name = QString("");
QString text = QString("");
QVariantHash properties = QVariantHash();
QString colors = QString("");
QList<CardRelation *> relatedCards, reverseRelatedCards;
auto _sets = SetToPrintingsMap();
int tableRow = 0;
bool cipt = false;
bool landscapeOrientation = false;
bool isToken = false;
bool upsideDown = false;
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement) {
break;
}
xmlName = xml.name().toString();
// variable - assigned properties
if (xmlName == "name") {
name = xml.readElementText(QXmlStreamReader::IncludeChildElements);
} else if (xmlName == "text") {
text = xml.readElementText(QXmlStreamReader::IncludeChildElements);
} else if (xmlName == "color" || xmlName == "colors") {
colors.append(xml.readElementText(QXmlStreamReader::IncludeChildElements));
} else if (xmlName == "token") {
isToken = static_cast<bool>(xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt());
// generic properties
} else if (xmlName == "manacost") {
properties.insert("manacost", xml.readElementText(QXmlStreamReader::IncludeChildElements));
} else if (xmlName == "cmc") {
properties.insert("cmc", xml.readElementText(QXmlStreamReader::IncludeChildElements));
} else if (xmlName == "type") {
QString type = xml.readElementText(QXmlStreamReader::IncludeChildElements);
properties.insert("type", type);
properties.insert("maintype", getMainCardType(type));
} else if (xmlName == "pt") {
properties.insert("pt", xml.readElementText(QXmlStreamReader::IncludeChildElements));
} else if (xmlName == "loyalty") {
properties.insert("loyalty", xml.readElementText(QXmlStreamReader::IncludeChildElements));
// positioning info
} else if (xmlName == "tablerow") {
tableRow = xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt();
} else if (xmlName == "cipt") {
cipt = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
} else if (xmlName == "landscapeOrientation") {
landscapeOrientation = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
} else if (xmlName == "upsidedown") {
upsideDown = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
// sets
} else if (xmlName == "set") {
// NOTE: attributes must be read before readElementText()
QXmlStreamAttributes attrs = xml.attributes();
QString setName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
PrintingInfo setInfo(internalAddSet(setName));
if (attrs.hasAttribute("muId")) {
setInfo.setProperty("muid", attrs.value("muId").toString());
}
if (attrs.hasAttribute("muId")) {
setInfo.setProperty("uuid", attrs.value("uuId").toString());
}
if (attrs.hasAttribute("picURL")) {
setInfo.setProperty("picurl", attrs.value("picURL").toString());
}
if (attrs.hasAttribute("num")) {
setInfo.setProperty("num", attrs.value("num").toString());
}
if (attrs.hasAttribute("rarity")) {
setInfo.setProperty("rarity", attrs.value("rarity").toString());
}
_sets[setName].append(setInfo);
// related cards
} else if (xmlName == "related" || xmlName == "reverse-related") {
CardRelationType attach = CardRelationType::DoesNotAttach;
bool exclude = false;
bool variable = false;
int count = 1;
QXmlStreamAttributes attrs = xml.attributes();
QString cardName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
if (attrs.hasAttribute("count")) {
if (attrs.value("count").toString().indexOf("x=") == 0) {
variable = true;
count = attrs.value("count").toString().remove(0, 2).toInt();
} else if (attrs.value("count").toString().indexOf("x") == 0) {
variable = true;
} else {
count = attrs.value("count").toString().toInt();
}
if (count < 1) {
count = 1;
}
}
if (attrs.hasAttribute("attach")) {
attach = CardRelationType::AttachTo;
}
if (attrs.hasAttribute("exclude")) {
exclude = true;
}
auto *relation = new CardRelation(cardName, attach, exclude, variable, count);
if (xmlName == "reverse-related") {
reverseRelatedCards << relation;
} else {
relatedCards << relation;
}
} else if (!xmlName.isEmpty()) {
qCInfo(CockatriceXml3Log) << "Unknown card property" << xmlName << ", trying to continue anyway";
xml.skipCurrentElement();
}
}
if (name.isEmpty()) {
qCWarning(CockatriceXml3Log) << "Encountered card with empty name; skipping";
continue;
}
properties.insert("colors", colors);
CardInfoPtr newCard =
CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, cipt,
landscapeOrientation, tableRow, upsideDown);
emit addCard(newCard);
}
}
}
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set)
{
if (set.isNull()) {
qCWarning(CockatriceXml3Log) << "&operator<< set is nullptr";
return xml;
}
xml.writeStartElement("set");
xml.writeTextElement("name", set->getShortName());
xml.writeTextElement("longname", set->getLongName());
xml.writeTextElement("settype", set->getSetType());
xml.writeTextElement("releasedate", set->getReleaseDate().toString(Qt::ISODate));
xml.writeEndElement();
return xml;
}
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &info)
{
if (info.isNull()) {
qCWarning(CockatriceXml3Log) << "operator<< info is nullptr";
return xml;
}
QString tmpString;
xml.writeStartElement("card");
// variable - assigned properties
xml.writeTextElement("name", info->getName());
xml.writeTextElement("text", info->getText());
if (info->getIsToken()) {
xml.writeTextElement("token", "1");
}
// generic properties
xml.writeTextElement("manacost", info->getProperty("manacost"));
xml.writeTextElement("cmc", info->getProperty("cmc"));
xml.writeTextElement("type", info->getProperty("type"));
int colorSize = info->getColors().size();
for (int i = 0; i < colorSize; ++i) {
xml.writeTextElement("color", info->getColors().at(i));
}
tmpString = info->getProperty("pt");
if (!tmpString.isEmpty()) {
xml.writeTextElement("pt", tmpString);
}
tmpString = info->getProperty("loyalty");
if (!tmpString.isEmpty()) {
xml.writeTextElement("loyalty", tmpString);
}
// sets
const SetToPrintingsMap setMap = info->getSets();
for (const auto &printings : setMap) {
for (const PrintingInfo &set : printings) {
xml.writeStartElement("set");
xml.writeAttribute("rarity", set.getProperty("rarity"));
xml.writeAttribute("muId", set.getProperty("muid"));
xml.writeAttribute("uuId", set.getProperty("uuid"));
tmpString = set.getProperty("num");
if (!tmpString.isEmpty()) {
xml.writeAttribute("num", tmpString);
}
tmpString = set.getProperty("picurl");
if (!tmpString.isEmpty()) {
xml.writeAttribute("picURL", tmpString);
}
xml.writeCharacters(set.getSet()->getShortName());
xml.writeEndElement();
}
}
// related cards
const QList<CardRelation *> related = info->getRelatedCards();
for (auto i : related) {
xml.writeStartElement("related");
if (i->getDoesAttach()) {
xml.writeAttribute("attach", "attach");
}
if (i->getIsCreateAllExclusion()) {
xml.writeAttribute("exclude", "exclude");
}
if (i->getIsVariable()) {
if (1 == i->getDefaultCount()) {
xml.writeAttribute("count", "x");
} else {
xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount()));
}
} else if (1 != i->getDefaultCount()) {
xml.writeAttribute("count", QString::number(i->getDefaultCount()));
}
xml.writeCharacters(i->getName());
xml.writeEndElement();
}
const QList<CardRelation *> reverseRelated = info->getReverseRelatedCards();
for (auto i : reverseRelated) {
xml.writeStartElement("reverse-related");
if (i->getDoesAttach()) {
xml.writeAttribute("attach", "attach");
}
if (i->getIsCreateAllExclusion()) {
xml.writeAttribute("exclude", "exclude");
}
if (i->getIsVariable()) {
if (1 == i->getDefaultCount()) {
xml.writeAttribute("count", "x");
} else {
xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount()));
}
} else if (1 != i->getDefaultCount()) {
xml.writeAttribute("count", QString::number(i->getDefaultCount()));
}
xml.writeCharacters(i->getName());
xml.writeEndElement();
}
// positioning
xml.writeTextElement("tablerow", QString::number(info->getTableRow()));
if (info->getCipt()) {
xml.writeTextElement("cipt", "1");
}
if (info->getLandscapeOrientation()) {
xml.writeTextElement("landscapeOrientation", "1");
}
if (info->getUpsideDownArt()) {
xml.writeTextElement("upsidedown", "1");
}
xml.writeEndElement(); // card
return xml;
}
bool CockatriceXml3Parser::saveToFile(SetNameMap _sets,
CardNameMap cards,
const QString &fileName,
const QString &sourceUrl,
const QString &sourceVersion)
{
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly)) {
return false;
}
QXmlStreamWriter xml(&file);
xml.setAutoFormatting(true);
xml.writeStartDocument();
xml.writeStartElement(COCKATRICE_XML3_TAGNAME);
xml.writeAttribute("version", QString::number(COCKATRICE_XML3_TAGVER));
xml.writeAttribute("xmlns:xsi", COCKATRICE_XML_XSI_NAMESPACE);
xml.writeAttribute("xsi:schemaLocation", COCKATRICE_XML3_SCHEMALOCATION);
xml.writeStartElement("info");
xml.writeTextElement("author", QCoreApplication::applicationName() + QString(" %1").arg(VERSION_STRING));
xml.writeTextElement("createdAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
xml.writeTextElement("sourceUrl", sourceUrl);
xml.writeTextElement("sourceVersion", sourceVersion);
xml.writeEndElement();
if (_sets.count() > 0) {
xml.writeStartElement("sets");
for (CardSetPtr set : _sets) {
xml << set;
}
xml.writeEndElement();
}
if (cards.count() > 0) {
xml.writeStartElement("cards");
for (CardInfoPtr card : cards) {
xml << card;
}
xml.writeEndElement();
}
xml.writeEndElement(); // cockatrice_carddatabase
xml.writeEndDocument();
return true;
}

View file

@ -0,0 +1,441 @@
#include "card/card_database/parser/cockatrice_xml_4.h"
#include "card/card_relation/card_relation.h"
#include "settings/cache_settings.h"
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
#include <QXmlStreamReader>
#include <version_string.h>
#define COCKATRICE_XML4_TAGNAME "cockatrice_carddatabase"
#define COCKATRICE_XML4_TAGVER 4
#define COCKATRICE_XML4_SCHEMALOCATION \
"https://raw.githubusercontent.com/Cockatrice/Cockatrice/master/doc/carddatabase_v4/cards.xsd"
bool CockatriceXml4Parser::getCanParseFile(const QString &fileName, QIODevice &device)
{
qCInfo(CockatriceXml4Log) << "Trying to parse: " << fileName;
if (!fileName.endsWith(".xml", Qt::CaseInsensitive)) {
qCInfo(CockatriceXml4Log) << "Parsing failed: wrong extension";
return false;
}
QXmlStreamReader xml(&device);
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::StartElement) {
if (xml.name().toString() == COCKATRICE_XML4_TAGNAME) {
int version = xml.attributes().value("version").toString().toInt();
if (version == COCKATRICE_XML4_TAGVER) {
return true;
} else {
qCInfo(CockatriceXml4Log) << "Parsing failed: wrong version" << version;
return false;
}
} else {
qCInfo(CockatriceXml4Log) << "Parsing failed: wrong element tag" << xml.name();
return false;
}
}
}
return true;
}
void CockatriceXml4Parser::parseFile(QIODevice &device)
{
QXmlStreamReader xml(&device);
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::StartElement) {
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement) {
break;
}
auto xmlName = xml.name().toString();
if (xmlName == "sets") {
loadSetsFromXml(xml);
} else if (xmlName == "cards") {
loadCardsFromXml(xml);
} else if (!xmlName.isEmpty()) {
qCInfo(CockatriceXml4Log) << "Unknown item" << xmlName << ", trying to continue anyway";
xml.skipCurrentElement();
}
}
}
}
if (xml.hasError()) {
QString preamble = tr("Parse error at line %1 col %2:").arg(xml.lineNumber()).arg(xml.columnNumber());
qCWarning(CockatriceXml4Log).noquote() << preamble << xml.errorString();
}
}
void CockatriceXml4Parser::loadSetsFromXml(QXmlStreamReader &xml)
{
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement) {
break;
}
auto xmlName = xml.name().toString();
if (xmlName == "set") {
QString shortName, longName, setType;
QDate releaseDate;
short priority;
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement) {
break;
}
xmlName = xml.name().toString();
if (xmlName == "name") {
shortName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
} else if (xmlName == "longname") {
longName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
} else if (xmlName == "settype") {
setType = xml.readElementText(QXmlStreamReader::IncludeChildElements);
} else if (xmlName == "releasedate") {
releaseDate =
QDate::fromString(xml.readElementText(QXmlStreamReader::IncludeChildElements), Qt::ISODate);
} else if (xmlName == "priority") {
priority = xml.readElementText(QXmlStreamReader::IncludeChildElements).toShort();
} else if (!xmlName.isEmpty()) {
qCInfo(CockatriceXml4Log) << "Unknown set property" << xmlName << ", trying to continue anyway";
xml.skipCurrentElement();
}
}
internalAddSet(shortName, longName, setType, releaseDate, static_cast<CardSet::Priority>(priority));
}
}
}
QVariantHash CockatriceXml4Parser::loadCardPropertiesFromXml(QXmlStreamReader &xml)
{
QVariantHash properties = QVariantHash();
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement) {
break;
}
auto xmlName = xml.name().toString();
if (!xmlName.isEmpty()) {
properties.insert(xmlName, xml.readElementText(QXmlStreamReader::IncludeChildElements));
}
}
return properties;
}
void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
{
bool includeRebalancedCards = SettingsCache::instance().getIncludeRebalancedCards();
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement) {
break;
}
auto xmlName = xml.name().toString();
if (xmlName == "card") {
QString name = QString("");
QString text = QString("");
QVariantHash properties = QVariantHash();
QList<CardRelation *> relatedCards, reverseRelatedCards;
auto _sets = SetToPrintingsMap();
int tableRow = 0;
bool cipt = false;
bool landscapeOrientation = false;
bool isToken = false;
bool upsideDown = false;
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement) {
break;
}
xmlName = xml.name().toString();
// variable - assigned properties
if (xmlName == "name") {
name = xml.readElementText(QXmlStreamReader::IncludeChildElements);
} else if (xmlName == "text") {
text = xml.readElementText(QXmlStreamReader::IncludeChildElements);
} else if (xmlName == "token") {
isToken = static_cast<bool>(xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt());
// generic properties
} else if (xmlName == "prop") {
properties = loadCardPropertiesFromXml(xml);
// positioning info
} else if (xmlName == "tablerow") {
tableRow = xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt();
} else if (xmlName == "cipt") {
cipt = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
} else if (xmlName == "landscapeOrientation") {
landscapeOrientation = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
} else if (xmlName == "upsidedown") {
upsideDown = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
// sets
} else if (xmlName == "set") {
// NOTE: attributes but be read before readElementText()
QXmlStreamAttributes attrs = xml.attributes();
QString setName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
auto set = internalAddSet(setName);
if (set->getEnabled()) {
PrintingInfo printingInfo(set);
for (QXmlStreamAttribute attr : attrs) {
QString attrName = attr.name().toString();
if (attrName == "picURL")
attrName = "picurl";
printingInfo.setProperty(attrName, attr.value().toString());
}
// 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 (includeRebalancedCards || printingInfo.getProperty("isRebalanced") != "true") {
_sets[setName].append(printingInfo);
}
}
// related cards
} else if (xmlName == "related" || xmlName == "reverse-related") {
CardRelationType attachType = CardRelationType::DoesNotAttach;
bool exclude = false;
bool variable = false;
bool persistent = false;
int count = 1;
QXmlStreamAttributes attrs = xml.attributes();
QString cardName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
if (attrs.hasAttribute("count")) {
if (attrs.value("count").toString().indexOf("x=") == 0) {
variable = true;
count = attrs.value("count").toString().remove(0, 2).toInt();
} else if (attrs.value("count").toString().indexOf("x") == 0) {
variable = true;
} else {
count = attrs.value("count").toString().toInt();
}
if (count < 1) {
count = 1;
}
}
if (attrs.hasAttribute("attach")) {
attachType = attrs.value("attach").toString() == "transform" ? CardRelationType::TransformInto
: CardRelationType::AttachTo;
}
if (attrs.hasAttribute("exclude")) {
exclude = true;
}
if (attrs.hasAttribute("persistent")) {
persistent = true;
}
auto *relation = new CardRelation(cardName, attachType, exclude, variable, count, persistent);
if (xmlName == "reverse-related") {
reverseRelatedCards << relation;
} else {
relatedCards << relation;
}
} else if (!xmlName.isEmpty()) {
qCInfo(CockatriceXml4Log) << "Unknown card property" << xmlName << ", trying to continue anyway";
xml.skipCurrentElement();
}
}
if (name.isEmpty()) {
qCWarning(CockatriceXml4Log) << "Encountered card with empty name; skipping";
continue;
}
CardInfoPtr newCard =
CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, cipt,
landscapeOrientation, tableRow, upsideDown);
emit addCard(newCard);
}
}
}
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set)
{
if (set.isNull()) {
qCWarning(CockatriceXml4Log) << "&operator<< set is nullptr";
return xml;
}
xml.writeStartElement("set");
xml.writeTextElement("name", set->getShortName());
xml.writeTextElement("longname", set->getLongName());
xml.writeTextElement("settype", set->getSetType());
xml.writeTextElement("releasedate", set->getReleaseDate().toString(Qt::ISODate));
xml.writeTextElement("priority", QString::number(set->getPriority()));
xml.writeEndElement();
return xml;
}
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &info)
{
if (info.isNull()) {
qCWarning(CockatriceXml4Log) << "operator<< info is nullptr";
return xml;
}
QString tmpString;
xml.writeStartElement("card");
// variable - assigned properties
xml.writeTextElement("name", info->getName());
xml.writeTextElement("text", info->getText());
if (info->getIsToken()) {
xml.writeTextElement("token", "1");
}
// generic properties
xml.writeStartElement("prop");
for (QString propName : info->getProperties()) {
xml.writeTextElement(propName, info->getProperty(propName));
}
xml.writeEndElement();
// sets
for (const auto &printings : info->getSets()) {
for (const PrintingInfo &set : printings) {
xml.writeStartElement("set");
for (const QString &propName : set.getProperties()) {
xml.writeAttribute(propName, set.getProperty(propName));
}
xml.writeCharacters(set.getSet()->getShortName());
xml.writeEndElement();
}
}
// related cards
const QList<CardRelation *> related = info->getRelatedCards();
for (auto i : related) {
xml.writeStartElement("related");
if (i->getDoesAttach()) {
xml.writeAttribute("attach", i->getAttachTypeAsString());
}
if (i->getIsCreateAllExclusion()) {
xml.writeAttribute("exclude", "exclude");
}
if (i->getIsPersistent()) {
xml.writeAttribute("persistent", "persistent");
}
if (i->getIsVariable()) {
if (1 == i->getDefaultCount()) {
xml.writeAttribute("count", "x");
} else {
xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount()));
}
} else if (1 != i->getDefaultCount()) {
xml.writeAttribute("count", QString::number(i->getDefaultCount()));
}
xml.writeCharacters(i->getName());
xml.writeEndElement();
}
const QList<CardRelation *> reverseRelated = info->getReverseRelatedCards();
for (auto i : reverseRelated) {
xml.writeStartElement("reverse-related");
if (i->getDoesAttach()) {
xml.writeAttribute("attach", i->getAttachTypeAsString());
}
if (i->getIsCreateAllExclusion()) {
xml.writeAttribute("exclude", "exclude");
}
if (i->getIsPersistent()) {
xml.writeAttribute("persistent", "persistent");
}
if (i->getIsVariable()) {
if (1 == i->getDefaultCount()) {
xml.writeAttribute("count", "x");
} else {
xml.writeAttribute("count", "x=" + QString::number(i->getDefaultCount()));
}
} else if (1 != i->getDefaultCount()) {
xml.writeAttribute("count", QString::number(i->getDefaultCount()));
}
xml.writeCharacters(i->getName());
xml.writeEndElement();
}
// positioning
xml.writeTextElement("tablerow", QString::number(info->getTableRow()));
if (info->getCipt()) {
xml.writeTextElement("cipt", "1");
}
if (info->getLandscapeOrientation()) {
xml.writeTextElement("landscapeOrientation", "1");
}
if (info->getUpsideDownArt()) {
xml.writeTextElement("upsidedown", "1");
}
xml.writeEndElement(); // card
return xml;
}
bool CockatriceXml4Parser::saveToFile(SetNameMap _sets,
CardNameMap cards,
const QString &fileName,
const QString &sourceUrl,
const QString &sourceVersion)
{
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly)) {
return false;
}
QXmlStreamWriter xml(&file);
xml.setAutoFormatting(true);
xml.writeStartDocument();
xml.writeStartElement(COCKATRICE_XML4_TAGNAME);
xml.writeAttribute("version", QString::number(COCKATRICE_XML4_TAGVER));
xml.writeAttribute("xmlns:xsi", COCKATRICE_XML_XSI_NAMESPACE);
xml.writeAttribute("xsi:schemaLocation", COCKATRICE_XML4_SCHEMALOCATION);
xml.writeStartElement("info");
xml.writeTextElement("author", QCoreApplication::applicationName() + QString(" %1").arg(VERSION_STRING));
xml.writeTextElement("createdAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
xml.writeTextElement("sourceUrl", sourceUrl);
xml.writeTextElement("sourceVersion", sourceVersion);
xml.writeEndElement();
if (_sets.count() > 0) {
xml.writeStartElement("sets");
for (CardSetPtr set : _sets) {
xml << set;
}
xml.writeEndElement();
}
if (cards.count() > 0) {
xml.writeStartElement("cards");
for (CardInfoPtr card : cards) {
xml << card;
}
xml.writeEndElement();
}
xml.writeEndElement(); // cockatrice_carddatabase
xml.writeEndDocument();
return true;
}

View file

@ -0,0 +1,208 @@
#include "card/card_info.h"
#include "card/card_printing/printing_info.h"
#include "card/card_relation/card_relation.h"
#include "card/card_set/card_set.h"
#include "card/game_specific_terms.h"
#include "settings/cache_settings.h"
#include <QDir>
#include <QRegularExpression>
#include <QSharedPointer>
#include <QString>
#include <QVariant>
#include <algorithm>
#include <utility>
class CardRelation;
class CardSet;
class CardInfo;
using CardInfoPtr = QSharedPointer<CardInfo>;
CardInfo::CardInfo(const QString &_name,
const QString &_text,
bool _isToken,
QVariantHash _properties,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
SetToPrintingsMap _sets,
bool _cipt,
bool _landscapeOrientation,
int _tableRow,
bool _upsideDownArt)
: name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards),
reverseRelatedCards(_reverseRelatedCards), setsToPrintings(std::move(_sets)), cipt(_cipt),
landscapeOrientation(_landscapeOrientation), tableRow(_tableRow), upsideDownArt(_upsideDownArt)
{
simpleName = CardInfo::simplifyName(name);
refreshCachedSetNames();
}
CardInfoPtr CardInfo::newInstance(const QString &_name)
{
return newInstance(_name, QString(), false, QVariantHash(), QList<CardRelation *>(), QList<CardRelation *>(),
SetToPrintingsMap(), false, false, 0, false);
}
CardInfoPtr CardInfo::newInstance(const QString &_name,
const QString &_text,
bool _isToken,
QVariantHash _properties,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
SetToPrintingsMap _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 &printings : _sets) {
for (const PrintingInfo &printing : printings) {
printing.getSet()->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 PrintingInfo _info)
{
if (!_set->contains(smartThis)) {
_set->append(smartThis);
}
if (!setsToPrintings[_set->getShortName()].contains(_info)) {
setsToPrintings[_set->getShortName()].append(_info);
}
refreshCachedSetNames();
}
void CardInfo::combineLegalities(const QVariantHash &props)
{
QHashIterator<QString, QVariant> 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 &printings : setsToPrintings) {
for (const auto &printing : printings) {
if (printing.getSet()->getEnabled()) {
setList << printing.getSet()->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');
}
}
void CardInfo::resetReverseRelatedCards2Me()
{
for (CardRelation *cardRelation : this->getReverseRelatedCards2Me()) {
cardRelation->deleteLater();
}
reverseRelatedCardsToMe = QList<CardRelation *>();
}
// 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);
}

View file

@ -0,0 +1,75 @@
#include "card/card_info_comparator.h"
CardInfoComparator::CardInfoComparator(const QStringList &properties, Qt::SortOrder order)
: m_properties(properties), m_order(order)
{
}
bool CardInfoComparator::operator()(const CardInfoPtr &a, const CardInfoPtr &b) const
{
// Iterate over each property in the list
for (const QString &property : m_properties) {
QVariant valueA = getProperty(a, property);
QVariant valueB = getProperty(b, property);
// Compare the current property
if (valueA != valueB) {
// If values differ, perform comparison
return compareVariants(valueA, valueB) ? (m_order == Qt::AscendingOrder) : (m_order == Qt::DescendingOrder);
}
}
// If all properties are equal, return false (indicating they are considered equal for sorting purposes)
return false;
}
bool CardInfoComparator::compareVariants(const QVariant &a, const QVariant &b) const
{
// Determine the type of QVariant based on Qt version
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (a.typeId() != b.typeId()) {
#else
if (a.type() != b.type()) {
#endif
// If they are not the same type, compare as strings
return a.toString() < b.toString();
}
// Perform type-specific comparison
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
switch (static_cast<int>(a.typeId())) {
#else
switch (static_cast<int>(a.type())) {
#endif
case static_cast<int>(QMetaType::Int):
return a.toInt() < b.toInt();
case static_cast<int>(QMetaType::Double):
return a.toDouble() < b.toDouble();
case static_cast<int>(QMetaType::QString):
return a.toString() < b.toString();
case static_cast<int>(QMetaType::Bool):
return a.toBool() < b.toBool();
default:
// Default to comparing as strings
return a.toString() < b.toString();
}
}
QVariant CardInfoComparator::getProperty(const CardInfoPtr &card, const QString &property) const
{
// Check if the property exists in the main fields of the class
if (property == "name") {
return card->getName();
} else if (property == "text") {
return card->getText();
} else if (property == "isToken") {
return card->getIsToken();
}
// Otherwise, check if it's a custom property in the QVariantHash
if (card->hasProperty(property)) {
return card->getProperty(property);
}
return QVariant(); // Return an invalid variant if the property does not exist
}

View file

@ -0,0 +1,82 @@
#include "card/card_printing/exact_card.h"
#include "card/card_info.h"
#include "card/card_printing/printing_info.h"
/**
* Default constructor.
* This will set the CardInfoPtr to null.
* The printing will be the default-constructed PrintingInfo.
*/
ExactCard::ExactCard()
{
}
/**
* @param _card The card. Can be null.
* @param _printing The printing. Can be empty.
*/
ExactCard::ExactCard(const CardInfoPtr &_card, const PrintingInfo &_printing) : card(_card), printing(_printing)
{
}
bool ExactCard::operator==(const ExactCard &other) const
{
return this->card == other.card && this->printing == other.printing;
}
/**
* Convenience method to safely get the card's name.
* @return The name in the CardInfo, or an empty string if card is null
*/
QString ExactCard::getName() const
{
return card.isNull() ? "" : card->getName();
}
/**
* Gets a view of the underlying cardInfoPtr.
* @return A const reference to the CardInfo, or an empty CardInfo if card is null
*/
const CardInfo &ExactCard::getInfo() const
{
if (card.isNull()) {
static CardInfoPtr emptyCard = CardInfo::newInstance("");
return *emptyCard;
}
return *card;
}
/**
* The key used to identify this exact printing in the cache
*/
QString ExactCard::getPixmapCacheKey() const
{
QString uuid = printing.getUuid();
QString suffix = uuid.isEmpty() ? "" : "_" + uuid;
return QLatin1String("card_") + card->getName() + suffix;
}
/**
* Checks if the card is null or empty.
*/
bool ExactCard::isEmpty() const
{
return card.isNull() || card->getName().isEmpty();
}
/**
* Returns true if isEmpty() is false
*/
ExactCard::operator bool() const
{
return !isEmpty();
}
/**
* Gets the CardInfo to emit the pixmapUpdated signal
*/
void ExactCard::emitPixmapUpdated() const
{
emit card->pixmapUpdated(printing);
}

View file

@ -0,0 +1,15 @@
#include "card/card_printing/printing_info.h"
#include "card/card_set/card_set.h"
PrintingInfo::PrintingInfo(const CardSetPtr &_set) : set(_set)
{
}
/**
* Gets the uuid property of the printing, or an empty string if the property isn't present
*/
QString PrintingInfo::getUuid() const
{
return properties.value("uuid").toString();
}

View file

@ -0,0 +1,14 @@
#include "card/card_relation/card_relation.h"
#include "card/card_relation/card_relation_type.h"
CardRelation::CardRelation(const QString &_name,
CardRelationType _attachType,
bool _isCreateAllExclusion,
bool _isVariableCount,
int _defaultCount,
bool _isPersistent)
: name(_name), attachType(_attachType), isCreateAllExclusion(_isCreateAllExclusion),
isVariableCount(_isVariableCount), defaultCount(_defaultCount), isPersistent(_isPersistent)
{
}

View file

@ -0,0 +1,81 @@
#include "card/card_set/card_set.h"
#include "settings/cache_settings.h"
const char *CardSet::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<QString> 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);
}

View file

@ -0,0 +1,127 @@
#include "card/card_set/card_set_list.h"
class CardSetList::KeyCompareFunctor
{
public:
inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const
{
if (a.isNull() || b.isNull()) {
// qCWarning(CardInfoLog) << "SetList::KeyCompareFunctor a or b is null";
return false;
}
return a->getSortKey() < b->getSortKey();
}
};
void CardSetList::sortByKey()
{
std::sort(begin(), end(), KeyCompareFunctor());
}
int CardSetList::getEnabledSetsNum()
{
int num = 0;
for (int i = 0; i < size(); ++i) {
CardSetPtr set = at(i);
if (set && set->getEnabled()) {
++num;
}
}
return num;
}
int CardSetList::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 CardSetList::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 CardSetList::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 CardSetList::enableAll()
{
for (int i = 0; i < size(); ++i) {
CardSetPtr set = at(i);
if (set == nullptr) {
// qCWarning(CardInfoLog) << "enabledAll has null";
continue;
}
if (!set->getIsKnownIgnored()) {
set->setIsKnown(true);
}
set->setEnabled(true);
}
}
void CardSetList::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 CardSetList::guessSortKeys()
{
defaultSort();
for (int i = 0; i < size(); ++i) {
CardSetPtr set = at(i);
if (set.isNull()) {
// qCWarning(CardInfoLog) << "guessSortKeys set is null";
continue;
}
set->setSortKey(i);
}
}
void CardSetList::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
}
});
}

View file

@ -0,0 +1,55 @@
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
set(HEADERS
include/settings/cache_settings.h
include/settings/card_counter_settings.h
include/settings/card_database_settings.h
include/settings/card_override_settings.h
include/settings/debug_settings.h
include/settings/download_settings.h
include/settings/game_filters_settings.h
include/settings/layouts_settings.h
include/settings/message_settings.h
include/settings/recents_settings.h
include/settings/servers_settings.h
include/settings/settings_manager.h
include/settings/shortcut_treeview.h
include/settings/shortcuts_settings.h
)
if (Qt6_FOUND)
qt6_wrap_cpp(MOC_SOURCES ${HEADERS})
elseif (Qt5_FOUND)
qt5_wrap_cpp(MOC_SOURCES ${HEADERS})
endif ()
add_library(settingslib STATIC
src/cache_settings.cpp
src/card_counter_settings.cpp
src/card_database_settings.cpp
src/card_override_settings.cpp
src/debug_settings.cpp
src/download_settings.cpp
src/game_filters_settings.cpp
src/layouts_settings.cpp
src/message_settings.cpp
src/recents_settings.cpp
src/servers_settings.cpp
src/settings_manager.cpp
src/shortcut_treeview.cpp
src/shortcuts_settings.cpp
${MOC_SOURCES}
)
target_include_directories(settingslib
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
PUBLIC ${CMAKE_SOURCE_DIR}/common
PUBLIC ${CMAKE_SOURCE_DIR}/cockatrice/libs/utility/include
PUBLIC ${CMAKE_SOURCE_DIR}/cockatrice/src/client/network
)
target_link_libraries(settingslib
PUBLIC cockatrice_common ${COCKATRICE_QT_MODULES}
)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,35 @@
/**
* @file card_counter_settings.h
* @ingroup GameSettings
* @brief TODO: Document this.
*/
#ifndef CARD_COUNTER_SETTINGS_H
#define CARD_COUNTER_SETTINGS_H
#include "settings_manager.h"
#include <QObject>
class QSettings;
class QColor;
class CardCounterSettings : public SettingsManager
{
Q_OBJECT
public:
CardCounterSettings(const QString &settingsPath, QObject *parent = nullptr);
QColor color(int counterId) const;
QString displayName(int counterId) const;
public slots:
void setColor(int counterId, const QColor &color);
signals:
void colorChanged(int counterId, const QColor &color);
};
#endif // CARD_COUNTER_SETTINGS_H

View file

@ -0,0 +1,39 @@
/**
* @file card_database_settings.h
* @ingroup CardDatabase
* @ingroup CardSettings
* @brief TODO: Document this.
*/
#ifndef CARDDATABASESETTINGS_H
#define CARDDATABASESETTINGS_H
#include "settings_manager.h"
#include <QObject>
#include <QSettings>
#include <QVariant>
class CardDatabaseSettings : public SettingsManager
{
Q_OBJECT
friend class SettingsCache;
public:
void setSortKey(QString shortName, unsigned int sortKey);
void setEnabled(QString shortName, bool enabled);
void setIsKnown(QString shortName, bool isknown);
unsigned int getSortKey(QString shortName);
bool isEnabled(QString shortName);
bool isKnown(QString shortName);
signals:
public slots:
private:
explicit CardDatabaseSettings(const QString &settingPath, QObject *parent = nullptr);
CardDatabaseSettings(const CardDatabaseSettings & /*other*/);
};
#endif // CARDDATABASESETTINGS_H

View file

@ -0,0 +1,32 @@
/**
* @file card_override_settings.h
* @ingroup CardSettings
* @brief TODO: Document this.
*/
#ifndef COCKATRICE_CARD_OVERRIDE_SETTINGS_H
#define COCKATRICE_CARD_OVERRIDE_SETTINGS_H
#include "../common/card_ref.h"
#include "settings_manager.h"
#include <QObject>
class CardOverrideSettings : public SettingsManager
{
Q_OBJECT
friend class SettingsCache;
public:
void setCardPreferenceOverride(const CardRef &cardRef);
void deleteCardPreferenceOverride(const QString &cardName);
QString getCardPreferenceOverride(const QString &cardName);
private:
explicit CardOverrideSettings(const QString &settingPath, QObject *parent = nullptr);
CardOverrideSettings(const CardOverrideSettings & /*other*/);
};
#endif // COCKATRICE_CARD_OVERRIDE_SETTINGS_H

View file

@ -0,0 +1,28 @@
/**
* @file debug_settings.h
* @ingroup CoreSettings
* @brief TODO: Document this.
*/
#ifndef DEBUG_SETTINGS_H
#define DEBUG_SETTINGS_H
#include "settings_manager.h"
class DebugSettings : public SettingsManager
{
Q_OBJECT
friend class SettingsCache;
explicit DebugSettings(const QString &settingPath, QObject *parent = nullptr);
DebugSettings(const DebugSettings & /*other*/);
public:
bool getShowCardId();
bool getLocalGameOnStartup();
int getLocalGamePlayerCount();
QString getDeckPathForPlayer(const QString &playerName);
};
#endif // DEBUG_SETTINGS_H

View file

@ -0,0 +1,29 @@
/**
* @file download_settings.h
* @ingroup NetworkSettings
* @brief TODO: Document this.
*/
#ifndef COCKATRICE_DOWNLOADSETTINGS_H
#define COCKATRICE_DOWNLOADSETTINGS_H
#include "settings_manager.h"
#include <QObject>
class DownloadSettings : public SettingsManager
{
Q_OBJECT
friend class SettingsCache;
static const QStringList DEFAULT_DOWNLOAD_URLS;
public:
explicit DownloadSettings(const QString &, QObject *);
QStringList getAllURLs();
void setDownloadUrls(const QStringList &downloadURLs);
void resetToDefaultURLs();
};
#endif // COCKATRICE_DOWNLOADSETTINGS_H

View file

@ -0,0 +1,66 @@
/**
* @file game_filters_settings.h
* @ingroup Lobby
* @ingroup GameSettings
* @brief TODO: Document this.
*/
#ifndef GAMEFILTERSSETTINGS_H
#define GAMEFILTERSSETTINGS_H
#include "settings_manager.h"
class GameFiltersSettings : public SettingsManager
{
Q_OBJECT
friend class SettingsCache;
public:
bool isHideBuddiesOnlyGames();
bool isHideFullGames();
bool isHideGamesThatStarted();
bool isHidePasswordProtectedGames();
bool isHideIgnoredUserGames();
bool isHideNotBuddyCreatedGames();
void setHideOpenDecklistGames(bool hide);
bool isHideOpenDecklistGames();
QString getGameNameFilter();
QString getCreatorNameFilter();
int getMinPlayers();
int getMaxPlayers();
QTime getMaxGameAge();
bool isGameTypeEnabled(QString gametype);
bool isShowOnlyIfSpectatorsCanWatch();
bool isShowSpectatorPasswordProtected();
bool isShowOnlyIfSpectatorsCanChat();
bool isShowOnlyIfSpectatorsCanSeeHands();
void setHideBuddiesOnlyGames(bool hide);
void setHideIgnoredUserGames(bool hide);
void setHideFullGames(bool hide);
void setHideGamesThatStarted(bool hide);
void setHidePasswordProtectedGames(bool hide);
void setHideNotBuddyCreatedGames(bool hide);
void setGameNameFilter(QString gameName);
void setCreatorNameFilter(QString creatorName);
void setMinPlayers(int min);
void setMaxPlayers(int max);
void setMaxGameAge(const QTime &maxGameAge);
void setGameTypeEnabled(QString gametype, bool enabled);
void setGameHashedTypeEnabled(QString gametypeHASHED, bool enabled);
void setShowOnlyIfSpectatorsCanWatch(bool show);
void setShowSpectatorPasswordProtected(bool show);
void setShowOnlyIfSpectatorsCanChat(bool show);
void setShowOnlyIfSpectatorsCanSeeHands(bool show);
signals:
public slots:
private:
explicit GameFiltersSettings(const QString &settingPath, QObject *parent = nullptr);
GameFiltersSettings(const GameFiltersSettings & /*other*/);
QString hashGameType(const QString &gameType) const;
};
#endif // GAMEFILTERSSETTINGS_H

View file

@ -0,0 +1,72 @@
/**
* @file layouts_settings.h
* @ingroup CoreSettings
* @brief TODO: Document this.
*/
#ifndef LAYOUTSSETTINGS_H
#define LAYOUTSSETTINGS_H
#include "settings_manager.h"
#include <QSize>
class LayoutsSettings : public SettingsManager
{
Q_OBJECT
friend class SettingsCache;
public:
void setDeckEditorLayoutState(const QByteArray &value);
void setDeckEditorGeometry(const QByteArray &value);
void setDeckEditorCardSize(const QSize &value);
void setDeckEditorDeckSize(const QSize &value);
void setDeckEditorPrintingSelectorSize(const QSize &value);
void setDeckEditorFilterSize(const QSize &value);
void setDeckEditorDbHeaderState(const QByteArray &value);
void setSetsDialogHeaderState(const QByteArray &value);
void setGamePlayAreaGeometry(const QByteArray &value);
void setGamePlayAreaState(const QByteArray &value);
void setGameCardInfoSize(const QSize &value);
void setGameMessageLayoutSize(const QSize &value);
void setGamePlayerListSize(const QSize &value);
void setReplayPlayAreaGeometry(const QByteArray &value);
void setReplayPlayAreaState(const QByteArray &value);
void setReplayCardInfoSize(const QSize &value);
void setReplayMessageLayoutSize(const QSize &value);
void setReplayPlayerListSize(const QSize &value);
void setReplayReplaySize(const QSize &value);
const QByteArray getDeckEditorLayoutState();
const QByteArray getDeckEditorGeometry();
QSize getDeckEditorCardSize();
QSize getDeckEditorDeckSize();
QSize getDeckEditorPrintingSelectorSize();
QSize getDeckEditorFilterSize();
const QByteArray getDeckEditorDbHeaderState();
const QByteArray getSetsDialogHeaderState();
const QByteArray getGamePlayAreaLayoutState();
const QByteArray getGamePlayAreaGeometry();
const QSize getGameCardInfoSize();
const QSize getGameMessageLayoutSize();
const QSize getGamePlayerListSize();
const QByteArray getReplayPlayAreaLayoutState();
const QByteArray getReplayPlayAreaGeometry();
const QSize getReplayCardInfoSize();
const QSize getReplayMessageLayoutSize();
const QSize getReplayPlayerListSize();
const QSize getReplayReplaySize();
signals:
public slots:
private:
explicit LayoutsSettings(const QString &settingPath, QObject *parent = nullptr);
LayoutsSettings(const LayoutsSettings & /*other*/);
};
#endif // LAYOUTSSETTINGS_H

View file

@ -0,0 +1,33 @@
/**
* @file message_settings.h
* @ingroup NetworkSettings
* @brief TODO: Document this.
*/
#ifndef MESSAGESETTINGS_H
#define MESSAGESETTINGS_H
#include "settings_manager.h"
class MessageSettings : public SettingsManager
{
Q_OBJECT
friend class SettingsCache;
public:
int getCount();
QString getMessageAt(int index);
void setCount(int count);
void setMessageAt(int index, QString message);
signals:
void messageMacrosChanged();
public slots:
private:
explicit MessageSettings(const QString &settingPath, QObject *parent = nullptr);
MessageSettings(const MessageSettings & /*other*/);
};
#endif // MESSAGESETTINGS_H

View file

@ -0,0 +1,32 @@
/**
* @file recents_settings.h
* @ingroup DeckSettings
* @brief TODO: Document this.
*/
#ifndef RECENTS_SETTINGS_H
#define RECENTS_SETTINGS_H
#include "settings_manager.h"
class RecentsSettings : public SettingsManager
{
Q_OBJECT
friend class SettingsCache;
explicit RecentsSettings(const QString &settingPath, QObject *parent = nullptr);
RecentsSettings(const RecentsSettings & /*other*/);
public:
QStringList getRecentlyOpenedDeckPaths();
void clearRecentlyOpenedDeckPaths();
void updateRecentlyOpenedDeckPaths(const QString &deckPath);
QString getLatestDeckDirPath();
void setLatestDeckDirPath(const QString &dirPath);
signals:
void recentlyOpenedDeckPathsChanged();
};
#endif // RECENTS_SETTINGS_H

View file

@ -0,0 +1,77 @@
/**
* @file servers_settings.h
* @ingroup NetworkSettings
* @brief TODO: Document this.
*/
#ifndef SERVERSSETTINGS_H
#define SERVERSSETTINGS_H
#include "settings_manager.h"
#include <QLoggingCategory>
#include <QObject>
#define SERVERSETTINGS_DEFAULT_HOST "server.cockatrice.us"
#define SERVERSETTINGS_DEFAULT_PORT "4748"
inline Q_LOGGING_CATEGORY(ServersSettingsLog, "servers_settings");
class ServersSettings : public SettingsManager
{
Q_OBJECT
friend class SettingsCache;
public:
int getPreviousHostLogin();
int getPrevioushostindex(const QString &);
QStringList getPreviousHostList();
QString getPrevioushostName();
QString getHostname(QString defaultHost = SERVERSETTINGS_DEFAULT_HOST);
QString getPort(QString defaultPort = SERVERSETTINGS_DEFAULT_PORT);
QString getPlayerName(QString defaultName = "");
QString getFPHostname(QString defaultHost = SERVERSETTINGS_DEFAULT_HOST);
QString getFPPort(QString defaultPort = SERVERSETTINGS_DEFAULT_PORT);
QString getFPPlayerName(QString defaultName = "");
QString getPassword();
QString getSaveName(QString defaultname = "");
QString getSite(QString defaultName = "");
bool getSavePassword();
int getAutoConnect();
void setPreviousHostLogin(int previous);
void setPrevioushostName(const QString &);
void setPreviousHostList(QStringList list);
void setAutoConnect(int autoconnect);
void setSite(QString site);
void setFPHostName(QString hostname);
void setFPPort(QString port);
void setFPPlayerName(QString playerName);
void addNewServer(const QString &saveName,
const QString &serv,
const QString &port,
const QString &username,
const QString &password,
bool savePassword,
const QString &site = QString());
void removeServer(QString servAddr);
bool updateExistingServer(QString saveName,
QString serv,
QString port,
QString username,
QString password,
bool savePassword,
QString site = QString());
bool updateExistingServerWithoutLoss(QString saveName,
QString serv = QString(),
QString port = QString(),
QString site = QString());
void setClearDebugLogStatus(bool abIsChecked);
bool getClearDebugLogStatus(bool abDefaultValue);
private:
explicit ServersSettings(const QString &settingPath, QObject *parent = nullptr);
ServersSettings(const ServersSettings & /*other*/);
};
#endif // SERVERSSETTINGS_H

View file

@ -0,0 +1,29 @@
/**
* @file settings_manager.h
* @ingroup Settings
* @brief TODO: Document this.
*/
#ifndef SETTINGSMANAGER_H
#define SETTINGSMANAGER_H
#include <QObject>
#include <QSettings>
#include <QStringList>
#include <QVariant>
class SettingsManager : public QObject
{
Q_OBJECT
public:
explicit SettingsManager(const QString &settingPath, QObject *parent = nullptr);
QVariant getValue(const QString &name, const QString &group = "", const QString &subGroup = "");
void sync();
protected:
QSettings settings;
void setValue(const QVariant &value, const QString &name, const QString &group = "", const QString &subGroup = "");
void deleteValue(const QString &name, const QString &group = "", const QString &subGroup = "");
};
#endif // SETTINGSMANAGER_H

View file

@ -0,0 +1,53 @@
/**
* @file shortcut_treeview.h
* @ingroup CoreSettings
* @brief TODO: Document this.
*/
#ifndef SHORTCUT_TREEVIEW_H
#define SHORTCUT_TREEVIEW_H
#include <QModelIndex>
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
#include <QTreeView>
/**
* Custom implementation of QSortFilterProxyModel that appends the source and parent strings together when filtering
*/
class ShortcutFilterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit ShortcutFilterProxyModel(QObject *parent = nullptr);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
};
class ShortcutTreeView : public QTreeView
{
Q_OBJECT
public:
explicit ShortcutTreeView(QWidget *parent);
void retranslateUi();
signals:
void currentItemChanged(const QString &shortcut);
public slots:
void updateSearchString(const QString &searchString);
private:
QStandardItemModel *shortcutsModel;
ShortcutFilterProxyModel *proxyModel;
void populateShortcutsModel();
private slots:
void refreshShortcuts();
protected:
void currentChanged(const QModelIndex &current, const QModelIndex &previous) override;
};
#endif // SHORTCUT_TREEVIEW_H

View file

@ -0,0 +1,737 @@
/**
* @file shortcuts_settings.h
* @ingroup CoreSettings
* @brief TODO: Document this.
*/
#ifndef SHORTCUTSSETTINGS_H
#define SHORTCUTSSETTINGS_H
#include <QApplication>
#include <QKeySequence>
#include <QLoggingCategory>
#include <QSettings>
inline Q_LOGGING_CATEGORY(ShortcutsSettingsLog, "shortcuts_settings");
class ShortcutGroup
{
public:
enum Groups
{
Main_Window,
Deck_Editor,
Game_Lobby,
Card_Counters,
Player_Counters,
Power_Toughness,
Game_Phases,
Playing_Area,
Move_selected,
View,
Move_top,
Move_bottom,
Gameplay,
Drawing,
Chat_room,
Game_window,
Load_deck,
Replays,
Tabs
};
static QString getGroupName(ShortcutGroup::Groups group)
{
switch (group) {
case Main_Window:
return QApplication::translate("shortcutsTab", "Main Window");
case Deck_Editor:
return QApplication::translate("shortcutsTab", "Deck Editor");
case Game_Lobby:
return QApplication::translate("shortcutsTab", "Game Lobby");
case Card_Counters:
return QApplication::translate("shortcutsTab", "Card Counters");
case Player_Counters:
return QApplication::translate("shortcutsTab", "Player Counters");
case Power_Toughness:
return QApplication::translate("shortcutsTab", "Power and Toughness");
case Game_Phases:
return QApplication::translate("shortcutsTab", "Game Phases");
case Playing_Area:
return QApplication::translate("shortcutsTab", "Playing Area");
case Move_selected:
return QApplication::translate("shortcutsTab", "Move Selected Card");
case View:
return QApplication::translate("shortcutsTab", "View");
case Move_top:
return QApplication::translate("shortcutsTab", "Move Top Card");
case Move_bottom:
return QApplication::translate("shortcutsTab", "Move Bottom Card");
case Gameplay:
return QApplication::translate("shortcutsTab", "Gameplay");
case Drawing:
return QApplication::translate("shortcutsTab", "Drawing");
case Chat_room:
return QApplication::translate("shortcutsTab", "Chat Room");
case Game_window:
return QApplication::translate("shortcutsTab", "Game Window");
case Load_deck:
return QApplication::translate("shortcutsTab", "Load Deck from Clipboard");
case Replays:
return QApplication::translate("shortcutsTab", "Replays");
case Tabs:
return QApplication::translate("shortcutsTab", "Tabs");
}
return {};
}
};
class ShortcutKey : public QList<QKeySequence>
{
public:
explicit ShortcutKey(const QString &_name = QString(),
QList _sequence = QList(),
ShortcutGroup::Groups _group = ShortcutGroup::Main_Window);
void setSequence(const QList &_sequence)
{
QList::operator=(_sequence);
};
QString getName() const
{
return QApplication::translate("shortcutsTab", name.toUtf8().data());
};
QString getGroupName() const
{
return ShortcutGroup::getGroupName(group);
};
private:
QString name;
ShortcutGroup::Groups group;
};
class ShortcutsSettings : public QObject
{
Q_OBJECT
public:
explicit ShortcutsSettings(const QString &settingsFilePath, QObject *parent = nullptr);
ShortcutKey getDefaultShortcut(const QString &name) const;
ShortcutKey getShortcut(const QString &name) const;
QKeySequence getSingleShortcut(const QString &name) const;
QString getDefaultShortcutString(const QString &name) const;
QString getShortcutString(const QString &name) const;
QString getShortcutFriendlyName(const QString &shortcutName) const;
QList<QString> getAllShortcutKeys() const
{
return shortCuts.keys();
};
void setShortcuts(const QString &name, const QList<QKeySequence> &Sequence);
void setShortcuts(const QString &name, const QKeySequence &Sequence);
void setShortcuts(const QString &name, const QString &sequences);
bool isKeyAllowed(const QString &name, const QString &sequences) const;
bool isValid(const QString &name, const QString &sequences) const;
QStringList findOverlaps(const QString &name, const QString &sequences) const;
void resetAllShortcuts();
void clearAllShortcuts();
void migrateShortcuts();
signals:
void shortCutChanged();
private:
const QChar sep = ';';
const QString custom = "Custom"; // name of custom group in shortCutsFile
QString settingsFilePath;
QHash<QString, ShortcutKey> shortCuts;
QString stringifySequence(const QList<QKeySequence> &Sequence) const;
QList<QKeySequence> parseSequenceString(const QString &stringSequence) const;
const QHash<QString, ShortcutKey> defaultShortCuts = {
{"MainWindow/aCheckCardUpdates", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Check for Card Updates..."),
parseSequenceString(""),
ShortcutGroup::Main_Window)},
{"MainWindow/aConnect", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Connect..."),
parseSequenceString("Ctrl+L"),
ShortcutGroup::Main_Window)},
{"MainWindow/aDisconnect", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Disconnect"),
parseSequenceString(""),
ShortcutGroup::Main_Window)},
{"MainWindow/aExit",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exit"), parseSequenceString(""), ShortcutGroup::Main_Window)},
{"MainWindow/aFullScreen", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Full screen"),
parseSequenceString("Ctrl+F"),
ShortcutGroup::Main_Window)},
{"MainWindow/aRegister", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Register..."),
parseSequenceString(""),
ShortcutGroup::Main_Window)},
{"MainWindow/aSettings", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Settings..."),
parseSequenceString("Ctrl+Shift+P"),
ShortcutGroup::Main_Window)},
{"MainWindow/aSinglePlayer", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Start a Local Game..."),
parseSequenceString("Ctrl+Shift+L"),
ShortcutGroup::Main_Window)},
{"MainWindow/aWatchReplay", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Watch Replay..."),
parseSequenceString(""),
ShortcutGroup::Main_Window)},
{"MainWindow/aStatusBar", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Show Status Bar"),
parseSequenceString(""),
ShortcutGroup::Main_Window)},
{"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)},
{"TabDeckEditor/aClearFilterOne", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Clear Selected Filter"),
parseSequenceString(""),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aClose",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Close"), parseSequenceString(""), ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aDecrement", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Card"),
parseSequenceString("-"),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aManageSets", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Manage Sets..."),
parseSequenceString(""),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aEditTokens", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Edit Custom Tokens..."),
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)},
{"TabDeckEditor/aLoadDeck", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Load Deck..."),
parseSequenceString("Ctrl+O"),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aLoadDeckFromClipboard",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Load Deck from Clipboard..."),
parseSequenceString("Ctrl+Shift+V"),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aEditDeckInClipboard",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Edit Deck in Clipboard, Annotated"),
parseSequenceString(""),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aEditDeckInClipboardRaw",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Edit Deck in Clipboard"),
parseSequenceString(""),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aNewDeck", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "New Deck"),
parseSequenceString("Ctrl+N"),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aOpenCustomFolder",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Open Custom Pictures Folder"),
parseSequenceString(""),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aPrintDeck", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Print Deck..."),
parseSequenceString("Ctrl+P"),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aRemoveCard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Delete Card"),
parseSequenceString(""),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aResetLayout", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Reset Layout"),
parseSequenceString(""),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aSaveDeck", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Save Deck"),
parseSequenceString("Ctrl+S"),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aSaveDeckAs", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Save Deck as..."),
parseSequenceString("Ctrl+Shift+S"),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aSaveDeckToClipboard",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Save Deck to Clipboard, Annotated"),
parseSequenceString("Ctrl+Shift+C"),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aSaveDeckToClipboardNoSetInfo",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Save Deck to Clipboard, Annotated (No Set Info)"),
parseSequenceString(""),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aSaveDeckToClipboardRaw",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Save Deck to Clipboard"),
parseSequenceString("Ctrl+Shift+R"),
ShortcutGroup::Deck_Editor)},
{"TabDeckEditor/aSaveDeckToClipboardRawNoSetInfo",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Save Deck to Clipboard (No Set Info)"),
parseSequenceString(""),
ShortcutGroup::Deck_Editor)},
{"DeckViewContainer/loadLocalButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Load Local Deck..."),
parseSequenceString("Ctrl+O"),
ShortcutGroup::Game_Lobby)},
{"DeckViewContainer/loadRemoteButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Load Remote Deck..."),
parseSequenceString("Ctrl+Alt+O"),
ShortcutGroup::Game_Lobby)},
{"DeckViewContainer/loadFromClipboardButton",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Load Deck from Clipboard..."),
parseSequenceString("Ctrl+Shift+V"),
ShortcutGroup::Game_Lobby)},
{"DeckViewContainer/unloadDeckButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Unload Deck"),
parseSequenceString("Ctrl+Alt+U"),
ShortcutGroup::Game_Lobby)},
{"DeckViewContainer/readyStartButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Ready to Start"),
parseSequenceString("Ctrl+Shift+S"),
ShortcutGroup::Game_Lobby)},
{"DeckViewContainer/sideboardLockButton",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Sideboard Lock"),
parseSequenceString("Ctrl+Shift+B"),
ShortcutGroup::Game_Lobby)},
{"DeckViewContainer/forceStartGameButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Force Start"),
parseSequenceString(""),
ShortcutGroup::Game_Lobby)},
{"Player/aCCMagenta", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Card Counter (F)"),
parseSequenceString(""),
ShortcutGroup::Card_Counters)},
{"Player/aRCMagenta", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Card Counter (F)"),
parseSequenceString(""),
ShortcutGroup::Card_Counters)},
{"Player/aSCMagenta", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Card Counters (F)..."),
parseSequenceString(""),
ShortcutGroup::Card_Counters)},
{"Player/aCCPurple", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Card Counter (E)"),
parseSequenceString(""),
ShortcutGroup::Card_Counters)},
{"Player/aRCPurple", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Card Counter (E)"),
parseSequenceString(""),
ShortcutGroup::Card_Counters)},
{"Player/aSCPurple", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Card Counters (E)..."),
parseSequenceString(""),
ShortcutGroup::Card_Counters)},
{"Player/aCCCyan", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Card Counter(D)"),
parseSequenceString(""),
ShortcutGroup::Card_Counters)},
{"Player/aRCCyan", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Card Counter (D)"),
parseSequenceString(""),
ShortcutGroup::Card_Counters)},
{"Player/aSCCyan", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Card Counters (D)..."),
parseSequenceString(""),
ShortcutGroup::Card_Counters)},
{"Player/aCCGreen", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Card Counter (C)"),
parseSequenceString("Ctrl+>"),
ShortcutGroup::Card_Counters)},
{"Player/aRCGreen", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Card Counter (C)"),
parseSequenceString("Ctrl+<"),
ShortcutGroup::Card_Counters)},
{"Player/aSCGreen", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Card Counters (C)..."),
parseSequenceString("Ctrl+?"),
ShortcutGroup::Card_Counters)},
{"Player/aCCYellow", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Card Counter (B)"),
parseSequenceString("Ctrl+."),
ShortcutGroup::Card_Counters)},
{"Player/aRCYellow", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Card Counter (B)"),
parseSequenceString("Ctrl+,"),
ShortcutGroup::Card_Counters)},
{"Player/aSCYellow", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Card Counters (B)..."),
parseSequenceString("Ctrl+/"),
ShortcutGroup::Card_Counters)},
{"Player/aCCRed", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Card Counter (A)"),
parseSequenceString("Alt+."),
ShortcutGroup::Card_Counters)},
{"Player/aRCRed", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Card Counter (A)"),
parseSequenceString("Alt+,"),
ShortcutGroup::Card_Counters)},
{"Player/aSCRed", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Card Counters (A)..."),
parseSequenceString("Alt+/"),
ShortcutGroup::Card_Counters)},
{"Player/aInc", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Life Counter"),
parseSequenceString("F12"),
ShortcutGroup::Player_Counters)},
{"Player/aDec", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Life Counter"),
parseSequenceString("F11"),
ShortcutGroup::Player_Counters)},
{"Player/aSet", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Life Counters..."),
parseSequenceString("Ctrl+L"),
ShortcutGroup::Player_Counters)},
{"Player/aIncCounter_w", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add White Counter"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aDecCounter_w", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove White Counter"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aSetCounter_w", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set White Counters..."),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aIncCounter_u", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Blue Counter"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aDecCounter_u", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Blue Counter"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aSetCounter_u", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Blue Counters..."),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aIncCounter_b", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Black Counter"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aDecCounter_b", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Black Counter"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aSetCounter_b", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Black Counters..."),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aIncCounter_r", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Red Counter"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aDecCounter_r", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Red Counter"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aSetCounter_r", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Red Counters..."),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aIncCounter_g", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Green Counter"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aDecCounter_g", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Green Counter"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aSetCounter_g", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Green Counters..."),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aIncCounter_x", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Colorless Counter"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aDecCounter_x", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Colorless Counter"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aSetCounter_x", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Colorless Counters..."),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aIncCounter_storm", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Other Counter"),
parseSequenceString("Ctrl+]"),
ShortcutGroup::Player_Counters)},
{"Player/aDecCounter_storm", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Other Counter"),
parseSequenceString("Ctrl+["),
ShortcutGroup::Player_Counters)},
{"Player/aSetCounter_storm", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Other Counters..."),
parseSequenceString("Ctrl+\\"),
ShortcutGroup::Player_Counters)},
{"Player/aIncrementAllCardCounters",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Increment all card counters"),
parseSequenceString("Ctrl+Shift+A"),
ShortcutGroup::Playing_Area)},
{"Player/aIncP", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Power (+1/+0)"),
parseSequenceString("Ctrl++;Ctrl+="),
ShortcutGroup::Power_Toughness)},
{"Player/aDecP", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Power (-1/-0)"),
parseSequenceString("Ctrl+-"),
ShortcutGroup::Power_Toughness)},
{"Player/aFlowP", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Move Toughness to Power (+1/-1)"),
parseSequenceString(""),
ShortcutGroup::Power_Toughness)},
{"Player/aIncT", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Toughness (+0/+1)"),
parseSequenceString("Alt++;Alt+="),
ShortcutGroup::Power_Toughness)},
{"Player/aDecT", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Toughness (-0/-1)"),
parseSequenceString("Alt+-"),
ShortcutGroup::Power_Toughness)},
{"Player/aFlowT", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Move Power to Toughness (-1/+1)"),
parseSequenceString(""),
ShortcutGroup::Power_Toughness)},
{"Player/aIncPT", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Power and Toughness (+1/+1)"),
parseSequenceString("Ctrl+Alt++;Ctrl+Alt+="),
ShortcutGroup::Power_Toughness)},
{"Player/aDecPT", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Power and Toughness (-1/-1)"),
parseSequenceString("Ctrl+Alt+-"),
ShortcutGroup::Power_Toughness)},
{"Player/aSetPT", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Power and Toughness..."),
parseSequenceString("Ctrl+P"),
ShortcutGroup::Power_Toughness)},
{"Player/aResetPT", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Reset Power and Toughness"),
parseSequenceString("Ctrl+Alt+0"),
ShortcutGroup::Power_Toughness)},
{"Player/phase0", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Untap"),
parseSequenceString("F5"),
ShortcutGroup::Game_Phases)},
{"Player/phase1",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Upkeep"), parseSequenceString(""), ShortcutGroup::Game_Phases)},
{"Player/phase2",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Draw"), parseSequenceString("F6"), ShortcutGroup::Game_Phases)},
{"Player/phase3", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "First Main Phase"),
parseSequenceString("F7"),
ShortcutGroup::Game_Phases)},
{"Player/phase4", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Start Combat"),
parseSequenceString("F8"),
ShortcutGroup::Game_Phases)},
{"Player/phase5",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Attack"), parseSequenceString(""), ShortcutGroup::Game_Phases)},
{"Player/phase6",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Block"), parseSequenceString(""), ShortcutGroup::Game_Phases)},
{"Player/phase7",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Damage"), parseSequenceString(""), ShortcutGroup::Game_Phases)},
{"Player/phase8", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "End Combat"),
parseSequenceString(""),
ShortcutGroup::Game_Phases)},
{"Player/phase9", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Second Main Phase"),
parseSequenceString("F9"),
ShortcutGroup::Game_Phases)},
{"Player/phase10",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "End"), parseSequenceString("F10"), ShortcutGroup::Game_Phases)},
{"Player/aNextPhase", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Next Phase"),
parseSequenceString("Ctrl+Space;Tab"),
ShortcutGroup::Game_Phases)},
{"Player/aNextPhaseAction", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Next Phase Action"),
parseSequenceString("Shift+Tab"),
ShortcutGroup::Game_Phases)},
{"Player/aNextTurn", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Next Turn"),
parseSequenceString("Ctrl+Return;Ctrl+Enter"),
ShortcutGroup::Game_Phases)},
{"Player/aHide", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Hide Card in Reveal Window"),
parseSequenceString("Alt+H"),
ShortcutGroup::Playing_Area)},
{"Player/aTap", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Tap / Untap Card"),
parseSequenceString(""),
ShortcutGroup::Playing_Area)},
{"Player/aUntapAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Untap All"),
parseSequenceString("Ctrl+U"),
ShortcutGroup::Playing_Area)},
{"Player/aDoesntUntap", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Untap"),
parseSequenceString("Alt+U"),
ShortcutGroup::Playing_Area)},
{"Player/aFlip", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Turn Card Over"),
parseSequenceString("Alt+F"),
ShortcutGroup::Playing_Area)},
{"Player/aPeek", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Peek Card"),
parseSequenceString("Alt+L"),
ShortcutGroup::Playing_Area)},
{"Player/aPlay", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Play Card"),
parseSequenceString(""),
ShortcutGroup::Playing_Area)},
{"Player/aAttach", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Attach Card..."),
parseSequenceString("Ctrl+Alt+A"),
ShortcutGroup::Playing_Area)},
{"Player/aUnattach", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Unattach Card"),
parseSequenceString("Ctrl+Alt+U"),
ShortcutGroup::Playing_Area)},
{"Player/aClone", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Clone Card"),
parseSequenceString("Ctrl+J"),
ShortcutGroup::Playing_Area)},
{"Player/aCreateToken", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Create Token..."),
parseSequenceString("Ctrl+T"),
ShortcutGroup::Playing_Area)},
{"Player/aCreateRelatedTokens", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Create All Related Tokens"),
parseSequenceString("Ctrl+Shift+T"),
ShortcutGroup::Playing_Area)},
{"Player/aCreateAnotherToken", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Create Another Token"),
parseSequenceString("Ctrl+G"),
ShortcutGroup::Playing_Area)},
{"Player/aSetAnnotation", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Annotation..."),
parseSequenceString("Alt+N"),
ShortcutGroup::Playing_Area)},
{"Player/aSelectAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Select All Cards in Zone"),
parseSequenceString("Ctrl+A"),
ShortcutGroup::Playing_Area)},
{"Player/aSelectRow", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Select All Cards in Row"),
parseSequenceString("Ctrl+Shift+X"),
ShortcutGroup::Playing_Area)},
{"Player/aSelectColumn", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Select All Cards in Column"),
parseSequenceString("Ctrl+Shift+C"),
ShortcutGroup::Playing_Area)},
{"Player/aMoveToBottomLibrary", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Bottom of Library"),
parseSequenceString("Ctrl+B"),
ShortcutGroup::Move_selected)},
{"Player/aMoveToExile", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile"),
parseSequenceString(""),
ShortcutGroup::Move_selected)},
{"Player/aMoveToGraveyard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard"),
parseSequenceString("Ctrl+Del"),
ShortcutGroup::Move_selected)},
{"Player/aMoveToHand",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Hand"), parseSequenceString(""), ShortcutGroup::Move_selected)},
{"Player/aMoveToTopLibrary", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Top of Library"),
parseSequenceString(""),
ShortcutGroup::Move_selected)},
{"Player/aPlayFacedown", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Battlefield, Face Down"),
parseSequenceString(""),
ShortcutGroup::Move_selected)},
{"Player/aPlay", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Battlefield"),
parseSequenceString(""),
ShortcutGroup::Move_selected)},
{"Player/aViewHand",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Hand"), parseSequenceString(""), ShortcutGroup::View)},
{"Player/aSortHand", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Sort Hand"),
parseSequenceString("Ctrl+Shift+H"),
ShortcutGroup::View)},
{"Player/aViewGraveyard",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard"), parseSequenceString("F4"), ShortcutGroup::View)},
{"Player/aViewLibrary",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Library"), parseSequenceString("F3"), ShortcutGroup::View)},
{"Player/aViewRfg",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile"), parseSequenceString(""), ShortcutGroup::View)},
{"Player/aViewSideboard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Sideboard"),
parseSequenceString("Ctrl+F3"),
ShortcutGroup::View)},
{"Player/aViewTopCards", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Top Cards of Library"),
parseSequenceString("Ctrl+W"),
ShortcutGroup::View)},
{"Player/aViewBottomCards", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Bottom Cards of Library"),
parseSequenceString("Ctrl+Shift+W"),
ShortcutGroup::View)},
{"Player/aCloseMostRecentZoneView", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Close Recent View"),
parseSequenceString("Esc"),
ShortcutGroup::View)},
{"Player/aMoveTopToPlay", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Stack"),
parseSequenceString("Ctrl+Y"),
ShortcutGroup::Move_top)},
{"Player/aMoveTopToPlayFaceDown", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Battlefield, Face Down"),
parseSequenceString("Ctrl+Shift+E"),
ShortcutGroup::Move_top)},
{"Player/aMoveTopCardToGraveyard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard"),
parseSequenceString("Alt+Y"),
ShortcutGroup::Move_top)},
{"Player/aMoveTopCardsToGraveyard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard (Multiple)"),
parseSequenceString("Alt+M"),
ShortcutGroup::Move_top)},
{"Player/aMoveTopCardToExile",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile"), parseSequenceString(""), ShortcutGroup::Move_top)},
{"Player/aMoveTopCardsToExile", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile (Multiple)"),
parseSequenceString(""),
ShortcutGroup::Move_top)},
{"Player/aMoveTopCardsUntil", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Stack Until Found"),
parseSequenceString("Ctrl+Shift+Y"),
ShortcutGroup::Move_top)},
{"Player/aMoveTopCardToBottom", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Bottom of Library"),
parseSequenceString(""),
ShortcutGroup::Move_top)},
{"Player/aMoveBottomToPlay",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Stack"), parseSequenceString(""), ShortcutGroup::Move_bottom)},
{"Player/aMoveBottomToPlayFaceDown", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Battlefield, Face Down"),
parseSequenceString(""),
ShortcutGroup::Move_bottom)},
{"Player/aMoveBottomCardToGrave", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard"),
parseSequenceString(""),
ShortcutGroup::Move_bottom)},
{"Player/aMoveBottomCardsToGrave", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard (Multiple)"),
parseSequenceString(""),
ShortcutGroup::Move_bottom)},
{"Player/aMoveBottomCardToExile",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile"), parseSequenceString(""), ShortcutGroup::Move_bottom)},
{"Player/aMoveBottomCardsToExile", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile (Multiple)"),
parseSequenceString(""),
ShortcutGroup::Move_bottom)},
{"Player/aMoveBottomCardToTop", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Top of Library"),
parseSequenceString(""),
ShortcutGroup::Move_bottom)},
{"Player/aDrawBottomCard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Draw Bottom Card"),
parseSequenceString(""),
ShortcutGroup::Move_bottom)},
{"Player/aDrawBottomCards", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Draw Multiple Cards from Bottom..."),
parseSequenceString(""),
ShortcutGroup::Move_bottom)},
{"Player/aDrawArrow", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Draw Arrow..."),
parseSequenceString("Alt+A"),
ShortcutGroup::Gameplay)},
{"Player/aRemoveLocalArrows", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Local Arrows"),
parseSequenceString("Ctrl+R"),
ShortcutGroup::Gameplay)},
{"Player/aLeaveGame", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Leave Game"),
parseSequenceString("Ctrl+Q"),
ShortcutGroup::Gameplay)},
{"Player/aConcede",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Concede"), parseSequenceString("F2"), ShortcutGroup::Gameplay)},
{"Player/aRollDie", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Roll Dice..."),
parseSequenceString("Ctrl+I"),
ShortcutGroup::Gameplay)},
{"Player/aShuffle", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Shuffle Library"),
parseSequenceString("Ctrl+S"),
ShortcutGroup::Gameplay)},
{"Player/aShuffleTopCards", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Shuffle Top Cards of Library"),
parseSequenceString(""),
ShortcutGroup::Gameplay)},
{"Player/aShuffleBottomCards", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Shuffle Bottom Cards of Library"),
parseSequenceString(""),
ShortcutGroup::Gameplay)},
{"Player/aMulligan", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Mulligan"),
parseSequenceString("Ctrl+M"),
ShortcutGroup::Drawing)},
{"Player/aDrawCard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Draw a Card"),
parseSequenceString("Ctrl+D"),
ShortcutGroup::Drawing)},
{"Player/aDrawCards", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Draw Multiple Cards..."),
parseSequenceString("Ctrl+E"),
ShortcutGroup::Drawing)},
{"Player/aUndoDraw", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Undo Draw"),
parseSequenceString("Ctrl+Shift+D"),
ShortcutGroup::Drawing)},
{"Player/aAlwaysRevealTopCard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Always Reveal Top Card"),
parseSequenceString("Ctrl+N"),
ShortcutGroup::Drawing)},
{"Player/aAlwaysLookAtTopCard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Always Look At Top Card"),
parseSequenceString("Ctrl+Shift+N"),
ShortcutGroup::Drawing)},
{"Player/aRotateViewCW", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Rotate View Clockwise"),
parseSequenceString(""),
ShortcutGroup::Gameplay)},
{"Player/aRotateViewCCW", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Rotate View Counterclockwise"),
parseSequenceString(""),
ShortcutGroup::Gameplay)},
{"Player/unfocusTextBox", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Unfocus Text Box"),
parseSequenceString("Esc"),
ShortcutGroup::Chat_room)},
{"Player/aFocusChat", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Focus Chat"),
parseSequenceString("Shift+Return"),
ShortcutGroup::Chat_room)},
{"tab_room/aClearChat", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Clear Chat"),
parseSequenceString("F12"),
ShortcutGroup::Chat_room)},
{"Player/aResetLayout", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Reset Layout"),
parseSequenceString(""),
ShortcutGroup::Game_window)},
{"DlgLoadDeckFromClipboard/refreshButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Refresh"),
parseSequenceString("F5"),
ShortcutGroup::Load_deck)},
{"Replays/aSkipForward", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Skip Forward"),
parseSequenceString("Right"),
ShortcutGroup::Replays)},
{"Replays/aSkipBackward", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Skip Backward"),
parseSequenceString("Left"),
ShortcutGroup::Replays)},
{"Replays/aSkipForwardBig", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Skip Forward by a lot"),
parseSequenceString("Ctrl+Right"),
ShortcutGroup::Replays)},
{"Replays/aSkipBackwardBig", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Skip Backward by a lot"),
parseSequenceString("Ctrl+Left"),
ShortcutGroup::Replays)},
{"Replays/playButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Play/Pause"),
parseSequenceString("Space"),
ShortcutGroup::Replays)},
{"Replays/fastForwardButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Fast Forward"),
parseSequenceString("Ctrl+P"),
ShortcutGroup::Replays)},
{"Tabs/aTabDeckEditor",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Deck Editor"), parseSequenceString(""), ShortcutGroup::Tabs)},
{"Tabs/aTabHome",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Home"), parseSequenceString(""), ShortcutGroup::Tabs)},
{"Tabs/aTabVisualDeckStorage", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Visual Deck Storage"),
parseSequenceString(""),
ShortcutGroup::Tabs)},
{"Tabs/aTabDeckStorage",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Deck Storage"), parseSequenceString(""), ShortcutGroup::Tabs)},
{"Tabs/aTabReplays",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Replays"), parseSequenceString(""), ShortcutGroup::Tabs)},
{"Tabs/aTabServer",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Server"), parseSequenceString(""), ShortcutGroup::Tabs)},
{"Tabs/aTabAccount",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Account"), parseSequenceString(""), ShortcutGroup::Tabs)},
{"Tabs/aTabAdmin", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Administration"),
parseSequenceString(""),
ShortcutGroup::Tabs)},
{"Tabs/aTabLogs",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Logs"), parseSequenceString(""), ShortcutGroup::Tabs)},
};
};
#endif // SHORTCUTSSETTINGS_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,56 @@
#include "../include/settings/card_counter_settings.h"
#include <QColor>
#include <QSettings>
#include <QtMath>
CardCounterSettings::CardCounterSettings(const QString &settingsPath, QObject *parent)
: SettingsManager(settingsPath + "global.ini", parent)
{
}
void CardCounterSettings::setColor(int counterId, const QColor &color)
{
QString key = QString("cards/counters/%1/color").arg(counterId);
if (settings.value(key).value<QColor>() == color)
return;
settings.setValue(key, color);
emit colorChanged(counterId, color);
}
QColor CardCounterSettings::color(int counterId) const
{
QColor defaultColor;
if (counterId < 6) {
// Preserve legacy colors
defaultColor = QColor::fromHsv(counterId * 60, 150, 255);
} else {
// Future-proof support for more counters with pseudo-random colors
int h = (counterId * 37) % 360;
int s = 128 + 64 * qSin((counterId * 97) * 0.1); // 64-192
int v = 196 + 32 * qSin((counterId * 101) * 0.07); // 164-228
defaultColor = QColor::fromHsv(h, s, v);
}
return settings.value(QString("cards/counters/%1/color").arg(counterId), defaultColor).value<QColor>();
}
QString CardCounterSettings::displayName(int counterId) const
{
// Currently, card counters name are fixed to A, B, ..., Z, AA, AB, ...
auto nChars = 1 + counterId / 26;
QString str;
str.resize(nChars);
for (auto it = str.rbegin(); it != str.rend(); ++it) {
*it = QChar('A' + (counterId) % 26);
counterId /= 26;
}
return str;
}

View file

@ -0,0 +1,36 @@
#include "../include/settings/card_database_settings.h"
CardDatabaseSettings::CardDatabaseSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "cardDatabase.ini", parent)
{
}
void CardDatabaseSettings::setSortKey(QString shortName, unsigned int sortKey)
{
setValue(sortKey, "sortkey", "sets", std::move(shortName));
}
void CardDatabaseSettings::setEnabled(QString shortName, bool enabled)
{
setValue(enabled, "enabled", "sets", std::move(shortName));
}
void CardDatabaseSettings::setIsKnown(QString shortName, bool isknown)
{
setValue(isknown, "isknown", "sets", std::move(shortName));
}
unsigned int CardDatabaseSettings::getSortKey(QString shortName)
{
return getValue("sortkey", "sets", std::move(shortName)).toUInt();
}
bool CardDatabaseSettings::isEnabled(QString shortName)
{
return getValue("enabled", "sets", std::move(shortName)).toBool();
}
bool CardDatabaseSettings::isKnown(QString shortName)
{
return getValue("isknown", "sets", std::move(shortName)).toBool();
}

View file

@ -0,0 +1,21 @@
#include "../include/settings/card_override_settings.h"
CardOverrideSettings::CardOverrideSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "cardPreferenceOverrides.ini", parent)
{
}
void CardOverrideSettings::setCardPreferenceOverride(const CardRef &cardRef)
{
setValue(cardRef.providerId, cardRef.name, "cards");
}
void CardOverrideSettings::deleteCardPreferenceOverride(const QString &cardName)
{
deleteValue(cardName, "cards");
}
QString CardOverrideSettings::getCardPreferenceOverride(const QString &cardName)
{
return getValue(cardName, "cards").toString();
}

View file

@ -0,0 +1,32 @@
#include "../include/settings/debug_settings.h"
#include <QtCore/QFile>
DebugSettings::DebugSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "debug.ini", parent)
{
// Create the default debug.ini if it doesn't exist yet
if (!QFile(settingPath + "debug.ini").exists()) {
QFile::copy(":/resources/config/debug.ini", settingPath + "debug.ini");
}
}
bool DebugSettings::getShowCardId()
{
return getValue("showCardId", "debug").toBool();
}
bool DebugSettings::getLocalGameOnStartup()
{
return getValue("onStartup", "localgame").toBool();
}
int DebugSettings::getLocalGamePlayerCount()
{
return getValue("playerCount", "localgame").toInt();
}
QString DebugSettings::getDeckPathForPlayer(const QString &playerName)
{
return getValue(playerName, "localgame", "deck").toString();
}

View file

@ -0,0 +1,29 @@
#include "../include/settings/download_settings.h"
#include "../include/settings/settings_manager.h"
const QStringList DownloadSettings::DEFAULT_DOWNLOAD_URLS = {
"https://api.scryfall.com/cards/!set:uuid!?format=image&face=!prop:side!",
"https://api.scryfall.com/cards/multiverse/!set:muid!?format=image",
"https://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!set:muid!&type=card",
"https://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"};
DownloadSettings::DownloadSettings(const QString &settingPath, QObject *parent = nullptr)
: SettingsManager(settingPath + "downloads.ini", parent)
{
}
void DownloadSettings::setDownloadUrls(const QStringList &downloadURLs)
{
setValue(QVariant::fromValue(downloadURLs), "urls", "downloads");
}
QStringList DownloadSettings::getAllURLs()
{
return getValue("urls", "downloads").toStringList();
}
void DownloadSettings::resetToDefaultURLs()
{
setValue(QVariant::fromValue(DEFAULT_DOWNLOAD_URLS), "urls", "downloads");
}

View file

@ -0,0 +1,208 @@
#include "../include/settings/game_filters_settings.h"
#include <QCryptographicHash>
#include <QTime>
GameFiltersSettings::GameFiltersSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "gamefilters.ini", parent)
{
}
/*
* The game type might contain special characters, so to use it in
* QSettings we just hash it.
*/
QString GameFiltersSettings::hashGameType(const QString &gameType) const
{
return QCryptographicHash::hash(gameType.toUtf8(), QCryptographicHash::Md5).toHex();
}
void GameFiltersSettings::setHideBuddiesOnlyGames(bool hide)
{
setValue(hide, "hide_buddies_only_games", "filter_games");
}
bool GameFiltersSettings::isHideBuddiesOnlyGames()
{
QVariant previous = getValue("hide_buddies_only_games", "filter_games");
return previous == QVariant() || previous.toBool();
}
void GameFiltersSettings::setHideFullGames(bool hide)
{
setValue(hide, "hide_full_games", "filter_games");
}
bool GameFiltersSettings::isHideFullGames()
{
QVariant previous = getValue("hide_full_games", "filter_games");
return !(previous == QVariant()) && previous.toBool();
}
void GameFiltersSettings::setHideGamesThatStarted(bool hide)
{
setValue(hide, "hide_games_that_started", "filter_games");
}
bool GameFiltersSettings::isHideGamesThatStarted()
{
QVariant previous = getValue("hide_games_that_started", "filter_games");
return !(previous == QVariant()) && previous.toBool();
}
void GameFiltersSettings::setHidePasswordProtectedGames(bool hide)
{
setValue(hide, "hide_password_protected_games", "filter_games");
}
bool GameFiltersSettings::isHidePasswordProtectedGames()
{
QVariant previous = getValue("hide_password_protected_games", "filter_games");
return previous == QVariant() || previous.toBool();
}
void GameFiltersSettings::setHideIgnoredUserGames(bool hide)
{
setValue(hide, "hide_ignored_user_games", "filter_games");
}
bool GameFiltersSettings::isHideIgnoredUserGames()
{
QVariant previous = getValue("hide_ignored_user_games", "filter_games");
return !(previous == QVariant()) && previous.toBool();
}
void GameFiltersSettings::setHideNotBuddyCreatedGames(bool hide)
{
setValue(hide, "hide_not_buddy_created_games", "filter_games");
}
bool GameFiltersSettings::isHideNotBuddyCreatedGames()
{
QVariant previous = getValue("hide_not_buddy_created_games", "filter_games");
return !(previous == QVariant()) && previous.toBool();
}
void GameFiltersSettings::setHideOpenDecklistGames(bool hide)
{
setValue(hide, "hide_open_decklist_games", "filter_games");
}
bool GameFiltersSettings::isHideOpenDecklistGames()
{
QVariant previous = getValue("hide_open_decklist_games", "filter_games");
return !(previous == QVariant()) && previous.toBool();
}
void GameFiltersSettings::setGameNameFilter(QString gameName)
{
setValue(gameName, "game_name_filter", "filter_games");
}
QString GameFiltersSettings::getGameNameFilter()
{
return getValue("game_name_filter", "filter_games").toString();
}
void GameFiltersSettings::setCreatorNameFilter(QString creatorName)
{
setValue(creatorName, "creator_name_filter", "filter_games");
}
QString GameFiltersSettings::getCreatorNameFilter()
{
return getValue("creator_name_filter", "filter_games").toString();
}
void GameFiltersSettings::setMinPlayers(int min)
{
setValue(min, "min_players", "filter_games");
}
int GameFiltersSettings::getMinPlayers()
{
QVariant previous = getValue("min_players", "filter_games");
return previous == QVariant() ? 1 : previous.toInt();
}
void GameFiltersSettings::setMaxPlayers(int max)
{
setValue(max, "max_players", "filter_games");
}
int GameFiltersSettings::getMaxPlayers()
{
QVariant previous = getValue("max_players", "filter_games");
return previous == QVariant() ? 99 : previous.toInt();
}
void GameFiltersSettings::setMaxGameAge(const QTime &maxGameAge)
{
setValue(maxGameAge, "max_game_age_time", "filter_games");
}
QTime GameFiltersSettings::getMaxGameAge()
{
QVariant previous = getValue("max_game_age_time", "filter_games");
return previous.toTime();
}
void GameFiltersSettings::setGameTypeEnabled(QString gametype, bool enabled)
{
setValue(enabled, "game_type/" + hashGameType(gametype), "filter_games");
}
void GameFiltersSettings::setGameHashedTypeEnabled(QString gametypeHASHED, bool enabled)
{
setValue(enabled, gametypeHASHED, "filter_games");
}
bool GameFiltersSettings::isGameTypeEnabled(QString gametype)
{
QVariant previous = getValue("game_type/" + hashGameType(gametype), "filter_games");
return previous == QVariant() ? false : previous.toBool();
}
void GameFiltersSettings::setShowOnlyIfSpectatorsCanWatch(bool show)
{
setValue(show, "show_only_if_spectators_can_watch", "filter_games");
}
bool GameFiltersSettings::isShowOnlyIfSpectatorsCanWatch()
{
QVariant previous = getValue("show_only_if_spectators_can_watch", "filter_games");
return previous == QVariant() ? false : previous.toBool();
}
void GameFiltersSettings::setShowSpectatorPasswordProtected(bool show)
{
setValue(show, "show_spectator_password_protected", "filter_games");
}
bool GameFiltersSettings::isShowSpectatorPasswordProtected()
{
QVariant previous = getValue("show_spectator_password_protected", "filter_games");
return previous == QVariant() ? true : previous.toBool();
}
void GameFiltersSettings::setShowOnlyIfSpectatorsCanChat(bool show)
{
setValue(show, "show_only_if_spectators_can_chat", "filter_games");
}
bool GameFiltersSettings::isShowOnlyIfSpectatorsCanChat()
{
QVariant previous = getValue("show_only_if_spectators_can_chat", "filter_games");
return previous == QVariant() ? true : previous.toBool();
}
void GameFiltersSettings::setShowOnlyIfSpectatorsCanSeeHands(bool show)
{
setValue(show, "show_only_if_spectators_can_see_hands", "filter_games");
}
bool GameFiltersSettings::isShowOnlyIfSpectatorsCanSeeHands()
{
QVariant previous = getValue("show_only_if_spectators_can_see_hands", "filter_games");
return previous == QVariant() ? true : previous.toBool();
}

View file

@ -0,0 +1,207 @@
#include "../include/settings/layouts_settings.h"
LayoutsSettings::LayoutsSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "layouts.ini", parent)
{
}
const QByteArray LayoutsSettings::getDeckEditorLayoutState()
{
return getValue("layouts/deckEditor_state").toByteArray();
}
void LayoutsSettings::setDeckEditorLayoutState(const QByteArray &value)
{
setValue(value, "layouts/deckEditor_state");
}
const QByteArray LayoutsSettings::getDeckEditorGeometry()
{
return getValue("layouts/deckEditor_geometry").toByteArray();
}
void LayoutsSettings::setDeckEditorGeometry(const QByteArray &value)
{
setValue(value, "layouts/deckEditor_geometry");
}
QSize LayoutsSettings::getDeckEditorCardSize()
{
QVariant previous = getValue("layouts/deckEditor_CardSize");
return previous == QVariant() ? QSize(250, 500) : previous.toSize();
}
void LayoutsSettings::setDeckEditorCardSize(const QSize &value)
{
setValue(value, "layouts/deckEditor_CardSize");
}
QSize LayoutsSettings::getDeckEditorDeckSize()
{
QVariant previous = getValue("layouts/deckEditor_DeckSize");
return previous == QVariant() ? QSize(250, 360) : previous.toSize();
}
void LayoutsSettings::setDeckEditorDeckSize(const QSize &value)
{
setValue(value, "layouts/deckEditor_DeckSize");
}
QSize LayoutsSettings::getDeckEditorPrintingSelectorSize()
{
QVariant previous = getValue("layouts/deckEditor_PrintingSelectorSize");
return previous == QVariant() ? QSize(525, 250) : previous.toSize();
}
void LayoutsSettings::setDeckEditorPrintingSelectorSize(const QSize &value)
{
setValue(value, "layouts/deckEditor_PrintingSelectorSize");
}
QSize LayoutsSettings::getDeckEditorFilterSize()
{
QVariant previous = getValue("layouts/deckEditor_FilterSize");
return previous == QVariant() ? QSize(250, 250) : previous.toSize();
}
void LayoutsSettings::setDeckEditorFilterSize(const QSize &value)
{
setValue(value, "layouts/deckEditor_FilterSize");
}
const QByteArray LayoutsSettings::getDeckEditorDbHeaderState()
{
return getValue("layouts/deckEditorDbHeader_state").toByteArray();
}
void LayoutsSettings::setDeckEditorDbHeaderState(const QByteArray &value)
{
setValue(value, "layouts/deckEditorDbHeader_state");
}
const QByteArray LayoutsSettings::getSetsDialogHeaderState()
{
return getValue("layouts/setsDialogHeader_state").toByteArray();
}
void LayoutsSettings::setSetsDialogHeaderState(const QByteArray &value)
{
setValue(value, "layouts/setsDialogHeader_state");
}
void LayoutsSettings::setGamePlayAreaGeometry(const QByteArray &value)
{
setValue(value, "layouts/gameplayarea_geometry");
}
void LayoutsSettings::setGamePlayAreaState(const QByteArray &value)
{
setValue(value, "layouts/gameplayarea_state");
}
const QByteArray LayoutsSettings::getGamePlayAreaLayoutState()
{
return getValue("layouts/gameplayarea_state").toByteArray();
}
const QByteArray LayoutsSettings::getGamePlayAreaGeometry()
{
return getValue("layouts/gameplayarea_geometry").toByteArray();
}
const QSize LayoutsSettings::getGameCardInfoSize()
{
QVariant previous = getValue("layouts/gameplayarea_CardInfoSize");
return previous == QVariant() ? QSize(250, 360) : previous.toSize();
}
void LayoutsSettings::setGameCardInfoSize(const QSize &value)
{
setValue(value, "layouts/gameplayarea_CardInfoSize");
}
const QSize LayoutsSettings::getGameMessageLayoutSize()
{
QVariant previous = getValue("layouts/gameplayarea_MessageLayoutSize");
return previous == QVariant() ? QSize(250, 250) : previous.toSize();
}
void LayoutsSettings::setGameMessageLayoutSize(const QSize &value)
{
setValue(value, "layouts/gameplayarea_MessageLayoutSize");
}
const QSize LayoutsSettings::getGamePlayerListSize()
{
QVariant previous = getValue("layouts/gameplayarea_PlayerListSize");
return previous == QVariant() ? QSize(250, 50) : previous.toSize();
}
void LayoutsSettings::setGamePlayerListSize(const QSize &value)
{
setValue(value, "layouts/gameplayarea_PlayerListSize");
}
void LayoutsSettings::setReplayPlayAreaGeometry(const QByteArray &value)
{
setValue(value, "layouts/replayplayarea_geometry");
}
void LayoutsSettings::setReplayPlayAreaState(const QByteArray &value)
{
setValue(value, "layouts/replayplayarea_state");
}
const QByteArray LayoutsSettings::getReplayPlayAreaLayoutState()
{
return getValue("layouts/replayplayarea_state").toByteArray();
}
const QByteArray LayoutsSettings::getReplayPlayAreaGeometry()
{
return getValue("layouts/replayplayarea_geometry").toByteArray();
}
const QSize LayoutsSettings::getReplayCardInfoSize()
{
QVariant previous = getValue("layouts/replayplayarea_CardInfoSize");
return previous == QVariant() ? QSize(250, 360) : previous.toSize();
}
void LayoutsSettings::setReplayCardInfoSize(const QSize &value)
{
setValue(value, "layouts/replayplayarea_CardInfoSize");
}
const QSize LayoutsSettings::getReplayMessageLayoutSize()
{
QVariant previous = getValue("layouts/replayplayarea_MessageLayoutSize");
return previous == QVariant() ? QSize(250, 200) : previous.toSize();
}
void LayoutsSettings::setReplayMessageLayoutSize(const QSize &value)
{
setValue(value, "layouts/replayplayarea_MessageLayoutSize");
}
const QSize LayoutsSettings::getReplayPlayerListSize()
{
QVariant previous = getValue("layouts/replayplayarea_PlayerListSize");
return previous == QVariant() ? QSize(250, 50) : previous.toSize();
}
void LayoutsSettings::setReplayPlayerListSize(const QSize &value)
{
setValue(value, "layouts/replayplayarea_PlayerListSize");
}
const QSize LayoutsSettings::getReplayReplaySize()
{
QVariant previous = getValue("layouts/replayplayarea_ReplaySize");
return previous == QVariant() ? QSize(900, 100) : previous.toSize();
}
void LayoutsSettings::setReplayReplaySize(const QSize &value)
{
setValue(value, "layouts/replayplayarea_ReplaySize");
}

View file

@ -0,0 +1,26 @@
#include "../include/settings/message_settings.h"
MessageSettings::MessageSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "messages.ini", parent)
{
}
QString MessageSettings::getMessageAt(int index)
{
return getValue(QString("msg%1").arg(index), "messages").toString();
}
int MessageSettings::getCount()
{
return getValue("count", "messages").toInt();
}
void MessageSettings::setCount(int count)
{
setValue(count, "count", "messages");
}
void MessageSettings::setMessageAt(int index, QString message)
{
setValue(message, QString("msg%1").arg(index), "messages");
}

View file

@ -0,0 +1,42 @@
#include "../include/settings/recents_settings.h"
#define MAX_RECENT_DECK_COUNT 10
RecentsSettings::RecentsSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "recents.ini", parent)
{
}
QStringList RecentsSettings::getRecentlyOpenedDeckPaths()
{
return getValue("deckpaths", "deckbuilder").toStringList();
}
void RecentsSettings::clearRecentlyOpenedDeckPaths()
{
deleteValue("deckpaths", "deckbuilder");
emit recentlyOpenedDeckPathsChanged();
}
void RecentsSettings::updateRecentlyOpenedDeckPaths(const QString &deckPath)
{
auto deckPaths = getValue("deckpaths", "deckbuilder").toStringList();
deckPaths.removeAll(deckPath);
deckPaths.prepend(deckPath);
while (deckPaths.size() > MAX_RECENT_DECK_COUNT) {
deckPaths.removeLast();
}
setValue(deckPaths, "deckpaths", "deckbuilder");
emit recentlyOpenedDeckPathsChanged();
}
QString RecentsSettings::getLatestDeckDirPath()
{
return getValue("latestDeckDir", "dirs").toString();
}
void RecentsSettings::setLatestDeckDirPath(const QString &dirPath)
{
setValue(dirPath, "latestDeckDir", "dirs");
}

View file

@ -0,0 +1,291 @@
#include "../include/settings/servers_settings.h"
#include <QDebug>
#include <utility>
ServersSettings::ServersSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "servers.ini", parent)
{
}
void ServersSettings::setPreviousHostLogin(int previous)
{
setValue(previous, "previoushostlogin", "server");
}
int ServersSettings::getPreviousHostLogin()
{
QVariant previous = getValue("previoushostlogin", "server");
return previous == QVariant() ? 1 : previous.toInt();
}
void ServersSettings::setPreviousHostList(QStringList list)
{
setValue(list, "previoushosts", "server");
}
QStringList ServersSettings::getPreviousHostList()
{
return getValue("previoushosts", "server").toStringList();
}
void ServersSettings::setPrevioushostName(const QString &name)
{
setValue(name, "previoushostName", "server");
}
QString ServersSettings::getSaveName(QString defaultname)
{
int index = getPrevioushostindex(getPrevioushostName());
QVariant saveName = getValue(QString("saveName%1").arg(index), "server", "server_details");
return saveName == QVariant() ? std::move(defaultname) : saveName.toString();
}
QString ServersSettings::getSite(QString defaultSite)
{
int index = getPrevioushostindex(getPrevioushostName());
QVariant site = getValue(QString("site%1").arg(index), "server", "server_details");
return site == QVariant() ? std::move(defaultSite) : site.toString();
}
QString ServersSettings::getPrevioushostName()
{
QVariant value = getValue("previoushostName", "server");
return value == QVariant() ? "Rooster Ranges" : value.toString();
}
int ServersSettings::getPrevioushostindex(const QString &saveName)
{
int size = getValue("totalServers", "server", "server_details").toInt();
for (int i = 0; i <= size; ++i)
if (saveName == getValue(QString("saveName%1").arg(i), "server", "server_details").toString())
return i;
return -1;
}
QString ServersSettings::getHostname(QString defaultHost)
{
int index = getPrevioushostindex(getPrevioushostName());
QVariant hostname = getValue(QString("server%1").arg(index), "server", "server_details");
return hostname == QVariant() ? std::move(defaultHost) : hostname.toString();
}
QString ServersSettings::getPort(QString defaultPort)
{
int index = getPrevioushostindex(getPrevioushostName());
QVariant port = getValue(QString("port%1").arg(index), "server", "server_details");
qCDebug(ServersSettingsLog) << "getPort() index = " << index << " port.val = " << port.toString();
return port == QVariant() ? std::move(defaultPort) : port.toString();
}
QString ServersSettings::getPlayerName(QString defaultName)
{
int index = getPrevioushostindex(getPrevioushostName());
QVariant name = getValue(QString("username%1").arg(index), "server", "server_details");
qCDebug(ServersSettingsLog) << "getPlayerName() index = " << index << " name.val = " << name.toString();
return name == QVariant() ? std::move(defaultName) : name.toString();
}
QString ServersSettings::getPassword()
{
int index = getPrevioushostindex(getPrevioushostName());
if (getSavePassword())
return getValue(QString("password%1").arg(index), "server", "server_details").toString();
return QString();
}
bool ServersSettings::getSavePassword()
{
int index = getPrevioushostindex(getPrevioushostName());
bool save = getValue(QString("savePassword%1").arg(index), "server", "server_details").toBool();
return save;
}
void ServersSettings::setAutoConnect(int autoconnect)
{
setValue(autoconnect, "auto_connect", "server");
}
int ServersSettings::getAutoConnect()
{
QVariant autoconnect = getValue("auto_connect", "server");
return autoconnect == QVariant() ? 0 : autoconnect.toInt();
}
void ServersSettings::setFPHostName(QString hostname)
{
setValue(hostname, "fphostname", "server");
}
QString ServersSettings::getFPHostname(QString defaultHost)
{
QVariant hostname = getValue("fphostname", "server");
return hostname == QVariant() ? std::move(defaultHost) : hostname.toString();
}
void ServersSettings::setFPPort(QString port)
{
setValue(port, "fpport", "server");
}
QString ServersSettings::getFPPort(QString defaultPort)
{
QVariant port = getValue("fpport", "server");
return port == QVariant() ? std::move(defaultPort) : port.toString();
}
void ServersSettings::setFPPlayerName(QString playerName)
{
setValue(playerName, "fpplayername", "server");
}
QString ServersSettings::getFPPlayerName(QString defaultName)
{
QVariant name = getValue("fpplayername", "server");
return name == QVariant() ? std::move(defaultName) : name.toString();
}
void ServersSettings::setClearDebugLogStatus(bool abIsChecked)
{
setValue(abIsChecked, "save_debug_log", "server");
}
bool ServersSettings::getClearDebugLogStatus(bool abDefaultValue)
{
QVariant cbFlushLog = getValue("save_debug_log", "server");
return cbFlushLog == QVariant() ? abDefaultValue : cbFlushLog.toBool();
}
void ServersSettings::addNewServer(const QString &saveName,
const QString &serv,
const QString &port,
const QString &username,
const QString &password,
bool savePassword,
const QString &site)
{
if (updateExistingServer(saveName, serv, port, username, password, savePassword, site))
return;
int index = getValue("totalServers", "server", "server_details").toInt() + 1;
setValue(saveName, QString("saveName%1").arg(index), "server", "server_details");
setValue(serv, QString("server%1").arg(index), "server", "server_details");
setValue(port, QString("port%1").arg(index), "server", "server_details");
setValue(username, QString("username%1").arg(index), "server", "server_details");
setValue(savePassword, QString("savePassword%1").arg(index), "server", "server_details");
setValue(index, "totalServers", "server", "server_details");
setValue(password, QString("password%1").arg(index), "server", "server_details");
setValue(site, QString("site%1").arg(index), "server", "server_details");
}
void ServersSettings::removeServer(QString servAddr)
{
int size = getValue("totalServers", "server", "server_details").toInt();
bool found = false;
for (int i = 0; i <= size; ++i) {
if (!found) {
// find entry and overwrite it
if (servAddr == getValue(QString("server%1").arg(i), "server", "server_details").toString()) {
found = true;
}
} else {
// move all other entries after it one back, overwriting the previous one
int previous = i - 1; // we delete only one entry
setValue(getValue(QString("server%1").arg(i), "server", "server_details"),
QString("server%1").arg(previous), "server", "server_details");
setValue(getValue(QString("port%1").arg(i), "server", "server_details"), QString("port%1").arg(previous),
"server", "server_details");
setValue(getValue(QString("username%1").arg(i), "server", "server_details"),
QString("username%1").arg(previous), "server", "server_details");
setValue(getValue(QString("savePassword%1").arg(i), "server", "server_details"),
QString("savePassword%1").arg(previous), "server", "server_details");
setValue(getValue(QString("password%1").arg(i), "server", "server_details"),
QString("password%1").arg(previous), "server", "server_details");
setValue(getValue(QString("saveName%1").arg(i), "server", "server_details"),
QString("saveName%1").arg(previous), "server", "server_details");
setValue(getValue(QString("site%1").arg(i), "server", "server_details"), QString("site%1").arg(previous),
"server", "server_details");
}
}
// if we have deleted an entry, adjust the total
if (found) {
setValue(size - 1, "totalServers", "server", "server_details");
// delete last value
deleteValue(QString("server%1").arg(size), "server", "server_details");
deleteValue(QString("port%1").arg(size), "server", "server_details");
deleteValue(QString("username%1").arg(size), "server", "server_details");
deleteValue(QString("savePassword%1").arg(size), "server", "server_details");
deleteValue(QString("password%1").arg(size), "server", "server_details");
deleteValue(QString("saveName%1").arg(size), "server", "server_details");
deleteValue(QString("site%1").arg(size), "server", "server_details");
}
}
/**
* Will only update fields with new values, ignores empty values
*/
bool ServersSettings::updateExistingServerWithoutLoss(QString saveName, QString serv, QString port, QString site)
{
int size = getValue("totalServers", "server", "server_details").toInt();
for (int i = 0; i <= size; ++i) {
if (serv == getValue(QString("server%1").arg(i), "server", "server_details").toString()) {
if (!port.isEmpty()) {
setValue(port, QString("port%1").arg(i), "server", "server_details");
}
if (!site.isEmpty()) {
setValue(site, QString("site%1").arg(i), "server", "server_details");
}
setValue(saveName, QString("saveName%1").arg(i), "server", "server_details");
return true;
}
}
return false;
}
bool ServersSettings::updateExistingServer(QString saveName,
QString serv,
QString port,
QString username,
QString password,
bool savePassword,
QString site)
{
int size = getValue("totalServers", "server", "server_details").toInt();
for (int i = 0; i <= size; ++i) {
if (serv == getValue(QString("server%1").arg(i), "server", "server_details").toString()) {
setValue(port, QString("port%1").arg(i), "server", "server_details");
if (!username.isEmpty()) {
setValue(username, QString("username%1").arg(i), "server", "server_details");
}
if (savePassword && !password.isEmpty()) {
setValue(password, QString("password%1").arg(i), "server", "server_details");
} else {
setValue(QString(), QString("password%1").arg(i), "server", "server_details");
}
if (!site.isEmpty()) {
setValue(site, QString("site%1").arg(i), "server", "server_details");
}
setValue(savePassword, QString("savePassword%1").arg(i), "server", "server_details");
setValue(saveName, QString("saveName%1").arg(i), "server", "server_details");
return true;
}
}
return false;
}

View file

@ -0,0 +1,82 @@
#include "../include/settings/settings_manager.h"
SettingsManager::SettingsManager(const QString &settingPath, QObject *parent)
: QObject(parent), settings(settingPath, QSettings::IniFormat)
{
}
void SettingsManager::setValue(const QVariant &value,
const QString &name,
const QString &group,
const QString &subGroup)
{
if (!group.isEmpty()) {
settings.beginGroup(group);
}
if (!subGroup.isEmpty()) {
settings.beginGroup(subGroup);
}
settings.setValue(name, value);
if (!subGroup.isEmpty()) {
settings.endGroup();
}
if (!group.isEmpty()) {
settings.endGroup();
}
}
void SettingsManager::deleteValue(const QString &name, const QString &group, const QString &subGroup)
{
if (!group.isEmpty()) {
settings.beginGroup(group);
}
if (!subGroup.isEmpty()) {
settings.beginGroup(subGroup);
}
settings.remove(name);
if (!subGroup.isEmpty()) {
settings.endGroup();
}
if (!group.isEmpty()) {
settings.endGroup();
}
}
QVariant SettingsManager::getValue(const QString &name, const QString &group, const QString &subGroup)
{
if (!group.isEmpty()) {
settings.beginGroup(group);
}
if (!subGroup.isEmpty()) {
settings.beginGroup(subGroup);
}
QVariant value = settings.value(name);
if (!subGroup.isEmpty()) {
settings.endGroup();
}
if (!group.isEmpty()) {
settings.endGroup();
}
return value;
}
/**
* Calls sync on the underlying QSettings object
*/
void SettingsManager::sync()
{
settings.sync();
}

View file

@ -0,0 +1,167 @@
#include "../include/settings/shortcut_treeview.h"
#include "../include/settings/cache_settings.h"
#include "../include/settings/shortcuts_settings.h"
#include <QHeaderView>
ShortcutFilterProxyModel::ShortcutFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
{
}
/**
* Appends the parent and source row together before doing the regex match.
*/
bool ShortcutFilterProxyModel::filterAcceptsRow(const int sourceRow, const QModelIndex &sourceParent) const
{
QModelIndex nameIndex = sourceModel()->index(sourceRow, filterKeyColumn(), sourceParent);
QModelIndex parentIndex = sourceModel()->index(sourceParent.row(), filterKeyColumn(), sourceParent.parent());
QString name = sourceModel()->data(nameIndex).toString();
QString parentName = sourceModel()->data(parentIndex).toString();
QString searchedString = parentName + " " + name;
return searchedString.contains(filterRegularExpression());
}
ShortcutTreeView::ShortcutTreeView(QWidget *parent) : QTreeView(parent)
{
// model
shortcutsModel = new QStandardItemModel(this);
shortcutsModel->setColumnCount(3);
populateShortcutsModel();
// filter proxy
proxyModel = new ShortcutFilterProxyModel(this);
proxyModel->setRecursiveFilteringEnabled(true);
proxyModel->setSourceModel(shortcutsModel);
proxyModel->setDynamicSortFilter(true);
proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
proxyModel->setFilterKeyColumn(0);
QTreeView::setModel(proxyModel);
// treeview
hideColumn(2);
setUniformRowHeights(true);
setAlternatingRowColors(true);
setSortingEnabled(true);
proxyModel->sort(0, Qt::AscendingOrder);
header()->setSectionResizeMode(QHeaderView::Interactive);
header()->setSortIndicator(0, Qt::AscendingOrder);
setSelectionMode(SingleSelection);
setSelectionBehavior(SelectRows);
expandAll();
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&ShortcutTreeView::refreshShortcuts);
}
void ShortcutTreeView::populateShortcutsModel()
{
QHash<QString, QStandardItem *> parentItems;
QStandardItem *curParent = nullptr;
for (const auto &key : SettingsCache::instance().shortcuts().getAllShortcutKeys()) {
QString name = SettingsCache::instance().shortcuts().getShortcut(key).getName();
QString group = SettingsCache::instance().shortcuts().getShortcut(key).getGroupName();
QString shortcut = SettingsCache::instance().shortcuts().getShortcutString(key);
if (parentItems.contains(group)) {
curParent = parentItems.value(group);
} else {
curParent = new QStandardItem(group);
static QFont font = curParent->font();
font.setBold(true);
curParent->setFont(font);
parentItems.insert(group, curParent);
}
QList<QStandardItem *> list = {};
list << new QStandardItem(name) << new QStandardItem(shortcut) << new QStandardItem(key);
curParent->appendRow(list);
}
for (const auto &parent : parentItems) {
shortcutsModel->appendRow(parent);
}
}
void ShortcutTreeView::retranslateUi()
{
shortcutsModel->setHeaderData(0, Qt::Horizontal, tr("Action"));
shortcutsModel->setHeaderData(1, Qt::Horizontal, tr("Shortcut"));
refreshShortcuts();
}
/**
* Loops over the model and reloads all rows
*/
static void loopOverModel(QAbstractItemModel *model, const QModelIndex &parent = QModelIndex())
{
for (int r = 0; r < model->rowCount(parent); ++r) {
const auto friendlyNameIndex = model->index(r, 0, parent);
if (model->hasChildren(friendlyNameIndex)) {
const auto childIndex = model->index(0, 2, friendlyNameIndex);
const auto key = model->data(childIndex).toString();
const auto shortcutGroupName = SettingsCache::instance().shortcuts().getShortcut(key).getGroupName();
model->setData(friendlyNameIndex, shortcutGroupName);
loopOverModel(model, friendlyNameIndex);
} else {
const auto shortcutSequenceIndex = model->index(r, 1, parent);
const auto keyIndex = model->index(r, 2, parent);
const auto key = model->data(keyIndex).toString();
const auto shortcutKey = SettingsCache::instance().shortcuts().getShortcut(key).getName();
const auto shortcutSequence = SettingsCache::instance().shortcuts().getShortcutString(key);
model->setData(friendlyNameIndex, shortcutKey);
model->setData(shortcutSequenceIndex, shortcutSequence);
}
}
}
void ShortcutTreeView::refreshShortcuts()
{
loopOverModel(shortcutsModel);
}
void ShortcutTreeView::currentChanged(const QModelIndex &current, const QModelIndex & /* previous */)
{
QTreeView::scrollTo(current, QAbstractItemView::EnsureVisible);
if (current.parent().isValid()) {
auto shortcutName = model()->data(model()->index(current.row(), 2, current.parent())).toString();
emit currentItemChanged(shortcutName);
} else {
// emit empty string if the selection is a category header
emit currentItemChanged("");
}
}
/**
* The search string is split by word.
* A String is a match as long as it contains all the words in the search string in order
*/
void ShortcutTreeView::updateSearchString(const QString &searchString)
{
#if QT_VERSION > QT_VERSION_CHECK(5, 14, 0)
const auto skipEmptyParts = Qt::SkipEmptyParts;
#else
const auto skipEmptyParts = QString::SkipEmptyParts;
#endif
QStringList searchWords = searchString.split(" ", skipEmptyParts);
auto escapeRegex = [](const QString &s) { return QRegularExpression::escape(s); };
std::transform(searchWords.begin(), searchWords.end(), searchWords.begin(), escapeRegex);
auto regex = QRegularExpression(searchWords.join(".*"), QRegularExpression::CaseInsensitiveOption);
proxyModel->setFilterRegularExpression(regex);
expandAll();
}

View file

@ -0,0 +1,263 @@
#include "../include/settings/shortcuts_settings.h"
#include <QDebug>
#include <QFile>
#include <QMessageBox>
#include <QStringList>
#include <utility>
ShortcutKey::ShortcutKey(const QString &_name, QList<QKeySequence> _sequence, ShortcutGroup::Groups _group)
: QList<QKeySequence>(_sequence), name(_name), group(_group)
{
}
ShortcutsSettings::ShortcutsSettings(const QString &settingsPath, QObject *parent) : QObject(parent)
{
shortCuts = defaultShortCuts;
settingsFilePath = settingsPath;
settingsFilePath.append("shortcuts.ini");
bool exists = QFile(settingsFilePath).exists();
QSettings shortCutsFile(settingsFilePath, QSettings::IniFormat);
if (exists) {
shortCutsFile.beginGroup(custom);
const QStringList customKeys = shortCutsFile.allKeys();
QMap<QString, QString> invalidItems;
for (QStringList::const_iterator it = customKeys.constBegin(); it != customKeys.constEnd(); ++it) {
QString stringSequence = shortCutsFile.value(*it).toString();
// check whether shortcut name exists
if (!shortCuts.contains(*it)) {
qCWarning(ShortcutsSettingsLog) << "Unknown shortcut name:" << *it;
continue;
}
// check whether shortcut is forbidden
if (isKeyAllowed(*it, stringSequence)) {
auto shortcut = getShortcut(*it);
shortcut.setSequence(parseSequenceString(stringSequence));
shortCuts.insert(*it, shortcut);
} else {
invalidItems.insert(*it, stringSequence);
}
}
shortCutsFile.endGroup();
if (!invalidItems.isEmpty()) {
// warning message in case of invalid items
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Warning);
msgBox.setText(tr("Your configuration file contained invalid shortcuts.\n"
"Please check your shortcut settings!"));
QString detailedMessage = tr("The following shortcuts have been set to default:\n");
for (QMap<QString, QString>::const_iterator item = invalidItems.constBegin();
item != invalidItems.constEnd(); ++item) {
detailedMessage += item.key() + " - \"" + item.value() + "\"\n";
}
msgBox.setDetailedText(detailedMessage);
msgBox.exec();
}
}
}
/// PR 5079 changes Textbox/unfocusTextBox to Player/unfocusTextBox and tab_game/aFocusChat to Player/aFocusChat.
/// A migration is necessary to let players keep their already configured shortcuts.
void ShortcutsSettings::migrateShortcuts()
{
if (QFile(settingsFilePath).exists()) {
QSettings shortCutsFile(settingsFilePath, QSettings::IniFormat);
shortCutsFile.beginGroup(custom);
if (shortCutsFile.contains("Textbox/unfocusTextBox")) {
qCInfo(ShortcutsSettingsLog)
<< "[ShortcutsSettings] Textbox/unfocusTextBox shortcut found. Migrating to Player/unfocusTextBox.";
QString unfocusTextBox = shortCutsFile.value("Textbox/unfocusTextBox", "").toString();
this->setShortcuts("Player/unfocusTextBox", unfocusTextBox);
shortCutsFile.remove("Textbox/unfocusTextBox");
}
if (shortCutsFile.contains("tab_game/aFocusChat")) {
qCInfo(ShortcutsSettingsLog)
<< "[ShortcutsSettings] tab_game/aFocusChat shortcut found. Migrating to Player/aFocusChat.";
QString aFocusChat = shortCutsFile.value("tab_game/aFocusChat", "").toString();
this->setShortcuts("Player/aFocusChat", aFocusChat);
shortCutsFile.remove("tab_game/aFocusChat");
}
// PR #5564 changes "MainWindow/aDeckEditor" to "Tabs/aTabDeckEditor"
if (shortCutsFile.contains("MainWindow/aDeckEditor")) {
qCInfo(ShortcutsSettingsLog) << "MainWindow/aDeckEditor shortcut found. Migrating to Tabs/aTabDeckEditor.";
QString keySequence = shortCutsFile.value("MainWindow/aDeckEditor", "").toString();
this->setShortcuts("Tabs/aTabDeckEditor", keySequence);
shortCutsFile.remove("MainWindow/aDeckEditor");
}
shortCutsFile.endGroup();
}
}
ShortcutKey ShortcutsSettings::getDefaultShortcut(const QString &name) const
{
return defaultShortCuts.value(name, ShortcutKey());
}
ShortcutKey ShortcutsSettings::getShortcut(const QString &name) const
{
if (shortCuts.contains(name)) {
return shortCuts.value(name);
}
return getDefaultShortcut(name);
}
/**
* Gets the first shortcut for the given action.
*
* NOTE: In most cases you should be using ShortcutsSettings::getShortcut instead,
* as that will return all shortcuts if there are multiple shortcuts.
* The only reason to use this method is if an object does not accept multiple shortcuts, such as with QButtons.
*/
QKeySequence ShortcutsSettings::getSingleShortcut(const QString &name) const
{
return getShortcut(name).at(0);
}
QString ShortcutsSettings::getDefaultShortcutString(const QString &name) const
{
return stringifySequence(getDefaultShortcut(name));
}
QString ShortcutsSettings::getShortcutString(const QString &name) const
{
return stringifySequence(getShortcut(name));
}
QString ShortcutsSettings::getShortcutFriendlyName(const QString &shortcutName) const
{
for (auto it = defaultShortCuts.cbegin(); it != defaultShortCuts.cend(); ++it) {
if (shortcutName == it.key()) {
return it.value().getName();
}
}
return {};
}
QString ShortcutsSettings::stringifySequence(const QList<QKeySequence> &Sequence) const
{
QStringList stringSequence;
for (const auto &i : Sequence) {
stringSequence.append(i.toString(QKeySequence::PortableText));
}
return stringSequence.join(sep);
}
QList<QKeySequence> ShortcutsSettings::parseSequenceString(const QString &stringSequence) const
{
QList<QKeySequence> SequenceList;
for (const QString &shortcut : stringSequence.split(sep)) {
SequenceList.append(QKeySequence(shortcut, QKeySequence::PortableText));
}
return SequenceList;
}
void ShortcutsSettings::setShortcuts(const QString &name, const QList<QKeySequence> &Sequence)
{
shortCuts[name].setSequence(Sequence);
QSettings shortCutsFile(settingsFilePath, QSettings::IniFormat);
shortCutsFile.beginGroup(custom);
shortCutsFile.setValue(name, stringifySequence(Sequence));
shortCutsFile.endGroup();
emit shortCutChanged();
}
void ShortcutsSettings::setShortcuts(const QString &name, const QKeySequence &Sequence)
{
setShortcuts(name, QList<QKeySequence>{Sequence});
}
void ShortcutsSettings::setShortcuts(const QString &name, const QString &sequences)
{
setShortcuts(name, parseSequenceString(sequences));
}
void ShortcutsSettings::resetAllShortcuts()
{
shortCuts = defaultShortCuts;
QSettings shortCutsFile(settingsFilePath, QSettings::IniFormat);
shortCutsFile.beginGroup(custom);
shortCutsFile.remove("");
shortCutsFile.endGroup();
emit shortCutChanged();
}
void ShortcutsSettings::clearAllShortcuts()
{
QSettings shortCutsFile(settingsFilePath, QSettings::IniFormat);
shortCutsFile.beginGroup(custom);
for (auto it = shortCuts.begin(); it != shortCuts.end(); ++it) {
it.value().setSequence(parseSequenceString(""));
shortCutsFile.setValue(it.key(), "");
}
shortCutsFile.endGroup();
emit shortCutChanged();
}
bool ShortcutsSettings::isKeyAllowed(const QString &name, const QString &sequences) const
{
// if the shortcut is not to be used in deck-editor then it doesn't matter
if (name.startsWith("Player") || name.startsWith("Replays")) {
return true;
}
QString checkSequence = sequences.split(sep).last();
QStringList forbiddenKeys{"Del", "Backspace", "Down", "Up", "Left", "Right",
"Return", "Enter", "Menu", "Ctrl+Alt+-", "Ctrl+Alt+=", "Ctrl+Alt+[",
"Ctrl+Alt+]", "Tab", "Space", "Shift+S", "Shift+Left", "Shift+Right"};
return !forbiddenKeys.contains(checkSequence);
}
/**
* Checks that the shortcut doesn't overlap with an existing shortcut
*
* @param name The name of the shortcut
* @param sequences The shortcut key sequence
* @return Whether the shortcut is valid.
*/
bool ShortcutsSettings::isValid(const QString &name, const QString &sequences) const
{
return findOverlaps(name, sequences).isEmpty();
}
/**
* Checks if the shortcut is a shortcut that is active in all windows
*/
static bool isAlwaysActiveShortcut(const QString &shortcutName)
{
return shortcutName.startsWith("MainWindow") || shortcutName.startsWith("Tabs");
}
QStringList ShortcutsSettings::findOverlaps(const QString &name, const QString &sequences) const
{
QString checkSequence = sequences.split(sep).last();
QString checkKey = name.left(name.indexOf("/"));
QStringList overlaps;
for (const auto &key : shortCuts.keys()) {
if (key.startsWith(checkKey) || isAlwaysActiveShortcut(key) || isAlwaysActiveShortcut(checkKey)) {
QString storedSequence = stringifySequence(shortCuts.value(key));
if (storedSequence.split(sep).contains(checkSequence)) {
overlaps.append(getShortcutFriendlyName(key));
}
}
}
return overlaps;
}

View file

@ -0,0 +1,32 @@
cmake_minimum_required(VERSION 3.16)
project(Utility VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
set(UTILITY_SOURCES
src/key_signals.cpp
src/levenshtein.cpp
src/logger.cpp
)
set(UTILITY_HEADERS
include/utility/key_signals.h
include/utility/levenshtein.h
include/utility/logger.h
include/utility/macros.h
)
qt_add_library(utility STATIC
${UTILITY_SOURCES}
${UTILITY_HEADERS}
)
target_include_directories(utility
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_link_libraries(utility
PUBLIC ${COCKATRICE_QT_MODULES}
)

View file

@ -0,0 +1,36 @@
/**
* @file key_signals.h
* @ingroup Core
* @ingroup UI
* @brief TODO: Document this.
*/
#ifndef KEYSIGNALS_H
#define KEYSIGNALS_H
#include <QEvent>
#include <QObject>
class KeySignals : public QObject
{
Q_OBJECT
signals:
void onEnter();
void onCtrlEnter();
void onCtrlAltEnter();
void onShiftLeft();
void onShiftRight();
void onDelete();
void onCtrlAltMinus();
void onCtrlAltEqual();
void onCtrlAltLBracket();
void onCtrlAltRBracket();
void onShiftS();
void onCtrlC();
protected:
bool eventFilter(QObject *, QEvent *event) override;
};
#endif

View file

@ -0,0 +1,14 @@
/**
* @file levenshtein.h
* @ingroup Core
* @brief TODO: Document this.
*/
#ifndef LEVENSHTEIN_H
#define LEVENSHTEIN_H
#include <QString>
int levenshteinDistance(const QString &s1, const QString &s2);
#endif // LEVENSHTEIN_H

View file

@ -0,0 +1,72 @@
/**
* @file logger.h
* @ingroup Core
* @brief TODO: Document this.
*/
#ifndef LOGGER_H
#define LOGGER_H
#include <QFile>
#include <QMutex>
#include <QString>
#include <QTextStream>
#include <QVector>
#if defined(Q_PROCESSOR_X86_32)
#define BUILD_ARCHITECTURE "32-bit"
#elif defined(Q_PROCESSOR_X86_64)
#define BUILD_ARCHITECTURE "64-bit"
#elif defined(Q_PROCESSOR_ARM)
#define BUILD_ARCHITECTURE "ARM"
#else
#define BUILD_ARCHITECTURE "unknown"
#endif
class Logger : public QObject
{
Q_OBJECT
public:
static Logger &getInstance()
{
static Logger instance;
return instance;
}
void logToFile(bool enabled);
void log(QtMsgType type, const QMessageLogContext &ctx, const QString &message);
QString getClientVersion();
QString getClientOperatingSystem();
QString getSystemArchitecture();
QString getSystemLocale();
QString getClientInstallInfo();
QList<QString> getLogBuffer()
{
return logBuffer;
}
private:
Logger();
~Logger() override;
// Singleton - Don't implement copy constructor and assign operator
Logger(Logger const &);
void operator=(Logger const &);
bool logToFileEnabled;
QTextStream fileStream;
QFile fileHandle;
QList<QString> logBuffer;
QMutex mutex;
protected:
void openLogfileSession();
void closeLogfileSession();
protected slots:
void internalLog(const QString &message);
signals:
void logEntryAdded(const QString &message);
};
#endif

View file

@ -0,0 +1,17 @@
#ifndef COCKATRICE_MACROS_H
#define COCKATRICE_MACROS_H
#include <QtGlobal>
// Qt6.7 changed how stateChanged functionality
// of QCheckBoxes work.
// See https://doc.qt.io/qt-6/qcheckbox.html#checkStateChanged
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
#define QT_STATE_CHANGED checkStateChanged
#define QT_STATE_CHANGED_T Qt::CheckState
#else
#define QT_STATE_CHANGED stateChanged
#define QT_STATE_CHANGED_T int
#endif
#endif // COCKATRICE_MACROS_H

View file

@ -0,0 +1,74 @@
#include "../include/utility/key_signals.h"
#include <QKeyEvent>
bool KeySignals::eventFilter(QObject * /*object*/, QEvent *event)
{
QKeyEvent *kevent;
if (event->type() != QEvent::KeyPress)
return false;
kevent = static_cast<QKeyEvent *>(event);
switch (kevent->key()) {
case Qt::Key_Return:
case Qt::Key_Enter:
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
emit onCtrlAltEnter();
else if (kevent->modifiers() & Qt::ControlModifier)
emit onCtrlEnter();
else
emit onEnter();
break;
case Qt::Key_Right:
if (kevent->modifiers() & Qt::ShiftModifier)
emit onShiftRight();
break;
case Qt::Key_Left:
if (kevent->modifiers() & Qt::ShiftModifier)
emit onShiftLeft();
break;
case Qt::Key_Delete:
case Qt::Key_Backspace:
emit onDelete();
break;
case Qt::Key_Minus:
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
emit onCtrlAltMinus();
break;
case Qt::Key_Equal:
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
emit onCtrlAltEqual();
break;
case Qt::Key_BracketLeft:
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
emit onCtrlAltLBracket();
break;
case Qt::Key_BracketRight:
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
emit onCtrlAltRBracket();
break;
case Qt::Key_S:
if (kevent->modifiers() & Qt::ShiftModifier)
emit onShiftS();
break;
case Qt::Key_C:
if (kevent->modifiers() & Qt::ControlModifier)
emit onCtrlC();
break;
default:
return false;
}
return false;
}

View file

@ -0,0 +1,25 @@
#include "../include/utility/levenshtein.h"
#include <algorithm>
#include <vector>
int levenshteinDistance(const QString &s1, const QString &s2)
{
int len1 = s1.size();
int len2 = s2.size();
std::vector<std::vector<int>> dp(len1 + 1, std::vector<int>(len2 + 1));
for (int i = 0; i <= len1; i++)
dp[i][0] = i;
for (int j = 0; j <= len2; j++)
dp[0][j] = j;
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;
dp[i][j] = std::min({dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost});
}
}
return dp[len1][len2];
}

View file

@ -0,0 +1,142 @@
#include "../include/utility/logger.h"
#include "version_string.h"
#include <QApplication>
#include <QDateTime>
#include <QLocale>
#include <QSysInfo>
#include <iostream>
#define LOGGER_MAX_ENTRIES 128
#define LOGGER_FILENAME "qdebug.txt"
Logger::Logger() : logToFileEnabled(false)
{
logBuffer.append(getClientVersion());
logBuffer.append(getSystemArchitecture());
logBuffer.append(getSystemLocale());
logBuffer.append(getClientInstallInfo());
logBuffer.append(QString("-").repeated(75));
std::cerr << getClientVersion().toStdString() << std::endl;
std::cerr << getSystemArchitecture().toStdString() << std::endl;
std::cerr << getSystemLocale().toStdString() << std::endl;
std::cerr << getClientInstallInfo().toStdString() << std::endl;
}
Logger::~Logger()
{
closeLogfileSession();
logBuffer.clear();
}
void Logger::logToFile(bool enabled)
{
if (enabled) {
openLogfileSession();
} else {
closeLogfileSession();
}
}
QString Logger::getClientVersion()
{
return "Client Version: " + QString::fromStdString(VERSION_STRING);
}
void Logger::openLogfileSession()
{
if (logToFileEnabled) {
return;
}
fileHandle.setFileName(LOGGER_FILENAME);
fileHandle.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text);
fileStream.setDevice(&fileHandle);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
fileStream << "Log session started at " << QDateTime::currentDateTime().toString() << Qt::endl;
fileStream << getClientVersion() << Qt::endl;
fileStream << getSystemArchitecture() << Qt::endl;
fileStream << getClientInstallInfo() << Qt::endl;
#else
fileStream << "Log session started at " << QDateTime::currentDateTime().toString() << endl;
fileStream << getClientVersion() << endl;
fileStream << getSystemArchitecture() << endl;
fileStream << getClientInstallInfo() << endl;
#endif
logToFileEnabled = true;
}
void Logger::closeLogfileSession()
{
if (!logToFileEnabled)
return;
logToFileEnabled = false;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << Qt::endl;
#else
fileStream << "Log session closed at " << QDateTime::currentDateTime().toString() << endl;
#endif
fileHandle.close();
}
void Logger::log(QtMsgType /* type */, const QMessageLogContext & /* ctx */, const QString &message)
{
QMetaObject::invokeMethod(this, "internalLog", Qt::QueuedConnection, Q_ARG(const QString &, message));
}
void Logger::internalLog(const QString &message)
{
QMutexLocker locker(&mutex);
logBuffer.append(message);
if (logBuffer.size() > LOGGER_MAX_ENTRIES) {
logBuffer.removeAt(1);
}
emit logEntryAdded(message);
std::cerr << message.toStdString() << std::endl; // Print to stdout
if (logToFileEnabled) {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
fileStream << message << Qt::endl; // Print to fileStream
#else
fileStream << message << endl; // Print to fileStream
#endif
}
}
QString Logger::getSystemArchitecture()
{
QString result;
if (!getClientOperatingSystem().isEmpty()) {
// We don't want translatable strings in the 'Debug Log' for easier troubleshooting
result.append(QString("Client Operating System: ") + getClientOperatingSystem() + "\n");
}
result.append(QString("Build Architecture: ") + QString::fromStdString(BUILD_ARCHITECTURE) + "\n");
result.append(QString("Qt Version: ") + QT_VERSION_STR);
return result;
}
QString Logger::getClientOperatingSystem()
{
return QSysInfo::prettyProductName();
}
QString Logger::getSystemLocale()
{
QString result(QString("System Locale: ") + QLocale().name());
return result;
}
QString Logger::getClientInstallInfo()
{
// don't rely on settingsCache->getIsPortableBuild() since the logger is initialized earlier
bool isPortable = QFile::exists(qApp->applicationDirPath() + "/portable.dat");
QString result(QString("Install Mode: ") + (isPortable ? "Portable" : "Standard"));
return result;
}