1247 lines
No EOL
37 KiB
C++
1247 lines
No EOL
37 KiB
C++
#include "carddatabase.h"
|
|
#include "settingscache.h"
|
|
#include <QCryptographicHash>
|
|
#include <QDir>
|
|
#include <QDirIterator>
|
|
#include <QFile>
|
|
#include <QTextStream>
|
|
#include <QSettings>
|
|
#include <QSvgRenderer>
|
|
#include <QPainter>
|
|
#include <QUrl>
|
|
#include <QSet>
|
|
#include <QNetworkAccessManager>
|
|
#include <QNetworkReply>
|
|
#include <QNetworkRequest>
|
|
#include <QDebug>
|
|
#include <QImageReader>
|
|
#include <QMessageBox>
|
|
|
|
const int CardDatabase::versionNeeded = 3;
|
|
const char* CardDatabase::TOKENS_SETNAME = "TK";
|
|
|
|
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSet *set)
|
|
{
|
|
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;
|
|
}
|
|
|
|
CardSet::CardSet(const QString &_shortName, const QString &_longName, const QString &_setType, const QDate &_releaseDate)
|
|
: shortName(_shortName), longName(_longName), releaseDate(_releaseDate), setType(_setType)
|
|
{
|
|
loadSetOptions();
|
|
}
|
|
|
|
QString CardSet::getCorrectedShortName() const
|
|
{
|
|
// Because windows is horrible.
|
|
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::setSortKey(unsigned int _sortKey)
|
|
{
|
|
sortKey = _sortKey;
|
|
|
|
QSettings settings;
|
|
settings.beginGroup("sets");
|
|
settings.beginGroup(shortName);
|
|
settings.setValue("sortkey", sortKey);
|
|
}
|
|
|
|
void CardSet::loadSetOptions()
|
|
{
|
|
QSettings settings;
|
|
settings.beginGroup("sets");
|
|
settings.beginGroup(shortName);
|
|
|
|
sortKey = settings.value("sortkey", 0).toInt();
|
|
enabled = settings.value("enabled", false).toBool();
|
|
isknown = settings.value("isknown", false).toBool();
|
|
// qDebug() << "load set" << shortName << "key" << sortKey;
|
|
}
|
|
|
|
void CardSet::setEnabled(bool _enabled)
|
|
{
|
|
enabled = _enabled;
|
|
|
|
QSettings settings;
|
|
settings.beginGroup("sets");
|
|
settings.beginGroup(shortName);
|
|
settings.setValue("enabled", enabled);
|
|
}
|
|
|
|
void CardSet::setIsKnown(bool _isknown)
|
|
{
|
|
isknown = _isknown;
|
|
|
|
QSettings settings;
|
|
settings.beginGroup("sets");
|
|
settings.beginGroup(shortName);
|
|
settings.setValue("isknown", isknown);
|
|
}
|
|
|
|
class SetList::KeyCompareFunctor {
|
|
public:
|
|
inline bool operator()(CardSet *a, CardSet *b) const
|
|
{
|
|
return a->getSortKey() < b->getSortKey();
|
|
}
|
|
};
|
|
|
|
void SetList::sortByKey()
|
|
{
|
|
qSort(begin(), end(), KeyCompareFunctor());
|
|
}
|
|
|
|
class SetList::EnabledAndKeyCompareFunctor {
|
|
public:
|
|
inline bool operator()(CardSet *a, CardSet *b) const
|
|
{
|
|
if(a->getEnabled())
|
|
{
|
|
if(b->getEnabled())
|
|
{
|
|
// both enabled: sort by key
|
|
return a->getSortKey() < b->getSortKey();
|
|
} else {
|
|
// only a enabled
|
|
return true;
|
|
}
|
|
} else {
|
|
if(b->getEnabled())
|
|
{
|
|
// only b enabled
|
|
return false;
|
|
} else {
|
|
// both disabled: sort by key
|
|
return a->getSortKey() < b->getSortKey();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void SetList::sortByEnabledAndKey()
|
|
{
|
|
qSort(begin(), end(), EnabledAndKeyCompareFunctor());
|
|
}
|
|
|
|
int SetList::getEnabledSetsNum()
|
|
{
|
|
int num=0;
|
|
for (int i = 0; i < size(); ++i)
|
|
{
|
|
CardSet *set = at(i);
|
|
if(set->getEnabled())
|
|
++num;
|
|
}
|
|
return num;
|
|
}
|
|
|
|
int SetList::getUnknownSetsNum()
|
|
{
|
|
int num=0;
|
|
for (int i = 0; i < size(); ++i)
|
|
{
|
|
CardSet *set = at(i);
|
|
if(!set->getIsKnown())
|
|
++num;
|
|
}
|
|
return num;
|
|
}
|
|
|
|
void SetList::enableAllUnknown()
|
|
{
|
|
for (int i = 0; i < size(); ++i)
|
|
{
|
|
CardSet *set = at(i);
|
|
if(!set->getIsKnown())
|
|
{
|
|
set->setIsKnown(true);
|
|
set->setEnabled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetList::enableAll()
|
|
{
|
|
for (int i = 0; i < size(); ++i)
|
|
{
|
|
CardSet *set = at(i);
|
|
set->setIsKnown(true);
|
|
set->setEnabled(true);
|
|
}
|
|
}
|
|
|
|
void SetList::markAllAsKnown()
|
|
{
|
|
for (int i = 0; i < size(); ++i)
|
|
{
|
|
CardSet *set = at(i);
|
|
if(!set->getIsKnown())
|
|
{
|
|
set->setIsKnown(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetList::guessSortKeys()
|
|
{
|
|
// sort by release date DESC; invalid dates to the bottom.
|
|
QDate distantFuture(2050, 1, 1);
|
|
int aHundredYears = 36500;
|
|
for (int i = 0; i < size(); ++i)
|
|
{
|
|
CardSet *set = at(i);
|
|
QDate date = set->getReleaseDate();
|
|
if(date.isNull())
|
|
set->setSortKey(aHundredYears);
|
|
else
|
|
set->setSortKey(date.daysTo(distantFuture));
|
|
}
|
|
}
|
|
|
|
PictureToLoad::PictureToLoad(CardInfo *_card, bool _hq)
|
|
: card(_card), setIndex(0), hq(_hq)
|
|
{
|
|
if (card) {
|
|
sortedSets = card->getSets();
|
|
sortedSets.sortByEnabledAndKey();
|
|
}
|
|
}
|
|
|
|
bool PictureToLoad::nextSet()
|
|
{
|
|
if (setIndex == sortedSets.size() - 1)
|
|
return false;
|
|
++setIndex;
|
|
return true;
|
|
}
|
|
|
|
QString PictureToLoad::getSetName() const
|
|
{
|
|
if (setIndex < sortedSets.size())
|
|
return sortedSets[setIndex]->getCorrectedShortName();
|
|
else
|
|
return QString("");
|
|
}
|
|
|
|
CardSet *PictureToLoad::getCurrentSet() const
|
|
{
|
|
if (setIndex < sortedSets.size())
|
|
return sortedSets[setIndex];
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
QStringList PictureLoader::md5Blacklist = QStringList()
|
|
<< "db0c48db407a907c16ade38de048a441"; // card back returned by gatherer when card is not found
|
|
|
|
PictureLoader::PictureLoader(const QString &__picsPath, bool _picDownload, bool _picDownloadHq, QObject *parent)
|
|
: QObject(parent),
|
|
_picsPath(__picsPath), picDownload(_picDownload), picDownloadHq(_picDownloadHq),
|
|
downloadRunning(false), loadQueueRunning(false)
|
|
{
|
|
connect(this, SIGNAL(startLoadQueue()), this, SLOT(processLoadQueue()), Qt::QueuedConnection);
|
|
|
|
networkManager = new QNetworkAccessManager(this);
|
|
connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(picDownloadFinished(QNetworkReply *)));
|
|
}
|
|
|
|
PictureLoader::~PictureLoader()
|
|
{
|
|
// This does not work with the destroyed() signal as this destructor is called after the main event loop is done.
|
|
thread()->quit();
|
|
}
|
|
|
|
void PictureLoader::processLoadQueue()
|
|
{
|
|
if (loadQueueRunning)
|
|
return;
|
|
|
|
loadQueueRunning = true;
|
|
forever {
|
|
mutex.lock();
|
|
if (loadQueue.isEmpty()) {
|
|
mutex.unlock();
|
|
loadQueueRunning = false;
|
|
return;
|
|
}
|
|
cardBeingLoaded = loadQueue.takeFirst();
|
|
mutex.unlock();
|
|
|
|
QString setName = cardBeingLoaded.getSetName();
|
|
QString correctedCardname = cardBeingLoaded.getCard()->getCorrectedName();
|
|
qDebug() << "Trying to load picture (set: " << setName << " card: " << correctedCardname << ")";
|
|
|
|
//The list of paths to the folders in which to search for images
|
|
QList<QString> picsPaths = QList<QString>() << _picsPath + "/CUSTOM/" + correctedCardname;
|
|
|
|
if(!setName.isEmpty())
|
|
{
|
|
picsPaths << _picsPath + "/" + setName + "/" + correctedCardname
|
|
<< _picsPath + "/downloadedPics/" + setName + "/" + correctedCardname;
|
|
}
|
|
|
|
QImage image;
|
|
QImageReader imgReader;
|
|
imgReader.setDecideFormatFromContent(true);
|
|
bool found = false;
|
|
|
|
//Iterates through the list of paths, searching for images with the desired name with any QImageReader-supported extension
|
|
for (int i = 0; i < picsPaths.length() && !found; i ++) {
|
|
imgReader.setFileName(picsPaths.at(i));
|
|
if (imgReader.read(&image)) {
|
|
qDebug() << "Picture found on disk (set: " << setName << " card: " << correctedCardname << ")";
|
|
emit imageLoaded(cardBeingLoaded.getCard(), image);
|
|
found = true;
|
|
break;
|
|
}
|
|
imgReader.setFileName(picsPaths.at(i) + ".full");
|
|
if (imgReader.read(&image)) {
|
|
qDebug() << "Picture.full found on disk (set: " << setName << " card: " << correctedCardname << ")";
|
|
emit imageLoaded(cardBeingLoaded.getCard(), image);
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
if (picDownload) {
|
|
qDebug() << "Picture NOT found, trying to download (set: " << setName << " card: " << correctedCardname << ")";
|
|
cardsToDownload.append(cardBeingLoaded);
|
|
cardBeingLoaded=0;
|
|
if (!downloadRunning)
|
|
startNextPicDownload();
|
|
} else {
|
|
if (cardBeingLoaded.nextSet())
|
|
{
|
|
qDebug() << "Picture NOT found and download disabled, moving to next set (newset: " << setName << " card: " << correctedCardname << ")";
|
|
mutex.lock();
|
|
loadQueue.prepend(cardBeingLoaded);
|
|
cardBeingLoaded=0;
|
|
mutex.unlock();
|
|
} else {
|
|
qDebug() << "Picture NOT found, download disabled, no more sets to try: BAILING OUT (oldset: " << setName << " card: " << correctedCardname << ")";
|
|
emit imageLoaded(cardBeingLoaded.getCard(), QImage());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
QString PictureLoader::getPicUrl()
|
|
{
|
|
if (!picDownload) return QString("");
|
|
|
|
CardInfo *card = cardBeingDownloaded.getCard();
|
|
CardSet *set=cardBeingDownloaded.getCurrentSet();
|
|
QString picUrl = QString("");
|
|
|
|
// if sets have been defined for the card, they can contain custom picUrls
|
|
if(set)
|
|
{
|
|
// first check if Hq is enabled and a custom Hq card url exists in cards.xml
|
|
if(picDownloadHq)
|
|
{
|
|
picUrl = card->getCustomPicURLHq(set->getShortName());
|
|
if (!picUrl.isEmpty())
|
|
return picUrl;
|
|
}
|
|
|
|
// then, test for a custom, non-Hq card url in cards.xml
|
|
picUrl = card->getCustomPicURL(set->getShortName());
|
|
if (!picUrl.isEmpty())
|
|
return picUrl;
|
|
}
|
|
|
|
// if a card has a muid, use the default url; if not, use the fallback
|
|
int muid = set ? card->getMuId(set->getShortName()) : 0;
|
|
if(muid)
|
|
picUrl = picDownloadHq ? settingsCache->getPicUrlHq() : settingsCache->getPicUrl();
|
|
else
|
|
picUrl = picDownloadHq ? settingsCache->getPicUrlHqFallback() : settingsCache->getPicUrlFallback();
|
|
|
|
picUrl.replace("!name!", QUrl::toPercentEncoding(card->getCorrectedName()));
|
|
picUrl.replace("!name_lower!", QUrl::toPercentEncoding(card->getCorrectedName().toLower()));
|
|
picUrl.replace("!cardid!", QUrl::toPercentEncoding(QString::number(muid)));
|
|
if (set)
|
|
{
|
|
picUrl.replace("!setcode!", QUrl::toPercentEncoding(set->getShortName()));
|
|
picUrl.replace("!setcode_lower!", QUrl::toPercentEncoding(set->getShortName().toLower()));
|
|
picUrl.replace("!setname!", QUrl::toPercentEncoding(set->getLongName()));
|
|
picUrl.replace("!setname_lower!", QUrl::toPercentEncoding(set->getLongName().toLower()));
|
|
}
|
|
|
|
if (
|
|
picUrl.contains("!name!") ||
|
|
picUrl.contains("!name_lower!") ||
|
|
picUrl.contains("!setcode!") ||
|
|
picUrl.contains("!setcode_lower!") ||
|
|
picUrl.contains("!setname!") ||
|
|
picUrl.contains("!setname_lower!") ||
|
|
picUrl.contains("!cardid!")
|
|
)
|
|
{
|
|
qDebug() << "Insufficient card data to download" << card->getName() << "Url:" << picUrl;
|
|
return QString("");
|
|
}
|
|
|
|
return picUrl;
|
|
}
|
|
|
|
void PictureLoader::startNextPicDownload()
|
|
{
|
|
if (cardsToDownload.isEmpty()) {
|
|
cardBeingDownloaded = 0;
|
|
downloadRunning = false;
|
|
return;
|
|
}
|
|
|
|
downloadRunning = true;
|
|
|
|
cardBeingDownloaded = cardsToDownload.takeFirst();
|
|
|
|
QString picUrl = getPicUrl();
|
|
if (picUrl.isEmpty()) {
|
|
downloadRunning = false;
|
|
picDownloadFailed();
|
|
} else {
|
|
QUrl url(picUrl);
|
|
|
|
QNetworkRequest req(url);
|
|
qDebug() << "starting picture download:" << cardBeingDownloaded.getCard()->getName() << "Url:" << req.url();
|
|
networkManager->get(req);
|
|
}
|
|
}
|
|
|
|
void PictureLoader::picDownloadFailed()
|
|
{
|
|
if (cardBeingDownloaded.nextSet())
|
|
{
|
|
qDebug() << "Picture NOT found, download failed, moving to next set (newset: " << cardBeingDownloaded.getSetName() << " card: " << cardBeingDownloaded.getCard()->getCorrectedName() << ")";
|
|
mutex.lock();
|
|
loadQueue.prepend(cardBeingDownloaded);
|
|
mutex.unlock();
|
|
emit startLoadQueue();
|
|
} else {
|
|
qDebug() << "Picture NOT found, download failed, no more sets to try: BAILING OUT (oldset: " << cardBeingDownloaded.getSetName() << " card: " << cardBeingDownloaded.getCard()->getCorrectedName() << ")";
|
|
cardBeingDownloaded = 0;
|
|
emit imageLoaded(cardBeingDownloaded.getCard(), QImage());
|
|
}
|
|
}
|
|
|
|
void PictureLoader::picDownloadFinished(QNetworkReply *reply)
|
|
{
|
|
QString picsPath = _picsPath;
|
|
if (reply->error()) {
|
|
qDebug() << "Download failed:" << reply->errorString();
|
|
}
|
|
|
|
const QByteArray &picData = reply->peek(reply->size()); //peek is used to keep the data in the buffer for use by QImageReader
|
|
|
|
// check if the image is blacklisted
|
|
QString md5sum = QCryptographicHash::hash(picData, QCryptographicHash::Md5).toHex();
|
|
if(md5Blacklist.contains(md5sum))
|
|
{
|
|
qDebug() << "Picture downloaded, but blacklisted (" << md5sum << "), will consider it as not found";
|
|
picDownloadFailed();
|
|
reply->deleteLater();
|
|
startNextPicDownload();
|
|
return;
|
|
}
|
|
|
|
QImage testImage;
|
|
|
|
QImageReader imgReader;
|
|
imgReader.setDecideFormatFromContent(true);
|
|
imgReader.setDevice(reply);
|
|
QString extension = "." + imgReader.format(); //the format is determined prior to reading the QImageReader data into a QImage object, as that wipes the QImageReader buffer
|
|
if (extension == ".jpeg")
|
|
extension = ".jpg";
|
|
|
|
if (imgReader.read(&testImage)) {
|
|
QString setName = cardBeingDownloaded.getSetName();
|
|
if(!setName.isEmpty())
|
|
{
|
|
if (!QDir().mkpath(picsPath + "/downloadedPics/" + setName)) {
|
|
qDebug() << picsPath + "/downloadedPics/" + setName + " could not be created.";
|
|
return;
|
|
}
|
|
|
|
QFile newPic(picsPath + "/downloadedPics/" + setName + "/" + cardBeingDownloaded.getCard()->getCorrectedName() + extension);
|
|
if (!newPic.open(QIODevice::WriteOnly))
|
|
return;
|
|
newPic.write(picData);
|
|
newPic.close();
|
|
}
|
|
|
|
emit imageLoaded(cardBeingDownloaded.getCard(), testImage);
|
|
} else {
|
|
picDownloadFailed();
|
|
}
|
|
|
|
reply->deleteLater();
|
|
startNextPicDownload();
|
|
}
|
|
|
|
void PictureLoader::loadImage(CardInfo *card)
|
|
{
|
|
QMutexLocker locker(&mutex);
|
|
|
|
// avoid queueing the same card more than once
|
|
if(card == cardBeingLoaded.getCard() || card == cardBeingDownloaded.getCard())
|
|
return;
|
|
|
|
foreach(PictureToLoad pic, loadQueue)
|
|
{
|
|
if(pic.getCard() == card)
|
|
return;
|
|
}
|
|
|
|
loadQueue.append(PictureToLoad(card));
|
|
emit startLoadQueue();
|
|
}
|
|
|
|
void PictureLoader::setPicsPath(const QString &path)
|
|
{
|
|
QMutexLocker locker(&mutex);
|
|
_picsPath = path;
|
|
}
|
|
|
|
void PictureLoader::setPicDownload(bool _picDownload)
|
|
{
|
|
QMutexLocker locker(&mutex);
|
|
picDownload = _picDownload;
|
|
}
|
|
|
|
void PictureLoader::setPicDownloadHq(bool _picDownloadHq)
|
|
{
|
|
QMutexLocker locker(&mutex);
|
|
picDownloadHq = _picDownloadHq;
|
|
}
|
|
|
|
CardInfo::CardInfo(CardDatabase *_db,
|
|
const QString &_name,
|
|
bool _isToken,
|
|
const QString &_manacost,
|
|
const QString &_cmc,
|
|
const QString &_cardtype,
|
|
const QString &_powtough,
|
|
const QString &_text,
|
|
const QStringList &_colors,
|
|
const QStringList &_relatedCards,
|
|
bool _upsideDownArt,
|
|
int _loyalty,
|
|
bool _cipt,
|
|
int _tableRow,
|
|
const SetList &_sets,
|
|
const QStringMap &_customPicURLs,
|
|
const QStringMap &_customPicURLsHq,
|
|
MuidMap _muIds
|
|
)
|
|
: db(_db),
|
|
name(_name),
|
|
isToken(_isToken),
|
|
sets(_sets),
|
|
manacost(_manacost),
|
|
cmc(_cmc),
|
|
cardtype(_cardtype),
|
|
powtough(_powtough),
|
|
text(_text),
|
|
colors(_colors),
|
|
relatedCards(_relatedCards),
|
|
upsideDownArt(_upsideDownArt),
|
|
loyalty(_loyalty),
|
|
customPicURLs(_customPicURLs),
|
|
customPicURLsHq(_customPicURLsHq),
|
|
muIds(_muIds),
|
|
cipt(_cipt),
|
|
tableRow(_tableRow)
|
|
{
|
|
pixmapCacheKey = QLatin1String("card_") + name;
|
|
simpleName = CardInfo::simplifyName(name);
|
|
|
|
for (int i = 0; i < sets.size(); i++)
|
|
sets[i]->append(this);
|
|
}
|
|
|
|
CardInfo::~CardInfo()
|
|
{
|
|
clearPixmapCache();
|
|
}
|
|
|
|
QString CardInfo::getMainCardType() const
|
|
{
|
|
QString result = getCardType();
|
|
/*
|
|
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;
|
|
}
|
|
|
|
QString CardInfo::getCorrectedName() const
|
|
{
|
|
QString result = name;
|
|
// Fire // Ice, Circle of Protection: Red, "Ach! Hans, Run!", Who/What/When/Where/Why, Question Elemental?
|
|
return result.remove(" // ").remove(':').remove('"').remove('?').replace('/', ' ');
|
|
}
|
|
|
|
void CardInfo::addToSet(CardSet *set)
|
|
{
|
|
set->append(this);
|
|
sets << set;
|
|
}
|
|
|
|
void CardInfo::loadPixmap(QPixmap &pixmap)
|
|
{
|
|
if(QPixmapCache::find(pixmapCacheKey, &pixmap))
|
|
return;
|
|
|
|
pixmap = QPixmap();
|
|
|
|
if (getName().isEmpty()) {
|
|
pixmap.load(settingsCache->getCardBackPicturePath());
|
|
return;
|
|
}
|
|
|
|
db->loadImage(this);
|
|
}
|
|
|
|
void CardInfo::imageLoaded(const QImage &image)
|
|
{
|
|
if (!image.isNull()) {
|
|
if(upsideDownArt)
|
|
{
|
|
QImage mirrorImage = image.mirrored(true, true);
|
|
QPixmapCache::insert(pixmapCacheKey, QPixmap::fromImage(mirrorImage));
|
|
} else {
|
|
QPixmapCache::insert(pixmapCacheKey, QPixmap::fromImage(image));
|
|
}
|
|
emit pixmapUpdated();
|
|
}
|
|
}
|
|
|
|
void CardInfo::getPixmap(QSize size, QPixmap &pixmap)
|
|
{
|
|
QString key = QLatin1String("card_") + name + QLatin1Char('_') + QString::number(size.width()) + QString::number(size.height());
|
|
if(QPixmapCache::find(key, &pixmap))
|
|
return;
|
|
|
|
QPixmap bigPixmap;
|
|
loadPixmap(bigPixmap);
|
|
if (bigPixmap.isNull()) {
|
|
if (!getName().isEmpty()) {
|
|
pixmap = QPixmap(); // null
|
|
return;
|
|
} else {
|
|
QSvgRenderer svg(QString(":/back.svg"));
|
|
bigPixmap = QPixmap(svg.defaultSize());
|
|
bigPixmap.fill(Qt::transparent);
|
|
QPainter painter(&bigPixmap);
|
|
svg.render(&painter);
|
|
}
|
|
}
|
|
|
|
pixmap = bigPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
QPixmapCache::insert(key, pixmap);
|
|
}
|
|
|
|
void CardInfo::clearPixmapCache()
|
|
{
|
|
//qDebug() << "Deleting pixmap for" << name;
|
|
QPixmapCache::remove(pixmapCacheKey);
|
|
}
|
|
|
|
void CardInfo::clearPixmapCacheMiss()
|
|
{
|
|
QPixmap pixmap;
|
|
if(!QPixmapCache::find(pixmapCacheKey, &pixmap))
|
|
return;
|
|
|
|
if (pixmap.isNull())
|
|
clearPixmapCache();
|
|
}
|
|
|
|
void CardInfo::updatePixmapCache()
|
|
{
|
|
qDebug() << "Updating pixmap cache for" << name;
|
|
clearPixmapCache();
|
|
QPixmap tmp;
|
|
loadPixmap(tmp);
|
|
|
|
emit pixmapUpdated();
|
|
}
|
|
|
|
QString CardInfo::simplifyName(const QString &name) {
|
|
QString simpleName(name);
|
|
|
|
// So Aetherling would work, but not Ætherling since 'Æ' would get replaced
|
|
// with nothing.
|
|
simpleName.replace("æ", "ae");
|
|
simpleName.replace("Æ", "AE");
|
|
|
|
// Replace Jötun Grunt with Jotun Grunt.
|
|
simpleName = simpleName.normalized(QString::NormalizationForm_KD);
|
|
|
|
// Replace dashes with spaces so that we can say "garruk the veil cursed"
|
|
// instead of the unintuitive "garruk the veilcursed".
|
|
simpleName = simpleName.replace("-", " ");
|
|
|
|
simpleName.remove(QRegExp("[^a-zA-Z0-9 ]"));
|
|
simpleName = simpleName.toLower();
|
|
return simpleName;
|
|
}
|
|
|
|
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfo *info)
|
|
{
|
|
xml.writeStartElement("card");
|
|
xml.writeTextElement("name", info->getName());
|
|
|
|
const SetList &sets = info->getSets();
|
|
QString tmpString;
|
|
QString tmpSet;
|
|
for (int i = 0; i < sets.size(); i++) {
|
|
xml.writeStartElement("set");
|
|
|
|
tmpSet=sets[i]->getShortName();
|
|
xml.writeAttribute("muId", QString::number(info->getMuId(tmpSet)));
|
|
|
|
tmpString = info->getCustomPicURL(tmpSet);
|
|
if(!tmpString.isEmpty())
|
|
xml.writeAttribute("picURL", tmpString);
|
|
|
|
tmpString = info->getCustomPicURLHq(tmpSet);
|
|
if(!tmpString.isEmpty())
|
|
xml.writeAttribute("picURLHq", tmpString);
|
|
|
|
xml.writeCharacters(tmpSet);
|
|
xml.writeEndElement();
|
|
}
|
|
const QStringList &colors = info->getColors();
|
|
for (int i = 0; i < colors.size(); i++)
|
|
xml.writeTextElement("color", colors[i]);
|
|
|
|
const QStringList &related = info->getRelatedCards();
|
|
for (int i = 0; i < related.size(); i++)
|
|
xml.writeTextElement("related", related[i]);
|
|
|
|
xml.writeTextElement("manacost", info->getManaCost());
|
|
xml.writeTextElement("cmc", info->getCmc());
|
|
xml.writeTextElement("type", info->getCardType());
|
|
if (!info->getPowTough().isEmpty())
|
|
xml.writeTextElement("pt", info->getPowTough());
|
|
xml.writeTextElement("tablerow", QString::number(info->getTableRow()));
|
|
xml.writeTextElement("text", info->getText());
|
|
if (info->getMainCardType() == "Planeswalker")
|
|
xml.writeTextElement("loyalty", QString::number(info->getLoyalty()));
|
|
if (info->getCipt())
|
|
xml.writeTextElement("cipt", "1");
|
|
if (info->getIsToken())
|
|
xml.writeTextElement("token", "1");
|
|
if (info->getUpsideDownArt())
|
|
xml.writeTextElement("upsidedown", "1");
|
|
xml.writeEndElement(); // card
|
|
|
|
return xml;
|
|
}
|
|
|
|
CardDatabase::CardDatabase(QObject *parent)
|
|
: QObject(parent), noCard(0), loadStatus(NotLoaded)
|
|
{
|
|
connect(settingsCache, SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged()));
|
|
connect(settingsCache, SIGNAL(cardDatabasePathChanged()), this, SLOT(loadCardDatabase()));
|
|
connect(settingsCache, SIGNAL(tokenDatabasePathChanged()), this, SLOT(loadTokenDatabase()));
|
|
connect(settingsCache, SIGNAL(picDownloadChanged()), this, SLOT(picDownloadChanged()));
|
|
connect(settingsCache, SIGNAL(picDownloadHqChanged()), this, SLOT(picDownloadHqChanged()));
|
|
|
|
loadCardDatabase();
|
|
loadTokenDatabase();
|
|
|
|
pictureLoaderThread = new QThread;
|
|
pictureLoader = new PictureLoader(settingsCache->getPicsPath(), settingsCache->getPicDownload(), settingsCache->getPicDownloadHq());
|
|
pictureLoader->moveToThread(pictureLoaderThread);
|
|
connect(pictureLoader, SIGNAL(imageLoaded(CardInfo *, const QImage &)), this, SLOT(imageLoaded(CardInfo *, const QImage &)));
|
|
pictureLoaderThread->start(QThread::LowPriority);
|
|
|
|
noCard = new CardInfo(this);
|
|
QPixmap tmp;
|
|
noCard->loadPixmap(tmp); // cache pixmap for card back
|
|
connect(settingsCache, SIGNAL(cardBackPicturePathChanged()), noCard, SLOT(updatePixmapCache()));
|
|
}
|
|
|
|
CardDatabase::~CardDatabase()
|
|
{
|
|
clear();
|
|
delete noCard;
|
|
|
|
pictureLoader->deleteLater();
|
|
pictureLoaderThread->wait();
|
|
delete pictureLoaderThread;
|
|
}
|
|
|
|
void CardDatabase::clear()
|
|
{
|
|
QHashIterator<QString, CardSet *> setIt(sets);
|
|
while (setIt.hasNext()) {
|
|
setIt.next();
|
|
delete setIt.value();
|
|
}
|
|
sets.clear();
|
|
|
|
QHashIterator<QString, CardInfo *> i(cards);
|
|
while (i.hasNext()) {
|
|
i.next();
|
|
delete i.value();
|
|
}
|
|
cards.clear();
|
|
|
|
// The pointers themselves were already deleted, so we don't delete them
|
|
// again.
|
|
simpleNameCards.clear();
|
|
}
|
|
|
|
void CardDatabase::addCard(CardInfo *card)
|
|
{
|
|
cards.insert(card->getName(), card);
|
|
simpleNameCards.insert(card->getSimpleName(), card);
|
|
emit cardAdded(card);
|
|
}
|
|
|
|
void CardDatabase::removeCard(CardInfo *card)
|
|
{
|
|
cards.remove(card->getName());
|
|
simpleNameCards.remove(card->getSimpleName());
|
|
emit cardRemoved(card);
|
|
}
|
|
|
|
CardInfo *CardDatabase::getCard(const QString &cardName, bool createIfNotFound) {
|
|
return getCardFromMap(cards, cardName, createIfNotFound);
|
|
}
|
|
|
|
CardInfo *CardDatabase::getCardBySimpleName(const QString &cardName, bool createIfNotFound) {
|
|
QString simpleName = CardInfo::simplifyName(cardName);
|
|
return getCardFromMap(simpleNameCards, simpleName, createIfNotFound);
|
|
}
|
|
|
|
CardSet *CardDatabase::getSet(const QString &setName)
|
|
{
|
|
if (sets.contains(setName))
|
|
return sets.value(setName);
|
|
else {
|
|
CardSet *newSet = new CardSet(setName);
|
|
sets.insert(setName, newSet);
|
|
return newSet;
|
|
}
|
|
}
|
|
|
|
SetList CardDatabase::getSetList() const
|
|
{
|
|
SetList result;
|
|
QHashIterator<QString, CardSet *> i(sets);
|
|
while (i.hasNext()) {
|
|
i.next();
|
|
result << i.value();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void CardDatabase::clearPixmapCache()
|
|
{
|
|
// This also clears the cards in simpleNameCards since they point to the
|
|
// same object.
|
|
QHashIterator<QString, CardInfo *> i(cards);
|
|
while (i.hasNext()) {
|
|
i.next();
|
|
i.value()->clearPixmapCache();
|
|
}
|
|
if (noCard)
|
|
noCard->clearPixmapCache();
|
|
|
|
QPixmapCache::clear();
|
|
}
|
|
|
|
void CardDatabase::loadSetsFromXml(QXmlStreamReader &xml)
|
|
{
|
|
while (!xml.atEnd()) {
|
|
if (xml.readNext() == QXmlStreamReader::EndElement)
|
|
break;
|
|
if (xml.name() == "set") {
|
|
QString shortName, longName, setType;
|
|
QDate releaseDate;
|
|
while (!xml.atEnd()) {
|
|
if (xml.readNext() == QXmlStreamReader::EndElement)
|
|
break;
|
|
if (xml.name() == "name")
|
|
shortName = xml.readElementText();
|
|
else if (xml.name() == "longname")
|
|
longName = xml.readElementText();
|
|
else if (xml.name() == "settype")
|
|
setType = xml.readElementText();
|
|
else if (xml.name() == "releasedate")
|
|
releaseDate = QDate::fromString(xml.readElementText(), Qt::ISODate);
|
|
}
|
|
|
|
CardSet * newSet = getSet(shortName);
|
|
newSet->setLongName(longName);
|
|
newSet->setSetType(setType);
|
|
newSet->setReleaseDate(releaseDate);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CardDatabase::loadCardsFromXml(QXmlStreamReader &xml, bool tokens)
|
|
{
|
|
while (!xml.atEnd()) {
|
|
if (xml.readNext() == QXmlStreamReader::EndElement)
|
|
break;
|
|
if (xml.name() == "card") {
|
|
QString name, manacost, cmc, type, pt, text;
|
|
QStringList colors, relatedCards;
|
|
QStringMap customPicURLs, customPicURLsHq;
|
|
MuidMap muids;
|
|
SetList sets;
|
|
int tableRow = 0;
|
|
int loyalty = 0;
|
|
bool cipt = false;
|
|
bool isToken = false;
|
|
bool upsideDown = false;
|
|
while (!xml.atEnd()) {
|
|
if (xml.readNext() == QXmlStreamReader::EndElement)
|
|
break;
|
|
if (xml.name() == "name")
|
|
name = xml.readElementText();
|
|
else if (xml.name() == "manacost")
|
|
manacost = xml.readElementText();
|
|
else if (xml.name() == "cmc")
|
|
cmc = xml.readElementText();
|
|
else if (xml.name() == "type")
|
|
type = xml.readElementText();
|
|
else if (xml.name() == "pt")
|
|
pt = xml.readElementText();
|
|
else if (xml.name() == "text")
|
|
text = xml.readElementText();
|
|
else if (xml.name() == "set") {
|
|
QXmlStreamAttributes attrs = xml.attributes();
|
|
QString setName = xml.readElementText();
|
|
sets.append(getSet(setName));
|
|
if (attrs.hasAttribute("muId")) {
|
|
muids[setName] = attrs.value("muId").toString().toInt();
|
|
}
|
|
if (attrs.hasAttribute("picURL")) {
|
|
customPicURLs[setName] = attrs.value("picURL").toString();
|
|
}
|
|
if (attrs.hasAttribute("picURLHq")) {
|
|
customPicURLsHq[setName] = attrs.value("picURLHq").toString();
|
|
}
|
|
} else if (xml.name() == "color")
|
|
colors << xml.readElementText();
|
|
else if (xml.name() == "related")
|
|
relatedCards << xml.readElementText();
|
|
else if (xml.name() == "tablerow")
|
|
tableRow = xml.readElementText().toInt();
|
|
else if (xml.name() == "cipt")
|
|
cipt = (xml.readElementText() == "1");
|
|
else if (xml.name() == "upsidedown")
|
|
upsideDown = (xml.readElementText() == "1");
|
|
else if (xml.name() == "loyalty")
|
|
loyalty = xml.readElementText().toInt();
|
|
else if (xml.name() == "token")
|
|
isToken = xml.readElementText().toInt();
|
|
}
|
|
|
|
if (isToken == tokens) {
|
|
addCard(new CardInfo(this, name, isToken, manacost, cmc, type, pt, text, colors, relatedCards, upsideDown, loyalty, cipt, tableRow, sets, customPicURLs, customPicURLsHq, muids));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CardInfo *CardDatabase::getCardFromMap(CardNameMap &cardMap, const QString &cardName, bool createIfNotFound) {
|
|
if (cardName.isEmpty())
|
|
return noCard;
|
|
else if (cardMap.contains(cardName))
|
|
return cardMap.value(cardName);
|
|
else if (createIfNotFound) {
|
|
CardInfo *newCard = new CardInfo(this, cardName, true);
|
|
newCard->addToSet(getSet(CardDatabase::TOKENS_SETNAME));
|
|
cardMap.insert(cardName, newCard);
|
|
return newCard;
|
|
} else
|
|
return 0;
|
|
}
|
|
|
|
LoadStatus CardDatabase::loadFromFile(const QString &fileName, bool tokens)
|
|
{
|
|
QFile file(fileName);
|
|
file.open(QIODevice::ReadOnly);
|
|
if (!file.isOpen())
|
|
return FileError;
|
|
|
|
QXmlStreamReader xml(&file);
|
|
while (!xml.atEnd()) {
|
|
if (xml.readNext() == QXmlStreamReader::StartElement) {
|
|
if (xml.name() != "cockatrice_carddatabase")
|
|
return Invalid;
|
|
int version = xml.attributes().value("version").toString().toInt();
|
|
if (version < versionNeeded) {
|
|
qDebug() << "loadFromFile(): Version too old: " << version;
|
|
return VersionTooOld;
|
|
}
|
|
while (!xml.atEnd()) {
|
|
if (xml.readNext() == QXmlStreamReader::EndElement)
|
|
break;
|
|
if (xml.name() == "sets")
|
|
loadSetsFromXml(xml);
|
|
else if (xml.name() == "cards")
|
|
loadCardsFromXml(xml, tokens);
|
|
}
|
|
}
|
|
}
|
|
qDebug() << cards.size() << "cards in" << sets.size() << "sets loaded";
|
|
|
|
if (cards.isEmpty()) return NoCards;
|
|
|
|
return Ok;
|
|
}
|
|
|
|
bool CardDatabase::saveToFile(const QString &fileName, bool tokens)
|
|
{
|
|
QFile file(fileName);
|
|
if (!file.open(QIODevice::WriteOnly))
|
|
return false;
|
|
QXmlStreamWriter xml(&file);
|
|
|
|
xml.setAutoFormatting(true);
|
|
xml.writeStartDocument();
|
|
xml.writeStartElement("cockatrice_carddatabase");
|
|
xml.writeAttribute("version", QString::number(versionNeeded));
|
|
|
|
if (!tokens) {
|
|
xml.writeStartElement("sets");
|
|
QHashIterator<QString, CardSet *> setIterator(sets);
|
|
while (setIterator.hasNext())
|
|
xml << setIterator.next().value();
|
|
xml.writeEndElement(); // sets
|
|
}
|
|
|
|
xml.writeStartElement("cards");
|
|
QHashIterator<QString, CardInfo *> cardIterator(cards);
|
|
while (cardIterator.hasNext()) {
|
|
CardInfo *card = cardIterator.next().value();
|
|
if (tokens == card->getIsToken()) {
|
|
xml << card;
|
|
}
|
|
}
|
|
xml.writeEndElement(); // cards
|
|
|
|
xml.writeEndElement(); // cockatrice_carddatabase
|
|
xml.writeEndDocument();
|
|
|
|
return true;
|
|
}
|
|
|
|
void CardDatabase::picDownloadChanged()
|
|
{
|
|
pictureLoader->setPicDownload(settingsCache->getPicDownload());
|
|
if (settingsCache->getPicDownload()) {
|
|
QHashIterator<QString, CardInfo *> cardIterator(cards);
|
|
while (cardIterator.hasNext())
|
|
cardIterator.next().value()->clearPixmapCacheMiss();
|
|
}
|
|
}
|
|
|
|
void CardDatabase::picDownloadHqChanged()
|
|
{
|
|
pictureLoader->setPicDownloadHq(settingsCache->getPicDownloadHq());
|
|
if (settingsCache->getPicDownloadHq()) {
|
|
QHashIterator<QString, CardInfo *> cardIterator(cards);
|
|
while (cardIterator.hasNext())
|
|
cardIterator.next().value()->clearPixmapCacheMiss();
|
|
}
|
|
}
|
|
|
|
void CardDatabase::emitCardListChanged()
|
|
{
|
|
emit cardListChanged();
|
|
}
|
|
|
|
LoadStatus CardDatabase::loadCardDatabase(const QString &path, bool tokens)
|
|
{
|
|
LoadStatus tempLoadStatus = NotLoaded;
|
|
if (!path.isEmpty())
|
|
tempLoadStatus = loadFromFile(path, tokens);
|
|
|
|
if (tempLoadStatus == Ok) {
|
|
SetList allSets;
|
|
QHashIterator<QString, CardSet *> setsIterator(sets);
|
|
while (setsIterator.hasNext())
|
|
allSets.append(setsIterator.next().value());
|
|
allSets.sortByKey();
|
|
|
|
if(!tokens)
|
|
checkUnknownSets();
|
|
emit cardListChanged();
|
|
}
|
|
|
|
if (!tokens) {
|
|
loadStatus = tempLoadStatus;
|
|
qDebug() << "loadCardDatabase(): Path = " << path << " Status = " << loadStatus;
|
|
}
|
|
|
|
|
|
return tempLoadStatus;
|
|
}
|
|
|
|
void CardDatabase::loadCardDatabase()
|
|
{
|
|
loadCardDatabase(settingsCache->getCardDatabasePath(), false);
|
|
}
|
|
|
|
void CardDatabase::loadTokenDatabase()
|
|
{
|
|
loadCardDatabase(settingsCache->getTokenDatabasePath(), true);
|
|
}
|
|
|
|
void CardDatabase::loadCustomCardDatabases(const QString &path)
|
|
{
|
|
QDir dir(path);
|
|
if(!dir.exists())
|
|
return;
|
|
|
|
foreach(QString fileName, dir.entryList(QStringList("*.xml"), QDir::Files | QDir::Readable, QDir::Name | QDir::IgnoreCase))
|
|
{
|
|
loadCardDatabase(dir.absoluteFilePath(fileName), false);
|
|
}
|
|
}
|
|
|
|
QStringList CardDatabase::getAllColors() const
|
|
{
|
|
QSet<QString> colors;
|
|
QHashIterator<QString, CardInfo *> cardIterator(cards);
|
|
while (cardIterator.hasNext()) {
|
|
const QStringList &cardColors = cardIterator.next().value()->getColors();
|
|
if (cardColors.isEmpty())
|
|
colors.insert("X");
|
|
else
|
|
for (int i = 0; i < cardColors.size(); ++i)
|
|
colors.insert(cardColors[i]);
|
|
}
|
|
return colors.toList();
|
|
}
|
|
|
|
QStringList CardDatabase::getAllMainCardTypes() const
|
|
{
|
|
QSet<QString> types;
|
|
QHashIterator<QString, CardInfo *> cardIterator(cards);
|
|
while (cardIterator.hasNext())
|
|
types.insert(cardIterator.next().value()->getMainCardType());
|
|
return types.toList();
|
|
}
|
|
|
|
void CardDatabase::cacheCardPixmaps(const QStringList &cardNames)
|
|
{
|
|
QPixmap tmp;
|
|
// never cache more than 300 cards at once for a single deck
|
|
int max = qMin(cardNames.size(), 300);
|
|
for (int i = 0; i < max; ++i)
|
|
getCard(cardNames[i])->loadPixmap(tmp);
|
|
}
|
|
|
|
void CardDatabase::loadImage(CardInfo *card)
|
|
{
|
|
pictureLoader->loadImage(card);
|
|
}
|
|
|
|
void CardDatabase::imageLoaded(CardInfo *card, QImage image)
|
|
{
|
|
card->imageLoaded(image);
|
|
}
|
|
|
|
void CardDatabase::picsPathChanged()
|
|
{
|
|
pictureLoader->setPicsPath(settingsCache->getPicsPath());
|
|
clearPixmapCache();
|
|
}
|
|
|
|
void CardDatabase::checkUnknownSets()
|
|
{
|
|
SetList sets = getSetList();
|
|
|
|
// no set is enabled. Probably this is the first time running trice
|
|
if(!sets.getEnabledSetsNum())
|
|
{
|
|
sets.guessSortKeys();
|
|
sets.sortByKey();
|
|
sets.enableAll();
|
|
|
|
detectedFirstRun = true;
|
|
return;
|
|
}
|
|
|
|
detectedFirstRun = false;
|
|
|
|
int numUnknownSets = sets.getUnknownSetsNum();
|
|
// no unkown sets.
|
|
if(!numUnknownSets)
|
|
return;
|
|
|
|
QMessageBox msgbox(QMessageBox::Question, tr("New sets found"), tr("%1 new set(s) have been found in the card database. Do you want to enable them?").arg(numUnknownSets), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
|
|
|
|
switch(msgbox.exec())
|
|
{
|
|
case QMessageBox::No:
|
|
sets.markAllAsKnown();
|
|
break;
|
|
case QMessageBox::Yes:
|
|
sets.enableAllUnknown();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
bool CardDatabase::hasDetectedFirstRun()
|
|
{
|
|
if(detectedFirstRun)
|
|
{
|
|
detectedFirstRun=false;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
} |