diff --git a/cockatrice/src/decklistmodel.cpp b/cockatrice/src/decklistmodel.cpp index ec10ff05..c55af013 100644 --- a/cockatrice/src/decklistmodel.cpp +++ b/cockatrice/src/decklistmodel.cpp @@ -472,13 +472,7 @@ void DeckListModel::printDeckList(QPrinter *printer) doc.print(printer); } -void DeckListModel::pricesUpdated(InnerDecklistNode *node) +void DeckListModel::pricesUpdated() { - if (!node) - node = root; - - if (node->isEmpty()) - return; - - emit dataChanged(createIndex(0, 2, node->at(0)), createIndex(node->size() - 1, 2, node->last())); + emit layoutChanged(); } diff --git a/cockatrice/src/decklistmodel.h b/cockatrice/src/decklistmodel.h index 2873c818..f4e93e3b 100644 --- a/cockatrice/src/decklistmodel.h +++ b/cockatrice/src/decklistmodel.h @@ -51,7 +51,7 @@ public: void cleanList(); DeckLoader *getDeckList() const { return deckList; } void setDeckList(DeckLoader *_deck); - void pricesUpdated(InnerDecklistNode *node = 0); + void pricesUpdated(); private: DeckLoader *deckList; InnerDecklistNode *root; diff --git a/cockatrice/src/dlg_settings.cpp b/cockatrice/src/dlg_settings.cpp index 14115824..0950ec60 100644 --- a/cockatrice/src/dlg_settings.cpp +++ b/cockatrice/src/dlg_settings.cpp @@ -18,11 +18,13 @@ #include #include #include +#include #include #include "carddatabase.h" #include "dlg_settings.h" #include "main.h" #include "settingscache.h" +#include "priceupdater.h" GeneralSettingsPage::GeneralSettingsPage() { @@ -531,9 +533,30 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() priceTagsCheckBox = new QCheckBox; priceTagsCheckBox->setChecked(settingsCache->getPriceTagFeature()); connect(priceTagsCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPriceTagFeature(int))); - + + priceTagSource0 = new QRadioButton; + priceTagSource1 = new QRadioButton; + + switch(settingsCache->getPriceTagSource()) + { + case AbstractPriceUpdater::DBPriceSource: + priceTagSource1->setChecked(true); + break; + case AbstractPriceUpdater::BLPPriceSource: + default: + priceTagSource0->setChecked(true); + break; + } + + connect(priceTagSource0, SIGNAL(toggled(bool)), this, SLOT(radioPriceTagSourceClicked(bool))); + connect(priceTagSource1, SIGNAL(toggled(bool)), this, SLOT(radioPriceTagSourceClicked(bool))); + + connect(this, SIGNAL(priceTagSourceChanged(int)), settingsCache, SLOT(setPriceTagSource(int))); + QGridLayout *generalGrid = new QGridLayout; generalGrid->addWidget(priceTagsCheckBox, 0, 0); + generalGrid->addWidget(priceTagSource0, 1, 0); + generalGrid->addWidget(priceTagSource1, 2, 0); generalGroupBox = new QGroupBox; generalGroupBox->setLayout(generalGrid); @@ -546,10 +569,26 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() void DeckEditorSettingsPage::retranslateUi() { - priceTagsCheckBox->setText(tr("Enable &price tag feature (using data from blacklotusproject.com)")); + priceTagsCheckBox->setText(tr("Enable &price tag feature")); + priceTagSource0->setText(tr("using data from blacklotusproject.com")); + priceTagSource1->setText(tr("using data from deckbrew.com")); generalGroupBox->setTitle(tr("General")); } +void DeckEditorSettingsPage::radioPriceTagSourceClicked(bool checked) +{ + if(!checked) + return; + + int source=AbstractPriceUpdater::BLPPriceSource; + if(priceTagSource0->isChecked()) + source=AbstractPriceUpdater::BLPPriceSource; + if(priceTagSource1->isChecked()) + source=AbstractPriceUpdater::DBPriceSource; + + emit priceTagSourceChanged(source); +} + MessagesSettingsPage::MessagesSettingsPage() { aAdd = new QAction(this); diff --git a/cockatrice/src/dlg_settings.h b/cockatrice/src/dlg_settings.h index 8462f612..55685e1e 100644 --- a/cockatrice/src/dlg_settings.h +++ b/cockatrice/src/dlg_settings.h @@ -15,6 +15,7 @@ class QCheckBox; class QLabel; class QCloseEvent; class QSpinBox; +class QRadioButton; class AbstractSettingsPage : public QWidget { public: @@ -100,8 +101,13 @@ class DeckEditorSettingsPage : public AbstractSettingsPage { public: DeckEditorSettingsPage(); void retranslateUi(); +private slots: + void radioPriceTagSourceClicked(bool checked); +signals: + void priceTagSourceChanged(int _priceTagSource); private: QCheckBox *priceTagsCheckBox; + QRadioButton *priceTagSource0, *priceTagSource1; QGroupBox *generalGroupBox; }; diff --git a/cockatrice/src/priceupdater.cpp b/cockatrice/src/priceupdater.cpp index a0d32a32..2a1a012e 100644 --- a/cockatrice/src/priceupdater.cpp +++ b/cockatrice/src/priceupdater.cpp @@ -4,25 +4,46 @@ */ #include #include +#include #include "qt-json/json.h" #include "priceupdater.h" +#include "main.h" +#include "carddatabase.h" + +#if QT_VERSION < 0x050000 + // for Qt::escape() + #include +#endif + /** * Constructor. * * @param _deck deck. */ -PriceUpdater::PriceUpdater(const DeckList *_deck) +AbstractPriceUpdater::AbstractPriceUpdater(const DeckList *_deck) { nam = new QNetworkAccessManager(this); deck = _deck; } +// blacklotusproject.com + +/** + * Constructor. + * + * @param _deck deck. + */ +BLPPriceUpdater::BLPPriceUpdater(const DeckList *_deck) +: AbstractPriceUpdater(_deck) +{ +} + /** * Update the prices of the cards in deckList. */ -void PriceUpdater::updatePrices() +void BLPPriceUpdater::updatePrices() { QString q = "http://blacklotusproject.com/json/?cards="; QStringList cards = deck->getCardList(); @@ -38,7 +59,7 @@ void PriceUpdater::updatePrices() /** * Called when the download of the json file with the prices is finished. */ -void PriceUpdater::downloadFinished() +void BLPPriceUpdater::downloadFinished() { QNetworkReply *reply = static_cast(sender()); bool ok; @@ -82,3 +103,183 @@ void PriceUpdater::downloadFinished() deleteLater(); emit finishedUpdate(); } + +// deckbrew.com + +/** + * Constructor. + * + * @param _deck deck. + */ +DBPriceUpdater::DBPriceUpdater(const DeckList *_deck) +: AbstractPriceUpdater(_deck) +{ +} + +/** + * Update the prices of the cards in deckList. + */ +void DBPriceUpdater::updatePrices() +{ + QString base = "https://api.deckbrew.com/mtg/cards", q = ""; + QStringList cards = deck->getCardList(); + muidMap.clear(); + urls.clear(); + CardInfo * card; + int muid; + SetList sets; + bool bNotFirst=false; + + for (int i = 0; i < cards.size(); ++i) { + card = db->getCard(cards[i], false); + sets = card->getSets(); + for(int j = 0; j < sets.size(); ++j) + { + muid=card->getMuId(sets[j]->getShortName()); + //qDebug() << "muid " << muid << " card: " << cards[i] << endl; + if(bNotFirst) + { + q += QString("&m=%1").arg(muid); + } else { + q += QString("?m=%1").arg(muid); + bNotFirst = true; + } + muidMap.insert(muid, cards[i]); + + if(q.length() > 240) + { + urls.append(base + q); + bNotFirst=false; + q = ""; + } + } + } + if(q.length() > 0) + urls.append(base + q); + + requestNext(); +} + +void DBPriceUpdater::requestNext() +{ + if(urls.empty()) + return; + + QUrl url(urls.takeFirst(), QUrl::TolerantMode); + //qDebug() << "request prices from: " << url.toString() << endl; + QNetworkReply *reply = nam->get(QNetworkRequest(url)); + connect(reply, SIGNAL(finished()), this, SLOT(downloadFinished())); +} + +/** + * Called when the download of the json file with the prices is finished. + */ +void DBPriceUpdater::downloadFinished() +{ + QNetworkReply *reply = static_cast(sender()); + bool ok; + QString tmp = QString(reply->readAll()); + + // Errors are incapsulated in an object, check for them first + QVariantMap resultMap = QtJson::Json::parse(tmp, ok).toMap(); + if (!ok) { + QMessageBox::critical(this, tr("Error"), tr("A problem has occured while fetching card prices.")); + reply->deleteLater(); + if(urls.isEmpty()) + { + deleteLater(); + emit finishedUpdate(); + } else { + requestNext(); + } + } + + if(resultMap.contains("errors")) + { + QMessageBox::critical(this, tr("Error"), tr("A problem has occured while fetching card prices:") + + "
" + +#if QT_VERSION < 0x050000 + Qt::escape(resultMap["errors"].toList().first().toString()) +#else + resultMap["errors"].toList().first().toString().toHtmlEscaped() +#endif + ); + reply->deleteLater(); + if(urls.isEmpty()) + { + deleteLater(); + emit finishedUpdate(); + } else { + requestNext(); + } + } + + // Good results are a list + QVariantList resultList = QtJson::Json::parse(tmp, ok).toList(); + if (!ok) { + QMessageBox::critical(this, tr("Error"), tr("A problem has occured while fetching card prices.")); + reply->deleteLater(); + if(urls.isEmpty()) + { + deleteLater(); + emit finishedUpdate(); + } else { + requestNext(); + } + } + + QMap cardsPrice; + QListIterator it(resultList); + while (it.hasNext()) { + QVariantMap cardMap = it.next().toMap(); + + + // get sets list + QList editions = cardMap.value("editions").toList(); + foreach (QVariant ed, editions) + { + // retrieve card name "as we know it" from the muid + QVariantMap edition = ed.toMap(); + QString set = edition.value("set_id").toString(); + + int muid = edition.value("multiverse_id").toString().toInt(); + if(!muidMap.contains(muid)) + continue; + + QString name=muidMap.value(muid); + // Prices are in USD cents + float price = edition.value("price").toMap().value("median").toString().toFloat() / 100; + //qDebug() << "card " << name << " set " << set << " price " << price << endl; + + /** + * Make sure Masters Edition (MED) isn't the set, as it doesn't + * physically exist. Also check the price to see that the cheapest set + * ends up as the final price. + */ + if (set != "MED" && price > 0 && (!cardsPrice.contains(name) || cardsPrice.value(name) > price)) + cardsPrice.insert(name, price); + } + } + + InnerDecklistNode *listRoot = deck->getRoot(); + for (int i = 0; i < listRoot->size(); i++) { + InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); + for (int j = 0; j < currentZone->size(); j++) { + DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); + if (!currentCard) + continue; + float price = cardsPrice[currentCard->getName()]; + if(price > 0) + currentCard->setPrice(price); + } + } + + reply->deleteLater(); + if(urls.isEmpty()) + { + deleteLater(); + emit finishedUpdate(); + } else { + requestNext(); + } +} diff --git a/cockatrice/src/priceupdater.h b/cockatrice/src/priceupdater.h index a95252eb..afe582b6 100644 --- a/cockatrice/src/priceupdater.h +++ b/cockatrice/src/priceupdater.h @@ -1,28 +1,57 @@ #ifndef PRICEUPDATER_H #define PRICEUPDATER_H -#include +#include #include "decklist.h" class QNetworkAccessManager; +// If we don't typedef this, won't compile on OS X < 10.9 +typedef QMap MuidStringMap; + /** * Price Updater. * * @author Marcio Ribeiro */ -class PriceUpdater : public QObject +class AbstractPriceUpdater : public QWidget { Q_OBJECT -private: +public: + enum PriceSource { BLPPriceSource, DBPriceSource }; +protected: const DeckList *deck; QNetworkAccessManager *nam; signals: void finishedUpdate(); -private slots: - void downloadFinished(); +protected slots: + virtual void downloadFinished() = 0; public: - PriceUpdater(const DeckList *deck); - void updatePrices(); + AbstractPriceUpdater(const DeckList *deck); + virtual void updatePrices() = 0; +}; + +class BLPPriceUpdater : public AbstractPriceUpdater +{ + Q_OBJECT +protected: + virtual void downloadFinished(); +public: + BLPPriceUpdater(const DeckList *deck); + virtual void updatePrices(); +}; + +class DBPriceUpdater : public AbstractPriceUpdater +{ + Q_OBJECT +protected: + MuidStringMap muidMap; + QList urls; +protected: + virtual void downloadFinished(); + void requestNext(); +public: + DBPriceUpdater(const DeckList *deck); + virtual void updatePrices(); }; #endif diff --git a/cockatrice/src/settingscache.cpp b/cockatrice/src/settingscache.cpp index 892630f5..5f504aa3 100644 --- a/cockatrice/src/settingscache.cpp +++ b/cockatrice/src/settingscache.cpp @@ -45,6 +45,7 @@ SettingsCache::SettingsCache() soundPath = settings->value("sound/path").toString(); priceTagFeature = settings->value("deckeditor/pricetags", false).toBool(); + priceTagSource = settings->value("deckeditor/pricetagsource", 0).toInt(); ignoreUnregisteredUsers = settings->value("chat/ignore_unregistered", false).toBool(); } @@ -247,6 +248,12 @@ void SettingsCache::setPriceTagFeature(int _priceTagFeature) emit priceTagFeatureChanged(priceTagFeature); } +void SettingsCache::setPriceTagSource(int _priceTagSource) +{ + priceTagSource = _priceTagSource; + settings->setValue("deckeditor/pricetagsource", priceTagSource); +} + void SettingsCache::setIgnoreUnregisteredUsers(bool _ignoreUnregisteredUsers) { ignoreUnregisteredUsers = _ignoreUnregisteredUsers; diff --git a/cockatrice/src/settingscache.h b/cockatrice/src/settingscache.h index 68b5129e..3b4d612d 100644 --- a/cockatrice/src/settingscache.h +++ b/cockatrice/src/settingscache.h @@ -53,6 +53,7 @@ private: bool soundEnabled; QString soundPath; bool priceTagFeature; + int priceTagSource; bool ignoreUnregisteredUsers; QString picUrl; QString picUrlHq; @@ -87,6 +88,7 @@ public: bool getSoundEnabled() const { return soundEnabled; } QString getSoundPath() const { return soundPath; } bool getPriceTagFeature() const { return priceTagFeature; } + int getPriceTagSource() const { return priceTagSource; } bool getIgnoreUnregisteredUsers() const { return ignoreUnregisteredUsers; } QString getPicUrl() const { return picUrl; } QString getPicUrlHq() const { return picUrlHq; } @@ -121,6 +123,7 @@ public slots: void setSoundEnabled(int _soundEnabled); void setSoundPath(const QString &_soundPath); void setPriceTagFeature(int _priceTagFeature); + void setPriceTagSource(int _priceTagSource); void setIgnoreUnregisteredUsers(bool _ignoreUnregisteredUsers); void setPicUrl(const QString &_picUrl); void setPicUrlHq(const QString &_picUrlHq); diff --git a/cockatrice/src/tab_deck_editor.cpp b/cockatrice/src/tab_deck_editor.cpp index 1bf9d96b..e402ea11 100644 --- a/cockatrice/src/tab_deck_editor.cpp +++ b/cockatrice/src/tab_deck_editor.cpp @@ -651,7 +651,19 @@ void TabDeckEditor::setPriceTagFeatureEnabled(int enabled) void TabDeckEditor::actUpdatePrices() { aUpdatePrices->setDisabled(true); - PriceUpdater *up = new PriceUpdater(deckModel->getDeckList()); + AbstractPriceUpdater *up; + + switch(settingsCache->getPriceTagSource()) + { + case AbstractPriceUpdater::DBPriceSource: + up = new DBPriceUpdater(deckModel->getDeckList()); + break; + case AbstractPriceUpdater::BLPPriceSource: + default: + up = new BLPPriceUpdater(deckModel->getDeckList()); + break; + } + connect(up, SIGNAL(finishedUpdate()), this, SLOT(finishedUpdatingPrices())); up->updatePrices(); }