mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-04-27 07:48:01 -07:00
* Squashed Commits Lint things. Set focus back to deckView after selecting a card to enable keyboard navigation. Bump scrollbar to top when selecting a new card. Update card info on hover. Layout cleanups Add +- to buttons. Merge buttons into card picture. Cleanup size, min 2 cards by default in rows Support layout settings config and set min to 525 so two cols are visible by default for printings, when opened Move Printing Selector to before Deck, and visible true Null safety for setCard. Turn down the dropshadow a little. Make PrintingSelector dockable, don't duplicate sets when bumping them to the front of the list. When swapping cards between mainboard and sideboard, use preferred printing if no uuid is available (i.e. null). Reorder includes... Unwonk an include. Give the card widget a snazzy drop shadow, appease the linter gods. Handle jumping between segments Remember scale factor when initializing new widgets. Cleanup Select Card works (Not M->SB tho) Resize word-wrapped label properly. Fix the layouting, mostly. remove tx Build Fix Squashed Commits Load and store redirects properly. Layouting is fun :) * Group PrintingSelectorCardDisplayWidgets into distinct containers for alignment purposes. Override resizeEvent() properly. Word wrap properly. Keep widget sizes uniform for aesthetic reasons (grid pattern). Label stuff, center card picture widget, allow cardSizeSlider to scale down. Replace cards which have no uuid in the decklist when first selecting a printing. Add buttons for previous and next card in DeckList. Add a card size slider. Move sort options initialization to implementation file. Explicitly nullptr the parent for the PrintingSelector. Address PR comments (minor cleanups). Hook up to the rows removed signal to update card counts properly. Include QDebug. Add labels to the mainboard/sideboard button boxes. Implement a search bar. Expand node recursively when we add a new card. Only create image widgets for the printing selector if it is visible in order to not slow down image loading. Minor Tweaks Invert decklist export logic to write out setName, collectorNumber, providerId value if it is NOT empty. Linting. Update CardCounts properly, update PrintingSelector on Database selection. Initialize sideboard card count label properly. Split mainboard/sideboard display and increment/decrement buttons. Add button to sort all sortOptions by ascending or descending order. Add option to sort by release date in ascending or descending order. Add PrintingSelector to database view. Display placeholder image before loading. Fix deckEditor crash on mainboard/sideboard swap by correcting column index to providerId instead of shortName. Include currentZoneName, fix the column when updating from DeckView indexChanged to be UUID and not setShortName so cards are properly fetched again. The most minor linter change you've ever seen. Null checks are important. Linter again. Linter and refactor to providerId. Sort properly, (We don't need a map, we need a list, a map won't be ordered right [i.e. 1, 10, 11, 2, 3, 4, ..., 9]) Sort alphabetically or by preference. Hook printingSelector up to the CardInfoFrameWidget. Allow info from CardFrame to be retrieved, properly initialize PrintingSelector again. Refactors to reflect CardInfoPicture becoming CardInfoPictureWidget. Make PrintingSelector re-usable by introducing setCard(). Make PrintingSelector use the CardFrame, not the database index. Add a new selector widget for printings. * Support multiple <set> tags per card within the database This will allow us to show off all different printings for cards that might appear multiple times in a set (alt arts, Secret Lairs, etc.) * Support Flip Cards with related art * Minor Cleanup * Minor Cleanup * Release Date DESC default * Load widgets in batches. * Refactor local batch variables to be class variables/defines. * Clear timer on updateDisplay. * Fix Timer & Builds on Qt5 * Not Override * Yes Override * Yes Override * Lint * Can't override in function definition. * Resize setName to picture width on initialization. Also add a new signal to card_info_picture_widget to emit when the scale factor changes. Hook this up to the setName resizing method to ensure card size updates trigger it appropriately after initialization. Clean up unused enter and resize methods that just delegated to base-class. * Add ability to force preferred set art to be loaded for every card. * Show related cards from printing selector by right-clicking card image. * fix build * Fix UST cards * Inc QDebug * Fix Qt5 Builds * Fix Qt5 Builds * Fix Qt5 Builds * Fix Qt5 Builds * Fix Qt5 Builds * Fix cards being able to jump between side and mainboard * Don't hide PrintingSelector button widgets if the deck contains a card from the set. * Update PrintingSelector properly on DeckListModel::dataChanged * Add option to disable bumping sets to the front of the list if the deck contains cards from the set. * Linter behave. * Linter behave. * Fix mocks. * Fix cards without providerIds being counted for all cards. * Flip preference sort so descending means "Most to least preferred". * Set the index correctly when removing a non-providerId printing for a providerId printing to avoid jumping to the next card. * Move the "Next/Previous" card buttons to their own widget. * Move the card size slider to its own widget. * Lint the makelist. * Linter * Crash fix * Move the sorting options to their own widget. * Move the search bar to its own widget. * Minor cleanup * Minor cleanup * Minor cleanup * Only overwrite card in deck if UUID _and_ Number missing * Adjust font size when adjusting card size. * Clean up some imports. * Pivot to a view options toolbar. * Persist sort options and change default to 'preference'. * Lint. * Remember how many cards were originally in deck when replacing with uuid version. * Relabel buttons for clarity. * Fix tests. * Fix tests properly. * Fix dbconverter mock. * Try to wrangle font sizes. * Update mainboard/sideboard labels correctly. * Initialize button sizes correctly. * Label texts are supposed to be white. * Adjust another deckModel->findCard call to include number parameter. * Style buttons again. * Negative currentSize means we don't render the widget yet, return a default value. * Clean up debug statements. * Boop the mainboard/sideboard label and the cardCount after a little bit of delay to make sure they initialize at the right size. * Persist card size slider selection in SettingsCache. * Good Lint Inc. * updateCardCount to get white color in initializer * Make the view display options functional. * Comment ALL the things. * Lint the things. * Brief accidentally nuked some constants. * Proper Qt slot for checkboxes. * Don't use timers, Qt provides ShowEvent for anything necessary before the widget is shown. * Cleanup from Reading * Cleanup Lints * Minor --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> Co-authored-by: Zach Halpern <zahalpern+github@gmail.com>
888 lines
26 KiB
C++
888 lines
26 KiB
C++
#include "card_database.h"
|
|
|
|
#include "../../client/network/spoiler_background_updater.h"
|
|
#include "../../client/ui/picture_loader.h"
|
|
#include "../../settings/cache_settings.h"
|
|
#include "../../utility/card_set_comparator.h"
|
|
#include "../game_specific_terms.h"
|
|
#include "./card_database_parser/cockatrice_xml_3.h"
|
|
#include "./card_database_parser/cockatrice_xml_4.h"
|
|
|
|
#include <QCryptographicHash>
|
|
#include <QDebug>
|
|
#include <QDir>
|
|
#include <QDirIterator>
|
|
#include <QFile>
|
|
#include <QMessageBox>
|
|
#include <QRegularExpression>
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
const char *CardDatabase::TOKENS_SETNAME = "TK";
|
|
|
|
CardSet::CardSet(const QString &_shortName,
|
|
const QString &_longName,
|
|
const QString &_setType,
|
|
const QDate &_releaseDate,
|
|
const CardSet::Priority _priority)
|
|
: shortName(_shortName), longName(_longName), releaseDate(_releaseDate), setType(_setType), priority(_priority)
|
|
{
|
|
loadSetOptions();
|
|
}
|
|
|
|
CardSetPtr CardSet::newInstance(const QString &_shortName,
|
|
const QString &_longName,
|
|
const QString &_setType,
|
|
const QDate &_releaseDate,
|
|
const Priority _priority)
|
|
{
|
|
CardSetPtr ptr(new CardSet(_shortName, _longName, _setType, _releaseDate, _priority));
|
|
// ptr->setSmartPointer(ptr);
|
|
return ptr;
|
|
}
|
|
|
|
QString CardSet::getCorrectedShortName() const
|
|
{
|
|
// For Windows machines.
|
|
QSet<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);
|
|
}
|
|
|
|
class SetList::KeyCompareFunctor
|
|
{
|
|
public:
|
|
inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const
|
|
{
|
|
if (a.isNull() || b.isNull()) {
|
|
qDebug() << "SetList::KeyCompareFunctor a or b is null";
|
|
return false;
|
|
}
|
|
|
|
return a->getSortKey() < b->getSortKey();
|
|
}
|
|
};
|
|
|
|
void SetList::sortByKey()
|
|
{
|
|
std::sort(begin(), end(), KeyCompareFunctor());
|
|
}
|
|
|
|
int SetList::getEnabledSetsNum()
|
|
{
|
|
int num = 0;
|
|
for (int i = 0; i < size(); ++i) {
|
|
CardSetPtr set = at(i);
|
|
if (set && set->getEnabled()) {
|
|
++num;
|
|
}
|
|
}
|
|
return num;
|
|
}
|
|
|
|
int SetList::getUnknownSetsNum()
|
|
{
|
|
int num = 0;
|
|
for (int i = 0; i < size(); ++i) {
|
|
CardSetPtr set = at(i);
|
|
if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
|
|
++num;
|
|
}
|
|
}
|
|
return num;
|
|
}
|
|
|
|
QStringList SetList::getUnknownSetsNames()
|
|
{
|
|
QStringList sets = QStringList();
|
|
for (int i = 0; i < size(); ++i) {
|
|
CardSetPtr set = at(i);
|
|
if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
|
|
sets << set->getShortName();
|
|
}
|
|
}
|
|
return sets;
|
|
}
|
|
|
|
void SetList::enableAllUnknown()
|
|
{
|
|
for (int i = 0; i < size(); ++i) {
|
|
CardSetPtr set = at(i);
|
|
if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
|
|
set->setIsKnown(true);
|
|
set->setEnabled(true);
|
|
} else if (set && set->getIsKnownIgnored() && !set->getEnabled()) {
|
|
set->setEnabled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetList::enableAll()
|
|
{
|
|
for (int i = 0; i < size(); ++i) {
|
|
CardSetPtr set = at(i);
|
|
|
|
if (set == nullptr) {
|
|
qDebug() << "enabledAll has null";
|
|
continue;
|
|
}
|
|
|
|
if (!set->getIsKnownIgnored()) {
|
|
set->setIsKnown(true);
|
|
}
|
|
|
|
set->setEnabled(true);
|
|
}
|
|
}
|
|
|
|
void SetList::markAllAsKnown()
|
|
{
|
|
for (int i = 0; i < size(); ++i) {
|
|
CardSetPtr set = at(i);
|
|
if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) {
|
|
set->setIsKnown(true);
|
|
set->setEnabled(false);
|
|
} else if (set && set->getIsKnownIgnored() && !set->getEnabled()) {
|
|
set->setEnabled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetList::guessSortKeys()
|
|
{
|
|
defaultSort();
|
|
for (int i = 0; i < size(); ++i) {
|
|
CardSetPtr set = at(i);
|
|
if (set.isNull()) {
|
|
qDebug() << "guessSortKeys set is null";
|
|
continue;
|
|
}
|
|
set->setSortKey(i);
|
|
}
|
|
}
|
|
|
|
void SetList::defaultSort()
|
|
{
|
|
std::sort(begin(), end(), [](const CardSetPtr &a, const CardSetPtr &b) {
|
|
// Sort by priority, then by release date, then by short name
|
|
if (a->getPriority() != b->getPriority()) {
|
|
return a->getPriority() < b->getPriority(); // lowest first
|
|
} else if (a->getReleaseDate() != b->getReleaseDate()) {
|
|
return a->getReleaseDate() > b->getReleaseDate(); // most recent first
|
|
} else {
|
|
return a->getShortName() < b->getShortName(); // alphabetically
|
|
}
|
|
});
|
|
}
|
|
|
|
CardInfoPerSet::CardInfoPerSet(const CardSetPtr &_set) : set(_set)
|
|
{
|
|
}
|
|
|
|
CardInfo::CardInfo(const QString &_name,
|
|
const QString &_text,
|
|
bool _isToken,
|
|
QVariantHash _properties,
|
|
const QList<CardRelation *> &_relatedCards,
|
|
const QList<CardRelation *> &_reverseRelatedCards,
|
|
CardInfoPerSetMap _sets,
|
|
bool _cipt,
|
|
int _tableRow,
|
|
bool _upsideDownArt)
|
|
: name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards),
|
|
reverseRelatedCards(_reverseRelatedCards), sets(std::move(_sets)), cipt(_cipt), tableRow(_tableRow),
|
|
upsideDownArt(_upsideDownArt)
|
|
{
|
|
pixmapCacheKey = QLatin1String("card_") + name;
|
|
simpleName = CardInfo::simplifyName(name);
|
|
|
|
refreshCachedSetNames();
|
|
}
|
|
|
|
CardInfo::~CardInfo()
|
|
{
|
|
PictureLoader::clearPixmapCache(smartThis);
|
|
}
|
|
|
|
CardInfoPtr CardInfo::newInstance(const QString &_name,
|
|
const QString &_text,
|
|
bool _isToken,
|
|
QVariantHash _properties,
|
|
const QList<CardRelation *> &_relatedCards,
|
|
const QList<CardRelation *> &_reverseRelatedCards,
|
|
CardInfoPerSetMap _sets,
|
|
bool _cipt,
|
|
int _tableRow,
|
|
bool _upsideDownArt)
|
|
{
|
|
CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_properties), _relatedCards, _reverseRelatedCards,
|
|
_sets, _cipt, _tableRow, _upsideDownArt));
|
|
ptr->setSmartPointer(ptr);
|
|
|
|
for (const auto &cardInfoPerSetList : _sets) {
|
|
for (const CardInfoPerSet &set : cardInfoPerSetList) {
|
|
set.getPtr()->append(ptr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
QString CardInfo::getCorrectedName() const
|
|
{
|
|
// remove all the characters reserved in windows file paths,
|
|
// other oses only disallow a subset of these so it covers all
|
|
static const QRegularExpression rmrx(R"(( // |[*<>:"\\?\x00-\x08\x10-\x1f]))");
|
|
static const QRegularExpression spacerx(R"([/\x09-\x0f])");
|
|
static const QString space(' ');
|
|
QString result = name;
|
|
// Fire // Ice, Circle of Protection: Red, "Ach! Hans, Run!", Who/What/When/Where/Why, Question Elemental?
|
|
return result.remove(rmrx).replace(spacerx, space);
|
|
}
|
|
|
|
void CardInfo::addToSet(const CardSetPtr &_set, const CardInfoPerSet _info)
|
|
{
|
|
_set->append(smartThis);
|
|
sets[_set->getShortName()].append(_info);
|
|
|
|
refreshCachedSetNames();
|
|
}
|
|
|
|
void CardInfo::refreshCachedSetNames()
|
|
{
|
|
QStringList setList;
|
|
// update the cached list of set names
|
|
for (const auto &cardInfoPerSetList : sets) {
|
|
for (const auto &set : cardInfoPerSetList) {
|
|
if (set.getPtr()->getEnabled()) {
|
|
setList << set.getPtr()->getShortName();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
setsNames = setList.join(", ");
|
|
}
|
|
|
|
QString CardInfo::simplifyName(const QString &name)
|
|
{
|
|
static const QRegularExpression spaceOrSplit("(\\s+|\\/\\/.*)");
|
|
static const QRegularExpression nonAlnum("[^a-z0-9]");
|
|
|
|
QString simpleName = name.toLower();
|
|
|
|
// remove spaces and right halves of split cards
|
|
simpleName.remove(spaceOrSplit);
|
|
|
|
// So Aetherling would work, but not Ætherling since 'Æ' would get replaced
|
|
// with nothing.
|
|
simpleName.replace("æ", "ae");
|
|
|
|
// Replace Jötun Grunt with Jotun Grunt.
|
|
simpleName = simpleName.normalized(QString::NormalizationForm_KD);
|
|
|
|
// remove all non alphanumeric characters from the name
|
|
simpleName.remove(nonAlnum);
|
|
return simpleName;
|
|
}
|
|
|
|
const QChar CardInfo::getColorChar() const
|
|
{
|
|
QString colors = getColors();
|
|
switch (colors.size()) {
|
|
case 0:
|
|
return QChar();
|
|
case 1:
|
|
return colors.at(0);
|
|
default:
|
|
return QChar('m');
|
|
}
|
|
}
|
|
|
|
CardDatabase::CardDatabase(QObject *parent) : QObject(parent), loadStatus(NotLoaded)
|
|
{
|
|
qRegisterMetaType<CardInfoPtr>("CardInfoPtr");
|
|
qRegisterMetaType<CardInfoPtr>("CardSetPtr");
|
|
|
|
// add new parsers here
|
|
availableParsers << new CockatriceXml4Parser;
|
|
availableParsers << new CockatriceXml3Parser;
|
|
|
|
for (auto &parser : availableParsers) {
|
|
connect(parser, SIGNAL(addCard(CardInfoPtr)), this, SLOT(addCard(CardInfoPtr)), Qt::DirectConnection);
|
|
connect(parser, SIGNAL(addSet(CardSetPtr)), this, SLOT(addSet(CardSetPtr)), Qt::DirectConnection);
|
|
}
|
|
|
|
connect(&SettingsCache::instance(), SIGNAL(cardDatabasePathChanged()), this, SLOT(loadCardDatabases()));
|
|
}
|
|
|
|
CardDatabase::~CardDatabase()
|
|
{
|
|
clear();
|
|
qDeleteAll(availableParsers);
|
|
}
|
|
|
|
void CardDatabase::clear()
|
|
{
|
|
clearDatabaseMutex->lock();
|
|
|
|
QHashIterator<QString, CardInfoPtr> i(cards);
|
|
while (i.hasNext()) {
|
|
i.next();
|
|
if (i.value()) {
|
|
removeCard(i.value());
|
|
}
|
|
}
|
|
|
|
cards.clear();
|
|
simpleNameCards.clear();
|
|
|
|
sets.clear();
|
|
ICardDatabaseParser::clearSetlist();
|
|
|
|
loadStatus = NotLoaded;
|
|
|
|
clearDatabaseMutex->unlock();
|
|
}
|
|
|
|
void CardDatabase::addCard(CardInfoPtr card)
|
|
{
|
|
if (card == nullptr) {
|
|
qDebug() << "addCard(nullptr)";
|
|
return;
|
|
}
|
|
|
|
// if card already exists just add the new set property
|
|
if (cards.contains(card->getName())) {
|
|
CardInfoPtr sameCard = cards[card->getName()];
|
|
for (const auto &cardInfoPerSetList : card->getSets()) {
|
|
for (const CardInfoPerSet &set : cardInfoPerSetList) {
|
|
sameCard->addToSet(set.getPtr(), set);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
addCardMutex->lock();
|
|
cards.insert(card->getName(), card);
|
|
simpleNameCards.insert(card->getSimpleName(), card);
|
|
addCardMutex->unlock();
|
|
emit cardAdded(card);
|
|
}
|
|
|
|
void CardDatabase::removeCard(CardInfoPtr card)
|
|
{
|
|
if (card.isNull()) {
|
|
qDebug() << "removeCard(nullptr)";
|
|
return;
|
|
}
|
|
|
|
for (auto *cardRelation : card->getRelatedCards())
|
|
cardRelation->deleteLater();
|
|
|
|
for (auto *cardRelation : card->getReverseRelatedCards())
|
|
cardRelation->deleteLater();
|
|
|
|
for (auto *cardRelation : card->getReverseRelatedCards2Me())
|
|
cardRelation->deleteLater();
|
|
|
|
removeCardMutex->lock();
|
|
cards.remove(card->getName());
|
|
simpleNameCards.remove(card->getSimpleName());
|
|
removeCardMutex->unlock();
|
|
emit cardRemoved(card);
|
|
}
|
|
|
|
CardInfoPtr CardDatabase::getCard(const QString &cardName) const
|
|
{
|
|
return getCardFromMap(cards, cardName);
|
|
}
|
|
|
|
QList<CardInfoPtr> CardDatabase::getCards(const QStringList &cardNames) const
|
|
{
|
|
QList<CardInfoPtr> cardInfos;
|
|
foreach (QString cardName, cardNames) {
|
|
CardInfoPtr ptr = getCardFromMap(cards, cardName);
|
|
if (ptr)
|
|
cardInfos.append(ptr);
|
|
}
|
|
|
|
return cardInfos;
|
|
}
|
|
|
|
CardInfoPtr CardDatabase::getCardByNameAndProviderId(const QString &cardName, const QString &providerId) const
|
|
{
|
|
auto info = getCard(cardName);
|
|
if (providerId.isNull() || providerId.isEmpty() || info.isNull()) {
|
|
return info;
|
|
}
|
|
|
|
for (const auto &cardInfoPerSetList : info->getSets()) {
|
|
for (const auto &set : cardInfoPerSetList) {
|
|
if (set.getProperty("uuid") == providerId) {
|
|
CardInfoPtr cardFromSpecificSet = info->clone();
|
|
cardFromSpecificSet->setPixmapCacheKey(QLatin1String("card_") + QString(info->getName()) +
|
|
QString("_") + QString(set.getProperty("uuid")));
|
|
return cardFromSpecificSet;
|
|
}
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
CardInfoPtr CardDatabase::getCardBySimpleName(const QString &cardName) const
|
|
{
|
|
return getCardFromMap(simpleNameCards, CardInfo::simplifyName(cardName));
|
|
}
|
|
|
|
CardInfoPtr CardDatabase::guessCard(const QString &cardName) const
|
|
{
|
|
CardInfoPtr temp = getCard(cardName);
|
|
if (temp == nullptr) { // get card by simple name instead
|
|
temp = getCardBySimpleName(cardName);
|
|
if (temp == nullptr) { // still could not find the card, so simplify the cardName too
|
|
QString simpleCardName = CardInfo::simplifyName(cardName);
|
|
temp = getCardBySimpleName(simpleCardName);
|
|
}
|
|
}
|
|
return temp; // returns nullptr if not found
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
void CardDatabase::addSet(CardSetPtr set)
|
|
{
|
|
sets.insert(set->getShortName(), set);
|
|
}
|
|
|
|
SetList CardDatabase::getSetList() const
|
|
{
|
|
SetList result;
|
|
QHashIterator<QString, CardSetPtr> i(sets);
|
|
while (i.hasNext()) {
|
|
i.next();
|
|
result << i.value();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
CardInfoPtr CardDatabase::getCardFromMap(const CardNameMap &cardMap, const QString &cardName) const
|
|
{
|
|
if (cardMap.contains(cardName))
|
|
return cardMap.value(cardName);
|
|
|
|
return {};
|
|
}
|
|
|
|
LoadStatus CardDatabase::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 CardDatabase::loadCardDatabase(const QString &path)
|
|
{
|
|
auto startTime = QTime::currentTime();
|
|
LoadStatus tempLoadStatus = NotLoaded;
|
|
if (!path.isEmpty()) {
|
|
loadFromFileMutex->lock();
|
|
tempLoadStatus = loadFromFile(path);
|
|
loadFromFileMutex->unlock();
|
|
}
|
|
|
|
int msecs = startTime.msecsTo(QTime::currentTime());
|
|
qDebug() << "[CardDatabase] loadCardDatabase(): Path =" << path << "Status =" << tempLoadStatus
|
|
<< "Cards =" << cards.size() << "Sets =" << sets.size() << QString("%1ms").arg(msecs);
|
|
|
|
return tempLoadStatus;
|
|
}
|
|
|
|
LoadStatus CardDatabase::loadCardDatabases()
|
|
{
|
|
reloadDatabaseMutex->lock();
|
|
|
|
qDebug() << "CardDatabase::loadCardDatabases start";
|
|
|
|
clear(); // remove old db
|
|
|
|
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
|
|
QDirIterator customDatabaseIterator(SettingsCache::instance().getCustomCardDatabasePath(), QStringList() << "*.xml",
|
|
QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
|
|
QStringList databasePaths;
|
|
while (customDatabaseIterator.hasNext()) {
|
|
customDatabaseIterator.next();
|
|
databasePaths.push_back(customDatabaseIterator.filePath());
|
|
}
|
|
databasePaths.sort();
|
|
|
|
for (auto i = 0; i < databasePaths.size(); ++i) {
|
|
const auto &databasePath = databasePaths.at(i);
|
|
qDebug() << "Loading Custom Set" << i << "(" << databasePath << ")";
|
|
loadCardDatabase(databasePath);
|
|
}
|
|
|
|
// AFTER all the cards have been loaded
|
|
|
|
// Refresh the pixmap cache keys for all cards by setting them to the UUID of the preferred printing
|
|
refreshPreferredPrintings();
|
|
// resolve the reverse-related tags
|
|
refreshCachedReverseRelatedCards();
|
|
|
|
if (loadStatus == Ok) {
|
|
checkUnknownSets(); // update deck editors, etc
|
|
qDebug() << "CardDatabase::loadCardDatabases success";
|
|
emit cardDatabaseLoadingFinished();
|
|
} else {
|
|
qDebug() << "CardDatabase::loadCardDatabases failed";
|
|
emit cardDatabaseLoadingFailed(); // bring up the settings dialog
|
|
}
|
|
|
|
reloadDatabaseMutex->unlock();
|
|
return loadStatus;
|
|
}
|
|
|
|
void CardDatabase::refreshPreferredPrintings()
|
|
{
|
|
for (const CardInfoPtr &card : cards) {
|
|
card->setPixmapCacheKey(QLatin1String("card_") + QString(card->getName()) + QString("_") +
|
|
QString(getPreferredPrintingProviderIdForCard(card->getName())));
|
|
}
|
|
}
|
|
|
|
CardInfoPerSet CardDatabase::getPreferredSetForCard(const QString &cardName) const
|
|
{
|
|
CardInfoPtr cardInfo = getCard(cardName);
|
|
if (!cardInfo) {
|
|
return CardInfoPerSet(nullptr);
|
|
}
|
|
|
|
CardInfoPerSetMap setMap = cardInfo->getSets();
|
|
if (setMap.empty()) {
|
|
return CardInfoPerSet(nullptr);
|
|
}
|
|
|
|
CardSetPtr preferredSet = nullptr;
|
|
CardInfoPerSet preferredCard;
|
|
SetPriorityComparator comparator;
|
|
|
|
for (const auto &cardInfoPerSetList : setMap) {
|
|
for (auto &cardInfoForSet : cardInfoPerSetList) {
|
|
CardSetPtr currentSet = cardInfoForSet.getPtr();
|
|
if (!preferredSet || comparator(currentSet, preferredSet)) {
|
|
preferredSet = currentSet;
|
|
preferredCard = cardInfoForSet;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (preferredSet) {
|
|
return preferredCard;
|
|
}
|
|
|
|
return CardInfoPerSet(nullptr);
|
|
}
|
|
|
|
CardInfoPerSet CardDatabase::getSpecificSetForCard(const QString &cardName, const QString &providerId) const
|
|
{
|
|
CardInfoPtr cardInfo = getCard(cardName);
|
|
if (!cardInfo) {
|
|
return CardInfoPerSet(nullptr);
|
|
}
|
|
|
|
CardInfoPerSetMap setMap = cardInfo->getSets();
|
|
if (setMap.empty()) {
|
|
return CardInfoPerSet(nullptr);
|
|
}
|
|
|
|
for (const auto &cardInfoPerSetList : setMap) {
|
|
for (auto &cardInfoForSet : cardInfoPerSetList) {
|
|
if (cardInfoForSet.getProperty("uuid") == providerId) {
|
|
return cardInfoForSet;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (providerId.isNull()) {
|
|
return getPreferredSetForCard(cardName);
|
|
}
|
|
|
|
return CardInfoPerSet(nullptr);
|
|
}
|
|
|
|
QString CardDatabase::getPreferredPrintingProviderIdForCard(const QString &cardName)
|
|
{
|
|
CardInfoPerSet preferredSetCardInfo = getPreferredSetForCard(cardName);
|
|
QString preferredPrintingProviderId = preferredSetCardInfo.getProperty(QString("uuid"));
|
|
if (preferredPrintingProviderId.isEmpty()) {
|
|
CardInfoPtr defaultCardInfo = getCard(cardName);
|
|
if (defaultCardInfo.isNull()) {
|
|
return cardName;
|
|
}
|
|
return defaultCardInfo->getName();
|
|
}
|
|
return preferredPrintingProviderId;
|
|
}
|
|
|
|
bool CardDatabase::isProviderIdForPreferredPrinting(const QString &cardName, const QString &providerId)
|
|
{
|
|
if (providerId.startsWith("card_")) {
|
|
return providerId ==
|
|
QLatin1String("card_") + cardName + QString("_") + getPreferredPrintingProviderIdForCard(cardName);
|
|
}
|
|
return providerId == getPreferredPrintingProviderIdForCard(cardName);
|
|
}
|
|
|
|
CardInfoPerSet CardDatabase::getSetInfoForCard(const CardInfoPtr &_card)
|
|
{
|
|
const CardInfoPerSetMap &setMap = _card->getSets();
|
|
if (setMap.empty()) {
|
|
return CardInfoPerSet(nullptr);
|
|
}
|
|
|
|
for (const auto &cardInfoPerSetList : setMap) {
|
|
for (const auto &cardInfoForSet : cardInfoPerSetList) {
|
|
if (QLatin1String("card_") + _card->getName() + QString("_") + cardInfoForSet.getProperty("uuid") ==
|
|
_card->getPixmapCacheKey()) {
|
|
return cardInfoForSet;
|
|
}
|
|
}
|
|
}
|
|
|
|
return CardInfoPerSet(nullptr);
|
|
}
|
|
|
|
void CardDatabase::refreshCachedReverseRelatedCards()
|
|
{
|
|
for (const CardInfoPtr &card : cards)
|
|
card->resetReverseRelatedCards2Me();
|
|
|
|
for (const CardInfoPtr &card : cards) {
|
|
if (card->getReverseRelatedCards().isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
foreach (CardRelation *cardRelation, card->getReverseRelatedCards()) {
|
|
const QString &targetCard = cardRelation->getName();
|
|
if (!cards.contains(targetCard)) {
|
|
continue;
|
|
}
|
|
|
|
auto *newCardRelation = new CardRelation(
|
|
card->getName(), cardRelation->getAttachType(), cardRelation->getIsCreateAllExclusion(),
|
|
cardRelation->getIsVariable(), cardRelation->getDefaultCount(), cardRelation->getIsPersistent());
|
|
cards.value(targetCard)->addReverseRelatedCards2Me(newCardRelation);
|
|
}
|
|
}
|
|
}
|
|
|
|
QStringList CardDatabase::getAllMainCardTypes() const
|
|
{
|
|
QSet<QString> types;
|
|
QHashIterator<QString, CardInfoPtr> cardIterator(cards);
|
|
while (cardIterator.hasNext()) {
|
|
types.insert(cardIterator.next().value()->getMainCardType());
|
|
}
|
|
return types.values();
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
bool CardDatabase::saveCustomTokensToFile()
|
|
{
|
|
QString fileName =
|
|
SettingsCache::instance().getCustomCardDatabasePath() + "/" + CardDatabase::TOKENS_SETNAME + ".xml";
|
|
|
|
SetNameMap tmpSets;
|
|
CardSetPtr customTokensSet = getSet(CardDatabase::TOKENS_SETNAME);
|
|
tmpSets.insert(CardDatabase::TOKENS_SETNAME, customTokensSet);
|
|
|
|
CardNameMap tmpCards;
|
|
for (const CardInfoPtr &card : cards) {
|
|
if (card->getSets().contains(CardDatabase::TOKENS_SETNAME)) {
|
|
tmpCards.insert(card->getName(), card);
|
|
}
|
|
}
|
|
|
|
availableParsers.first()->saveToFile(tmpSets, tmpCards, fileName);
|
|
return true;
|
|
}
|
|
|
|
CardRelation::CardRelation(const QString &_name,
|
|
AttachType _attachType,
|
|
bool _isCreateAllExclusion,
|
|
bool _isVariableCount,
|
|
int _defaultCount,
|
|
bool _isPersistent)
|
|
: name(_name), attachType(_attachType), isCreateAllExclusion(_isCreateAllExclusion),
|
|
isVariableCount(_isVariableCount), defaultCount(_defaultCount), isPersistent(_isPersistent)
|
|
{
|
|
}
|
|
|
|
void CardInfo::resetReverseRelatedCards2Me()
|
|
{
|
|
foreach (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);
|
|
}
|