diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 3c62074d..38b4a331 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -114,6 +114,7 @@ SET(cockatrice_SOURCES src/settings/messagesettings.cpp src/settings/gamefilterssettings.cpp src/settings/layoutssettings.cpp + src/settings/downloadsettings.cpp src/update_downloader.cpp src/logger.cpp src/releasechannel.cpp @@ -122,7 +123,7 @@ SET(cockatrice_SOURCES src/handle_public_servers.cpp src/carddbparser/cockatricexml3.cpp ${VERSION_STRING_CPP} -) + ) add_subdirectory(sounds) add_subdirectory(themes) diff --git a/cockatrice/src/dlg_settings.cpp b/cockatrice/src/dlg_settings.cpp index 62473fc4..f184da69 100644 --- a/cockatrice/src/dlg_settings.cpp +++ b/cockatrice/src/dlg_settings.cpp @@ -45,8 +45,6 @@ GeneralSettingsPage::GeneralSettingsPage() languageBox.setCurrentIndex(i); } - picDownloadCheckBox.setChecked(settingsCache->getPicDownload()); - // updates QList channels = settingsCache->getUpdateReleaseChannels(); foreach (ReleaseChannel *chan, channels) { @@ -64,27 +62,15 @@ GeneralSettingsPage::GeneralSettingsPage() pixmapCacheEdit.setValue(settingsCache->getPixmapCacheSize()); pixmapCacheEdit.setSuffix(" MB"); - defaultUrlEdit = new QLineEdit(settingsCache->getPicUrl()); - fallbackUrlEdit = new QLineEdit(settingsCache->getPicUrlFallback()); - showTipsOnStartup.setChecked(settingsCache->getShowTipsOnStartup()); - connect(&clearDownloadedPicsButton, SIGNAL(clicked()), this, SLOT(clearDownloadedPicsButtonClicked())); connect(&languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int))); - connect(&picDownloadCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownload(int))); connect(&pixmapCacheEdit, SIGNAL(valueChanged(int)), settingsCache, SLOT(setPixmapCacheSize(int))); connect(&updateReleaseChannelBox, SIGNAL(currentIndexChanged(int)), settingsCache, SLOT(setUpdateReleaseChannel(int))); connect(&updateNotificationCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setNotifyAboutUpdate(int))); - connect(&picDownloadCheckBox, SIGNAL(clicked(bool)), this, SLOT(setEnabledStatus(bool))); - connect(defaultUrlEdit, SIGNAL(textChanged(QString)), settingsCache, SLOT(setPicUrl(QString))); - connect(fallbackUrlEdit, SIGNAL(textChanged(QString)), settingsCache, SLOT(setPicUrlFallback(QString))); - connect(&defaultUrlRestoreButton, SIGNAL(clicked()), this, SLOT(defaultUrlRestoreButtonClicked())); - connect(&fallbackUrlRestoreButton, SIGNAL(clicked()), this, SLOT(fallbackUrlRestoreButtonClicked())); connect(&showTipsOnStartup, SIGNAL(clicked(bool)), settingsCache, SLOT(setShowTipsOnStartup(bool))); - setEnabledStatus(settingsCache->getPicDownload()); - auto *personalGrid = new QGridLayout; personalGrid->addWidget(&languageLabel, 0, 0); personalGrid->addWidget(&languageBox, 0, 1); @@ -94,18 +80,6 @@ GeneralSettingsPage::GeneralSettingsPage() personalGrid->addWidget(&pixmapCacheEdit, 2, 1); personalGrid->addWidget(&updateNotificationCheckBox, 3, 0); personalGrid->addWidget(&showTipsOnStartup, 4, 0); - personalGrid->addWidget(&picDownloadCheckBox, 5, 0); - personalGrid->addWidget(&urlLinkLabel, 5, 1); - personalGrid->addWidget(&defaultUrlLabel, 6, 0, 1, 1); - personalGrid->addWidget(defaultUrlEdit, 6, 1, 1, 1); - personalGrid->addWidget(&defaultUrlRestoreButton, 6, 2, 1, 1); - personalGrid->addWidget(&fallbackUrlLabel, 7, 0, 1, 1); - personalGrid->addWidget(fallbackUrlEdit, 7, 1, 1, 1); - personalGrid->addWidget(&fallbackUrlRestoreButton, 7, 2, 1, 1); - personalGrid->addWidget(&clearDownloadedPicsButton, 8, 1); - - urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); - urlLinkLabel.setOpenExternalLinks(true); personalGroupBox = new QGroupBox; personalGroupBox->setLayout(personalGrid); @@ -194,20 +168,6 @@ QString GeneralSettingsPage::languageName(const QString &qmFile) return translator.translate("i18n", DEFAULT_LANG_NAME); } -void GeneralSettingsPage::defaultUrlRestoreButtonClicked() -{ - QString path = PIC_URL_DEFAULT; - defaultUrlEdit->setText(path); - settingsCache->setPicUrl(path); -} - -void GeneralSettingsPage::fallbackUrlRestoreButtonClicked() -{ - QString path = PIC_URL_FALLBACK; - fallbackUrlEdit->setText(path); - settingsCache->setPicUrlFallback(path); -} - void GeneralSettingsPage::deckPathButtonClicked() { QString path = QFileDialog::getExistingDirectory(this, tr("Choose path")); @@ -238,30 +198,6 @@ void GeneralSettingsPage::picsPathButtonClicked() settingsCache->setPicsPath(path); } -void GeneralSettingsPage::clearDownloadedPicsButtonClicked() -{ - QString picsPath = settingsCache->getPicsPath() + "/downloadedPics/"; - QStringList dirs = QDir(picsPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot); - bool outerSuccessRemove = true; - for (int i = 0; i < dirs.length(); i++) { - QString currentPath = picsPath + dirs.at(i) + "/"; - QStringList files = QDir(currentPath).entryList(QDir::Files); - bool innerSuccessRemove = true; - for (int j = 0; j < files.length(); j++) - if (!QDir(currentPath).remove(files.at(j))) { - qDebug() << "Failed to remove " + currentPath.toUtf8() + files.at(j).toUtf8(); - outerSuccessRemove = false; - innerSuccessRemove = false; - } - if (innerSuccessRemove) - QDir(picsPath).rmdir(dirs.at(i)); - } - if (outerSuccessRemove) - QMessageBox::information(this, tr("Success"), tr("Downloaded card pictures have been reset.")); - else - QMessageBox::critical(this, tr("Error"), tr("One or more downloaded card pictures could not be cleared.")); -} - void GeneralSettingsPage::cardDatabasePathButtonClicked() { QString path = QFileDialog::getOpenFileName(this, tr("Choose path")); @@ -291,7 +227,6 @@ void GeneralSettingsPage::retranslateUi() { personalGroupBox->setTitle(tr("Personal settings")); languageLabel.setText(tr("Language:")); - picDownloadCheckBox.setText(tr("Download card pictures on the fly")); if (settingsCache->getIsPortableBuild()) { pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)")); @@ -305,26 +240,11 @@ void GeneralSettingsPage::retranslateUi() cardDatabasePathLabel.setText(tr("Card database:")); tokenDatabasePathLabel.setText(tr("Token database:")); pixmapCacheLabel.setText(tr("Picture cache size:")); - defaultUrlLabel.setText(tr("Primary download URL:")); - fallbackUrlLabel.setText(tr("Fallback download URL:")); - urlLinkLabel.setText( - QString("%2").arg(WIKI_CUSTOM_PIC_URL).arg(tr("How to set a custom picture url"))); - clearDownloadedPicsButton.setText(tr("Reset/clear downloaded pictures")); updateReleaseChannelLabel.setText(tr("Update channel")); updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client")); - defaultUrlRestoreButton.setText(tr("Reset")); - fallbackUrlRestoreButton.setText(tr("Reset")); showTipsOnStartup.setText(tr("Show tips on startup")); } -void GeneralSettingsPage::setEnabledStatus(bool status) -{ - defaultUrlEdit->setEnabled(status); - fallbackUrlEdit->setEnabled(status); - defaultUrlRestoreButton.setEnabled(status); - fallbackUrlRestoreButton.setEnabled(status); -} - AppearanceSettingsPage::AppearanceSettingsPage() { QString themeName = settingsCache->getThemeName(); @@ -498,6 +418,15 @@ void UserInterfaceSettingsPage::retranslateUi() DeckEditorSettingsPage::DeckEditorSettingsPage() { + picDownloadCheckBox.setChecked(settingsCache->getPicDownload()); + connect(&picDownloadCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownload(int))); + + urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); + urlLinkLabel.setOpenExternalLinks(true); + + connect(&clearDownloadedPicsButton, SIGNAL(clicked()), this, SLOT(clearDownloadedPicsButtonClicked())); + connect(&resetDownloadURLs, SIGNAL(clicked()), this, SLOT(resetDownloadedURLsButtonClicked())); + auto *lpGeneralGrid = new QGridLayout; auto *lpSpoilerGrid = new QGridLayout; @@ -515,9 +444,46 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() // Update the GUI depending on if the box is ticked or not setSpoilersEnabled(mcDownloadSpoilersCheckBox.isChecked()); - // Create the layout - lpGeneralGrid->addWidget(&mcGeneralMessageLabel, 0, 0); + urlList = new QListWidget; + urlList->setSelectionMode(QAbstractItemView::SingleSelection); + urlList->setAlternatingRowColors(true); + urlList->setDragEnabled(true); + urlList->setDragDropMode(QAbstractItemView::InternalMove); + connect(urlList->model(), SIGNAL(rowsMoved(const QModelIndex, int, int, const QModelIndex, int)), this, + SLOT(urlListChanged(const QModelIndex, int, int, const QModelIndex, int))); + for (int i = 0; i < settingsCache->downloads().getCount(); i++) + urlList->addItem(settingsCache->downloads().getDownloadUrlAt(i)); + + auto aAdd = new QAction(this); + aAdd->setIcon(QPixmap("theme:icons/increment")); + connect(aAdd, SIGNAL(triggered()), this, SLOT(actAddURL())); + auto aEdit = new QAction(this); + aEdit->setIcon(QPixmap("theme:icons/pencil")); + connect(aEdit, SIGNAL(triggered()), this, SLOT(actEditURL())); + auto aRemove = new QAction(this); + aRemove->setIcon(QPixmap("theme:icons/decrement")); + connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemoveURL())); + + auto *messageToolBar = new QToolBar; + messageToolBar->setOrientation(Qt::Vertical); + messageToolBar->addAction(aAdd); + messageToolBar->addAction(aRemove); + messageToolBar->addAction(aEdit); + messageToolBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + + auto *messageListLayout = new QHBoxLayout; + messageListLayout->addWidget(messageToolBar); + messageListLayout->addWidget(urlList); + + // Top Layout + lpGeneralGrid->addWidget(&picDownloadCheckBox, 0, 0); + lpGeneralGrid->addWidget(&resetDownloadURLs, 0, 1); + lpGeneralGrid->addLayout(messageListLayout, 1, 0, 1, 2); + lpGeneralGrid->addWidget(&urlLinkLabel, 2, 0); + lpGeneralGrid->addWidget(&clearDownloadedPicsButton, 2, 1); + + // Spoiler Layout lpSpoilerGrid->addWidget(&mcDownloadSpoilersCheckBox, 0, 0); lpSpoilerGrid->addWidget(&mcSpoilerSaveLabel, 1, 0); lpSpoilerGrid->addWidget(mpSpoilerSavePathLineEdit, 1, 1); @@ -543,6 +509,94 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() setLayout(lpMainLayout); } +void DeckEditorSettingsPage::resetDownloadedURLsButtonClicked() +{ + settingsCache->downloads().clear(); + urlList->clear(); + urlList->addItems(settingsCache->downloads().getAllURLs()); + QMessageBox::information(this, tr("Success"), tr("Download URLs have been reset.")); +} + +void DeckEditorSettingsPage::clearDownloadedPicsButtonClicked() +{ + QString picsPath = settingsCache->getPicsPath() + "/downloadedPics/"; + QStringList dirs = QDir(picsPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot); + bool outerSuccessRemove = true; + for (const auto &dir : dirs) { + QString currentPath = picsPath + dir + "/"; + QStringList files = QDir(currentPath).entryList(QDir::Files); + bool innerSuccessRemove = true; + for (int j = 0; j < files.length(); j++) { + if (!QDir(currentPath).remove(files.at(j))) { + qInfo() << "Failed to remove " + currentPath.toUtf8() + files.at(j).toUtf8(); + outerSuccessRemove = false; + innerSuccessRemove = false; + } + qInfo() << "Removed " << currentPath << files.at(j); + } + + if (innerSuccessRemove) { + bool success = QDir(picsPath).rmdir(dir); + if (!success) { + qInfo() << "Failed to remove inner directory" << picsPath; + } else { + qInfo() << "Removed" << currentPath; + } + } + } + if (outerSuccessRemove) { + QMessageBox::information(this, tr("Success"), tr("Downloaded card pictures have been reset.")); + } else { + QMessageBox::critical(this, tr("Error"), tr("One or more downloaded card pictures could not be cleared.")); + } +} + +void DeckEditorSettingsPage::actAddURL() +{ + bool ok; + QString msg = QInputDialog::getText(this, tr("Add URL"), tr("URL:"), QLineEdit::Normal, QString(), &ok); + if (ok) { + urlList->addItem(msg); + storeSettings(); + } +} + +void DeckEditorSettingsPage::actRemoveURL() +{ + if (urlList->currentItem() != nullptr) { + delete urlList->takeItem(urlList->currentRow()); + storeSettings(); + } +} + +void DeckEditorSettingsPage::actEditURL() +{ + if (urlList->currentItem()) { + QString oldText = urlList->currentItem()->text(); + bool ok; + QString msg = QInputDialog::getText(this, tr("Edit URL"), tr("URL:"), QLineEdit::Normal, oldText, &ok); + if (ok) { + urlList->currentItem()->setText(msg); + storeSettings(); + } + } +} + +void DeckEditorSettingsPage::storeSettings() +{ + qInfo() << "URL Priority Reset"; + settingsCache->downloads().clear(); + for (int i = 0; i < urlList->count(); i++) { + qInfo() << "Priority" << i << ":" << urlList->item(i)->text(); + settingsCache->downloads().setDownloadUrlAt(i, urlList->item(i)->text()); + } +} + +void DeckEditorSettingsPage::urlListChanged(const QModelIndex &, int, int, const QModelIndex &, int) +{ + storeSettings(); +} + void DeckEditorSettingsPage::updateSpoilers() { // Disable the button so the user can only press it once at a time @@ -603,14 +657,18 @@ void DeckEditorSettingsPage::setSpoilersEnabled(bool anInput) void DeckEditorSettingsPage::retranslateUi() { + mpGeneralGroupBox->setTitle(tr("URL Download Priority")); mpSpoilerGroupBox->setTitle(tr("Spoilers")); mcDownloadSpoilersCheckBox.setText(tr("Download Spoilers Automatically")); mcSpoilerSaveLabel.setText(tr("Spoiler Location:")); - mcGeneralMessageLabel.setText(tr("Hey, something's here finally!")); lastUpdatedLabel.setText(tr("Last Updated") + ": " + getLastUpdateTime()); infoOnSpoilersLabel.setText(tr("Spoilers download automatically on launch") + "\n" + tr("Press the button to manually update without relaunching") + "\n\n" + tr("Do not close settings until manual update complete")); + picDownloadCheckBox.setText(tr("Download card pictures on the fly")); + urlLinkLabel.setText(QString("%2").arg(WIKI_CUSTOM_PIC_URL).arg(tr("How to add a custom URL"))); + clearDownloadedPicsButton.setText(tr("Delete Downloaded Images")); + resetDownloadURLs.setText(tr("Reset Download URLs")); } MessagesSettingsPage::MessagesSettingsPage() @@ -689,12 +747,16 @@ MessagesSettingsPage::MessagesSettingsPage() aAdd = new QAction(this); aAdd->setIcon(QPixmap("theme:icons/increment")); + aAdd->setStatusTip(tr("Add New URL")); + connect(aAdd, SIGNAL(triggered()), this, SLOT(actAdd())); aEdit = new QAction(this); aEdit->setIcon(QPixmap("theme:icons/pencil")); + aEdit->setStatusTip(tr("Edit URL")); connect(aEdit, SIGNAL(triggered()), this, SLOT(actEdit())); aRemove = new QAction(this); aRemove->setIcon(QPixmap("theme:icons/decrement")); + aRemove->setStatusTip(tr("Remove URL")); connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemove())); auto *messageToolBar = new QToolBar; @@ -798,7 +860,7 @@ void MessagesSettingsPage::actEdit() void MessagesSettingsPage::actRemove() { - if (messageList->currentItem()) { + if (messageList->currentItem() != nullptr) { delete messageList->takeItem(messageList->currentRow()); storeSettings(); } @@ -1000,7 +1062,7 @@ void DlgSettings::setTab(int index) void DlgSettings::updateLanguage() { - qApp->removeTranslator(translator); + qApp->removeTranslator(translator); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) installNewTranslator(); } @@ -1094,7 +1156,7 @@ void DlgSettings::retranslateUi() generalButton->setText(tr("General")); appearanceButton->setText(tr("Appearance")); userInterfaceButton->setText(tr("User Interface")); - deckEditorButton->setText(tr("Deck Editor")); + deckEditorButton->setText(tr("Card Sources")); messagesButton->setText(tr("Chat")); soundButton->setText(tr("Sound")); shortcutsButton->setText(tr("Shortcuts")); diff --git a/cockatrice/src/dlg_settings.h b/cockatrice/src/dlg_settings.h index d35add0c..6dc44e12 100644 --- a/cockatrice/src/dlg_settings.h +++ b/cockatrice/src/dlg_settings.h @@ -43,13 +43,9 @@ private slots: void deckPathButtonClicked(); void replaysPathButtonClicked(); void picsPathButtonClicked(); - void clearDownloadedPicsButtonClicked(); void cardDatabasePathButtonClicked(); void tokenDatabasePathButtonClicked(); void languageBoxChanged(int index); - void setEnabledStatus(bool); - void defaultUrlRestoreButtonClicked(); - void fallbackUrlRestoreButtonClicked(); private: QStringList findQmFiles(); @@ -59,13 +55,10 @@ private: QLineEdit *picsPathEdit; QLineEdit *cardDatabasePathEdit; QLineEdit *tokenDatabasePathEdit; - QLineEdit *defaultUrlEdit; - QLineEdit *fallbackUrlEdit; QSpinBox pixmapCacheEdit; QGroupBox *personalGroupBox; QGroupBox *pathsGroupBox; QComboBox languageBox; - QCheckBox picDownloadCheckBox; QCheckBox updateNotificationCheckBox; QComboBox updateReleaseChannelBox; QLabel languageLabel; @@ -75,13 +68,7 @@ private: QLabel picsPathLabel; QLabel cardDatabasePathLabel; QLabel tokenDatabasePathLabel; - QLabel defaultUrlLabel; - QLabel fallbackUrlLabel; - QLabel urlLinkLabel; QLabel updateReleaseChannelLabel; - QPushButton clearDownloadedPicsButton; - QPushButton defaultUrlRestoreButton; - QPushButton fallbackUrlRestoreButton; QCheckBox showTipsOnStartup; }; @@ -143,19 +130,30 @@ public: QString getLastUpdateTime(); private slots: + void storeSettings(); + void urlListChanged(const QModelIndex &, int, int, const QModelIndex &, int); void setSpoilersEnabled(bool); void spoilerPathButtonClicked(); void updateSpoilers(); void unlockSettings(); + void actAddURL(); + void actRemoveURL(); + void actEditURL(); + void clearDownloadedPicsButtonClicked(); + void resetDownloadedURLsButtonClicked(); private: + QPushButton clearDownloadedPicsButton; + QPushButton resetDownloadURLs; + QLabel urlLinkLabel; + QCheckBox picDownloadCheckBox; + QListWidget *urlList; QCheckBox mcDownloadSpoilersCheckBox; QLabel msDownloadSpoilersLabel; QGroupBox *mpGeneralGroupBox; QGroupBox *mpSpoilerGroupBox; QLineEdit *mpSpoilerSavePathLineEdit; QLabel mcSpoilerSaveLabel; - QLabel mcGeneralMessageLabel; QLabel lastUpdatedLabel; QLabel infoOnSpoilersLabel; QPushButton *mpSpoilerPathButton; diff --git a/cockatrice/src/pictureloader.cpp b/cockatrice/src/pictureloader.cpp index 8d2c4b2c..252fab18 100644 --- a/cockatrice/src/pictureloader.cpp +++ b/cockatrice/src/pictureloader.cpp @@ -25,35 +25,9 @@ // never cache more than 300 cards at once for a single deck #define CACHED_CARD_PER_DECK_MAX 300 -// Other URLs we can use (TODO: Make this less messy) -#define GATHERER_DEFAULT "http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!cardid!&type=card" -#define GATHERER_FALLBACK "http://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card" - -class PictureToLoad::SetDownloadPriorityComparator -{ -public: - /* - * Returns true if a has higher download priority than b - * Enabled sets have priority over disabled sets - * Both groups follows the user-defined order - */ - inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const - { - if (a->getEnabled()) { - return !b->getEnabled() || a->getSortKey() < b->getSortKey(); - } else { - return !b->getEnabled() && a->getSortKey() < b->getSortKey(); - } - } -}; - PictureToLoad::PictureToLoad(CardInfoPtr _card) : card(std::move(_card)) { - /* #2479 will expand this into a list of Urls */ - urlTemplates.append(settingsCache->getPicUrl()); - urlTemplates.append(settingsCache->getPicUrlFallback()); - urlTemplates.append(GATHERER_DEFAULT); - urlTemplates.append(GATHERER_FALLBACK); + urlTemplates = settingsCache->downloads().getAllURLs(); if (card) { sortedSets = card->getSets(); @@ -78,7 +52,7 @@ void PictureToLoad::populateSetUrls() } } - foreach (QString urlTemplate, urlTemplates) { + for (const QString &urlTemplate : urlTemplates) { QString transformedUrl = transformUrl(urlTemplate); if (!transformedUrl.isEmpty()) { @@ -121,10 +95,8 @@ QString PictureToLoad::getSetName() const } } -QStringList PictureLoaderWorker::md5Blacklist = QStringList() - << "db0c48db407a907c16ade38de048a441"; // card back returned - // by gatherer when - // card is not found +// Card back returned by gatherer when card is not found +QStringList PictureLoaderWorker::md5Blacklist = QStringList() << "db0c48db407a907c16ade38de048a441"; PictureLoaderWorker::PictureLoaderWorker() : QObject(nullptr), downloadRunning(false), loadQueueRunning(false) { @@ -151,12 +123,12 @@ PictureLoaderWorker::~PictureLoaderWorker() void PictureLoaderWorker::processLoadQueue() { - if (loadQueueRunning) + if (loadQueueRunning) { return; + } loadQueueRunning = true; - forever - { + while (true) { mutex.lock(); if (loadQueue.isEmpty()) { mutex.unlock(); @@ -169,23 +141,26 @@ void PictureLoaderWorker::processLoadQueue() QString setName = cardBeingLoaded.getSetName(); QString cardName = cardBeingLoaded.getCard()->getName(); QString correctedCardName = cardBeingLoaded.getCard()->getCorrectedName(); + qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Trying to load picture"; - if (cardImageExistsOnDisk(setName, correctedCardName)) + if (cardImageExistsOnDisk(setName, correctedCardName)) { continue; + } if (picDownload) { qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Picture not found on disk, trying to download"; cardsToDownload.append(cardBeingLoaded); cardBeingLoaded.clear(); - if (!downloadRunning) + if (!downloadRunning) { startNextPicDownload(); + } } else { if (cardBeingLoaded.nextSet()) { qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Picture NOT found and download disabled, moving to next " - "set (newset: " + "set (new set: " << setName << " card: " << cardName << ")"; mutex.lock(); loadQueue.prepend(cardBeingLoaded); @@ -194,7 +169,7 @@ void PictureLoaderWorker::processLoadQueue() } else { qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Picture NOT found, download disabled, no more sets to " - "try: BAILING OUT (oldset: " + "try: BAILING OUT (old set: " << setName << " card: " << cardName << ")"; imageLoaded(cardBeingLoaded.getCard(), QImage()); } @@ -227,22 +202,22 @@ bool PictureLoaderWorker::cardImageExistsOnDisk(QString &setName, QString &corre // Iterates through the list of paths, searching for images with the desired // name with any QImageReader-supported // extension - for (int i = 0; i < picsPaths.length(); i++) { - imgReader.setFileName(picsPaths.at(i)); + for (const auto &picsPath : picsPaths) { + imgReader.setFileName(picsPath); if (imgReader.read(&image)) { qDebug() << "PictureLoader: [card: " << correctedCardname << " set: " << setName << "]: Picture found on disk."; imageLoaded(cardBeingLoaded.getCard(), image); return true; } - imgReader.setFileName(picsPaths.at(i) + ".full"); + imgReader.setFileName(picsPath + ".full"); if (imgReader.read(&image)) { qDebug() << "PictureLoader: [card: " << correctedCardname << " set: " << setName << "]: Picture.full found on disk."; imageLoaded(cardBeingLoaded.getCard(), image); return true; } - imgReader.setFileName(picsPaths.at(i) + ".xlhq"); + imgReader.setFileName(picsPath + ".xlhq"); if (imgReader.read(&image)) { qDebug() << "PictureLoader: [card: " << correctedCardname << " set: " << setName << "]: Picture.xlhq found on disk."; @@ -254,7 +229,7 @@ bool PictureLoaderWorker::cardImageExistsOnDisk(QString &setName, QString &corre return false; } -QString PictureToLoad::transformUrl(QString urlTemplate) const +QString PictureToLoad::transformUrl(const QString &urlTemplate) const { /* This function takes Url templates and substitutes actual card details into the url. This is used for making Urls with follow a predictable format @@ -289,7 +264,7 @@ QString PictureToLoad::transformUrl(QString urlTemplate) const transformMap["!setname_lower!"] = QString(); } - foreach (QString prop, transformMap.keys()) { + for (const QString &prop : transformMap.keys()) { if (transformedUrl.contains(prop)) { if (!transformMap[prop].isEmpty()) { transformedUrl.replace(prop, QUrl::toPercentEncoding(transformMap[prop])); @@ -380,8 +355,8 @@ void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply) return; } - const QByteArray &picData = reply->peek(reply->size()); // peek is used to keep the data in the buffer - // for use by QImageReader + // peek is used to keep the data in the buffer for use by QImageReader + const QByteArray &picData = reply->peek(reply->size()); if (imageIsBlackListed(picData)) { qDebug() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getName() @@ -403,8 +378,9 @@ void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply) // prior to reading the // QImageReader data // into a QImage object, as that wipes the QImageReader buffer - if (extension == ".jpeg") + if (extension == ".jpeg") { extension = ".jpg"; + } if (imgReader.read(&testImage)) { QString setName = cardBeingDownloaded.getSetName(); @@ -418,8 +394,9 @@ void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply) QFile newPic(picsPath + "/downloadedPics/" + setName + "/" + cardBeingDownloaded.getCard()->getCorrectedName() + extension); - if (!newPic.open(QIODevice::WriteOnly)) + if (!newPic.open(QIODevice::WriteOnly)) { return; + } newPic.write(picData); newPic.close(); } @@ -444,15 +421,16 @@ void PictureLoaderWorker::enqueueImageLoad(CardInfoPtr card) QMutexLocker locker(&mutex); // avoid queueing the same card more than once - if (!card || card == cardBeingLoaded.getCard() || card == cardBeingDownloaded.getCard()) + if (!card || card == cardBeingLoaded.getCard() || card == cardBeingDownloaded.getCard()) { return; + } - foreach (PictureToLoad pic, loadQueue) { + for (const PictureToLoad &pic : loadQueue) { if (pic.getCard() == card) return; } - foreach (PictureToLoad pic, cardsToDownload) { + for (const PictureToLoad &pic : cardsToDownload) { if (pic.getCard() == card) return; } @@ -474,7 +452,7 @@ void PictureLoaderWorker::picsPathChanged() customPicsPath = settingsCache->getCustomPicsPath(); } -PictureLoader::PictureLoader() : QObject(0) +PictureLoader::PictureLoader() : QObject(nullptr) { worker = new PictureLoaderWorker; connect(settingsCache, SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged())); @@ -501,20 +479,21 @@ void PictureLoader::getCardBackPixmap(QPixmap &pixmap, QSize size) void PictureLoader::getPixmap(QPixmap &pixmap, CardInfoPtr card, QSize size) { - if (card == nullptr) + if (card == nullptr) { return; + } // search for an exact size copy of the picure in cache QString key = card->getPixmapCacheKey(); - QString sizekey = key + QLatin1Char('_') + QString::number(size.width()) + QString::number(size.height()); - if (QPixmapCache::find(sizekey, &pixmap)) + QString sizeKey = key + QLatin1Char('_') + QString::number(size.width()) + QString::number(size.height()); + if (QPixmapCache::find(sizeKey, &pixmap)) return; // load the image and create a copy of the correct size QPixmap bigPixmap; if (QPixmapCache::find(key, &bigPixmap)) { pixmap = bigPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - QPixmapCache::insert(sizekey, pixmap); + QPixmapCache::insert(sizeKey, pixmap); return; } @@ -540,8 +519,9 @@ void PictureLoader::imageLoaded(CardInfoPtr card, const QImage &image) void PictureLoader::clearPixmapCache(CardInfoPtr card) { - if (card) + if (card) { QPixmapCache::remove(card->getPixmapCacheKey()); + } } void PictureLoader::clearPixmapCache() @@ -554,13 +534,15 @@ void PictureLoader::cacheCardPixmaps(QList cards) QPixmap tmp; int max = qMin(cards.size(), CACHED_CARD_PER_DECK_MAX); for (int i = 0; i < max; ++i) { - CardInfoPtr card = cards.at(i); - if (!card) + const CardInfoPtr &card = cards.at(i); + if (!card) { continue; + } QString key = card->getPixmapCacheKey(); - if (QPixmapCache::find(key, &tmp)) + if (QPixmapCache::find(key, &tmp)) { continue; + } getInstance().worker->enqueueImageLoad(card); } diff --git a/cockatrice/src/pictureloader.h b/cockatrice/src/pictureloader.h index b6c7a2c0..db5757e8 100644 --- a/cockatrice/src/pictureloader.h +++ b/cockatrice/src/pictureloader.h @@ -14,7 +14,23 @@ class QThread; class PictureToLoad { private: - class SetDownloadPriorityComparator; + class SetDownloadPriorityComparator + { + public: + /* + * Returns true if a has higher download priority than b + * Enabled sets have priority over disabled sets + * Both groups follows the user-defined order + */ + inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const + { + if (a->getEnabled()) { + return !b->getEnabled() || a->getSortKey() < b->getSortKey(); + } else { + return !b->getEnabled() && a->getSortKey() < b->getSortKey(); + } + } + }; CardInfoPtr card; QList sortedSets; @@ -24,7 +40,8 @@ private: CardSetPtr currentSet; public: - PictureToLoad(CardInfoPtr _card = CardInfoPtr()); + explicit PictureToLoad(CardInfoPtr _card = CardInfoPtr()); + CardInfoPtr getCard() const { return card; @@ -42,7 +59,7 @@ public: return currentSet; } QString getSetName() const; - QString transformUrl(QString urlTemplate) const; + QString transformUrl(const QString &urlTemplate) const; bool nextSet(); bool nextUrl(); void populateSetUrls(); @@ -52,8 +69,8 @@ class PictureLoaderWorker : public QObject { Q_OBJECT public: - PictureLoaderWorker(); - ~PictureLoaderWorker(); + explicit PictureLoaderWorker(); + ~PictureLoaderWorker() override; void enqueueImageLoad(CardInfoPtr card); @@ -70,8 +87,8 @@ private: PictureToLoad cardBeingDownloaded; bool picDownload, downloadRunning, loadQueueRunning; void startNextPicDownload(); - bool cardImageExistsOnDisk(QString &setName, QString &correctedCardname); - bool imageIsBlackListed(const QByteArray &picData); + bool cardImageExistsOnDisk(QString &, QString &); + bool imageIsBlackListed(const QByteArray &); private slots: void picDownloadFinished(QNetworkReply *reply); void picDownloadFailed(); @@ -96,8 +113,8 @@ public: } private: - PictureLoader(); - ~PictureLoader(); + explicit PictureLoader(); + ~PictureLoader() override; // Singleton - Don't implement copy constructor and assign operator PictureLoader(PictureLoader const &); void operator=(PictureLoader const &); diff --git a/cockatrice/src/settings/downloadsettings.cpp b/cockatrice/src/settings/downloadsettings.cpp new file mode 100644 index 00000000..4591b680 --- /dev/null +++ b/cockatrice/src/settings/downloadsettings.cpp @@ -0,0 +1,56 @@ +#include "downloadsettings.h" +#include "settingsmanager.h" + +DownloadSettings::DownloadSettings(const QString &settingPath, QObject *parent = nullptr) + : SettingsManager(settingPath + "downloads.ini", parent) +{ + downloadURLs = getValue("urls", "downloads").value(); +} + +void DownloadSettings::setDownloadUrlAt(int index, const QString &url) +{ + downloadURLs.insert(index, url); + setValue(QVariant::fromValue(downloadURLs), "urls", "downloads"); +} + +/** + * If reset or first run, this method contains the default URLs we will populate + */ +QStringList DownloadSettings::getAllURLs() +{ + // First run, these will be empty + if (downloadURLs.count() == 0) { + populateDefaultURLs(); + } + + return downloadURLs; +} + +void DownloadSettings::populateDefaultURLs() +{ + downloadURLs.clear(); + downloadURLs.append("https://api.scryfall.com/cards/!uuid!?format=image"); + downloadURLs.append("https://api.scryfall.com/cards/multiverse/!cardid!?format=image"); + downloadURLs.append("http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!cardid!&type=card"); + downloadURLs.append("http://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"); + setValue(QVariant::fromValue(downloadURLs), "urls", "downloads"); +} + +QString DownloadSettings::getDownloadUrlAt(int index) +{ + if (0 <= index && index < downloadURLs.size()) { + return downloadURLs[index]; + } + + return ""; +} + +int DownloadSettings::getCount() +{ + return downloadURLs.size(); +} + +void DownloadSettings::clear() +{ + downloadURLs.clear(); +} \ No newline at end of file diff --git a/cockatrice/src/settings/downloadsettings.h b/cockatrice/src/settings/downloadsettings.h new file mode 100644 index 00000000..f3c3e4d1 --- /dev/null +++ b/cockatrice/src/settings/downloadsettings.h @@ -0,0 +1,28 @@ +#ifndef COCKATRICE_DOWNLOADSETTINGS_H +#define COCKATRICE_DOWNLOADSETTINGS_H + +#include "settingsmanager.h" +#include + +class DownloadSettings : public SettingsManager +{ + Q_OBJECT + friend class SettingsCache; + +public: + explicit DownloadSettings(const QString &, QObject *); + + QStringList getAllURLs(); + QString getDownloadUrlAt(int); + void setDownloadUrlAt(int, const QString &); + int getCount(); + void clear(); + +private: + QStringList downloadURLs; + +private: + void populateDefaultURLs(); +}; + +#endif // COCKATRICE_DOWNLOADSETTINGS_H diff --git a/cockatrice/src/settingscache.cpp b/cockatrice/src/settingscache.cpp index 76c573f1..8586da97 100644 --- a/cockatrice/src/settingscache.cpp +++ b/cockatrice/src/settingscache.cpp @@ -164,6 +164,7 @@ SettingsCache::SettingsCache() messageSettings = new MessageSettings(settingsPath, this); gameFiltersSettings = new GameFiltersSettings(settingsPath, this); layoutsSettings = new LayoutsSettings(settingsPath, this); + downloadSettings = new DownloadSettings(settingsPath, this); if (!QFile(settingsPath + "global.ini").exists()) translateLegacySettings(); @@ -220,9 +221,6 @@ SettingsCache::SettingsCache() picDownload = settings->value("personal/picturedownload", true).toBool(); - picUrl = settings->value("personal/picUrl", PIC_URL_DEFAULT).toString(); - picUrlFallback = settings->value("personal/picUrlFallback", PIC_URL_FALLBACK).toString(); - mainWindowGeometry = settings->value("interface/main_window_geometry").toByteArray(); tokenDialogGeometry = settings->value("interface/token_dialog_geometry").toByteArray(); notificationsEnabled = settings->value("interface/notificationsenabled", true).toBool(); @@ -415,18 +413,6 @@ void SettingsCache::setPicDownload(int _picDownload) emit picDownloadChanged(); } -void SettingsCache::setPicUrl(const QString &_picUrl) -{ - picUrl = _picUrl; - settings->setValue("personal/picUrl", picUrl); -} - -void SettingsCache::setPicUrlFallback(const QString &_picUrlFallback) -{ - picUrlFallback = _picUrlFallback; - settings->setValue("personal/picUrlFallback", picUrlFallback); -} - void SettingsCache::setNotificationsEnabled(int _notificationsEnabled) { notificationsEnabled = static_cast(_notificationsEnabled); diff --git a/cockatrice/src/settingscache.h b/cockatrice/src/settingscache.h index b3d82889..34421fcf 100644 --- a/cockatrice/src/settingscache.h +++ b/cockatrice/src/settingscache.h @@ -2,6 +2,7 @@ #define SETTINGSCACHE_H #include "settings/carddatabasesettings.h" +#include "settings/downloadsettings.h" #include "settings/gamefilterssettings.h" #include "settings/layoutssettings.h" #include "settings/messagesettings.h" @@ -13,10 +14,6 @@ class ReleaseChannel; -// Fallbacks used for cards w/o MultiverseId -#define PIC_URL_DEFAULT "https://api.scryfall.com/cards/multiverse/!cardid!?format=image" -#define PIC_URL_FALLBACK "https://api.scryfall.com/cards/named?fuzzy=!name!&format=image" - // size should be a multiple of 64 #define PIXMAPCACHE_SIZE_DEFAULT 2047 #define PIXMAPCACHE_SIZE_MIN 64 @@ -61,6 +58,7 @@ private: MessageSettings *messageSettings; GameFiltersSettings *gameFiltersSettings; LayoutsSettings *layoutsSettings; + DownloadSettings *downloadSettings; QByteArray mainWindowGeometry; QByteArray tokenDialogGeometry; @@ -303,14 +301,6 @@ public: { return ignoreUnregisteredUserMessages; } - QString getPicUrl() const - { - return picUrl; - } - QString getPicUrlFallback() const - { - return picUrlFallback; - } int getPixmapCacheSize() const { return pixmapCacheSize; @@ -430,6 +420,10 @@ public: { return *layoutsSettings; } + DownloadSettings &downloads() const + { + return *downloadSettings; + } bool getIsPortableBuild() const { return isPortableBuild; @@ -478,8 +472,6 @@ public slots: void setSoundThemeName(const QString &_soundThemeName); void setIgnoreUnregisteredUsers(int _ignoreUnregisteredUsers); void setIgnoreUnregisteredUserMessages(int _ignoreUnregisteredUserMessages); - void setPicUrl(const QString &_picUrl); - void setPicUrlFallback(const QString &_picUrlFallback); void setPixmapCacheSize(const int _pixmapCacheSize); void setCardScaling(const int _scaleCards); void setShowMessagePopups(const int _showMessagePopups); diff --git a/oracle/CMakeLists.txt b/oracle/CMakeLists.txt index ec7b5d17..82df556f 100644 --- a/oracle/CMakeLists.txt +++ b/oracle/CMakeLists.txt @@ -22,6 +22,7 @@ SET(oracle_SOURCES ../cockatrice/src/settings/messagesettings.cpp ../cockatrice/src/settings/gamefilterssettings.cpp ../cockatrice/src/settings/layoutssettings.cpp + ../cockatrice/src/settings/downloadsettings.cpp ../cockatrice/src/thememanager.cpp ../cockatrice/src/qt-json/json.cpp ../cockatrice/src/releasechannel.cpp diff --git a/oracle/src/main.cpp b/oracle/src/main.cpp index 0005e077..523a53b7 100644 --- a/oracle/src/main.cpp +++ b/oracle/src/main.cpp @@ -34,7 +34,7 @@ int main(int argc, char *argv[]) QCoreApplication::setOrganizationName("Cockatrice"); QCoreApplication::setOrganizationDomain("cockatrice"); - // this can't be changed, as it influences the default savepath for cards.xml + // this can't be changed, as it influences the default save path for cards.xml QCoreApplication::setApplicationName("Cockatrice"); // If the program is opened with the -s flag, it will only do spoilers. Otherwise it will do MTGJSON/Tokens diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 4ac937cc..ec096f5c 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -97,7 +97,7 @@ CardInfoPtr OracleImporter::addCard(const QString &setName, bool mArtifact = false; if (cardType.endsWith("Artifact")) { for (int i = 0; i < cardTextRows.size(); ++i) { - cardTextRows[i].remove(QRegularExpression("\\\".*?\\\"")); + cardTextRows[i].remove(QRegularExpression(R"(\".*?\")")); if (cardTextRows[i].contains("{T}") && cardTextRows[i].contains("to your mana pool")) { mArtifact = true; } @@ -124,6 +124,7 @@ CardInfoPtr OracleImporter::addCard(const QString &setName, cards.insert(cardName, card); } + card->setMuId(setName, cardId); card->setUuId(setName, cardUuId); card->setSetNumber(setName, setNumber); @@ -152,7 +153,7 @@ int OracleImporter::importTextSpoiler(CardSetPtr set, const QVariant &data) QString setNumber; QString rarity; QString cardLoyalty; - bool upsideDown = false; + bool upsideDown; QMap splitCards; while (it.hasNext()) { @@ -187,14 +188,14 @@ int OracleImporter::importTextSpoiler(CardSetPtr set, const QVariant &data) : QString(""); cardText = map.contains("text") ? map.value("text").toString() : QString(""); cardId = map.contains("multiverseId") ? map.value("multiverseId").toInt() : 0; - cardUuId = map.contains("uuid") ? map.value("uuid").toString() : QString(""); + cardUuId = map.contains("scryfallId") ? map.value("scryfallId").toString() : QString(""); setNumber = map.contains("number") ? map.value("number").toString() : QString(""); rarity = map.contains("rarity") ? map.value("rarity").toString() : QString(""); cardLoyalty = map.contains("loyalty") ? map.value("loyalty").toString() : QString(""); colors = map.contains("colors") ? map.value("colors").toStringList() : QStringList(); relatedCards = QList(); if (map.contains("names")) - foreach (const QString &name, map.value("names").toStringList()) { + for (const QString &name : map.value("names").toStringList()) { if (name != cardName) relatedCards.append(new CardRelation(name, true)); } @@ -218,18 +219,18 @@ int OracleImporter::importTextSpoiler(CardSetPtr set, const QVariant &data) // split cards handling - get all unique card muids QList muids = splitCards.uniqueKeys(); - foreach (int muid, muids) { + for (int muid : muids) { // get all cards for this specific muid QList maps = splitCards.values(muid); QStringList names; // now, reorder the cards using the ordered list of names QMap orderedMaps; - foreach (QVariantMap map, maps) { + for (const QVariantMap &inner_map : maps) { if (names.isEmpty()) - names = map.contains("names") ? map.value("names").toStringList() : QStringList(); - QString name = map.value("name").toString(); + names = inner_map.contains("names") ? inner_map.value("names").toStringList() : QStringList(); + QString name = inner_map.value("name").toString(); int index = names.indexOf(name); - orderedMaps.insertMulti(index, map); + orderedMaps.insertMulti(index, inner_map); } // clean variables @@ -248,51 +249,51 @@ int OracleImporter::importTextSpoiler(CardSetPtr set, const QVariant &data) // loop cards and merge their contents QString prefix = QString(" // "); QString prefix2 = QString("\n\n---\n\n"); - foreach (QVariantMap map, orderedMaps.values()) { - if (map.contains("name")) { + for (const QVariantMap &inner_map : orderedMaps.values()) { + if (inner_map.contains("name")) { if (!cardName.isEmpty()) cardName += (orderedMaps.count() > 2) ? QString("/") : prefix; - cardName += map.value("name").toString(); + cardName += inner_map.value("name").toString(); } - if (map.contains("manaCost")) { + if (inner_map.contains("manaCost")) { if (!cardCost.isEmpty()) cardCost += prefix; - cardCost += map.value("manaCost").toString(); + cardCost += inner_map.value("manaCost").toString(); } - if (map.contains("convertedManaCost")) { + if (inner_map.contains("convertedManaCost")) { if (!cmc.isEmpty()) cmc += prefix; - cmc += map.value("convertedManaCost").toString(); + cmc += inner_map.value("convertedManaCost").toString(); } - if (map.contains("type")) { + if (inner_map.contains("type")) { if (!cardType.isEmpty()) cardType += prefix; - cardType += map.value("type").toString(); + cardType += inner_map.value("type").toString(); } - if (map.contains("power") || map.contains("toughness")) { + if (inner_map.contains("power") || inner_map.contains("toughness")) { if (!cardPT.isEmpty()) cardPT += prefix; - cardPT += map.value("power").toString() + QString('/') + map.value("toughness").toString(); + cardPT += inner_map.value("power").toString() + QString('/') + inner_map.value("toughness").toString(); } - if (map.contains("text")) { + if (inner_map.contains("text")) { if (!cardText.isEmpty()) cardText += prefix2; - cardText += map.value("text").toString(); + cardText += inner_map.value("text").toString(); } - if (map.contains("uuid")) { + if (inner_map.contains("uuid")) { if (cardUuId.isEmpty()) - cardUuId = map.value("uuid").toString(); + cardUuId = inner_map.value("uuid").toString(); } - if (map.contains("number")) { + if (inner_map.contains("number")) { if (setNumber.isEmpty()) - setNumber = map.value("number").toString(); + setNumber = inner_map.value("number").toString(); } - if (map.contains("rarity")) { + if (inner_map.contains("rarity")) { if (rarity.isEmpty()) - rarity = map.value("rarity").toString(); + rarity = inner_map.value("rarity").toString(); } - colors << map.value("colors").toStringList(); + colors << inner_map.value("colors").toStringList(); } colors.removeDuplicates();