diff --git a/CMakeLists.txt b/CMakeLists.txt index e60540e0..86a10e7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,9 +109,3 @@ option(WITH_ORACLE "build oracle" ON) if(WITH_ORACLE) add_subdirectory(oracle) endif() - -# Compile testclient (default off) -option(WITH_TESTCLIENT "build testclient" OFF) -if (WITH_TESTCLIENT) - add_subdirectory(testclient) -endif() \ No newline at end of file diff --git a/cockatrice/src/abstractcarddragitem.h b/cockatrice/src/abstractcarddragitem.h index e574f6c2..d7c3a40e 100644 --- a/cockatrice/src/abstractcarddragitem.h +++ b/cockatrice/src/abstractcarddragitem.h @@ -9,6 +9,7 @@ class CardInfo; class AbstractCardDragItem : public QObject, public QGraphicsItem { Q_OBJECT + Q_INTERFACES(QGraphicsItem) protected: AbstractCardItem *item; QPointF hotSpot; diff --git a/cockatrice/src/abstractcounter.h b/cockatrice/src/abstractcounter.h index 713c11c5..174cbf84 100644 --- a/cockatrice/src/abstractcounter.h +++ b/cockatrice/src/abstractcounter.h @@ -9,6 +9,7 @@ class QAction; class AbstractCounter : public QObject, public QGraphicsItem { Q_OBJECT + Q_INTERFACES(QGraphicsItem) protected: Player *player; int id; diff --git a/cockatrice/src/arrowitem.h b/cockatrice/src/arrowitem.h index 67517404..7e897d31 100644 --- a/cockatrice/src/arrowitem.h +++ b/cockatrice/src/arrowitem.h @@ -11,6 +11,7 @@ class ArrowTarget; class ArrowItem : public QObject, public QGraphicsItem { Q_OBJECT + Q_INTERFACES(QGraphicsItem) private: QPainterPath path; QMenu *menu; diff --git a/cockatrice/src/carddatabase.cpp b/cockatrice/src/carddatabase.cpp index e5c60e52..2e5c0ec7 100644 --- a/cockatrice/src/carddatabase.cpp +++ b/cockatrice/src/carddatabase.cpp @@ -14,7 +14,7 @@ #include #include -const int CardDatabase::versionNeeded = 2; +const int CardDatabase::versionNeeded = 3; CardSet::CardSet(const QString &_shortName, const QString &_longName) : shortName(_shortName), longName(_longName) @@ -80,11 +80,13 @@ bool PictureToLoad::nextSet() return true; } -PictureLoader::PictureLoader(const QString &__picsPath, bool _picDownload, QObject *parent) - : QObject(parent), _picsPath(__picsPath), picDownload(_picDownload), downloadRunning(false), loadQueueRunning(false) +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 *))); } @@ -99,7 +101,7 @@ void PictureLoader::processLoadQueue() { if (loadQueueRunning) return; - + loadQueueRunning = true; forever { mutex.lock(); @@ -113,7 +115,7 @@ void PictureLoader::processLoadQueue() QString correctedName = ptl.getCard()->getCorrectedName(); QString picsPath = _picsPath; QString setName = ptl.getSetName(); - + QImage image; if (!image.load(QString("%1/%2/%3.full.jpg").arg(picsPath).arg(setName).arg(correctedName))) if (!image.load(QString("%1/%2/%3%4.full.jpg").arg(picsPath).arg(setName).arg(correctedName).arg(1))) @@ -130,11 +132,25 @@ void PictureLoader::processLoadQueue() } continue; } - + emit imageLoaded(ptl.getCard(), image); } } +QString PictureLoader::getPicUrl(CardInfo *card) +{ + if (!picDownload) return 0; + + QString picUrl = picDownloadHq ? settingsCache->getPicUrlHq() : settingsCache->getPicUrl(); + picUrl.replace("!name!", QUrl::toPercentEncoding(card->getCorrectedName())); + CardSet *set = card->getPreferredSet(); + picUrl.replace("!setcode!", QUrl::toPercentEncoding(set->getShortName())); + picUrl.replace("!setname!", QUrl::toPercentEncoding(set->getLongName())); + picUrl.replace("!cardid!", QUrl::toPercentEncoding(QString::number(card->getPreferredMuId()))); + + return picUrl; +} + void PictureLoader::startNextPicDownload() { if (cardsToDownload.isEmpty()) { @@ -142,31 +158,28 @@ void PictureLoader::startNextPicDownload() downloadRunning = false; return; } - + downloadRunning = true; - + cardBeingDownloaded = cardsToDownload.takeFirst(); - QString picUrl; - if (cardBeingDownloaded.getStripped()) - picUrl = cardBeingDownloaded.getCard()->getPicURLSt(cardBeingDownloaded.getSetName()); - else if (cardBeingDownloaded.getHq()) { - picUrl = cardBeingDownloaded.getCard()->getPicURLHq(cardBeingDownloaded.getSetName()); - if (picUrl.isEmpty()) { - picUrl = cardBeingDownloaded.getCard()->getPicURL(cardBeingDownloaded.getSetName()); - cardBeingDownloaded.setHq(false); - } - } else - picUrl = cardBeingDownloaded.getCard()->getPicURL(cardBeingDownloaded.getSetName()); + + // TODO: Do something useful when picUrl is 0 or empty, etc + QString picUrl = getPicUrl(cardBeingDownloaded.getCard()); + QUrl url(picUrl); - + QNetworkRequest req(url); - qDebug() << "starting picture download:" << req.url(); + qDebug() << "starting picture download:" << cardBeingDownloaded.getCard()->getName() << "Url:" << req.url(); networkManager->get(req); } void PictureLoader::picDownloadFinished(QNetworkReply *reply) { QString picsPath = _picsPath; + if (reply->error()) { + qDebug() << "Download failed:" << reply->errorString(); + } + const QByteArray &picData = reply->readAll(); QImage testImage; if (testImage.loadFromData(picData)) { @@ -180,17 +193,17 @@ void PictureLoader::picDownloadFinished(QNetworkReply *reply) QDir dir(QString(picsPath + "/downloadedPics")); dir.mkdir(cardBeingDownloaded.getSetName()); } - + QString suffix; if (!cardBeingDownloaded.getStripped()) suffix = ".full"; - + QFile newPic(picsPath + "/downloadedPics/" + cardBeingDownloaded.getSetName() + "/" + cardBeingDownloaded.getCard()->getCorrectedName() + suffix + ".jpg"); if (!newPic.open(QIODevice::WriteOnly)) return; newPic.write(picData); newPic.close(); - + emit imageLoaded(cardBeingDownloaded.getCard(), testImage); } else if (cardBeingDownloaded.getHq()) { qDebug() << "HQ: received invalid picture. URL:" << reply->request().url(); @@ -207,7 +220,7 @@ void PictureLoader::picDownloadFinished(QNetworkReply *reply) } else emit imageLoaded(cardBeingDownloaded.getCard(), QImage()); } - + reply->deleteLater(); startNextPicDownload(); } @@ -215,7 +228,7 @@ void PictureLoader::picDownloadFinished(QNetworkReply *reply) void PictureLoader::loadImage(CardInfo *card, bool stripped) { QMutexLocker locker(&mutex); - + loadQueue.append(PictureToLoad(card, stripped)); emit startLoadQueue(); } @@ -232,6 +245,12 @@ void PictureLoader::setPicDownload(bool _picDownload) picDownload = _picDownload; } +void PictureLoader::setPicDownloadHq(bool _picDownloadHq) +{ + QMutexLocker locker(&mutex); + picDownloadHq = _picDownloadHq; +} + CardInfo::CardInfo(CardDatabase *_db, const QString &_name, bool _isToken, @@ -244,22 +263,18 @@ CardInfo::CardInfo(CardDatabase *_db, bool _cipt, int _tableRow, const SetList &_sets, - const QMap &_picURLs, - const QMap &_picURLsHq, - const QMap &_picURLsSt) + QMap _muIds) : db(_db), name(_name), isToken(_isToken), sets(_sets), + muIds(_muIds), manacost(_manacost), cardtype(_cardtype), powtough(_powtough), text(_text), colors(_colors), loyalty(_loyalty), - picURLs(_picURLs), - picURLsHq(_picURLsHq), - picURLsSt(_picURLsSt), cipt(_cipt), tableRow(_tableRow), pixmap(NULL) @@ -284,6 +299,8 @@ QString CardInfo::getMainCardType() const 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(); @@ -315,19 +332,12 @@ void CardInfo::addToSet(CardSet *set) sets << set; } -QString CardInfo::getPicURL() const -{ - SetList sortedSets = sets; - sortedSets.sortByKey(); - return picURLs.value(sortedSets.first()->getShortName()); -} - QPixmap *CardInfo::loadPixmap() { if (pixmap) return pixmap; pixmap = new QPixmap(); - + if (getName().isEmpty()) { pixmap->load(settingsCache->getCardBackPicturePath()); return pixmap; @@ -400,18 +410,33 @@ void CardInfo::updatePixmapCache() emit pixmapUpdated(); } +CardSet* CardInfo::getPreferredSet() +{ + SetList sortedSets = sets; + sortedSets.sortByKey(); + return sortedSets.first(); +} + +int CardInfo::getPreferredMuId() +{ + return muIds[getPreferredSet()->getShortName()]; +} + 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"); - xml.writeAttribute("picURL", info->getPicURL(sets[i]->getShortName())); - xml.writeAttribute("picURLHq", info->getPicURLHq(sets[i]->getShortName())); - xml.writeAttribute("picURLSt", info->getPicURLSt(sets[i]->getShortName())); - xml.writeCharacters(sets[i]->getShortName()); + + tmpSet=sets[i]->getShortName(); + xml.writeAttribute("muId", QString::number(info->getMuId(tmpSet))); + + xml.writeCharacters(tmpSet); xml.writeEndElement(); } const QStringList &colors = info->getColors(); @@ -436,18 +461,19 @@ QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfo *info) } CardDatabase::CardDatabase(QObject *parent) - : QObject(parent), loadSuccess(false), noCard(0) + : QObject(parent), loadStatus(NotLoaded), noCard(0) { 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()); + 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); @@ -572,7 +598,7 @@ void CardDatabase::loadCardsFromXml(QXmlStreamReader &xml) if (xml.name() == "card") { QString name, manacost, type, pt, text; QStringList colors; - QMap picURLs, picURLsHq, picURLsSt; + QMap muids; SetList sets; int tableRow = 0; int loyalty = 0; @@ -592,14 +618,12 @@ void CardDatabase::loadCardsFromXml(QXmlStreamReader &xml) else if (xml.name() == "text") text = xml.readElementText(); else if (xml.name() == "set") { - QString picURL = xml.attributes().value("picURL").toString(); - QString picURLHq = xml.attributes().value("picURLHq").toString(); - QString picURLSt = xml.attributes().value("picURLSt").toString(); + QXmlStreamAttributes attrs = xml.attributes(); QString setName = xml.readElementText(); sets.append(getSet(setName)); - picURLs.insert(setName, picURL); - picURLsHq.insert(setName, picURLHq); - picURLsSt.insert(setName, picURLSt); + if (attrs.hasAttribute("muId")) { + muids[setName] = attrs.value("muId").toString().toInt(); + } } else if (xml.name() == "color") colors << xml.readElementText(); else if (xml.name() == "tablerow") @@ -611,18 +635,18 @@ void CardDatabase::loadCardsFromXml(QXmlStreamReader &xml) else if (xml.name() == "token") isToken = xml.readElementText().toInt(); } - cardHash.insert(name, new CardInfo(this, name, isToken, manacost, type, pt, text, colors, loyalty, cipt, tableRow, sets, picURLs, picURLsHq, picURLsSt)); + cardHash.insert(name, new CardInfo(this, name, isToken, manacost, type, pt, text, colors, loyalty, cipt, tableRow, sets, muids)); } } } -bool CardDatabase::loadFromFile(const QString &fileName, bool tokens) +LoadStatus CardDatabase::loadFromFile(const QString &fileName, bool tokens) { QFile file(fileName); file.open(QIODevice::ReadOnly); if (!file.isOpen()) - return false; - + return FileError; + if (tokens) { QMutableHashIterator i(cardHash); while (i.hasNext()) { @@ -639,7 +663,7 @@ bool CardDatabase::loadFromFile(const QString &fileName, bool tokens) delete setIt.value(); } setHash.clear(); - + QMutableHashIterator i(cardHash); while (i.hasNext()) { i.next(); @@ -655,9 +679,12 @@ bool CardDatabase::loadFromFile(const QString &fileName, bool tokens) while (!xml.atEnd()) { if (xml.readNext() == QXmlStreamReader::StartElement) { if (xml.name() != "cockatrice_carddatabase") - return false; - if (xml.attributes().value("version").toString().toInt() < versionNeeded) - return false; + 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; @@ -669,7 +696,10 @@ bool CardDatabase::loadFromFile(const QString &fileName, bool tokens) } } qDebug() << cardHash.size() << "cards in" << setHash.size() << "sets loaded"; - return !cardHash.isEmpty(); + + if (cardHash.isEmpty()) return NoCards; + + return Ok; } bool CardDatabase::saveToFile(const QString &fileName, bool tokens) @@ -717,13 +747,23 @@ void CardDatabase::picDownloadChanged() } } -bool CardDatabase::loadCardDatabase(const QString &path, bool tokens) +void CardDatabase::picDownloadHqChanged() { - bool tempLoadSuccess = false; + pictureLoader->setPicDownloadHq(settingsCache->getPicDownloadHq()); + if (settingsCache->getPicDownloadHq()) { + QHashIterator cardIterator(cardHash); + while (cardIterator.hasNext()) + cardIterator.next().value()->clearPixmapCacheMiss(); + } +} + +LoadStatus CardDatabase::loadCardDatabase(const QString &path, bool tokens) +{ + LoadStatus tempLoadStatus = NotLoaded; if (!path.isEmpty()) - tempLoadSuccess = loadFromFile(path, tokens); - - if (tempLoadSuccess) { + tempLoadStatus = loadFromFile(path, tokens); + + if (tempLoadStatus == Ok) { SetList allSets; QHashIterator setsIterator(setHash); while (setsIterator.hasNext()) @@ -731,14 +771,17 @@ bool CardDatabase::loadCardDatabase(const QString &path, bool tokens) allSets.sortByKey(); for (int i = 0; i < allSets.size(); ++i) allSets[i]->setSortKey(i); - + emit cardListChanged(); } - - if (!tokens) - loadSuccess = tempLoadSuccess; - - return tempLoadSuccess; + + if (!tokens) { + loadStatus = tempLoadStatus; + qDebug() << "loadCardDatabase(): Status = " << loadStatus; + } + + + return tempLoadStatus; } void CardDatabase::loadCardDatabase() diff --git a/cockatrice/src/carddatabase.h b/cockatrice/src/carddatabase.h index 9d3f33f3..5e83080c 100644 --- a/cockatrice/src/carddatabase.h +++ b/cockatrice/src/carddatabase.h @@ -53,10 +53,8 @@ public: bool getStripped() const { return stripped; } QString getSetName() const { return sortedSets[setIndex]->getShortName(); } bool nextSet(); - bool getHq() const { return hq; } void setHq(bool _hq) { hq = _hq; } - }; class PictureLoader : public QObject { @@ -68,13 +66,15 @@ private: QNetworkAccessManager *networkManager; QList cardsToDownload; PictureToLoad cardBeingDownloaded; - bool picDownload, downloadRunning, loadQueueRunning; + bool picDownload, picDownloadHq, downloadRunning, loadQueueRunning; void startNextPicDownload(); + QString getPicUrl(CardInfo* card); public: - PictureLoader(const QString &__picsPath, bool _picDownload, QObject *parent = 0); + PictureLoader(const QString &__picsPath, bool _picDownload, bool _picDownloadHq, QObject *parent = 0); ~PictureLoader(); void setPicsPath(const QString &path); void setPicDownload(bool _picDownload); + void setPicDownloadHq(bool _picDownloadHq); void loadImage(CardInfo *card, bool stripped); private slots: void picDownloadFinished(QNetworkReply *reply); @@ -99,7 +99,7 @@ private: QString text; QStringList colors; int loyalty; - QMap picURLs, picURLsHq, picURLsSt; + QMap muIds; bool cipt; int tableRow; QPixmap *pixmap; @@ -117,9 +117,7 @@ public: bool _cipt = false, int _tableRow = 0, const SetList &_sets = SetList(), - const QStringMap &_picURLs = QStringMap(), - const QStringMap &_picURLsHq = QStringMap(), - const QStringMap &_picURLsSt = QStringMap()); + QMap muids = QMap()); ~CardInfo(); const QString &getName() const { return name; } bool getIsToken() const { return isToken; } @@ -136,25 +134,21 @@ public: void setText(const QString &_text) { text = _text; emit cardInfoChanged(this); } void setColors(const QStringList &_colors) { colors = _colors; emit cardInfoChanged(this); } const QStringList &getColors() const { return colors; } - QString getPicURL(const QString &set) const { return picURLs.value(set); } - QString getPicURLHq(const QString &set) const { return picURLsHq.value(set); } - QString getPicURLSt(const QString &set) const { return picURLsSt.value(set); } - QString getPicURL() const; - const QMap &getPicURLs() const { return picURLs; } + int getMuId(const QString &set) const { return muIds.value(set); } QString getMainCardType() const; QString getCorrectedName() const; int getTableRow() const { return tableRow; } void setTableRow(int _tableRow) { tableRow = _tableRow; } void setLoyalty(int _loyalty) { loyalty = _loyalty; emit cardInfoChanged(this); } - void setPicURL(const QString &_set, const QString &_picURL) { picURLs.insert(_set, _picURL); } - void setPicURLHq(const QString &_set, const QString &_picURL) { picURLsHq.insert(_set, _picURL); } - void setPicURLSt(const QString &_set, const QString &_picURL) { picURLsSt.insert(_set, _picURL); } + void setMuId(const QString &_set, const int &_muId) { muIds.insert(_set, _muId); } void addToSet(CardSet *set); QPixmap *loadPixmap(); QPixmap *getPixmap(QSize size); void clearPixmapCache(); void clearPixmapCacheMiss(); void imageLoaded(const QImage &image); + CardSet *getPreferredSet(); + int getPreferredMuId(); public slots: void updatePixmapCache(); signals: @@ -162,16 +156,18 @@ signals: void cardInfoChanged(CardInfo *card); }; +enum LoadStatus { Ok, VersionTooOld, Invalid, NotLoaded, FileError, NoCards }; + class CardDatabase : public QObject { Q_OBJECT protected: QHash cardHash; QHash setHash; - bool loadSuccess; CardInfo *noCard; - + QThread *pictureLoaderThread; PictureLoader *pictureLoader; + LoadStatus loadStatus; private: static const int versionNeeded; void loadCardsFromXml(QXmlStreamReader &xml); @@ -186,21 +182,23 @@ public: CardSet *getSet(const QString &setName); QList getCardList() const { return cardHash.values(); } SetList getSetList() const; - bool loadFromFile(const QString &fileName, bool tokens = false); + LoadStatus loadFromFile(const QString &fileName, bool tokens = false); bool saveToFile(const QString &fileName, bool tokens = false); QStringList getAllColors() const; QStringList getAllMainCardTypes() const; - bool getLoadSuccess() const { return loadSuccess; } + LoadStatus getLoadStatus() const { return loadStatus; } + bool getLoadSuccess() const { return loadStatus == Ok; } void cacheCardPixmaps(const QStringList &cardNames); void loadImage(CardInfo *card); public slots: void clearPixmapCache(); - bool loadCardDatabase(const QString &path, bool tokens = false); + LoadStatus loadCardDatabase(const QString &path, bool tokens = false); private slots: void imageLoaded(CardInfo *card, QImage image); void picDownloadChanged(); + void picDownloadHqChanged(); void picsPathChanged(); - + void loadCardDatabase(); void loadTokenDatabase(); signals: diff --git a/cockatrice/src/deck_loader.cpp b/cockatrice/src/deck_loader.cpp index 6a907ed7..4443b43b 100644 --- a/cockatrice/src/deck_loader.cpp +++ b/cockatrice/src/deck_loader.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "deck_loader.h" #include "decklist.h" @@ -44,18 +45,28 @@ bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt) QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return false; - + bool result = false; switch (fmt) { case PlainTextFormat: result = loadFromFile_Plain(&file); break; - case CockatriceFormat: result = loadFromFile_Native(&file); break; + case CockatriceFormat: + result = loadFromFile_Native(&file); + qDebug() << "Loaded from" << fileName << "-" << result; + if (!result) { + qDebug() << "Retying as plain format"; + file.seek(0); + result = loadFromFile_Plain(&file); + fmt = PlainTextFormat; + } + break; } if (result) { lastFileName = fileName; lastFileFormat = fmt; - + emit deckLoaded(); } + qDebug() << "Deck was loaded -" << result; return result; } @@ -66,7 +77,7 @@ bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId) lastFileName = QString(); lastFileFormat = CockatriceFormat; lastRemoteDeckId = remoteDeckId; - + emit deckLoaded(); } return result; diff --git a/cockatrice/src/decklistmodel.cpp b/cockatrice/src/decklistmodel.cpp index e8518046..00d92fae 100644 --- a/cockatrice/src/decklistmodel.cpp +++ b/cockatrice/src/decklistmodel.cpp @@ -14,7 +14,7 @@ #include "deck_loader.h" DeckListModel::DeckListModel(QObject *parent) - : QAbstractItemModel(parent) + : QAbstractItemModel(parent), lastKnownColumn(1), lastKnownOrder(Qt::AscendingOrder) { deckList = new DeckLoader; connect(deckList, SIGNAL(deckLoaded()), this, SLOT(rebuildTree())); @@ -275,23 +275,20 @@ QModelIndex DeckListModel::addCard(const QString &cardName, const QString &zoneN QString cardType = info->getMainCardType(); InnerDecklistNode *cardTypeNode = createNodeIfNeeded(cardType, zoneNode); + QModelIndex parentIndex = nodeToIndex(cardTypeNode); DecklistModelCardNode *cardNode = dynamic_cast(cardTypeNode->findChild(cardName)); if (!cardNode) { DecklistCardNode *decklistCard = deckList->addCard(cardName, zoneName); - QModelIndex parentIndex = nodeToIndex(cardTypeNode); beginInsertRows(parentIndex, cardTypeNode->size(), cardTypeNode->size()); cardNode = new DecklistModelCardNode(decklistCard, cardTypeNode); endInsertRows(); - sort(1); - emitRecursiveUpdates(parentIndex); - return nodeToIndex(cardNode); } else { cardNode->setNumber(cardNode->getNumber() + 1); - QModelIndex ind = nodeToIndex(cardNode); - emitRecursiveUpdates(ind); deckList->updateDeckHash(); - return ind; } + sort(lastKnownColumn, lastKnownOrder); + emitRecursiveUpdates(parentIndex); + return nodeToIndex(cardNode); } QModelIndex DeckListModel::nodeToIndex(AbstractDecklistNode *node) const @@ -318,7 +315,7 @@ void DeckListModel::sortHelper(InnerDecklistNode *node, Qt::SortOrder order) } } changePersistentIndexList(from, to); - + // Recursion for (int i = node->size() - 1; i >= 0; --i) { InnerDecklistNode *subNode = dynamic_cast(node->at(i)); @@ -327,9 +324,24 @@ void DeckListModel::sortHelper(InnerDecklistNode *node, Qt::SortOrder order) } } -void DeckListModel::sort(int /*column*/, Qt::SortOrder order) +void DeckListModel::sort(int column, Qt::SortOrder order) { + lastKnownColumn = column; + lastKnownOrder = order; + emit layoutAboutToBeChanged(); + DeckSortMethod sortMethod; + switch(column) { + case 0: + sortMethod = ByNumber; + break; + case 1: + sortMethod = ByName; + break; + case 2: + sortMethod = ByPrice; + } + root->setSortMethod(sortMethod); sortHelper(root, order); emit layoutChanged(); } diff --git a/cockatrice/src/decklistmodel.h b/cockatrice/src/decklistmodel.h index 27376c7a..2873c818 100644 --- a/cockatrice/src/decklistmodel.h +++ b/cockatrice/src/decklistmodel.h @@ -55,6 +55,8 @@ public: private: DeckLoader *deckList; InnerDecklistNode *root; + int lastKnownColumn; + Qt::SortOrder lastKnownOrder; InnerDecklistNode *createNodeIfNeeded(const QString &name, InnerDecklistNode *parent); QModelIndex nodeToIndex(AbstractDecklistNode *node) const; DecklistModelCardNode *findCardNode(const QString &cardName, const QString &zoneName) const; diff --git a/cockatrice/src/dlg_settings.cpp b/cockatrice/src/dlg_settings.cpp index 32d4f1fb..14115824 100644 --- a/cockatrice/src/dlg_settings.cpp +++ b/cockatrice/src/dlg_settings.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "carddatabase.h" #include "dlg_settings.h" #include "main.h" @@ -27,7 +28,7 @@ GeneralSettingsPage::GeneralSettingsPage() { languageLabel = new QLabel; languageBox = new QComboBox; - + QString setLanguage = settingsCache->getLang(); QStringList qmFiles = findQmFiles(); for (int i = 0; i < qmFiles.size(); i++) { @@ -36,27 +37,32 @@ GeneralSettingsPage::GeneralSettingsPage() if ((qmFiles[i] == setLanguage) || (setLanguage.isEmpty() && langName == tr("English"))) languageBox->setCurrentIndex(i); } - + picDownloadCheckBox = new QCheckBox; picDownloadCheckBox->setChecked(settingsCache->getPicDownload()); - + + picDownloadHqCheckBox = new QCheckBox; + picDownloadHqCheckBox->setChecked(settingsCache->getPicDownloadHq()); + connect(languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int))); connect(picDownloadCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownload(int))); - + connect(picDownloadHqCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownloadHq(int))); + QGridLayout *personalGrid = new QGridLayout; personalGrid->addWidget(languageLabel, 0, 0); personalGrid->addWidget(languageBox, 0, 1); personalGrid->addWidget(picDownloadCheckBox, 1, 0, 1, 2); - + personalGrid->addWidget(picDownloadHqCheckBox, 2, 0, 1, 2); + personalGroupBox = new QGroupBox; personalGroupBox->setLayout(personalGrid); - + deckPathLabel = new QLabel; deckPathEdit = new QLineEdit(settingsCache->getDeckPath()); deckPathEdit->setReadOnly(true); QPushButton *deckPathButton = new QPushButton("..."); connect(deckPathButton, SIGNAL(clicked()), this, SLOT(deckPathButtonClicked())); - + replaysPathLabel = new QLabel; replaysPathEdit = new QLineEdit(settingsCache->getReplaysPath()); replaysPathEdit->setReadOnly(true); @@ -183,6 +189,7 @@ void GeneralSettingsPage::retranslateUi() personalGroupBox->setTitle(tr("Personal settings")); languageLabel->setText(tr("Language:")); picDownloadCheckBox->setText(tr("Download card pictures on the fly")); + picDownloadHqCheckBox->setText(tr("Download high-quality card pictures")); pathsGroupBox->setTitle(tr("Paths")); deckPathLabel->setText(tr("Decks directory:")); replaysPathLabel->setText(tr("Replays directory:")); @@ -698,17 +705,65 @@ void DlgSettings::changeEvent(QEvent *event) void DlgSettings::closeEvent(QCloseEvent *event) { - if (!db->getLoadSuccess()) - if (QMessageBox::critical(this, tr("Error"), tr("Your card database is invalid. Would you like to go back and set the correct path?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + bool showLoadError = true; + QString loadErrorMessage = tr("Unknown Error loading card database"); + LoadStatus loadStatus = db->getLoadStatus(); + qDebug() << "Card Database load status: " << loadStatus; + switch(loadStatus) { + case Ok: + showLoadError = false; + break; + case Invalid: + loadErrorMessage = + tr("Your card database is invalid.\n\n" + "Cockatrice may not function correctly with an invalid database\n\n" + "You may need to rerun oracle to update your card database.\n\n" + "Would you like to change your database location setting?"); + break; + case VersionTooOld: + loadErrorMessage = + tr("Your card database version is too old.\n\n" + "This can cause problems loading card information or images\n\n" + "Usually this can be fixed by rerunning oracle to to update your card database.\n\n" + "Would you like to change your database location setting?"); + break; + case NotLoaded: + loadErrorMessage = + tr("Your card database did not finish loading\n\n" + "Please file a ticket at http://github.com/Daenyth/Cockatrice/issues with your cards.xml attached\n\n" + "Would you like to change your database location setting?"); + break; + case FileError: + loadErrorMessage = + tr("File Error loading your card database.\n\n" + "Would you like to change your database location setting?"); + break; + case NoCards: + loadErrorMessage = + tr("Your card database was loaded but contains no cards.\n\n" + "Would you like to change your database location setting?"); + break; + default: + loadErrorMessage = + tr("Unknown card database load status\n\n" + "Please file a ticket at http://github.com/Daenyth/Cockatrice/issues\n\n" + "Would you like to change your database location setting?"); + + break; + } + if (showLoadError) + if (QMessageBox::critical(this, tr("Error"), loadErrorMessage, QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { event->ignore(); return; } if (!QDir(settingsCache->getDeckPath()).exists() || settingsCache->getDeckPath().isEmpty()) + // TODO: Prompt to create it if (QMessageBox::critical(this, tr("Error"), tr("The path to your deck directory is invalid. Would you like to go back and set the correct path?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { event->ignore(); return; } if (!QDir(settingsCache->getPicsPath()).exists() || settingsCache->getPicsPath().isEmpty()) + // TODO: Prompt to create it if (QMessageBox::critical(this, tr("Error"), tr("The path to your card pictures directory is invalid. Would you like to go back and set the correct path?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { event->ignore(); return; diff --git a/cockatrice/src/dlg_settings.h b/cockatrice/src/dlg_settings.h index 5f87887c..8462f612 100644 --- a/cockatrice/src/dlg_settings.h +++ b/cockatrice/src/dlg_settings.h @@ -40,6 +40,7 @@ private: QGroupBox *personalGroupBox, *pathsGroupBox; QComboBox *languageBox; QCheckBox *picDownloadCheckBox; + QCheckBox *picDownloadHqCheckBox; QLabel *languageLabel, *deckPathLabel, *replaysPathLabel, *picsPathLabel, *cardDatabasePathLabel, *tokenDatabasePathLabel; }; diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index 2de76f9e..23ca8c7a 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -74,10 +74,18 @@ void installNewTranslator() qApp->installTranslator(translator); } +bool settingsValid() +{ + return QDir(settingsCache->getDeckPath()).exists() && + !settingsCache->getDeckPath().isEmpty() && + QDir(settingsCache->getPicsPath()).exists() && + !settingsCache->getPicsPath().isEmpty(); +} + int main(int argc, char *argv[]) { QApplication app(argc, argv); - + if (app.arguments().contains("--debug-output")) qInstallMsgHandler(myMessageOutput); #ifdef Q_OS_MAC @@ -97,7 +105,7 @@ int main(int argc, char *argv[]) QCoreApplication::setOrganizationName("Cockatrice"); QCoreApplication::setOrganizationDomain("cockatrice.de"); QCoreApplication::setApplicationName("Cockatrice"); - + if (translationPath.isEmpty()) { #ifdef Q_OS_MAC QDir translationsDir = baseDir; @@ -108,7 +116,7 @@ int main(int argc, char *argv[]) translationPath = app.applicationDirPath() + "/translations"; #endif } - + rng = new RNG_SFMT; settingsCache = new SettingsCache; db = new CardDatabase; @@ -116,7 +124,7 @@ int main(int argc, char *argv[]) qtTranslator = new QTranslator; translator = new QTranslator; installNewTranslator(); - + qsrand(QDateTime::currentDateTime().toTime_t()); bool startMainProgram = true; @@ -142,30 +150,30 @@ int main(int argc, char *argv[]) QDir().mkpath(dataDir + "/pics"); settingsCache->setPicsPath(dataDir + "/pics"); } - if (!db->getLoadSuccess() || !QDir(settingsCache->getDeckPath()).exists() || settingsCache->getDeckPath().isEmpty() || settingsCache->getPicsPath().isEmpty() || !QDir(settingsCache->getPicsPath()).exists()) { + if (!settingsValid() || db->getLoadStatus() != Ok) { + qDebug("main(): invalid settings or load status"); DlgSettings dlgSettings; dlgSettings.show(); app.exec(); - startMainProgram = (db->getLoadSuccess() && QDir(settingsCache->getDeckPath()).exists() && !settingsCache->getDeckPath().isEmpty() && QDir(settingsCache->getPicsPath()).exists() && !settingsCache->getPicsPath().isEmpty()); } - - if (startMainProgram) { + + if (settingsValid()) { qDebug("main(): starting main program"); soundEngine = new SoundEngine; qDebug("main(): SoundEngine constructor finished"); MainWindow ui; qDebug("main(): MainWindow constructor finished"); - + QIcon icon(":/resources/appicon.svg"); ui.setWindowIcon(icon); - + ui.show(); qDebug("main(): ui.show() finished"); - + app.exec(); } - + qDebug("Event loop finished, terminating..."); delete db; delete settingsCache; @@ -173,6 +181,6 @@ int main(int argc, char *argv[]) PingPixmapGenerator::clear(); CountryPixmapGenerator::clear(); UserLevelPixmapGenerator::clear(); - + return 0; } diff --git a/cockatrice/src/phasestoolbar.h b/cockatrice/src/phasestoolbar.h index 852f4f58..a91b020c 100644 --- a/cockatrice/src/phasestoolbar.h +++ b/cockatrice/src/phasestoolbar.h @@ -11,6 +11,7 @@ class GameCommand; class PhaseButton : public QObject, public QGraphicsItem { Q_OBJECT + Q_INTERFACES(QGraphicsItem) private: QString name; bool active, highlightable; @@ -39,6 +40,7 @@ protected: class PhasesToolbar : public QObject, public QGraphicsItem { Q_OBJECT + Q_INTERFACES(QGraphicsItem) private: QList buttonList; PhaseButton *nextTurnButton; diff --git a/cockatrice/src/player.h b/cockatrice/src/player.h index 9eed7c07..d8396e4d 100644 --- a/cockatrice/src/player.h +++ b/cockatrice/src/player.h @@ -58,7 +58,8 @@ class PendingCommand; class PlayerArea : public QObject, public QGraphicsItem { Q_OBJECT -private: + Q_INTERFACES(QGraphicsItem) +private: QBrush bgPixmapBrush; QRectF bRect; private slots: @@ -76,6 +77,7 @@ public: class Player : public QObject, public QGraphicsItem { Q_OBJECT + Q_INTERFACES(QGraphicsItem) signals: void openDeckEditor(const DeckLoader *deck); void newCardAdded(AbstractCardItem *card); diff --git a/cockatrice/src/priceupdater.cpp b/cockatrice/src/priceupdater.cpp index 07ded728..a0d32a32 100644 --- a/cockatrice/src/priceupdater.cpp +++ b/cockatrice/src/priceupdater.cpp @@ -27,7 +27,7 @@ void PriceUpdater::updatePrices() QString q = "http://blacklotusproject.com/json/?cards="; QStringList cards = deck->getCardList(); for (int i = 0; i < cards.size(); ++i) { - q += cards[i] + "|"; + q += cards[i].toLower() + "|"; } QUrl url(q.replace(' ', '+')); diff --git a/cockatrice/src/settingscache.cpp b/cockatrice/src/settingscache.cpp index b73d6d86..10dad8c9 100644 --- a/cockatrice/src/settingscache.cpp +++ b/cockatrice/src/settingscache.cpp @@ -4,23 +4,27 @@ SettingsCache::SettingsCache() { settings = new QSettings(this); - + lang = settings->value("personal/lang").toString(); - + deckPath = settings->value("paths/decks").toString(); replaysPath = settings->value("paths/replays").toString(); picsPath = settings->value("paths/pics").toString(); cardDatabasePath = settings->value("paths/carddatabase").toString(); tokenDatabasePath = settings->value("paths/tokendatabase").toString(); - + handBgPath = settings->value("zonebg/hand").toString(); stackBgPath = settings->value("zonebg/stack").toString(); tableBgPath = settings->value("zonebg/table").toString(); playerBgPath = settings->value("zonebg/playerarea").toString(); cardBackPicturePath = settings->value("paths/cardbackpicture").toString(); - - mainWindowGeometry = settings->value("interface/main_window_geometry").toByteArray(); + picDownload = settings->value("personal/picturedownload", true).toBool(); + picDownloadHq = settings->value("personal/picturedownloadhq", false).toBool(); + picUrl = settings->value("personal/picUrl", PIC_URL_DEFAULT).toString(); + picUrlHq = settings->value("personal/picUrlHq", PIC_URL_HQ_DEFAULT).toString(); + + mainWindowGeometry = settings->value("interface/main_window_geometry").toByteArray(); notificationsEnabled = settings->value("interface/notificationsenabled", true).toBool(); doubleClickToPlay = settings->value("interface/doubleclicktoplay", true).toBool(); playToStack = settings->value("interface/playtostack", false).toBool(); @@ -31,15 +35,15 @@ SettingsCache::SettingsCache() invertVerticalCoordinate = settings->value("table/invert_vertical", false).toBool(); minPlayersForMultiColumnLayout = settings->value("interface/min_players_multicolumn", 5).toInt(); tapAnimation = settings->value("cards/tapanimation", true).toBool(); - + zoneViewSortByName = settings->value("zoneview/sortbyname", true).toBool(); zoneViewSortByType = settings->value("zoneview/sortbytype", true).toBool(); - + soundEnabled = settings->value("sound/enabled", false).toBool(); soundPath = settings->value("sound/path").toString(); - + priceTagFeature = settings->value("deckeditor/pricetags", false).toBool(); - + ignoreUnregisteredUsers = settings->value("chat/ignore_unregistered", false).toBool(); } @@ -125,6 +129,25 @@ void SettingsCache::setPicDownload(int _picDownload) emit picDownloadChanged(); } +void SettingsCache::setPicDownloadHq(int _picDownloadHq) +{ + picDownloadHq = _picDownloadHq; + settings->setValue("personal/picturedownloadhq", picDownloadHq); + emit picDownloadHqChanged(); +} + +void SettingsCache::setPicUrl(const QString &_picUrl) +{ + picUrl = _picUrl; + settings->setValue("personal/picUrl", picUrl); +} + +void SettingsCache::setPicUrlHq(const QString &_picUrlHq) +{ + picUrlHq = _picUrlHq; + settings->setValue("personal/picUrlHq", picUrlHq); +} + void SettingsCache::setNotificationsEnabled(int _notificationsEnabled) { notificationsEnabled = _notificationsEnabled; @@ -219,6 +242,7 @@ void SettingsCache::setPriceTagFeature(int _priceTagFeature) { priceTagFeature = _priceTagFeature; settings->setValue("deckeditor/pricetags", priceTagFeature); + emit priceTagFeatureChanged(priceTagFeature); } void SettingsCache::setIgnoreUnregisteredUsers(bool _ignoreUnregisteredUsers) diff --git a/cockatrice/src/settingscache.h b/cockatrice/src/settingscache.h index 6c28227a..a9b9ce49 100644 --- a/cockatrice/src/settingscache.h +++ b/cockatrice/src/settingscache.h @@ -3,6 +3,9 @@ #include +#define PIC_URL_DEFAULT "http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!cardid!&type=card" +#define PIC_URL_HQ_DEFAULT "http://mtgimage.com/multiverseid/!cardid!.jpg" + class QSettings; class SettingsCache : public QObject { @@ -18,21 +21,24 @@ signals: void playerBgPathChanged(); void cardBackPicturePathChanged(); void picDownloadChanged(); + void picDownloadHqChanged(); void displayCardNamesChanged(); void horizontalHandChanged(); void invertVerticalCoordinateChanged(); void minPlayersForMultiColumnLayoutChanged(); void soundEnabledChanged(); void soundPathChanged(); + void priceTagFeatureChanged(int enabled); void ignoreUnregisteredUsersChanged(); private: QSettings *settings; - + QByteArray mainWindowGeometry; QString lang; QString deckPath, replaysPath, picsPath, cardDatabasePath, tokenDatabasePath; QString handBgPath, stackBgPath, tableBgPath, playerBgPath, cardBackPicturePath; bool picDownload; + bool picDownloadHq; bool notificationsEnabled; bool doubleClickToPlay; bool playToStack; @@ -48,6 +54,8 @@ private: QString soundPath; bool priceTagFeature; bool ignoreUnregisteredUsers; + QString picUrl; + QString picUrlHq; public: SettingsCache(); const QByteArray &getMainWindowGeometry() const { return mainWindowGeometry; } @@ -63,6 +71,7 @@ public: QString getPlayerBgPath() const { return playerBgPath; } QString getCardBackPicturePath() const { return cardBackPicturePath; } bool getPicDownload() const { return picDownload; } + bool getPicDownloadHq() const { return picDownloadHq; } bool getNotificationsEnabled() const { return notificationsEnabled; } bool getDoubleClickToPlay() const { return doubleClickToPlay; } bool getPlayToStack() const { return playToStack; } @@ -79,6 +88,8 @@ public: QString getSoundPath() const { return soundPath; } bool getPriceTagFeature() const { return priceTagFeature; } bool getIgnoreUnregisteredUsers() const { return ignoreUnregisteredUsers; } + QString getPicUrl() const { return picUrl; } + QString getPicUrlHq() const { return picUrlHq; } public slots: void setMainWindowGeometry(const QByteArray &_mainWindowGeometry); void setLang(const QString &_lang); @@ -93,6 +104,7 @@ public slots: void setPlayerBgPath(const QString &_playerBgPath); void setCardBackPicturePath(const QString &_cardBackPicturePath); void setPicDownload(int _picDownload); + void setPicDownloadHq(int _picDownloadHq); void setNotificationsEnabled(int _notificationsEnabled); void setDoubleClickToPlay(int _doubleClickToPlay); void setPlayToStack(int _playToStack); @@ -109,6 +121,8 @@ public slots: void setSoundPath(const QString &_soundPath); void setPriceTagFeature(int _priceTagFeature); void setIgnoreUnregisteredUsers(bool _ignoreUnregisteredUsers); + void setPicUrl(const QString &_picUrl); + void setPicUrlHq(const QString &_picUrlHq); }; extern SettingsCache *settingsCache; diff --git a/cockatrice/src/tab_deck_editor.cpp b/cockatrice/src/tab_deck_editor.cpp index 91c8819e..0c1d44e1 100644 --- a/cockatrice/src/tab_deck_editor.cpp +++ b/cockatrice/src/tab_deck_editor.cpp @@ -132,6 +132,8 @@ TabDeckEditor::TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent) deckView = new QTreeView(); deckView->setModel(deckModel); deckView->setUniformRowHeights(true); + deckView->setSortingEnabled(true); + deckView->sortByColumn(1, Qt::AscendingOrder); deckView->header()->setResizeMode(QHeaderView::ResizeToContents); deckView->installEventFilter(&deckViewKeySignals); connect(deckView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(updateCardInfoRight(const QModelIndex &, const QModelIndex &))); @@ -170,6 +172,7 @@ TabDeckEditor::TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent) connect(aUpdatePrices, SIGNAL(triggered()), this, SLOT(actUpdatePrices())); if (!settingsCache->getPriceTagFeature()) aUpdatePrices->setVisible(false); + connect(settingsCache, SIGNAL(priceTagFeatureChanged(int)), this, SLOT(setPriceTagFeatureEnabled(int))); QToolBar *deckToolBar = new QToolBar; deckToolBar->setOrientation(Qt::Vertical); @@ -180,7 +183,7 @@ TabDeckEditor::TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent) deckToolbarLayout->addStretch(); deckToolbarLayout->addWidget(deckToolBar); deckToolbarLayout->addStretch(); - + QVBoxLayout *rightFrame = new QVBoxLayout; rightFrame->addLayout(grid); rightFrame->addWidget(deckView, 10); @@ -191,7 +194,7 @@ TabDeckEditor::TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent) mainLayout->addLayout(middleFrame); mainLayout->addLayout(rightFrame); setLayout(mainLayout); - + aNewDeck = new QAction(QString(), this); aNewDeck->setShortcuts(QKeySequence::New); connect(aNewDeck, SIGNAL(triggered()), this, SLOT(actNewDeck())); @@ -633,6 +636,11 @@ void TabDeckEditor::actDecrement() offsetCountAtIndex(currentIndex, -1); } +void TabDeckEditor::setPriceTagFeatureEnabled(int enabled) +{ + aUpdatePrices->setVisible(enabled); +} + void TabDeckEditor::actUpdatePrices() { aUpdatePrices->setDisabled(true); @@ -655,10 +663,10 @@ void TabDeckEditor::setDeck(DeckLoader *_deck) nameEdit->setText(deckModel->getDeckList()->getName()); commentsEdit->setText(deckModel->getDeckList()->getComments()); updateHash(); - deckModel->sort(1); + deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); deckView->expandAll(); setModified(false); - + db->cacheCardPixmaps(deckModel->getDeckList()->getCardList()); deckView->expandAll(); setModified(false); diff --git a/cockatrice/src/tab_deck_editor.h b/cockatrice/src/tab_deck_editor.h index 3acf1d15..b3ba8dcf 100644 --- a/cockatrice/src/tab_deck_editor.h +++ b/cockatrice/src/tab_deck_editor.h @@ -50,7 +50,7 @@ private slots: void actEditSets(); void actEditTokens(); - + void actClearSearch(); void actAddCard(); @@ -67,6 +67,7 @@ private slots: void saveDeckRemoteFinished(const Response &r); void filterViewCustomContextMenu(const QPoint &point); void filterRemove(QAction *action); + void setPriceTagFeatureEnabled(int enabled); private: CardInfo *currentCardInfo() const; void addCardHelper(QString zoneName); diff --git a/cockatrice/src/zoneviewzone.h b/cockatrice/src/zoneviewzone.h index 727148a8..43134c78 100644 --- a/cockatrice/src/zoneviewzone.h +++ b/cockatrice/src/zoneviewzone.h @@ -11,6 +11,7 @@ class QGraphicsSceneWheelEvent; class ZoneViewZone : public SelectZone, public QGraphicsLayoutItem { Q_OBJECT + Q_INTERFACES(QGraphicsLayoutItem) private: QRectF bRect, optimumRect; int minRows, numberCards; diff --git a/common/decklist.cpp b/common/decklist.cpp index d9a3d7f3..e81a0629 100644 --- a/common/decklist.cpp +++ b/common/decklist.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "decklist.h" SideboardPlan::SideboardPlan(const QString &_name, const QList &_moveList) @@ -104,6 +105,13 @@ QString InnerDecklistNode::visibleNameFromName(const QString &_name) return _name; } +void InnerDecklistNode::setSortMethod(DeckSortMethod method) +{ + sortMethod = method; + for (int i = 0; i < size(); i++) + at(i)->setSortMethod(method); +} + QString InnerDecklistNode::getVisibleName() const { return visibleNameFromName(name); @@ -163,21 +171,95 @@ float InnerDecklistNode::recursivePrice(bool countTotalCards) const } bool InnerDecklistNode::compare(AbstractDecklistNode *other) const +{ + switch (sortMethod) { + case 0: + return compareNumber(other); + case 1: + return compareName(other); + case 2: + return comparePrice(other); + } +} + +bool InnerDecklistNode::compareNumber(AbstractDecklistNode *other) const { InnerDecklistNode *other2 = dynamic_cast(other); - if (other2) - return (getName() > other->getName()); - else + if (other2) { + int n1 = recursiveCount(true); + int n2 = other2->recursiveCount(true); + return (n1 != n2) ? (n1 > n2) : compareName(other); + } else { return false; + } +} + +bool InnerDecklistNode::compareName(AbstractDecklistNode *other) const +{ + InnerDecklistNode *other2 = dynamic_cast(other); + if (other2) { + return (getName() > other2->getName()); + } else { + return false; + } +} + +bool InnerDecklistNode::comparePrice(AbstractDecklistNode *other) const +{ + InnerDecklistNode *other2 = dynamic_cast(other); + if (other2) { + int p1 = 100*recursivePrice(true); + int p2 = 100*other2->recursivePrice(true); + return (p1 != p2) ? (p1 > p2) : compareName(other); + } else { + return false; + } } bool AbstractDecklistCardNode::compare(AbstractDecklistNode *other) const +{ + switch (sortMethod) { + case ByNumber: + return compareNumber(other); + case ByName: + return compareName(other); + case ByPrice: + return compareTotalPrice(other); + } +} + +bool AbstractDecklistCardNode::compareNumber(AbstractDecklistNode *other) const { AbstractDecklistCardNode *other2 = dynamic_cast(other); - if (other2) - return (getName() > other->getName()); - else + if (other2) { + int n1 = getNumber(); + int n2 = other2->getNumber(); + return (n1 != n2) ? (n1 > n2) : compareName(other); + } else { return true; + } +} + +bool AbstractDecklistCardNode::compareName(AbstractDecklistNode *other) const +{ + AbstractDecklistCardNode *other2 = dynamic_cast(other); + if (other2) { + return (getName() > other2->getName()); + } else { + return true; + } +} + +bool AbstractDecklistCardNode::compareTotalPrice(AbstractDecklistNode *other) const +{ + AbstractDecklistCardNode *other2 = dynamic_cast(other); + if (other2) { + int p1 = 100*getTotalPrice(); + int p2 = 100*other2->getTotalPrice(); + return (p1 != p2) ? (p1 > p2) : compareName(other); + } else { + return true; + } } class InnerDecklistNode::compareFunctor { @@ -360,6 +442,11 @@ void DeckList::write(QXmlStreamWriter *xml) bool DeckList::loadFromXml(QXmlStreamReader *xml) { + if (xml->error()) { + qDebug() << "Error loading deck from xml: " << xml->errorString(); + return false; + } + cleanList(); while (!xml->atEnd()) { xml->readNext(); @@ -374,6 +461,10 @@ bool DeckList::loadFromXml(QXmlStreamReader *xml) } } updateDeckHash(); + if (xml->error()) { + qDebug() << "Error loading deck from xml: " << xml->errorString(); + return false; + } return true; } @@ -396,8 +487,7 @@ QString DeckList::writeToString_Native() bool DeckList::loadFromFile_Native(QIODevice *device) { QXmlStreamReader xml(device); - loadFromXml(&xml); - return true; + return loadFromXml(&xml); } bool DeckList::saveToFile_Native(QIODevice *device) @@ -415,7 +505,7 @@ bool DeckList::saveToFile_Native(QIODevice *device) bool DeckList::loadFromStream_Plain(QTextStream &in) { cleanList(); - + InnerDecklistNode *main = 0, *side = 0; bool inSideboard = false; @@ -449,7 +539,7 @@ bool DeckList::loadFromStream_Plain(QTextStream &in) line.remove(rx); rx.setPattern("\\(.*\\)"); line.remove(rx); - //Filter out post card name editions + //Filter out post card name editions rx.setPattern("\\|.*$"); line.remove(rx); line = line.simplified(); @@ -462,10 +552,10 @@ bool DeckList::loadFromStream_Plain(QTextStream &in) continue; QString cardName = line.mid(i + 1); - // Common differences between cockatrice's card names - // and what's commonly used in decklists + // Common differences between cockatrice's card names + // and what's commonly used in decklists rx.setPattern("’"); - cardName.replace(rx, "'"); + cardName.replace(rx, "'"); rx.setPattern("Æ"); cardName.replace(rx, "AE"); rx.setPattern("^Aether"); diff --git a/common/decklist.h b/common/decklist.h index 2e8f4a9b..94df407a 100644 --- a/common/decklist.h +++ b/common/decklist.h @@ -29,24 +29,28 @@ public: SideboardPlan(const QString &_name = QString(), const QList &_moveList = QList()); bool readElement(QXmlStreamReader *xml); void write(QXmlStreamWriter *xml); - + QString getName() const { return name; } const QList &getMoveList() const { return moveList; } void setMoveList(const QList &_moveList); }; +enum DeckSortMethod { ByNumber, ByName, ByPrice }; + class AbstractDecklistNode { protected: InnerDecklistNode *parent; + DeckSortMethod sortMethod; public: AbstractDecklistNode(InnerDecklistNode *_parent = 0); virtual ~AbstractDecklistNode() { } + virtual void setSortMethod(DeckSortMethod method) { sortMethod = method; } virtual QString getName() const = 0; InnerDecklistNode *getParent() const { return parent; } int depth() const; virtual int height() const = 0; virtual bool compare(AbstractDecklistNode *other) const = 0; - + virtual bool readElement(QXmlStreamReader *xml) = 0; virtual void writeElement(QXmlStreamWriter *xml) = 0; }; @@ -59,6 +63,7 @@ public: InnerDecklistNode(const QString &_name = QString(), InnerDecklistNode *_parent = 0) : AbstractDecklistNode(_parent), name(_name) { } InnerDecklistNode(InnerDecklistNode *other, InnerDecklistNode *_parent = 0); virtual ~InnerDecklistNode(); + void setSortMethod(DeckSortMethod method); QString getName() const { return name; } void setName(const QString &_name) { name = _name; } static QString visibleNameFromName(const QString &_name); @@ -69,8 +74,11 @@ public: int recursiveCount(bool countTotalCards = false) const; float recursivePrice(bool countTotalCards = false) const; bool compare(AbstractDecklistNode *other) const; + bool compareNumber(AbstractDecklistNode *other) const; + bool compareName(AbstractDecklistNode *other) const; + bool comparePrice(AbstractDecklistNode *other) const; QVector > sort(Qt::SortOrder order = Qt::AscendingOrder); - + bool readElement(QXmlStreamReader *xml); void writeElement(QXmlStreamWriter *xml); }; @@ -87,7 +95,10 @@ public: float getTotalPrice() const { return getNumber() * getPrice(); } int height() const { return 0; } bool compare(AbstractDecklistNode *other) const; - + bool compareNumber(AbstractDecklistNode *other) const; + bool compareName(AbstractDecklistNode *other) const; + bool compareTotalPrice(AbstractDecklistNode *other) const; + bool readElement(QXmlStreamReader *xml); void writeElement(QXmlStreamWriter *xml); }; diff --git a/doc/sets.xml b/doc/sets.xml index 1afa3958..bcc7c20e 100644 --- a/doc/sets.xml +++ b/doc/sets.xml @@ -1,333 +1,254 @@ - + http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!cardid!&type=card -http://gatherer.wizards.com/Pages/Search/Default.aspx?output=spoiler&method=text&set=["!longname!"]&special=true - - 10E - Tenth Edition - - - 15ANN - 15th Anniversary - - - 4E - Fourth Edition - - - 5DN - Fifth Dawn - - - 5E - Fifth Edition - - - 6E - Classic Sixth Edition - - - 7E - Seventh Edition - - - 8EB - Eighth Edition Box Set - - - 8E - Eighth Edition - - - 9EB - Ninth Edition Box Set - - - 9E - Ninth Edition - - - AI - Alliances - - - ALA - Shards of Alara - - - AL - Limited Edition Alpha - - - AN - Arabian Nights - - - APAC - Asia Pacific Land Program - - - AP - Apocalypse - - - AQ - Antiquities - + +http://mtgjson.com/json/!name!.json ARB Alara Reborn + ALL + Alliances + + + ATQ + Antiquities + + + APC + Apocalypse + + + ARN + Arabian Nights + + ARC Archenemy - - ARENA - Arena League - - - AT - Anthologies - AVR Avacyn Restored - - BD - Beatdown Box Set + + BRB + Battle Royale Box Set - - BE - Limited Edition Beta + + BTD + Beatdown Box Set BOK Betrayers of Kamigawa - BR - Battle Royale Box Set - - - CED - Collector's Edition - - - CEDI - International Collectors' Edition - - - CFX - Conflux - - - CH - Chronicles + BNG + Born of the Gods CHK Champions of Kamigawa - CMA - Commander's Arsenal + CHR + Chronicles - CMD - Commander + 6ED + Classic Sixth Edition - CP - Champs - - - CS + CSP Coldsnap - CSTD - Coldsnap Theme Decks + CON + Conflux + + + CMA + Commander's Arsenal + + ^M + C13^M + Commander 2013 Edition^M - DCILM - Legend Membership - - - DDF - Duel Decks: Elspeth vs. Tezzeret - - - DDG - Duel Decks: Knights vs. Dragons - - - DDH - Duel Decks: Ajani vs. Nicol Bolas - - - DDI - Duel Decks: Venser vs. Koth - - - DDJ - Duel Decks: Izzet vs. Golgari - - - DDK - Duel Decks: Sorin vs. Tibalt - - - DGM - Dragon's Maze - - - DI - Dissension + DST + Darksteel DKA Dark Ascension - DK - The Dark + DIS + Dissension - DM - Deckmasters + DGM + Dragon's Maze - - DPA - Duels of the Planeswalkers + + DDH + Duel Decks: Ajani vs. Nicol Bolas - - DRC - Dragon Con - - - DS - Darksteel - - - DVD + + DDC Duel Decks: Divine vs. Demonic + + DDF + Duel Decks: Elspeth vs. Tezzeret + + + EVG + Duel Decks: Elves vs. Goblins + + + DDD + Duel Decks: Garruk vs. Liliana + + + DDL + Duel Decks: Heroes vs. Monsters + + + DDJ + Duel Decks: Izzet vs. Golgari + + + DD2 + Duel Decks: Jace vs. Chandra + + + DDM + Duel Decks: Jace vs. Vraska + + + DDG + Duel Decks: Knights vs. Dragons + + + DDE + Duel Decks: Phyrexia vs. the Coalition + + + DDK + Duel Decks: Sorin vs. Tibalt + + + DDI + Duel Decks: Venser vs. Koth + - EURO - European Land Program + 8ED + Eighth Edition EVE Eventide - EVG - Duel Decks: Elves vs. Goblins - - - EX + EXO Exodus - FE + FEM Fallen Empires - FNMP - Friday Night Magic + 5DN + Fifth Dawn + + + 5ED + Fifth Edition + + + 4ED + Fourth Edition + + + DRB + From the Vault: Dragons + + + V09 + From the Vault: Exiled + + + V11 + From the Vault: Legends + + + V12 + From the Vault: Realms + + + V10 + From the Vault: Relics + + + V13 + From the Vault: Twenty FUT Future Sight - FVD - From the Vault: Dragons - - - FVE - From the Vault: Exiled - - - FVL - From the Vault: Legends - - - FVR - From the Vault: Relics - - - GP + GPT Guildpact - - GPX - Grand Prix - - - GRC - WPN/Gateway - GTC Gatecrash - GURU - Guru - - - GVL - Duel Decks: Garruk vs. Liliana - - - HHO - Happy Holidays - - - HL + HML Homelands - IA + ICE Ice Age - - IN - Invasion - ISD Innistrad - ITP - Introductory Two-Player Set + INV + Invasion - JR - Judge Gift Program + JOU + Journey into Nyx - JU + JUD Judgment - JVC - Duel Decks: Jace vs. Chandra - - - LE - Legions - - - LG + LEG Legends - LW + LGN + Legions + + + LEA + Limited Edition Alpha + + + LEB + Limited Edition Beta + + + LRW Lorwyn @@ -347,63 +268,59 @@ Magic 2013 - MBP - Media Inserts + M14 + Magic 2014 Core Set + + + CMD + Magic: The Gathering-Commander + + + CNS + Magic: The Gathering—Conspiracy + + + MED + Masters Edition + + + ME2 + Masters Edition II + + + ME3 + Masters Edition III + + + ME4 + Masters Edition IV + + + MMQ + Mercadian Masques + + + MIR + Mirage + + + MRD + Mirrodin MBS Mirrodin Besieged + ^M + MMA^M + Modern Masters^M + ^ - ME2 - MTGO Masters Edition II - - - ME3 - MTGO Masters Edition III - - - ME4 - MTGO Masters Edition IV - - - MED - MTGO Masters Edition - - - MGBC - Multiverse Gift Box Cards - - - MGDC - Magic Game Day Cards - - - MI - Mirrodin - - - MLP - Magic: The Gathering Launch Parties - - - MM - Mercadian Masques - - - MPRP - Magic Player Rewards - - - MR - Mirage - - - MT + MOR Morningtide - NE + NMS Nemesis @@ -411,104 +328,80 @@ New Phyrexia - OD + 9ED + Ninth Edition + + + ODY Odyssey - ON + ONS Onslaught - P3K - Portal Three Kingdoms + PLC + Planar Chaos - + + HOP + Planechase + + PC2 Planechase 2012 Edition - PC - Planar Chaos + PLS + Planeshift - PCH - Planechase - - - PD2 - Premium Deck Series: Fire and Lightning - - - PD3 - Premium Deck Series: Graveborn - - - PDS - Premium Deck Series: Slivers + POR + Portal PO2 Portal Second Age - PO - Portal + PTK + Portal Three Kingdoms + + + PD2 + Premium Deck Series: Fire and Lightning + + + PD3 + Premium Deck Series: Graveborn + + + H09 + Premium Deck Series: Slivers + + + PPR + Promo set for Gatherer - POT - Portal Demogame - - - PR + PCY Prophecy - - PRO - Pro Tour - - - PS - Planeshift - - - PTC - Prerelease Events - - - PVC - Duel Decks: Phyrexia vs. The Coalition - RAV Ravnica: City of Guilds - - REP - Release Events - - - ROE - Rise of the Eldrazi - RTR Return to Ravnica - RV + 3ED Revised Edition - SC - Scourge - - - SH - Stronghold - - - SHM - Shadowmoor + ROE + Rise of the Eldrazi SOK @@ -519,97 +412,97 @@ Scars of Mirrodin - ST2K - Starter 2000 + SCG + Scourge - ST + 7ED + Seventh Edition + + + SHM + Shadowmoor + + + ALA + Shards of Alara + + + S99 Starter 1999 - SUM - Summer of Magic + S00 + Starter 2000 - SUS - Super Series + STH + Stronghold - THGT - Two-Headed Giant Tournament - - - TP + TMP Tempest - TR - Torment + 10E + Tenth Edition - TS + DRK + The Dark + + + THS + Theros + + + TSP Time Spiral - TSTS + TSB Time Spiral "Timeshifted" - UD - Urza's Destiny + TOR + Torment - - UG + + UGL Unglued - - UHAA - Unhinged Alternate Foils - - - UH + + UNH Unhinged - UL - Urza's Legacy - - - UN + 2ED Unlimited Edition - UQC - Celebration Cards + UDS + Urza's Destiny - US + ULG + Urza's Legacy + + + USG Urza's Saga - - V12 - From the Vault: Realms + + VAN + Vanguard - VI + VIS Visions - WL + WTH Weatherlight - - WMCQ - World Magic Cup Qualifiers - - - WOTC - WotC Online Store - - - WRL - Worlds - WWK Worldwake diff --git a/doc/usermanual/Usermanual.pdf b/doc/usermanual/Usermanual.pdf index 2fdcdb3f..3d279df3 100644 Binary files a/doc/usermanual/Usermanual.pdf and b/doc/usermanual/Usermanual.pdf differ diff --git a/doc/usermanual/Usermanual.tex b/doc/usermanual/Usermanual.tex index 634c0040..23fa5580 100644 --- a/doc/usermanual/Usermanual.tex +++ b/doc/usermanual/Usermanual.tex @@ -224,7 +224,7 @@ Start the oracle.exe (the installer does this automatically) and let it generate \end{enumerate} Congratulations, you may now use Cockatrice! -\subsubsection{Linux and BSD} +\subsubsection{Linux, BSD, OS X} The following procedures have been tested with Debian Wheezy, Fedora 18, XUbuntu 13.10, FreeBSD 9.1 and 10.0. If you use Gentoo with KDE you have the needed prerequisites and may continue with downloading the source. If you use Bodhi or Arch Linux (AUR) or another distribution that includes Cockatrice, you might install Cockatrice from the default packages -- though the package might be old, @@ -239,6 +239,7 @@ Before you install new software, you should update your system. The following in yum install qt-devel qt-mobility-devel protobuf-devel protobuf-compiler cmake} \item[FreeBSD 9] \shellcmd{pkg\_add -r qt4 qt4-linguist qt4-moc qt4-qmake qt4-rcc qt4-uic git cmake protobuf} \item[FreeBSD 10] \shellcmd{pkg install qt4 qt4-linguist qt4-moc qt4-qmake qt4-rcc qt4-uic git cmake protobuf} + \item[OS X] \shellcmd{brew install qt cmake protobuf} \end{description} \item Download the sources from github via \\ \shellcmd{cd\\ git clone https://github.com/Daenyth/Cockatrice.git} \item To compile the sources, change into the newly created directory, create a build directory and invoke cmake:\\ @@ -253,9 +254,6 @@ make}\\ The default paths for decks, pics, cards and tokens are located in \\ \shellcmd{/home//.local/share/data/Cockatrice/Cockatrice}. \end{enumerate} -\subsubsection{MacOS X} -TODO, please contribute this section! See Linux section, then use the \shellcmd{prepareMacRelease.sh} script from Cockatrice. - \subsection{Building the Server} You don't need your own server if you plan to play only. But as Cockatrice is open source you are free to run your own. The compilation works like already written above, but instead of invoking \shellcmd{cmake ..}, you have to do it like this: @@ -706,6 +704,8 @@ USE AT YOUR OWN RISK. \begin{enumerate} \item Open a command shell and install the prerequisites \begin{enumerate} + \item \shellcmd{cd /etc/yum.repos.d/} + \item \shellcmd{sudo wget http://kdeforge.unl.edu/apt/kde-redhat/epel/kde.repo} \item \shellcmd{yum -y groupinstall "development tools"} \item \shellcmd{rpm -Uvh http://dl.fedoraproject.org/pub/epel/6/x86\_64/epel-release-6-8.noarch.rpm} \item \shellcmd{yum -y install qt-mysql qt-devel qt-mobility-devel protobuf-devel protobuf-compiler cmake28 libgcrypt-devel} diff --git a/nsis/cockatrice.nsi b/nsis/cockatrice.nsi index 33cc496b..90ebfe50 100644 --- a/nsis/cockatrice.nsi +++ b/nsis/cockatrice.nsi @@ -91,7 +91,7 @@ SectionEnd Section "Start menu item" SecStartMenu createDirectory "$SMPROGRAMS\Cockatrice" - createShortCut "$SMPROGRAMS\Cockatrice\Cockatrice.lnk" "$INSTDIR\cockatrice.exe" + createShortCut "$SMPROGRAMS\Cockatrice\Cockatrice.lnk" "$INSTDIR\cockatrice.exe" '--debug-output' createShortCut "$SMPROGRAMS\Cockatrice\Oracle.lnk" "$INSTDIR\oracle.exe" createShortCut "$SMPROGRAMS\Cockatrice\Usermanual.lnk" "$INSTDIR\Usermanual.pdf" SectionEnd diff --git a/oracle/CMakeLists.txt b/oracle/CMakeLists.txt index dd544a1c..fddfe774 100644 --- a/oracle/CMakeLists.txt +++ b/oracle/CMakeLists.txt @@ -9,10 +9,11 @@ set(DESKTOPDIR share/applications CACHE STRING "path to .desktop files") SET(oracle_SOURCES src/main.cpp + src/oraclewizard.cpp src/oracleimporter.cpp - src/window_main.cpp ../cockatrice/src/carddatabase.cpp ../cockatrice/src/settingscache.cpp + ../cockatrice/src/qt-json/json.cpp ) set(ORACLE_LIBS) diff --git a/oracle/src/main.cpp b/oracle/src/main.cpp index a84f4975..44b900f3 100644 --- a/oracle/src/main.cpp +++ b/oracle/src/main.cpp @@ -1,6 +1,6 @@ #include #include -#include "window_main.h" +#include "oraclewizard.h" #include "settingscache.h" SettingsCache *settingsCache; @@ -12,13 +12,14 @@ int main(int argc, char *argv[]) QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); QCoreApplication::setOrganizationName("Cockatrice"); - QCoreApplication::setOrganizationDomain("cockatrice.de"); + QCoreApplication::setOrganizationDomain("cockatrice"); + // this can't be changed, as it influences the default savepath for cards.xml QCoreApplication::setApplicationName("Cockatrice"); settingsCache = new SettingsCache; - - WindowMain wnd; - wnd.show(); - + + OracleWizard wizard; + wizard.show(); + return app.exec(); } diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index f4463455..258e8850 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -1,330 +1,246 @@ #include "oracleimporter.h" #if QT_VERSION < 0x050000 -#include + #include #else -#include + #include #endif - -#include -#include -#include #include +#include "qt-json/json.h" + OracleImporter::OracleImporter(const QString &_dataDir, QObject *parent) - : CardDatabase(parent), dataDir(_dataDir), setIndex(-1) + : CardDatabase(parent), dataDir(_dataDir) { - buffer = new QBuffer(this); - http = new QHttp(this); - connect(http, SIGNAL(requestFinished(int, bool)), this, SLOT(httpRequestFinished(int, bool))); - connect(http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)), this, SLOT(readResponseHeader(const QHttpResponseHeader &))); - connect(http, SIGNAL(dataReadProgress(int, int)), this, SIGNAL(dataReadProgress(int, int))); -} - -bool OracleImporter::readSetsFromFile(const QString &fileName) -{ - QFile setsFile(fileName); - if (!setsFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox::critical(0, tr("Error"), tr("Cannot open file '%1'.").arg(fileName)); - return false; - } - - QXmlStreamReader xml(&setsFile); - return readSetsFromXml(xml); } bool OracleImporter::readSetsFromByteArray(const QByteArray &data) { - QXmlStreamReader xml(data); - return readSetsFromXml(xml); -} + QList newSetList; + + bool ok; + setsMap = QtJson::Json::parse(QString(data), ok).toMap(); + if (!ok) { + qDebug() << "error: QtJson::Json::parse()"; + return 0; + } + + QListIterator it(setsMap.values()); + QVariantMap map; -bool OracleImporter::readSetsFromXml(QXmlStreamReader &xml) -{ - QList newSetList; - - QString edition; - QString editionLong; - QString editionURL; - while (!xml.atEnd()) { - if (xml.readNext() == QXmlStreamReader::EndElement) - break; - if (xml.name() == "set") { - bool import = xml.attributes().value("import").toString().toInt(); - while (!xml.atEnd()) { - if (xml.readNext() == QXmlStreamReader::EndElement) - break; - if (xml.name() == "name") - edition = xml.readElementText(); - else if (xml.name() == "longname") - editionLong = xml.readElementText(); - else if (xml.name() == "url") - editionURL = xml.readElementText(); - } - newSetList.append(SetToDownload(edition, editionLong, editionURL, import)); - edition = editionLong = editionURL = QString(); - } else if (xml.name() == "picture_url") - pictureUrl = xml.readElementText(); - else if (xml.name() == "picture_url_hq") - pictureUrlHq = xml.readElementText(); - else if (xml.name() == "picture_url_st") - pictureUrlSt = xml.readElementText(); - else if (xml.name() == "set_url") - setUrl = xml.readElementText(); - } - if (newSetList.isEmpty()) - return false; - allSets = newSetList; - return true; + QString edition; + QString editionLong; + QVariant editionCards; + bool import; + + while (it.hasNext()) { + map = it.next().toMap(); + edition = map.value("code").toString(); + editionLong = map.value("name").toString(); + editionCards = map.value("cards"); + + // core and expansion sets are marked to be imported by default + import = (0 == QString::compare(map.value("type").toString(), QString("core"), Qt::CaseInsensitive) || + 0 == QString::compare(map.value("type").toString(), QString("expansion"), Qt::CaseInsensitive)); + + newSetList.append(SetToDownload(edition, editionLong, editionCards, import)); + } + + qSort(newSetList); + + if (newSetList.isEmpty()) + return false; + allSets = newSetList; + return true; } CardInfo *OracleImporter::addCard(const QString &setName, - QString cardName, - bool isToken, - int cardId, - const QString &cardCost, - const QString &cardType, - const QString &cardPT, - int cardLoyalty, - const QStringList &cardText) + QString cardName, + bool isToken, + int cardId, + QString &cardCost, + const QString &cardType, + const QString &cardPT, + int cardLoyalty, + const QStringList &cardText) { - QString fullCardText = cardText.join("\n"); - bool splitCard = false; - if (cardName.contains('(')) { - cardName.remove(QRegExp(" \\(.*\\)")); - splitCard = true; - } - // Workaround for card name weirdness - if (cardName.contains("XX")) - cardName.remove("XX"); - cardName = cardName.replace("Æ", "AE"); - cardName = cardName.replace("’", "'"); + QString fullCardText = cardText.join("\n"); + bool splitCard = false; + if (cardName.contains('(')) { + cardName.remove(QRegExp(" \\(.*\\)")); + splitCard = true; + } + // Workaround for card name weirdness + if (cardName.contains("XX")) + cardName.remove("XX"); + cardName = cardName.replace("Æ", "AE"); + cardName = cardName.replace("’", "'"); - CardInfo *card; - if (cardHash.contains(cardName)) { - card = cardHash.value(cardName); - if (splitCard && !card->getText().contains(fullCardText)) - card->setText(card->getText() + "\n---\n" + fullCardText); - } else { - bool mArtifact = false; - if (cardType.endsWith("Artifact")) - for (int i = 0; i < cardText.size(); ++i) - if (cardText[i].contains("{T}") && cardText[i].contains("to your mana pool")) - mArtifact = true; - - QStringList colors; - QStringList allColors = QStringList() << "W" << "U" << "B" << "R" << "G"; - for (int i = 0; i < allColors.size(); i++) - if (cardCost.contains(allColors[i])) - colors << allColors[i]; - - if (cardText.contains(cardName + " is white.")) - colors << "W"; - if (cardText.contains(cardName + " is blue.")) - colors << "U"; - if (cardText.contains(cardName + " is black.")) - colors << "B"; - if (cardText.contains(cardName + " is red.")) - colors << "R"; - if (cardText.contains(cardName + " is green.")) - colors << "G"; - - bool cipt = (cardText.contains(cardName + " enters the battlefield tapped.")); - - card = new CardInfo(this, cardName, isToken, cardCost, cardType, cardPT, fullCardText, colors, cardLoyalty, cipt); - int tableRow = 1; - QString mainCardType = card->getMainCardType(); - if ((mainCardType == "Land") || mArtifact) - tableRow = 0; - else if ((mainCardType == "Sorcery") || (mainCardType == "Instant")) - tableRow = 3; - else if (mainCardType == "Creature") - tableRow = 2; - card->setTableRow(tableRow); - - cardHash.insert(cardName, card); - } - card->setPicURL(setName, getPictureUrl(pictureUrl, cardId, cardName, setName)); - card->setPicURLHq(setName, getPictureUrl(pictureUrlHq, cardId, cardName, setName)); - card->setPicURLSt(setName, getPictureUrl(pictureUrlSt, cardId, cardName, setName)); - return card; + // Remove {} around mana costs + cardCost.remove(QChar('{')); + cardCost.remove(QChar('}')); + + CardInfo *card; + if (cardHash.contains(cardName)) { + card = cardHash.value(cardName); + if (splitCard && !card->getText().contains(fullCardText)) + card->setText(card->getText() + "\n---\n" + fullCardText); + } else { + bool mArtifact = false; + if (cardType.endsWith("Artifact")) + for (int i = 0; i < cardText.size(); ++i) + if (cardText[i].contains("{T}") && cardText[i].contains("to your mana pool")) + mArtifact = true; + + QStringList colors; + QStringList allColors = QStringList() << "W" << "U" << "B" << "R" << "G"; + for (int i = 0; i < allColors.size(); i++) + if (cardCost.contains(allColors[i])) + colors << allColors[i]; + + if (cardText.contains(cardName + " is white.")) + colors << "W"; + if (cardText.contains(cardName + " is blue.")) + colors << "U"; + if (cardText.contains(cardName + " is black.")) + colors << "B"; + if (cardText.contains(cardName + " is red.")) + colors << "R"; + if (cardText.contains(cardName + " is green.")) + colors << "G"; + + bool cipt = (cardText.contains(cardName + " enters the battlefield tapped.")); + + card = new CardInfo(this, cardName, isToken, cardCost, cardType, cardPT, fullCardText, colors, cardLoyalty, cipt); + int tableRow = 1; + QString mainCardType = card->getMainCardType(); + if ((mainCardType == "Land") || mArtifact) + tableRow = 0; + else if ((mainCardType == "Sorcery") || (mainCardType == "Instant")) + tableRow = 3; + else if (mainCardType == "Creature") + tableRow = 2; + card->setTableRow(tableRow); + + cardHash.insert(cardName, card); + } + card->setMuId(setName, cardId); + + return card; } -int OracleImporter::importTextSpoiler(CardSet *set, const QByteArray &data) +int OracleImporter::importTextSpoiler(CardSet *set, const QVariant &data) { - int cards = 0; - QString bufferContents(data); - - // Workaround for ampersand bug in text spoilers - int index = -1; - while ((index = bufferContents.indexOf('&', index + 1)) != -1) { - int semicolonIndex = bufferContents.indexOf(';', index); - if (semicolonIndex > 5) { - bufferContents.insert(index + 1, "amp;"); - index += 4; - } - } - - QDomDocument doc; - QString errorMsg; - int errorLine, errorColumn; - if (!doc.setContent(bufferContents, &errorMsg, &errorLine, &errorColumn)) - qDebug() << "error:" << errorMsg << "line:" << errorLine << "column:" << errorColumn; + int cards = 0; + + QListIterator it(data.toList()); + QVariantMap map; + QString cardName; + QString cardCost; + QString cardType; + QString cardPT; + QString cardText; + int cardId; + int cardLoyalty; + QMap splitCards; - QDomNodeList divs = doc.elementsByTagName("div"); - for (int i = 0; i < divs.size(); ++i) { - QDomElement div = divs.at(i).toElement(); - QDomNode divClass = div.attributes().namedItem("class"); - if (divClass.nodeValue() == "textspoiler") { - QString cardName, cardCost, cardType, cardPT, cardText; - int cardId = 0; - int cardLoyalty = 0; - - QDomNodeList trs = div.elementsByTagName("tr"); - for (int j = 0; j < trs.size(); ++j) { - QDomElement tr = trs.at(j).toElement(); - QDomNodeList tds = tr.elementsByTagName("td"); - if (tds.size() != 2) { - QStringList cardTextSplit = cardText.split("\n"); - for (int i = 0; i < cardTextSplit.size(); ++i) - cardTextSplit[i] = cardTextSplit[i].trimmed(); - - CardInfo *card = addCard(set->getShortName(), cardName, false, cardId, cardCost, cardType, cardPT, cardLoyalty, cardTextSplit); - if (!set->contains(card)) { - card->addToSet(set); - cards++; - } - cardName = cardCost = cardType = cardPT = cardText = QString(); - } else { - QString v1 = tds.at(0).toElement().text().simplified(); - QString v2 = tds.at(1).toElement().text().replace(trUtf8("—"), "-"); - - if (v1 == "Name") { - QDomElement a = tds.at(1).toElement().elementsByTagName("a").at(0).toElement(); - QString href = a.attributes().namedItem("href").nodeValue(); - cardId = href.mid(href.indexOf("multiverseid=") + 13).toInt(); - cardName = v2.simplified(); - } else if (v1 == "Cost:") - cardCost = v2.simplified(); - else if (v1 == "Type:") - cardType = v2.simplified(); - else if (v1 == "Pow/Tgh:") - cardPT = v2.simplified().remove('(').remove(')'); - else if (v1 == "Rules Text:") - cardText = v2.trimmed(); - else if (v1 == "Loyalty:") - cardLoyalty = v2.trimmed().remove('(').remove(')').toInt(); - } - } - break; - } - } - return cards; + while (it.hasNext()) { + map = it.next().toMap(); + if(0 == QString::compare(map.value("layout").toString(), QString("split"), Qt::CaseInsensitive)) + { + // Split card handling + cardId = map.contains("multiverseid") ? map.value("multiverseid").toInt() : 0; + if(splitCards.contains(cardId)) + { + // merge two split cards + QVariantMap tmpMap = splitCards.take(cardId); + QVariantMap * card1 = 0, * card2 = 0; + // same cardid + cardId = map.contains("multiverseid") ? map.value("multiverseid").toInt() : 0; + // this is currently an integer; can't accept 2 values + cardLoyalty = 0; + + // determine which subcard is the first one in the split + QStringList names=map.contains("names") ? map.value("names").toStringList() : QStringList(""); + if(names.count()>0 && + map.contains("name") && + 0 == QString::compare(map.value("name").toString(), names.at(0))) + { + // map is the left part of the split card, tmpMap is right part + card1 = ↦ + card2 = &tmpMap; + } else { + //tmpMap is the left part of the split card, map is right part + card1 = &tmpMap; + card2 = ↦ + } + + // add first card's data + cardName = card1->contains("name") ? card1->value("name").toString() : QString(""); + cardCost = card1->contains("manaCost") ? card1->value("manaCost").toString() : QString(""); + cardType = card1->contains("type") ? card1->value("type").toString() : QString(""); + cardPT = card1->contains("power") || card1->contains("toughness") ? card1->value("power").toString() + QString('/') + card1->value("toughness").toString() : QString(""); + cardText = card1->contains("text") ? card1->value("text").toString() : QString(""); + + // add second card's data + cardName += card2->contains("name") ? QString(" // ") + card2->value("name").toString() : QString(""); + cardCost += card2->contains("manaCost") ? QString(" // ") + card2->value("manaCost").toString() : QString(""); + cardType += card2->contains("type") ? QString(" // ") + card2->value("type").toString() : QString(""); + cardPT += card2->contains("power") || card2->contains("toughness") ? QString(" // ") + card2->value("power").toString() + QString('/') + card2->value("toughness").toString() : QString(""); + cardText += card2->contains("text") ? QString("\n\n---\n\n") + card2->value("text").toString() : QString(""); + } else { + // first card od a pair; enqueue for later merging + splitCards.insert(cardId, map); + continue; + } + } else { + // normal cards handling + cardName = map.contains("name") ? map.value("name").toString() : QString(""); + cardCost = map.contains("manaCost") ? map.value("manaCost").toString() : QString(""); + cardType = map.contains("type") ? map.value("type").toString() : QString(""); + cardPT = map.contains("power") || map.contains("toughness") ? map.value("power").toString() + QString('/') + map.value("toughness").toString() : QString(""); + cardText = map.contains("text") ? map.value("text").toString() : QString(""); + cardId = map.contains("multiverseid") ? map.value("multiverseid").toInt() : 0; + cardLoyalty = map.contains("loyalty") ? map.value("loyalty").toInt() : 0; + } + + CardInfo *card = addCard(set->getShortName(), cardName, false, cardId, cardCost, cardType, cardPT, cardLoyalty, cardText.split("\n")); + + if (!set->contains(card)) { + card->addToSet(set); + cards++; + } + } + + return cards; } -QString OracleImporter::getPictureUrl(QString url, int cardId, QString name, const QString &setName) const +int OracleImporter::startImport() { - if ((name == "Island") || (name == "Swamp") || (name == "Mountain") || (name == "Plains") || (name == "Forest")) - name.append("1"); - return url.replace("!cardid!", QString::number(cardId)).replace("!set!", setName).replace("!name!", name - .replace("ö", "o") -// .remove('\'') - .remove(" // ") -// .remove(',') -// .remove(':') -// .remove('.') - .remove(QRegExp("\\(.*\\)")) - .simplified() -// .replace(' ', '_') -// .replace('-', '_') - ); -} - -int OracleImporter::startDownload() -{ - clear(); - - setsToDownload.clear(); - for (int i = 0; i < allSets.size(); ++i) - if (allSets[i].getImport()) - setsToDownload.append(allSets[i]); - - if (setsToDownload.isEmpty()) - return 0; - setIndex = 0; - emit setIndexChanged(0, 0, setsToDownload[0].getLongName()); - - downloadNextFile(); - return setsToDownload.size(); -} - -void OracleImporter::downloadNextFile() -{ - QString urlString = setsToDownload[setIndex].getUrl(); - if (urlString.isEmpty()) - urlString = setUrl; - urlString = urlString.replace("!longname!", setsToDownload[setIndex].getLongName()); - - if (urlString.startsWith("http://")) { - QUrl url(urlString); - http->setHost(url.host(), QHttp::ConnectionModeHttp, url.port() == -1 ? 0 : url.port()); - QString path = QUrl::toPercentEncoding(urlString.mid(url.host().size() + 7).replace(' ', '+'), "?!$&'()*+,;=:@/"); - - buffer->close(); - buffer->setData(QByteArray()); - buffer->open(QIODevice::ReadWrite | QIODevice::Text); - reqId = http->get(path, buffer); - } else { - QFile file(dataDir + "/" + urlString); - file.open(QIODevice::ReadOnly | QIODevice::Text); - - buffer->close(); - buffer->setData(file.readAll()); - buffer->open(QIODevice::ReadWrite | QIODevice::Text); - reqId = 0; - httpRequestFinished(reqId, false); - } -} - -void OracleImporter::httpRequestFinished(int requestId, bool error) -{ - if (error) { - QMessageBox::information(0, tr("HTTP"), tr("Error.")); - return; - } - if (requestId != reqId) - return; - - CardSet *set = new CardSet(setsToDownload[setIndex].getShortName(), setsToDownload[setIndex].getLongName()); - if (!setHash.contains(set->getShortName())) - setHash.insert(set->getShortName(), set); - - buffer->seek(0); - buffer->close(); - int cards = importTextSpoiler(set, buffer->data()); - if (cards > 0) - ++setIndex; - - if (setIndex == setsToDownload.size()) { - emit setIndexChanged(cards, setIndex, QString()); - setIndex = -1; - } else { - downloadNextFile(); - emit setIndexChanged(cards, setIndex, setsToDownload[setIndex].getLongName()); - } -} - -void OracleImporter::readResponseHeader(const QHttpResponseHeader &responseHeader) -{ - switch (responseHeader.statusCode()) { - case 200: - case 301: - case 302: - case 303: - case 307: - break; - default: - QMessageBox::information(0, tr("HTTP"), tr("Download failed: %1.").arg(responseHeader.reasonPhrase())); - http->abort(); - deleteLater(); - } + clear(); + + int setCards = 0, setIndex= 0; + QListIterator it(allSets); + const SetToDownload * curSet; + + while (it.hasNext()) + { + curSet = & it.next(); + if(!curSet->getImport()) + continue; + + CardSet *set = new CardSet(curSet->getShortName(), curSet->getLongName()); + if (!setHash.contains(set->getShortName())) + setHash.insert(set->getShortName(), set); + + int setCards = importTextSpoiler(set, curSet->getCards()); + + ++setIndex; + + emit setIndexChanged(setCards, setIndex, curSet->getLongName()); + } + + emit setIndexChanged(setCards, setIndex, QString()); + + // total number of sets + return setIndex; } diff --git a/oracle/src/oracleimporter.h b/oracle/src/oracleimporter.h index 3e03586f..1f705eeb 100644 --- a/oracle/src/oracleimporter.h +++ b/oracle/src/oracleimporter.h @@ -1,53 +1,42 @@ #ifndef ORACLEIMPORTER_H #define ORACLEIMPORTER_H -#include -#include +#include -class QBuffer; -class QXmlStreamReader; +#include class SetToDownload { private: - QString shortName, longName, url; + QString shortName, longName; bool import; + QVariant cards; public: const QString &getShortName() const { return shortName; } const QString &getLongName() const { return longName; } - const QString &getUrl() const { return url; } + const QVariant &getCards() const { return cards; } bool getImport() const { return import; } void setImport(bool _import) { import = _import; } - SetToDownload(const QString &_shortName, const QString &_longName, const QString &_url, bool _import) - : shortName(_shortName), longName(_longName), url(_url), import(_import) { } + SetToDownload(const QString &_shortName, const QString &_longName, const QVariant &_cards, bool _import) + : shortName(_shortName), longName(_longName), cards(_cards), import(_import) { } + bool operator<(const SetToDownload &set) const { return longName.compare(set.longName, Qt::CaseInsensitive) < 0; } }; class OracleImporter : public CardDatabase { Q_OBJECT private: - QList allSets, setsToDownload; - QString pictureUrl, pictureUrlHq, pictureUrlSt, setUrl; + QList allSets; + QVariantMap setsMap; QString dataDir; - int setIndex; - int reqId; - QBuffer *buffer; - QHttp *http; - QString getPictureUrl(QString url, int cardId, QString name, const QString &setName) const; - void downloadNextFile(); - bool readSetsFromXml(QXmlStreamReader &xml); - CardInfo *addCard(const QString &setName, QString cardName, bool isToken, int cardId, const QString &cardCost, const QString &cardType, const QString &cardPT, int cardLoyalty, const QStringList &cardText); -private slots: - void httpRequestFinished(int requestId, bool error); - void readResponseHeader(const QHttpResponseHeader &responseHeader); + CardInfo *addCard(const QString &setName, QString cardName, bool isToken, int cardId, QString &cardCost, const QString &cardType, const QString &cardPT, int cardLoyalty, const QStringList &cardText); signals: - void setIndexChanged(int cardsImported, int setIndex, const QString &nextSetName); + void setIndexChanged(int cardsImported, int setIndex, const QString &setName); void dataReadProgress(int bytesRead, int totalBytes); public: OracleImporter(const QString &_dataDir, QObject *parent = 0); bool readSetsFromByteArray(const QByteArray &data); - bool readSetsFromFile(const QString &fileName); - int startDownload(); - int importTextSpoiler(CardSet *set, const QByteArray &data); + int startImport(); + int importTextSpoiler(CardSet *set, const QVariant &data); QList &getSets() { return allSets; } const QString &getDataDir() const { return dataDir; } }; diff --git a/oracle/src/oraclewizard.cpp b/oracle/src/oraclewizard.cpp new file mode 100644 index 00000000..75381b1d --- /dev/null +++ b/oracle/src/oraclewizard.cpp @@ -0,0 +1,398 @@ +#include +#include +#include +#include +#include + +#include "oraclewizard.h" +#include "oracleimporter.h" + +#define ALLSETS_URL "http://mtgjson.com/json/AllSets.json" + +OracleWizard::OracleWizard(QWidget *parent) + : QWizard(parent) +{ + settings = new QSettings(this); + importer = new OracleImporter(QDesktopServices::storageLocation(QDesktopServices::DataLocation), this); + + addPage(new IntroPage); + addPage(new LoadSetsPage); + addPage(new ChooseSetsPage); + addPage(new SaveSetsPage); + + setWindowTitle(tr("Oracle Importer")); + QWizard::setButtonText(QWizard::FinishButton, tr("Save")); +} + +void OracleWizard::accept() +{ + QDialog::accept(); +} + +void OracleWizard::enableButtons() +{ + button(QWizard::NextButton)->setDisabled(false); + button(QWizard::BackButton)->setDisabled(false); +} + +void OracleWizard::disableButtons() +{ + button(QWizard::NextButton)->setDisabled(true); + button(QWizard::BackButton)->setDisabled(true); +} + +IntroPage::IntroPage(QWidget *parent) + : OracleWizardPage(parent) +{ + setTitle(tr("Introduction")); + + label = new QLabel(tr("This wizard will import the list of sets and cards " + "that will be used by Cockatrice. You will need to " + "specify an url or a filename that will be used as a " + "source, and then choose the wanted sets from the list " + "of the available ones."), + this); + label->setWordWrap(true); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(label); + setLayout(layout); +} + +LoadSetsPage::LoadSetsPage(QWidget *parent) + : OracleWizardPage(parent), nam(0) +{ + setTitle(tr("Source selection")); + setSubTitle(tr("Please specify a source for the list of sets and cards. " + "You can specify an url address that will be download or " + "use an existing file from your computer.")); + + urlRadioButton = new QRadioButton(tr("Download url:"), this); + fileRadioButton = new QRadioButton(tr("Local file:"), this); + + urlLineEdit = new QLineEdit(this); + fileLineEdit = new QLineEdit(this); + + progressLabel = new QLabel(this); + progressBar = new QProgressBar(this); + + urlRadioButton->setChecked(true); + + fileButton = new QPushButton(tr("Choose file..."), this); + connect(fileButton, SIGNAL(clicked()), this, SLOT(actLoadSetsFile())); + + QGridLayout *layout = new QGridLayout(this); + layout->addWidget(urlRadioButton, 0, 0); + layout->addWidget(urlLineEdit, 0, 1); + layout->addWidget(fileRadioButton, 1, 0); + layout->addWidget(fileLineEdit, 1, 1); + layout->addWidget(fileButton, 2, 1, Qt::AlignRight); + layout->addWidget(progressLabel, 3, 0); + layout->addWidget(progressBar, 3, 1); + + connect(&watcher, SIGNAL(finished()), this, SLOT(importFinished())); + + setLayout(layout); +} + +void LoadSetsPage::initializePage() +{ + urlLineEdit->setText(wizard()->settings->value("allsetsurl", ALLSETS_URL).toString()); + + progressLabel->hide(); + progressBar->hide(); +} + +void LoadSetsPage::actLoadSetsFile() +{ + QFileDialog dialog(this, tr("Load sets file")); + dialog.setFileMode(QFileDialog::ExistingFile); + dialog.setNameFilter("Sets JSON file (*.json)"); + + if(!fileLineEdit->text().isEmpty() && QFile::exists(fileLineEdit->text())) + dialog.selectFile(fileLineEdit->text()); + + if (!dialog.exec()) + return; + + fileLineEdit->setText(dialog.selectedFiles().at(0)); +} + +bool LoadSetsPage::validatePage() +{ + // once the import is finished, we call next(); skip validation + if(wizard()->importer->getSets().count() > 0) + return true; + + // else, try to import sets + if(urlRadioButton->isChecked()) + { + QUrl url = QUrl::fromUserInput(urlLineEdit->text()); + if(!url.isValid()) + { + QMessageBox::critical(this, tr("Error"), tr("The provided url is not valid.")); + return false; + } + + progressLabel->setText(tr("Downloading (0MB)")); + // show an infinite progressbar + progressBar->setMaximum(0); + progressBar->setMinimum(0); + progressBar->setValue(0); + progressLabel->show(); + progressBar->show(); + + wizard()->disableButtons(); + setEnabled(false); + + if(!nam) + nam = new QNetworkAccessManager(this); + QNetworkReply *reply = nam->get(QNetworkRequest(url)); + + connect(reply, SIGNAL(finished()), this, SLOT(actDownloadFinishedSetsFile())); + connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(actDownloadProgressSetsFile(qint64, qint64))); + + } else if(fileRadioButton->isChecked()) { + QFile setsFile(fileLineEdit->text()); + if(!setsFile.exists()) + { + QMessageBox::critical(this, tr("Error"), tr("Please choose a file.")); + return false; + } + + if (!setsFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox::critical(0, tr("Error"), tr("Cannot open file '%1'.").arg(fileLineEdit->text())); + return false; + } + + wizard()->disableButtons(); + setEnabled(false); + + readSetsFromByteArray(setsFile.readAll()); + + } + return false; +} + +void LoadSetsPage::actDownloadProgressSetsFile(qint64 received, qint64 total) +{ + if(total > 0 && progressBar->maximum()==0) + { + progressBar->setMaximum(total); + progressBar->setValue(received); + } + progressLabel->setText(tr("Downloading (%1MB)").arg((int) received / 1048576)); +} + +void LoadSetsPage::actDownloadFinishedSetsFile() +{ + progressLabel->hide(); + progressBar->hide(); + + // check for a reply + QNetworkReply *reply = static_cast(sender()); + QNetworkReply::NetworkError errorCode = reply->error(); + if (errorCode != QNetworkReply::NoError) { + QMessageBox::critical(this, tr("Error"), tr("Network error: %1.").arg(reply->errorString())); + + wizard()->enableButtons(); + setEnabled(true); + + reply->deleteLater(); + return; + } + + // save allsets.json url, but only if the user customized it and download was successfull + if(urlLineEdit->text() != QString(ALLSETS_URL)) + wizard()->settings->setValue("allsetsurl", urlLineEdit->text()); + else + wizard()->settings->remove("allsetsurl"); + + readSetsFromByteArray(reply->readAll()); + reply->deleteLater(); +} + +void LoadSetsPage::readSetsFromByteArray(QByteArray data) +{ + // show an infinite progressbar + progressBar->setMaximum(0); + progressBar->setMinimum(0); + progressBar->setValue(0); + progressLabel->setText(tr("Parsing file")); + progressLabel->show(); + progressBar->show(); + + // Start the computation. + future = QtConcurrent::run(wizard()->importer, &OracleImporter::readSetsFromByteArray, data); + watcher.setFuture(future); +} + +void LoadSetsPage::importFinished() +{ + wizard()->enableButtons(); + setEnabled(true); + progressLabel->hide(); + progressBar->hide(); + + if(watcher.future().result()) + { + wizard()->next(); + } else { + QMessageBox::critical(this, tr("Error"), tr("The file was retrieved successfully, but it does not contain any sets data.")); + } +} + +ChooseSetsPage::ChooseSetsPage(QWidget *parent) + : OracleWizardPage(parent) +{ + setTitle(tr("Sets selection")); + setSubTitle(tr("The following sets has been found in the source file. " + "Please mark the sets that will be imported.")); + + checkBoxLayout = new QVBoxLayout; + + QWidget *checkboxFrame = new QWidget(this); + checkboxFrame->setLayout(checkBoxLayout); + + QScrollArea *checkboxArea = new QScrollArea(this); + checkboxArea->setWidget(checkboxFrame); + checkboxArea->setWidgetResizable(true); + + checkAllButton = new QPushButton(tr("&Check all")); + connect(checkAllButton, SIGNAL(clicked()), this, SLOT(actCheckAll())); + uncheckAllButton = new QPushButton(tr("&Uncheck all")); + connect(uncheckAllButton, SIGNAL(clicked()), this, SLOT(actUncheckAll())); + + QGridLayout *layout = new QGridLayout(this); + layout->addWidget(checkboxArea, 0, 0, 1, 2); + layout->addWidget(checkAllButton, 1, 0); + layout->addWidget(uncheckAllButton, 1, 1); + + setLayout(layout); +} + +void ChooseSetsPage::initializePage() +{ + // populate checkbox list + for (int i = 0; i < checkBoxList.size(); ++i) + delete checkBoxList[i]; + checkBoxList.clear(); + + QList &sets = wizard()->importer->getSets(); + for (int i = 0; i < sets.size(); ++i) { + QCheckBox *checkBox = new QCheckBox(sets[i].getLongName()); + checkBox->setChecked(sets[i].getImport()); + connect(checkBox, SIGNAL(stateChanged(int)), this, SLOT(checkBoxChanged(int))); + checkBoxLayout->addWidget(checkBox); + checkBoxList << checkBox; + } +} + +void ChooseSetsPage::checkBoxChanged(int state) +{ + QCheckBox *checkBox = qobject_cast(sender()); + QList &sets = wizard()->importer->getSets(); + for (int i = 0; i < sets.size(); ++i) + if (sets[i].getLongName() == checkBox->text()) { + sets[i].setImport(state); + break; + } +} + +void ChooseSetsPage::actCheckAll() +{ + for (int i = 0; i < checkBoxList.size(); ++i) + checkBoxList[i]->setChecked(true); +} + +void ChooseSetsPage::actUncheckAll() +{ + for (int i = 0; i < checkBoxList.size(); ++i) + checkBoxList[i]->setChecked(false); +} + +bool ChooseSetsPage::validatePage() +{ + for (int i = 0; i < checkBoxList.size(); ++i) + { + if(checkBoxList[i]->isChecked()) + return true; + } + + QMessageBox::critical(this, tr("Error"), tr("Please mark at least one set.")); + return false; +} + +SaveSetsPage::SaveSetsPage(QWidget *parent) + : OracleWizardPage(parent) +{ + setTitle(tr("Sets imported")); + setSubTitle(tr("The following sets has been imported. " + "Press \"Save\" to save the imported cards to the Cockatrice database.")); + + defaultPathCheckBox = new QCheckBox(this); + defaultPathCheckBox->setText(tr("Save to the default path (recommended)")); + defaultPathCheckBox->setChecked(true); + + messageLog = new QTextEdit(this); + messageLog->setReadOnly(true); + + QGridLayout *layout = new QGridLayout(this); + layout->addWidget(defaultPathCheckBox, 0, 0); + layout->addWidget(messageLog, 1, 0); + + setLayout(layout); +} + +void SaveSetsPage::cleanupPage() +{ + disconnect(wizard()->importer, SIGNAL(setIndexChanged(int, int, const QString &)), 0, 0); +} + +void SaveSetsPage::initializePage() +{ + messageLog->clear(); + + connect(wizard()->importer, SIGNAL(setIndexChanged(int, int, const QString &)), this, SLOT(updateTotalProgress(int, int, const QString &))); + + if (!wizard()->importer->startImport()) + QMessageBox::critical(this, tr("Error"), tr("No set has been imported.")); +} + +void SaveSetsPage::updateTotalProgress(int cardsImported, int setIndex, const QString &setName) +{ + if (setName.isEmpty()) { + messageLog->append("" + tr("Import finished: %1 cards.").arg(wizard()->importer->getCardList().size()) + ""); + } else { + messageLog->append(tr("%1: %2 cards imported").arg(setName).arg(cardsImported)); + } + messageLog->verticalScrollBar()->setValue(messageLog->verticalScrollBar()->maximum()); +} + +bool SaveSetsPage::validatePage() +{ + bool ok = false; + const QString dataDir = QDesktopServices::storageLocation(QDesktopServices::DataLocation); + QDir dir(dataDir); + if (!dir.exists()) + dir.mkpath(dataDir); + QString savePath = dataDir + "/cards.xml"; + do { + QString fileName; + if (savePath.isEmpty() || !defaultPathCheckBox->isChecked()) + fileName = QFileDialog::getSaveFileName(this, tr("Save card database"), dataDir + "/cards.xml", tr("XML card database (*.xml)")); + else { + fileName = savePath; + savePath.clear(); + } + if (fileName.isEmpty()) { + return false; + } + if (wizard()->importer->saveToFile(fileName)) + ok = true; + else + QMessageBox::critical(this, tr("Error"), tr("The file could not be saved to the desired location.")); + } while (!ok); + + return true; +} diff --git a/oracle/src/oraclewizard.h b/oracle/src/oraclewizard.h new file mode 100644 index 00000000..6e6a6ea8 --- /dev/null +++ b/oracle/src/oraclewizard.h @@ -0,0 +1,114 @@ +#ifndef ORACLEWIZARD_H +#define ORACLEWIZARD_H + +#include +#include +#include + +class QCheckBox; +class QGroupBox; +class QLabel; +class QLineEdit; +class QRadioButton; +class QProgressBar; +class QNetworkAccessManager; +class QTextEdit; +class QVBoxLayout; +class OracleImporter; +class QSettings; + +class OracleWizard : public QWizard +{ + Q_OBJECT +public: + OracleWizard(QWidget *parent = 0); + void accept(); + void enableButtons(); + void disableButtons(); +public: + OracleImporter *importer; + QSettings * settings; +}; + + +class OracleWizardPage : public QWizardPage +{ + Q_OBJECT +public: + OracleWizardPage(QWidget *parent = 0): QWizardPage(parent) {}; +protected: + inline OracleWizard *wizard() { return (OracleWizard*) QWizardPage::wizard(); }; +}; + +class IntroPage : public OracleWizardPage +{ + Q_OBJECT +public: + IntroPage(QWidget *parent = 0); +private: + QLabel *label; +}; + +class LoadSetsPage : public OracleWizardPage +{ + Q_OBJECT +public: + LoadSetsPage(QWidget *parent = 0); +protected: + void initializePage(); + bool validatePage(); + void readSetsFromByteArray(QByteArray data); +private: + QRadioButton *urlRadioButton; + QRadioButton *fileRadioButton; + QLineEdit *urlLineEdit; + QLineEdit *fileLineEdit; + QPushButton *fileButton; + QLabel *progressLabel; + QProgressBar * progressBar; + + QNetworkAccessManager *nam; + QFutureWatcher watcher; + QFuture future; +private slots: + void actLoadSetsFile(); + void actDownloadProgressSetsFile(qint64 received, qint64 total); + void actDownloadFinishedSetsFile(); + void importFinished(); +}; + +class ChooseSetsPage : public OracleWizardPage +{ + Q_OBJECT +public: + ChooseSetsPage(QWidget *parent = 0); +protected: + void initializePage(); + bool validatePage(); +private: + QPushButton *checkAllButton, *uncheckAllButton; + QVBoxLayout *checkBoxLayout; + QList checkBoxList; +private slots: + void actCheckAll(); + void actUncheckAll(); + void checkBoxChanged(int state); +}; + +class SaveSetsPage : public OracleWizardPage +{ + Q_OBJECT +public: + SaveSetsPage(QWidget *parent = 0); +private: + QTextEdit *messageLog; + QCheckBox * defaultPathCheckBox; +protected: + void initializePage(); + void cleanupPage(); + bool validatePage(); +private slots: + void updateTotalProgress(int cardsImported, int setIndex, const QString &setName); +}; + +#endif \ No newline at end of file diff --git a/oracle/src/window_main.cpp b/oracle/src/window_main.cpp deleted file mode 100644 index 6d4a1be9..00000000 --- a/oracle/src/window_main.cpp +++ /dev/null @@ -1,256 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "window_main.h" -#include "oracleimporter.h" - -const QString WindowMain::defaultSetsUrl = QString("http://www.woogerworks.com/files/sets.xml"); - -WindowMain::WindowMain(QWidget *parent) - : QMainWindow(parent) -{ -#if QT_VERSION < 0x050000 - importer = new OracleImporter(QDesktopServices::storageLocation(QDesktopServices::DataLocation), this); -#else - importer = new OracleImporter(QStandardPaths::standardLocations(QStandardPaths::DataLocation)).toString(), this); -#endif - nam = new QNetworkAccessManager(this); - - checkBoxLayout = new QVBoxLayout; - - QWidget *checkboxFrame = new QWidget; - checkboxFrame->setLayout(checkBoxLayout); - - QScrollArea *checkboxArea = new QScrollArea; - checkboxArea->setWidget(checkboxFrame); - checkboxArea->setWidgetResizable(true); - - checkAllButton = new QPushButton(tr("&Check all")); - connect(checkAllButton, SIGNAL(clicked()), this, SLOT(actCheckAll())); - uncheckAllButton = new QPushButton(tr("&Uncheck all")); - connect(uncheckAllButton, SIGNAL(clicked()), this, SLOT(actUncheckAll())); - - QHBoxLayout *checkAllButtonLayout = new QHBoxLayout; - checkAllButtonLayout->addWidget(checkAllButton); - checkAllButtonLayout->addWidget(uncheckAllButton); - - startButton = new QPushButton(tr("&Start download")); - connect(startButton, SIGNAL(clicked()), this, SLOT(actStart())); - - QVBoxLayout *settingsLayout = new QVBoxLayout; - settingsLayout->addWidget(checkboxArea); - settingsLayout->addLayout(checkAllButtonLayout); - settingsLayout->addWidget(startButton); - - totalLabel = new QLabel(tr("Total progress:")); - totalProgressBar = new QProgressBar; - nextSetLabel1 = new QLabel(tr("Current file:")); - nextSetLabel2 = new QLabel; - fileLabel = new QLabel(tr("Progress:")); - fileProgressBar = new QProgressBar; - - messageLog = new QTextEdit; - messageLog->setReadOnly(true); - - QGridLayout *grid = new QGridLayout; - grid->addWidget(totalLabel, 0, 0); - grid->addWidget(totalProgressBar, 0, 1); - grid->addWidget(nextSetLabel1, 1, 0); - grid->addWidget(nextSetLabel2, 1, 1); - grid->addWidget(fileLabel, 2, 0); - grid->addWidget(fileProgressBar, 2, 1); - grid->addWidget(messageLog, 3, 0, 1, 2); - - QHBoxLayout *mainLayout = new QHBoxLayout; - mainLayout->addLayout(settingsLayout, 6); - mainLayout->addSpacing(10); - mainLayout->addLayout(grid, 10); - - QWidget *centralWidget = new QWidget; - centralWidget->setLayout(mainLayout); - setCentralWidget(centralWidget); - - connect(importer, SIGNAL(setIndexChanged(int, int, const QString &)), this, SLOT(updateTotalProgress(int, int, const QString &))); - connect(importer, SIGNAL(dataReadProgress(int, int)), this, SLOT(updateFileProgress(int, int))); - - aLoadSetsFile = new QAction(tr("Load sets information from &file..."), this); - connect(aLoadSetsFile, SIGNAL(triggered()), this, SLOT(actLoadSetsFile())); - aDownloadSetsFile = new QAction(tr("&Download sets information..."), this); - connect(aDownloadSetsFile, SIGNAL(triggered()), this, SLOT(actDownloadSetsFile())); - aExit = new QAction(tr("E&xit"), this); - connect(aExit, SIGNAL(triggered()), this, SLOT(close())); - - fileMenu = menuBar()->addMenu(tr("&File")); - fileMenu->addAction(aLoadSetsFile); - fileMenu->addAction(aDownloadSetsFile); - fileMenu->addSeparator(); - fileMenu->addAction(aExit); - - setWindowTitle(tr("Oracle importer")); - setMinimumSize(750, 500); - - QStringList args = qApp->arguments(); - if (args.contains("-dlsets")) - downloadSetsFile(defaultSetsUrl); - - statusLabel = new QLabel; - statusBar()->addWidget(statusLabel); - statusLabel->setText(tr("Sets data not loaded.")); -} - -void WindowMain::updateSetList() -{ - for (int i = 0; i < checkBoxList.size(); ++i) - delete checkBoxList[i]; - checkBoxList.clear(); - - QList &sets = importer->getSets(); - for (int i = 0; i < sets.size(); ++i) { - QCheckBox *checkBox = new QCheckBox(sets[i].getLongName()); - checkBox->setChecked(sets[i].getImport()); - connect(checkBox, SIGNAL(stateChanged(int)), this, SLOT(checkBoxChanged(int))); - checkBoxLayout->addWidget(checkBox); - checkBoxList << checkBox; - } - statusLabel->setText(tr("Ready.")); -} - -void WindowMain::actLoadSetsFile() -{ - QFileDialog dialog(this, tr("Load sets file")); - dialog.setFileMode(QFileDialog::ExistingFile); - dialog.setNameFilter("Sets XML file (*.xml)"); - if (!dialog.exec()) - return; - - QString fileName = dialog.selectedFiles().at(0); - if (importer->readSetsFromFile(fileName)) - updateSetList(); - else - QMessageBox::critical(this, tr("Error"), tr("This file does not contain any sets data.")); -} - -void WindowMain::actDownloadSetsFile() -{ - QString url = QInputDialog::getText(this, tr("Load sets from URL"), tr("Please enter the URL of the sets file:"), QLineEdit::Normal, defaultSetsUrl); - if (!url.isEmpty()) - downloadSetsFile(url); -} - -void WindowMain::downloadSetsFile(const QString &url) -{ - QNetworkReply *reply = nam->get(QNetworkRequest(url)); - connect(reply, SIGNAL(finished()), this, SLOT(setsDownloadFinished())); -} - -void WindowMain::setsDownloadFinished() -{ - QNetworkReply *reply = static_cast(sender()); - QNetworkReply::NetworkError errorCode = reply->error(); - if (errorCode == QNetworkReply::NoError) { - if (importer->readSetsFromByteArray(reply->readAll())) - updateSetList(); - else - QMessageBox::critical(this, tr("Error"), tr("The file was retrieved successfully, but it does not contain any sets data.")); - } else - QMessageBox::critical(this, tr("Error"), tr("Network error: %1.").arg(reply->errorString())); - reply->deleteLater(); -} - -void WindowMain::updateTotalProgress(int cardsImported, int setIndex, const QString &nextSetName) -{ - if (setIndex != 0) - messageLog->append(QString("%1: %2 cards imported").arg(nextSetLabel2->text()).arg(cardsImported)); - totalProgressBar->setValue(setIndex); - if (nextSetName.isEmpty()) { - QMessageBox::information(this, tr("Oracle importer"), tr("Import finished: %1 cards.").arg(importer->getCardList().size())); - bool ok = false; -#if QT_VERSION < 0x050000 - const QString dataDir = QDesktopServices::storageLocation(QDesktopServices::DataLocation); -#else - const QString dataDir = QStandardPaths::standardLocations(QStandardPaths::DataLocation)).toString(); -#endif - QDir dir(dataDir); - if (!dir.exists()) - dir.mkpath(dataDir); - QString savePath = dataDir + "/cards.xml"; - do { - QString fileName; - if (savePath.isEmpty()) - fileName = QFileDialog::getSaveFileName(this, tr("Save card database"), dataDir + "/cards.xml", tr("XML card database (*.xml)")); - else { - fileName = savePath; - savePath.clear(); - } - if (fileName.isEmpty()) { - qApp->quit(); - return; - } - if (importer->saveToFile(fileName)) - ok = true; - else - QMessageBox::critical(this, tr("Error"), tr("The file could not be saved to the desired location.")); - } while (!ok); - qApp->quit(); - } else { - nextSetLabel2->setText(nextSetName); - } -} - -void WindowMain::updateFileProgress(int bytesRead, int totalBytes) -{ - fileProgressBar->setMaximum(totalBytes); - fileProgressBar->setValue(bytesRead); -} - -void WindowMain::actCheckAll() -{ - for (int i = 0; i < checkBoxList.size(); ++i) - checkBoxList[i]->setChecked(true); -} - -void WindowMain::actUncheckAll() -{ - for (int i = 0; i < checkBoxList.size(); ++i) - checkBoxList[i]->setChecked(false); -} - -void WindowMain::actStart() -{ - int setsCount = importer->startDownload(); - if (!setsCount) { - QMessageBox::critical(this, tr("Error"), tr("No sets to download selected.")); - return; - } - for (int i = 0; i < checkBoxList.size(); ++i) - checkBoxList[i]->setEnabled(false); - startButton->setEnabled(false); - totalProgressBar->setMaximum(setsCount); - statusLabel->setText(tr("Downloading card data...")); -} - -void WindowMain::checkBoxChanged(int state) -{ - QCheckBox *checkBox = qobject_cast(sender()); - QList &sets = importer->getSets(); - for (int i = 0; i < sets.size(); ++i) - if (sets[i].getLongName() == checkBox->text()) { - sets[i].setImport(state); - break; - } -} diff --git a/oracle/src/window_main.h b/oracle/src/window_main.h deleted file mode 100644 index 1fdba235..00000000 --- a/oracle/src/window_main.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef WINDOW_MAIN_H -#define WINDOW_MAIN_H - -#include -#include - -class OracleImporter; -class QLabel; -class QProgressBar; -class QTextEdit; -class QPushButton; -class QCheckBox; -class QVBoxLayout; -class QMenu; -class QAction; -class QNetworkAccessManager; - -class WindowMain : public QMainWindow { - Q_OBJECT -private: - static const QString defaultSetsUrl; - - OracleImporter *importer; - QNetworkAccessManager *nam; - - QMenu *fileMenu; - QAction *aLoadSetsFile, *aDownloadSetsFile, *aExit; - QPushButton *checkAllButton, *uncheckAllButton, *startButton; - QLabel *totalLabel, *fileLabel, *nextSetLabel1, *nextSetLabel2; - QProgressBar *totalProgressBar, *fileProgressBar; - QTextEdit *messageLog; - QVBoxLayout *checkBoxLayout; - QList checkBoxList; - QLabel *statusLabel; - - void downloadSetsFile(const QString &url); -private slots: - void updateTotalProgress(int cardsImported, int setIndex, const QString &nextSetName); - void updateFileProgress(int bytesRead, int totalBytes); - void updateSetList(); - void actCheckAll(); - void actUncheckAll(); - void actStart(); - void actLoadSetsFile(); - void actDownloadSetsFile(); - void setsDownloadFinished(); - void checkBoxChanged(int state); -public: - WindowMain(QWidget *parent = 0); -}; - -#endif