// // GuiMetaDataEd.cpp // // Game metadata edit user interface. // This interface is triggered from the GuiGamelistOptions menu. // The scraping interface is handled by GuiGameScraper which calls // ScraperSearchComponent. // #include "guis/GuiMetaDataEd.h" #include "components/ButtonComponent.h" #include "components/ComponentList.h" #include "components/DateTimeEditComponent.h" #include "components/MenuComponent.h" #include "components/RatingComponent.h" #include "components/SwitchComponent.h" #include "components/TextComponent.h" #include "guis/GuiGameScraper.h" #include "guis/GuiMsgBox.h" #include "guis/GuiTextEditPopup.h" #include "guis/GuiComplexTextEditPopup.h" #include "resources/Font.h" #include "utils/StringUtil.h" #include "views/ViewController.h" #include "CollectionSystemManager.h" #include "FileData.h" #include "FileFilterIndex.h" #include "SystemData.h" #include "Window.h" GuiMetaDataEd::GuiMetaDataEd( Window* window, MetaDataList* md, const std::vector& mdd, ScraperSearchParams scraperParams, const std::string& /*header*/, std::function saveCallback, std::function deleteFunc) : GuiComponent(window), mScraperParams(scraperParams), mBackground(window, ":/frame.png"), mGrid(window, Vector2i(1, 3)), mMetaDataDecl(mdd), mMetaData(md), mSavedCallback(saveCallback), mDeleteFunc(deleteFunc), mMetadataUpdated(false) { addChild(&mBackground); addChild(&mGrid); mHeaderGrid = std::make_shared(mWindow, Vector2i(1, 5)); mTitle = std::make_shared(mWindow, "EDIT METADATA", Font::get(FONT_SIZE_LARGE), 0x555555FF, ALIGN_CENTER); mSubtitle = std::make_shared(mWindow, Utils::String::toUpper(Utils::FileSystem::getFileName(scraperParams.game-> getPath())), Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER); mHeaderGrid->setEntry(mTitle, Vector2i(0, 1), false, true); mHeaderGrid->setEntry(mSubtitle, Vector2i(0, 3), false, true); mGrid.setEntry(mHeaderGrid, Vector2i(0, 0), false, true); mList = std::make_shared(mWindow); mGrid.setEntry(mList, Vector2i(0, 1), true, true); // Populate list. for (auto iter = mdd.cbegin(); iter != mdd.cend(); iter++) { std::shared_ptr ed; // Don't add statistics. if (iter->isStatistic) continue; // Don't show the launch string override entry if this option has been disabled. if (!Settings::getInstance()->getBool("LaunchstringOverride") && iter->type == MD_LAUNCHSTRING) { ed = std::make_shared(window, "", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT); assert(ed); ed->setValue(mMetaData->get(iter->key)); mEditors.push_back(ed); continue; } // Create ed and add it (and any related components) to mMenu. // ed's value will be set below. ComponentListRow row; auto lbl = std::make_shared(mWindow, Utils::String::toUpper(iter->displayName), Font::get(FONT_SIZE_SMALL), 0x777777FF); row.addElement(lbl, true); // Label. switch (iter->type) { case MD_BOOL: { ed = std::make_shared(window); row.addElement(ed, false, true); break; } case MD_RATING: { ed = std::make_shared(window); const float height = lbl->getSize().y() * 0.71f; ed->setSize(0, height); row.addElement(ed, false, true); auto spacer = std::make_shared(mWindow); spacer->setSize(Renderer::getScreenWidth() * 0.0025f, 0); row.addElement(spacer, false); // Pass input to the actual RatingComponent instead of the spacer. row.input_handler = std::bind(&GuiComponent::input, ed.get(), std::placeholders::_1, std::placeholders::_2); break; } case MD_DATE: { ed = std::make_shared(window); row.addElement(ed, false); auto spacer = std::make_shared(mWindow); spacer->setSize(Renderer::getScreenWidth() * 0.0025f, 0); row.addElement(spacer, false); // Pass input to the actual DateTimeEditComponent instead of the spacer. row.input_handler = std::bind(&GuiComponent::input, ed.get(), std::placeholders::_1, std::placeholders::_2); break; } case MD_TIME: { ed = std::make_shared(window, DateTimeEditComponent::DISP_RELATIVE_TO_NOW); row.addElement(ed, false); break; } case MD_LAUNCHSTRING: { ed = std::make_shared(window, "", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT); row.addElement(ed, true); auto spacer = std::make_shared(mWindow); spacer->setSize(Renderer::getScreenWidth() * 0.005f, 0); row.addElement(spacer, false); auto bracket = std::make_shared(mWindow); bracket->setImage(":/arrow.svg"); bracket->setResize(Vector2f(0, lbl->getFont()->getLetterHeight())); row.addElement(bracket, false); bool multiLine = false; const std::string title = iter->displayPrompt; auto updateVal = [ed](const std::string& newVal) { ed->setValue(newVal); }; // OK callback (apply new value to ed). std::string staticTextString = "Default value from es_systems.cfg:"; std::string defaultLaunchString = scraperParams.system-> getSystemEnvData()->mLaunchCommand; row.makeAcceptInputHandler([this, title, staticTextString, defaultLaunchString, ed, updateVal, multiLine] { mWindow->pushGui(new GuiComplexTextEditPopup(mWindow, title, staticTextString, defaultLaunchString, ed->getValue(), updateVal, multiLine)); }); break; } case MD_MULTILINE_STRING: default: { // MD_STRING. ed = std::make_shared(window, "", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT); row.addElement(ed, true); auto spacer = std::make_shared(mWindow); spacer->setSize(Renderer::getScreenWidth() * 0.005f, 0); row.addElement(spacer, false); auto bracket = std::make_shared(mWindow); bracket->setImage(":/arrow.svg"); bracket->setResize(Vector2f(0, lbl->getFont()->getLetterHeight())); row.addElement(bracket, false); bool multiLine = iter->type == MD_MULTILINE_STRING; const std::string title = iter->displayPrompt; // OK callback (apply new value to ed). auto updateVal = [ed](const std::string& newVal) { ed->setValue(newVal); }; row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] { mWindow->pushGui(new GuiTextEditPopup(mWindow, title, ed->getValue(), updateVal, multiLine)); }); break; } } assert(ed); mList->addRow(row); ed->setValue(mMetaData->get(iter->key)); mEditors.push_back(ed); } 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))); buttons.push_back(std::make_shared(mWindow, "SAVE", "save", [&] { save(); delete this; })); buttons.push_back(std::make_shared(mWindow, "CANCEL", "cancel", [&] { delete this; })); if (mDeleteFunc) { auto deleteFileAndSelf = [&] { mDeleteFunc(); delete this; }; auto deleteBtnFunc = [this, deleteFileAndSelf] { mWindow->pushGui( new GuiMsgBox(mWindow, "THIS WILL DELETE THE ACTUAL GAME FILE(S)!\nARE " "YOU SURE?", "YES", deleteFileAndSelf, "NO", nullptr)); }; buttons.push_back(std::make_shared(mWindow, "DELETE", "delete", deleteBtnFunc)); } mButtons = makeButtonGrid(mWindow, buttons); mGrid.setEntry(mButtons, Vector2i(0, 2), true, false); // Resize + center. float width = (float)Math::min(Renderer::getScreenHeight(), (int)(Renderer::getScreenWidth() * 0.90f)); setSize(width, Renderer::getScreenHeight() * 0.82f); setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, (Renderer::getScreenHeight() - mSize.y()) / 2); } void GuiMetaDataEd::onSizeChanged() { mBackground.fitTo(mSize, Vector3f::Zero(), Vector2f(-32, -32)); mGrid.setSize(mSize); const float titleHeight = mTitle->getFont()->getLetterHeight(); const float subtitleHeight = mSubtitle->getFont()->getLetterHeight(); const float titleSubtitleSpacing = mSize.y() * 0.03f; mGrid.setRowHeightPerc(0, (titleHeight + titleSubtitleSpacing + subtitleHeight + TITLE_VERT_PADDING) / mSize.y()); mGrid.setRowHeightPerc(2, mButtons->getSize().y() / mSize.y()); mHeaderGrid->setRowHeightPerc(1, titleHeight / mHeaderGrid->getSize().y()); mHeaderGrid->setRowHeightPerc(2, titleSubtitleSpacing / mHeaderGrid->getSize().y()); mHeaderGrid->setRowHeightPerc(3, subtitleHeight / mHeaderGrid->getSize().y()); } void GuiMetaDataEd::save() { // Remove game from index. mScraperParams.system->getIndex()->removeFromIndex(mScraperParams.game); for (unsigned int i = 0; i < mEditors.size(); i++) { if (mMetaDataDecl.at(i).isStatistic) continue; mMetaData->set(mMetaDataDecl.at(i).key, mEditors.at(i)->getValue()); } // Enter game in index. mScraperParams.system->getIndex()->addToIndex(mScraperParams.game); if (mSavedCallback) mSavedCallback(); // Update respective Collection Entries. CollectionSystemManager::get()->refreshCollectionSystems(mScraperParams.game); mScraperParams.system->onMetaDataSavePoint(); } void GuiMetaDataEd::fetch() { GuiGameScraper* scr = new GuiGameScraper(mWindow, mScraperParams, std::bind(&GuiMetaDataEd::fetchDone, this, std::placeholders::_1)); mWindow->pushGui(scr); } void GuiMetaDataEd::fetchDone(const ScraperSearchResult& result) { // Clone the mMetaData object. MetaDataList* metadata = nullptr; metadata = new MetaDataList(*mMetaData); mMetadataUpdated = ScraperSearchComponent::saveMetadata(result, *metadata); // Update the list with the scraped metadata values. for (unsigned int i = 0; i < mEditors.size(); i++) { const std::string& key = mMetaDataDecl.at(i).key; // if (mEditors.at(i)->getValue() != metadata->get(key)) { // mEditors.at(i)->setOpacity(150); // } mEditors.at(i)->setValue(metadata->get(key)); } delete metadata; } void GuiMetaDataEd::close(bool closeAllWindows) { // Find out if the user made any changes. bool dirty = mMetadataUpdated; for (unsigned int i = 0; i < mEditors.size(); i++) { const std::string& key = mMetaDataDecl.at(i).key; std::string mMetaDataValue = mMetaData->get(key); std::string mEditorsValue = mEditors.at(i)->getValue(); // Incredibly ugly workaround to avoid the "SAVE CHANGES?" window for games // with mising metadata for rating and release date. if (key == "rating" && (mMetaDataValue == "" || mMetaDataValue == "0.000000")) mMetaDataValue = "0"; if (key == "releasedate" && (mMetaDataValue == "" || mMetaDataValue == "not-a-date-time")) mMetaDataValue = "19700101T010000"; if (mMetaDataValue != mEditorsValue) { dirty = true; break; } } std::function closeFunc; if (!closeAllWindows) { closeFunc = [this] { delete this; }; } else { Window* window = mWindow; closeFunc = [window, this] { while (window->peekGui() != ViewController::get()) delete window->peekGui(); }; } if (dirty) { // Changes were made, ask if the user wants to save them. mWindow->pushGui(new GuiMsgBox(mWindow, "SAVE CHANGES?", "YES", [this, closeFunc] { save(); closeFunc(); }, "NO", closeFunc )); } else { closeFunc(); } } bool GuiMetaDataEd::input(InputConfig* config, Input input) { if (GuiComponent::input(config, input)) return true; const bool isStart = config->isMappedTo("start", input); if (input.value != 0 && (config->isMappedTo("b", input) || isStart)) { close(isStart); return true; } return false; } std::vector GuiMetaDataEd::getHelpPrompts() { std::vector prompts = mGrid.getHelpPrompts(); prompts.push_back(HelpPrompt("b", "back")); prompts.push_back(HelpPrompt("start", "close")); return prompts; }