diff --git a/es-app/src/MetaData.cpp b/es-app/src/MetaData.cpp index 130ce2344..c1b841739 100644 --- a/es-app/src/MetaData.cpp +++ b/es-app/src/MetaData.cpp @@ -38,10 +38,7 @@ const std::vector gameMDD(gameDecls, gameDecls + MetaDataDecl folderDecls[] = { {"name", MD_STRING, "", false, "name", "enter game name", true}, -{"sortname", MD_STRING, "", false, "sortname", "enter game sort name", false}, {"desc", MD_MULTILINE_STRING, "", false, "description", "enter description", true}, -{"rating", MD_RATING, "0", false, "rating", "enter rating", true}, -{"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date", true}, {"developer", MD_STRING, "unknown", false, "developer", "enter game developer", true}, {"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher", true}, {"genre", MD_STRING, "unknown", false, "genre", "enter game genre", true}, diff --git a/es-app/src/MetaData.h b/es-app/src/MetaData.h index d5db59a42..c5128d809 100644 --- a/es-app/src/MetaData.h +++ b/es-app/src/MetaData.h @@ -72,6 +72,8 @@ public: inline MetaDataListType getType() const { return mType; } inline const std::vector& getMDD() const { return getMDDByType(getType()); } + inline const std::vector& getMDD(MetaDataListType type) const + { return getMDDByType(type); } private: MetaDataListType mType; diff --git a/es-app/src/guis/GuiGamelistOptions.cpp b/es-app/src/guis/GuiGamelistOptions.cpp index b48e281b1..dbb0d00b7 100644 --- a/es-app/src/guis/GuiGamelistOptions.cpp +++ b/es-app/src/guis/GuiGamelistOptions.cpp @@ -126,14 +126,27 @@ GuiGamelistOptions::GuiGamelistOptions( mMenu.addRow(row); } - if (UIModeController::getInstance()->isUIModeFull() && !fromPlaceholder && - !(mSystem->isCollection() && file->getType() == FOLDER)) { - row.elements.clear(); - row.addElement(std::make_shared(mWindow, - "EDIT THIS GAME'S METADATA", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); - row.addElement(makeArrow(mWindow), false); - row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openMetaDataEd, this)); - mMenu.addRow(row); + if (file->getType() == FOLDER) { + if (UIModeController::getInstance()->isUIModeFull() && !fromPlaceholder && + !(mSystem->isCollection() && file->getType() == FOLDER)) { + row.elements.clear(); + row.addElement(std::make_shared(mWindow, + "EDIT THIS FOLDER'S METADATA", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); + row.addElement(makeArrow(mWindow), false); + row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openMetaDataEd, this)); + mMenu.addRow(row); + } + } + else { + if (UIModeController::getInstance()->isUIModeFull() && !fromPlaceholder && + !(mSystem->isCollection() && file->getType() == FOLDER)) { + row.elements.clear(); + row.addElement(std::make_shared(mWindow, + "EDIT THIS GAME'S METADATA", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); + row.addElement(makeArrow(mWindow), false); + row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openMetaDataEd, this)); + mMenu.addRow(row); + } } // Buttons. Logic to apply or cancel settings are handled by the destructor. @@ -234,10 +247,20 @@ void GuiGamelistOptions::openMetaDataEd() }; } - mWindow->pushGui(new GuiMetaDataEd(mWindow, &file->metadata, file->metadata.getMDD(), p, - Utils::FileSystem::getFileName(file->getPath()), std::bind( - &IGameListView::onFileChanged, ViewController::get()->getGameListView( - file->getSystem()).get(), file, FILE_METADATA_CHANGED), deleteBtnFunc)); + if (file->getType() == FOLDER) { + mWindow->pushGui(new GuiMetaDataEd(mWindow, &file->metadata, + file->metadata.getMDD(FOLDER_METADATA), p, + Utils::FileSystem::getFileName(file->getPath()), std::bind( + &IGameListView::onFileChanged, ViewController::get()->getGameListView( + file->getSystem()).get(), file, FILE_METADATA_CHANGED), deleteBtnFunc)); + } + else { + mWindow->pushGui(new GuiMetaDataEd(mWindow, &file->metadata, + file->metadata.getMDD(GAME_METADATA), p, + Utils::FileSystem::getFileName(file->getPath()), std::bind( + &IGameListView::onFileChanged, ViewController::get()->getGameListView( + file->getSystem()).get(), file, FILE_METADATA_CHANGED), deleteBtnFunc)); + } } void GuiGamelistOptions::jumpToLetter() diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index 22c463d42..dad0bcbc3 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -163,7 +163,7 @@ GuiMetaDataEd::GuiMetaDataEd( defaultLaunchString, ed, updateVal, multiLine] { mWindow->pushGui(new GuiComplexTextEditPopup(mWindow, getHelpStyle(), title, staticTextString, defaultLaunchString, ed->getValue(), - updateVal, multiLine, "SAVE")); + updateVal, multiLine, "APPLY")); }); break; } @@ -190,7 +190,7 @@ GuiMetaDataEd::GuiMetaDataEd( auto updateVal = [ed](const std::string& newVal) { ed->setValue(newVal); }; row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] { mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), title, - ed->getValue(), updateVal, multiLine, "SAVE")); + ed->getValue(), updateVal, multiLine, "APPLY")); }); break; } @@ -204,9 +204,11 @@ GuiMetaDataEd::GuiMetaDataEd( std::vector< std::shared_ptr > buttons; - if (!scraperParams.system->hasPlatformId(PlatformIds::PLATFORM_IGNORE)) - buttons.push_back(std::make_shared(mWindow, "SCRAPE", "scrape", - std::bind(&GuiMetaDataEd::fetch, this))); + if (mScraperParams.game->getType() != FOLDER) { + if (!scraperParams.system->hasPlatformId(PlatformIds::PLATFORM_IGNORE)) + buttons.push_back(std::make_shared(mWindow, "SCRAPE", "scrape", + std::bind(&GuiMetaDataEd::fetch, this))); + } buttons.push_back(std::make_shared(mWindow, "SAVE", "save metadata", [&] { save(); delete this; })); @@ -290,6 +292,15 @@ void GuiMetaDataEd::fetchDone(const ScraperSearchResult& result) MetaDataList* metadata = nullptr; metadata = new MetaDataList(*mMetaData); + // Check if any values were manually changed before starting the scraping. + // If so, it's these values we should compare against when scraping, not + // the values previously saved for the game. + for (unsigned int i = 0; i < mEditors.size(); i++) { + const std::string& key = mMetaDataDecl.at(i).key; + if (metadata->get(key) != mEditors[i]->getValue()) + metadata->set(key, mEditors[i]->getValue()); + } + mMetadataUpdated = GuiScraperSearch::saveMetadata(result, *metadata); // Update the list with the scraped metadata values. diff --git a/es-app/src/guis/GuiScraperMenu.cpp b/es-app/src/guis/GuiScraperMenu.cpp index d356661e7..b979a7ff1 100644 --- a/es-app/src/guis/GuiScraperMenu.cpp +++ b/es-app/src/guis/GuiScraperMenu.cpp @@ -202,13 +202,21 @@ void GuiScraperMenu::openOtherSettings() s->addSaveFunc([scrape_overwrite] { Settings::getInstance()->setBool("ScraperOverwriteData", scrape_overwrite->getState()); }); - // Automatic scraping. + // Interactive scraping. auto scraper_interactive = std::make_shared(mWindow); scraper_interactive->setState(Settings::getInstance()->getBool("ScraperInteractive")); s->addWithLabel("INTERACTIVE MODE", scraper_interactive); s->addSaveFunc([scraper_interactive] { Settings::getInstance()->setBool("ScraperInteractive", scraper_interactive->getState()); }); + // Semi-automatic scraping. + auto scraper_semiautomatic = std::make_shared(mWindow); + scraper_semiautomatic->setState(Settings::getInstance()->getBool("ScraperSemiautomatic")); + s->addWithLabel("AUTO-APPROVE SINGLE GAME MATCHES", scraper_semiautomatic); + s->addSaveFunc([scraper_semiautomatic] { + Settings::getInstance()->setBool("ScraperSemiautomatic", + scraper_semiautomatic->getState()); }); + mWindow->pushGui(s); } diff --git a/es-app/src/guis/GuiScraperMulti.cpp b/es-app/src/guis/GuiScraperMulti.cpp index 0ff251a1f..2755f0fa0 100644 --- a/es-app/src/guis/GuiScraperMulti.cpp +++ b/es-app/src/guis/GuiScraperMulti.cpp @@ -55,9 +55,15 @@ GuiScraperMulti::GuiScraperMulti( Font::get(FONT_SIZE_SMALL), 0x888888FF, ALIGN_CENTER); mGrid.setEntry(mSubtitle, Vector2i(0, 2), false, true); - mSearchComp = std::make_shared(mWindow, - approveResults ? GuiScraperSearch::ALWAYS_ACCEPT_MATCHING_CRC - : GuiScraperSearch::ALWAYS_ACCEPT_FIRST_RESULT); + if (approveResults && !Settings::getInstance()->getBool("ScraperSemiautomatic")) + mSearchComp = std::make_shared(mWindow, + GuiScraperSearch::NEVER_AUTO_ACCEPT); + else if (approveResults && Settings::getInstance()->getBool("ScraperSemiautomatic")) + mSearchComp = std::make_shared(mWindow, + GuiScraperSearch::ACCEPT_SINGLE_MATCHES); + else if (!approveResults) + mSearchComp = std::make_shared(mWindow, + GuiScraperSearch::ALWAYS_ACCEPT_FIRST_RESULT); mSearchComp->setAcceptCallback(std::bind(&GuiScraperMulti::acceptResult, this, std::placeholders::_1)); mSearchComp->setSkipCallback(std::bind(&GuiScraperMulti::skip, this)); diff --git a/es-app/src/guis/GuiScraperSearch.cpp b/es-app/src/guis/GuiScraperSearch.cpp index 3541f6063..d0a45c25b 100644 --- a/es-app/src/guis/GuiScraperSearch.cpp +++ b/es-app/src/guis/GuiScraperSearch.cpp @@ -299,22 +299,31 @@ void GuiScraperSearch::onSearchDone(const std::vector& resu } else { mFoundGame = true; - ComponentListRow row; - for (size_t i = 0; i < results.size(); i++) { - row.elements.clear(); - row.addElement(std::make_shared(mWindow, - Utils::String::toUpper(results.at(i).mdl.get("name")), font, color), true); - row.makeAcceptInputHandler([this, i] { returnResult(mScraperResults.at(i)); }); - mResultList->addRow(row); + if (!(mSearchType == ACCEPT_SINGLE_MATCHES && results.size() == 1)) { + ComponentListRow row; + + for (size_t i = 0; i < results.size(); i++) { + row.elements.clear(); + row.addElement(std::make_shared(mWindow, + Utils::String::toUpper(results.at(i).mdl.get("name")), font, color), true); + row.makeAcceptInputHandler([this, i] { returnResult(mScraperResults.at(i)); }); + mResultList->addRow(row); + } + mGrid.resetCursor(); } - mGrid.resetCursor(); } mBlockAccept = false; updateInfoPane(); - // If there is no scraping result or if there is no game media to download - // as a thumbnail, then proceed directly. + // If there is no thumbnail to download and we're in semi-automatic mode, proceed to return + // the results or we'll get stuck forever waiting for a thumbnail to be downloaded. + if (mSearchType == ACCEPT_SINGLE_MATCHES && results.size() == 1 && + mScraperResults.front().ThumbnailImageUrl == "") + returnResult(mScraperResults.front()); + + // For automatic mode, if there's no thumbnail to download or no matching games found, + // proceed directly or we'll get stuck forever. if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT) { if (mScraperResults.size() == 0 || (mScraperResults.size() > 0 && mScraperResults.front().ThumbnailImageUrl == "")) { @@ -324,7 +333,6 @@ void GuiScraperSearch::onSearchDone(const std::vector& resu returnResult(mScraperResults.front()); } } - } void GuiScraperSearch::onSearchError(const std::string& error) @@ -562,14 +570,11 @@ void GuiScraperSearch::updateThumbnail() mThumbnailReq.reset(); - // When the thumbnail has been downloaded and we are in non-interactive - // mode, we proceed to automatically download the rest of the media files. - // The reason to always complete the thumbnail download first is that it looks - // a lot more consistent in the GUI. And since the thumbnail is being cached - // anyway, this hardly takes any more time. Maybe rather the opposite as the - // image used for the thumbnail (cover or screenshot) would have had to be - // requested from the server again. - if (mSearchType == ALWAYS_ACCEPT_FIRST_RESULT && + // When the thumbnail has been downloaded and we are in automatic mode, or if + // we are in semi-automatic mode with a single matching game result, we proceed + // to immediately download the rest of the media files. + if ((mSearchType == ALWAYS_ACCEPT_FIRST_RESULT || + (mSearchType == ACCEPT_SINGLE_MATCHES && mScraperResults.size() == 1)) && mScraperResults.front().thumbnailDownloadStatus == COMPLETED) { if (mScraperResults.size() == 0) mSkipCallback(); @@ -645,6 +650,10 @@ bool GuiScraperSearch::saveMetadata( if (result.mdl.get(key) == metadata.get(key)) continue; + // Make sure to set releasedate to the proper default value. + if (key == "releasedate" && metadata.get(key) == "19700101T010000") + metadata.set(key, mMetaDataDecl.at(i).defaultValue); + // Overwrite all the other values if the flag to overwrite data has been set. if (Settings::getInstance()->getBool("ScraperOverwriteData")) { metadata.set(key, result.mdl.get(key)); diff --git a/es-app/src/guis/GuiScraperSearch.h b/es-app/src/guis/GuiScraperSearch.h index aca31e3b4..e6e1a2d09 100644 --- a/es-app/src/guis/GuiScraperSearch.h +++ b/es-app/src/guis/GuiScraperSearch.h @@ -32,7 +32,7 @@ class GuiScraperSearch : public GuiComponent public: enum SearchType { ALWAYS_ACCEPT_FIRST_RESULT, - ALWAYS_ACCEPT_MATCHING_CRC, + ACCEPT_SINGLE_MATCHES, NEVER_AUTO_ACCEPT }; diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index 1b6e75c38..304a1544d 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -398,13 +398,6 @@ bool ViewController::input(InputConfig* config, Input input) // to play when we've closed the menu. if (mSystemListView->isAnimationPlaying(0)) mSystemListView->finishAnimation(0); -// for (unsigned int i = 0; i > mGameListViews.size(); i++) -// mGameListViews[i].get()->finishAnimation(0); - - for (auto it = mGameListViews.begin(); it != mGameListViews.end(); it++) { - std::string teststring = it->second->getCursor()->getName(); - std::string teststring2; - } mWindow->pushGui(new GuiMenu(mWindow)); return true; diff --git a/es-core/src/InputManager.cpp b/es-core/src/InputManager.cpp index aa07aeddf..094c2d826 100644 --- a/es-core/src/InputManager.cpp +++ b/es-core/src/InputManager.cpp @@ -327,8 +327,8 @@ void InputManager::loadDefaultKBConfig() cfg->mapInput("b", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_BACKSPACE, 1, true)); cfg->mapInput("x", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_DELETE, 1, true)); cfg->mapInput("y", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_INSERT, 1, true)); - cfg->mapInput("start", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F1, 1, true)); - cfg->mapInput("select", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F2, 1, true)); + cfg->mapInput("start", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_ESCAPE, 1, true)); + cfg->mapInput("select", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F1, 1, true)); cfg->mapInput("leftshoulder", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_PAGEUP, 1, true)); cfg->mapInput("rightshoulder", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_PAGEDOWN, 1, true)); diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index c37409483..2cb52dc2a 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -137,6 +137,7 @@ void Settings::setDefaults() // mBoolMap["ScraperGenerateMiximages"] = false; // mBoolMap["ScraperGenerateThumbnails"] = false; mBoolMap["ScraperInteractive"] = true; + mBoolMap["ScraperSemiautomatic"] = true; mBoolMap["ScraperOverwriteData"] = false; mBoolMap["ScrapeMetadata"] = true; mBoolMap["ScrapeGameNames"] = true;