2020-09-21 17:17:34 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2020-05-26 16:34:33 +00:00
|
|
|
//
|
2020-09-21 17:17:34 +00:00
|
|
|
// EmulationStation Desktop Edition
|
2020-06-21 12:25:28 +00:00
|
|
|
// GuiMetaDataEd.cpp
|
2020-05-26 16:34:33 +00:00
|
|
|
//
|
2020-06-21 12:25:28 +00:00
|
|
|
// Game metadata edit user interface.
|
|
|
|
// This interface is triggered from the GuiGamelistOptions menu.
|
2021-08-09 14:47:36 +00:00
|
|
|
// The scraping interface is handled by GuiGameScraper which calls GuiScraperSearch.
|
2020-05-26 16:34:33 +00:00
|
|
|
//
|
|
|
|
|
2014-06-25 16:29:58 +00:00
|
|
|
#include "guis/GuiMetaDataEd.h"
|
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
#include "CollectionSystemsManager.h"
|
|
|
|
#include "FileData.h"
|
|
|
|
#include "FileFilterIndex.h"
|
2022-01-15 13:12:56 +00:00
|
|
|
#include "GamelistFileParser.h"
|
2021-11-26 22:24:43 +00:00
|
|
|
#include "MameNames.h"
|
2021-07-07 18:03:42 +00:00
|
|
|
#include "SystemData.h"
|
|
|
|
#include "Window.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
#include "components/ButtonComponent.h"
|
|
|
|
#include "components/ComponentList.h"
|
2018-10-13 01:08:15 +00:00
|
|
|
#include "components/DateTimeEditComponent.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
#include "components/MenuComponent.h"
|
2014-06-25 16:29:58 +00:00
|
|
|
#include "components/RatingComponent.h"
|
2017-06-12 16:38:59 +00:00
|
|
|
#include "components/SwitchComponent.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
#include "components/TextComponent.h"
|
|
|
|
#include "guis/GuiGameScraper.h"
|
|
|
|
#include "guis/GuiMsgBox.h"
|
2021-09-17 20:23:41 +00:00
|
|
|
#include "guis/GuiTextEditKeyboardPopup.h"
|
2014-06-25 16:29:58 +00:00
|
|
|
#include "guis/GuiTextEditPopup.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
#include "resources/Font.h"
|
2018-01-27 17:04:28 +00:00
|
|
|
#include "utils/StringUtil.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
#include "views/ViewController.h"
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2021-10-10 15:07:38 +00:00
|
|
|
#define TITLE_HEIGHT (mTitle->getFont()->getLetterHeight() + Renderer::getScreenHeight() * 0.060f)
|
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
GuiMetaDataEd::GuiMetaDataEd(Window* window,
|
|
|
|
MetaDataList* md,
|
|
|
|
const std::vector<MetaDataDecl>& mdd,
|
|
|
|
ScraperSearchParams scraperParams,
|
|
|
|
std::function<void()> saveCallback,
|
|
|
|
std::function<void()> clearGameFunc,
|
|
|
|
std::function<void()> deleteGameFunc)
|
2021-10-10 15:07:38 +00:00
|
|
|
: GuiComponent{window}
|
|
|
|
, mBackground{window, ":/graphics/frame.svg"}
|
2021-10-14 20:31:50 +00:00
|
|
|
, mGrid{window, glm::ivec2{2, 6}}
|
2021-10-10 15:07:38 +00:00
|
|
|
, mScraperParams{scraperParams}
|
2022-01-15 10:50:51 +00:00
|
|
|
, mControllerBadges{BadgeComponent::getGameControllers()}
|
2021-10-10 15:07:38 +00:00
|
|
|
, mMetaDataDecl{mdd}
|
|
|
|
, mMetaData{md}
|
|
|
|
, mSavedCallback{saveCallback}
|
|
|
|
, mClearGameFunc{clearGameFunc}
|
|
|
|
, mDeleteGameFunc{deleteGameFunc}
|
2022-01-15 10:50:51 +00:00
|
|
|
, mIsCustomCollection{false}
|
2021-10-10 15:07:38 +00:00
|
|
|
, mMediaFilesUpdated{false}
|
2021-12-02 16:34:30 +00:00
|
|
|
, mSavedMediaAndAborted{false}
|
2021-10-10 15:07:38 +00:00
|
|
|
, mInvalidEmulatorEntry{false}
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2022-01-15 10:50:51 +00:00
|
|
|
if (ViewController::getInstance()->getState().getSystem()->isCustomCollection() ||
|
|
|
|
ViewController::getInstance()->getState().getSystem()->getThemeFolder() ==
|
|
|
|
"custom-collections")
|
|
|
|
mIsCustomCollection = true;
|
2021-10-23 17:08:32 +00:00
|
|
|
|
|
|
|
// Remove the last "unknown" controller entry.
|
2021-10-23 20:49:34 +00:00
|
|
|
if (mControllerBadges.size() > 1)
|
|
|
|
mControllerBadges.pop_back();
|
2021-10-23 17:08:32 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
addChild(&mBackground);
|
|
|
|
addChild(&mGrid);
|
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
mTitle = std::make_shared<TextComponent>(mWindow, "EDIT METADATA", Font::get(FONT_SIZE_LARGE),
|
|
|
|
0x555555FF, ALIGN_CENTER);
|
2021-10-14 20:31:50 +00:00
|
|
|
mGrid.setEntry(mTitle, glm::ivec2{0, 0}, false, true, glm::ivec2{2, 2});
|
2021-01-31 20:30:47 +00:00
|
|
|
|
|
|
|
// Extract possible subfolders from the path.
|
2021-07-07 18:03:42 +00:00
|
|
|
std::string folderPath =
|
|
|
|
Utils::String::replace(Utils::FileSystem::getParent(scraperParams.game->getPath()),
|
|
|
|
scraperParams.system->getSystemEnvData()->mStartPath, "");
|
2021-01-31 20:30:47 +00:00
|
|
|
|
|
|
|
if (folderPath.size() >= 2) {
|
|
|
|
folderPath.erase(0, 1);
|
2021-07-07 18:03:42 +00:00
|
|
|
#if defined(_WIN64)
|
2021-01-31 20:33:32 +00:00
|
|
|
folderPath.push_back('\\');
|
2021-01-31 20:30:47 +00:00
|
|
|
folderPath = Utils::String::replace(folderPath, "/", "\\");
|
2021-07-07 18:03:42 +00:00
|
|
|
#else
|
2021-01-31 20:30:47 +00:00
|
|
|
folderPath.push_back('/');
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2021-01-31 20:30:47 +00:00
|
|
|
}
|
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
mSubtitle = std::make_shared<TextComponent>(
|
|
|
|
mWindow,
|
|
|
|
folderPath + Utils::FileSystem::getFileName(scraperParams.game->getPath()) + " [" +
|
|
|
|
Utils::String::toUpper(scraperParams.system->getName()) + "]" +
|
2021-01-31 20:30:47 +00:00
|
|
|
(scraperParams.game->getType() == FOLDER ? " " + ViewController::FOLDER_CHAR : ""),
|
2021-10-15 18:58:40 +00:00
|
|
|
Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER);
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2021-10-14 20:31:50 +00:00
|
|
|
mGrid.setEntry(mSubtitle, glm::ivec2{0, 2}, false, true, glm::ivec2{2, 1});
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
mList = std::make_shared<ComponentList>(mWindow);
|
2021-10-14 20:31:50 +00:00
|
|
|
mGrid.setEntry(mList, glm::ivec2{0, 4}, true, true, glm::ivec2{2, 1});
|
2021-10-10 16:15:37 +00:00
|
|
|
|
|
|
|
// Set up scroll indicators.
|
|
|
|
mScrollUp = std::make_shared<ImageComponent>(mWindow);
|
|
|
|
mScrollDown = std::make_shared<ImageComponent>(mWindow);
|
|
|
|
mScrollIndicator = std::make_shared<ScrollIndicatorComponent>(mList, mScrollUp, mScrollDown);
|
|
|
|
|
|
|
|
mScrollUp->setResize(0.0f, mTitle->getFont()->getLetterHeight() / 2.0f);
|
|
|
|
mScrollUp->setOrigin(0.0f, -0.35f);
|
|
|
|
|
|
|
|
mScrollDown->setResize(0.0f, mTitle->getFont()->getLetterHeight() / 2.0f);
|
|
|
|
mScrollDown->setOrigin(0.0f, 0.35f);
|
|
|
|
|
2021-10-14 20:31:50 +00:00
|
|
|
mGrid.setEntry(mScrollUp, glm::ivec2{1, 0}, false, false, glm::ivec2{1, 1});
|
|
|
|
mGrid.setEntry(mScrollDown, glm::ivec2{1, 1}, false, false, glm::ivec2{1, 1});
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
// Populate list.
|
2021-11-17 16:35:34 +00:00
|
|
|
for (auto it = mdd.cbegin(); it != mdd.cend(); ++it) {
|
2020-06-21 12:25:28 +00:00
|
|
|
std::shared_ptr<GuiComponent> ed;
|
2021-11-17 16:35:34 +00:00
|
|
|
std::string currentKey = it->key;
|
|
|
|
std::string originalValue = mMetaData->get(it->key);
|
2020-08-02 09:45:59 +00:00
|
|
|
std::string gamePath;
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2022-01-15 10:50:51 +00:00
|
|
|
// Only display the custom collections sortname entry if we're editing the game
|
|
|
|
// from within a custom collection.
|
|
|
|
if (currentKey == "collectionsortname" && !mIsCustomCollection)
|
|
|
|
continue;
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Don't add statistics.
|
2021-11-17 16:35:34 +00:00
|
|
|
if (it->isStatistic)
|
2020-06-21 12:25:28 +00:00
|
|
|
continue;
|
|
|
|
|
2021-09-04 09:21:55 +00:00
|
|
|
// Don't show the alternative emulator entry if the corresponding option has been disabled.
|
|
|
|
if (!Settings::getInstance()->getBool("AlternativeEmulatorPerGame") &&
|
2021-11-17 16:35:34 +00:00
|
|
|
it->type == MD_ALT_EMULATOR) {
|
2021-07-07 18:03:42 +00:00
|
|
|
ed = std::make_shared<TextComponent>(
|
|
|
|
window, "", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT), 0x777777FF, ALIGN_RIGHT);
|
2020-06-21 12:25:28 +00:00
|
|
|
assert(ed);
|
2021-11-17 16:35:34 +00:00
|
|
|
ed->setValue(mMetaData->get(it->key));
|
2020-06-21 12:25:28 +00:00
|
|
|
mEditors.push_back(ed);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// It's very important to put the element with the help prompt as the last row
|
|
|
|
// entry instead of for instance the spacer. That is so because ComponentList
|
|
|
|
// always looks for the help prompt at the back of the element stack.
|
|
|
|
ComponentListRow row;
|
2021-11-17 16:35:34 +00:00
|
|
|
auto lbl = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(it->displayName),
|
|
|
|
Font::get(FONT_SIZE_SMALL), 0x777777FF);
|
2020-06-21 12:25:28 +00:00
|
|
|
row.addElement(lbl, true); // Label.
|
|
|
|
|
2021-11-17 16:35:34 +00:00
|
|
|
switch (it->type) {
|
2021-07-07 18:03:42 +00:00
|
|
|
case MD_BOOL: {
|
2020-06-21 12:25:28 +00:00
|
|
|
ed = std::make_shared<SwitchComponent>(window);
|
2020-12-17 19:49:20 +00:00
|
|
|
// Make the switches slightly smaller.
|
2021-08-17 16:41:45 +00:00
|
|
|
glm::vec2 switchSize{ed->getSize() * 0.9f};
|
2021-10-23 17:08:32 +00:00
|
|
|
ed->setResize(ceilf(switchSize.x), switchSize.y);
|
2020-12-17 19:49:20 +00:00
|
|
|
|
2020-07-15 15:44:27 +00:00
|
|
|
ed->setChangedColor(ICONCOLOR_USERMARKED);
|
2020-06-21 12:25:28 +00:00
|
|
|
row.addElement(ed, false, true);
|
|
|
|
break;
|
|
|
|
}
|
2021-07-07 18:03:42 +00:00
|
|
|
case MD_RATING: {
|
2020-06-21 12:25:28 +00:00
|
|
|
auto spacer = std::make_shared<GuiComponent>(mWindow);
|
2021-07-07 18:03:42 +00:00
|
|
|
spacer->setSize(Renderer::getScreenWidth() * 0.0025f, 0.0f);
|
2020-06-21 12:25:28 +00:00
|
|
|
row.addElement(spacer, false);
|
|
|
|
|
2020-07-15 15:44:27 +00:00
|
|
|
ed = std::make_shared<RatingComponent>(window, true);
|
|
|
|
ed->setChangedColor(ICONCOLOR_USERMARKED);
|
2021-08-16 16:25:01 +00:00
|
|
|
const float height = lbl->getSize().y * 0.71f;
|
|
|
|
ed->setSize(0.0f, height);
|
2020-06-21 12:25:28 +00:00
|
|
|
row.addElement(ed, false, true);
|
|
|
|
|
|
|
|
// Pass input to the actual RatingComponent instead of the spacer.
|
2021-07-07 18:03:42 +00:00
|
|
|
row.input_handler = std::bind(&GuiComponent::input, ed.get(), std::placeholders::_1,
|
|
|
|
std::placeholders::_2);
|
2020-06-21 12:25:28 +00:00
|
|
|
break;
|
|
|
|
}
|
2021-07-07 18:03:42 +00:00
|
|
|
case MD_DATE: {
|
2020-06-21 12:25:28 +00:00
|
|
|
auto spacer = std::make_shared<GuiComponent>(mWindow);
|
2021-07-07 18:03:42 +00:00
|
|
|
spacer->setSize(Renderer::getScreenWidth() * 0.0025f, 0.0f);
|
2020-06-21 12:25:28 +00:00
|
|
|
row.addElement(spacer, false);
|
|
|
|
|
2021-03-20 10:08:28 +00:00
|
|
|
ed = std::make_shared<DateTimeEditComponent>(window, true);
|
2020-08-02 13:56:32 +00:00
|
|
|
ed->setOriginalColor(DEFAULT_TEXTCOLOR);
|
2020-07-15 15:44:27 +00:00
|
|
|
ed->setChangedColor(TEXTCOLOR_USERMARKED);
|
2020-06-21 12:25:28 +00:00
|
|
|
row.addElement(ed, false);
|
|
|
|
|
|
|
|
// Pass input to the actual DateTimeEditComponent instead of the spacer.
|
2021-07-07 18:03:42 +00:00
|
|
|
row.input_handler = std::bind(&GuiComponent::input, ed.get(), std::placeholders::_1,
|
|
|
|
std::placeholders::_2);
|
2020-06-21 12:25:28 +00:00
|
|
|
break;
|
|
|
|
}
|
2021-10-23 17:08:32 +00:00
|
|
|
case MD_CONTROLLER: {
|
|
|
|
ed = std::make_shared<TextComponent>(window, "",
|
|
|
|
Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT),
|
|
|
|
0x777777FF, ALIGN_RIGHT);
|
|
|
|
row.addElement(ed, true);
|
|
|
|
|
|
|
|
auto spacer = std::make_shared<GuiComponent>(mWindow);
|
|
|
|
spacer->setSize(Renderer::getScreenWidth() * 0.005f, 0.0f);
|
|
|
|
row.addElement(spacer, false);
|
|
|
|
|
|
|
|
auto bracket = std::make_shared<ImageComponent>(mWindow);
|
|
|
|
bracket->setImage(":/graphics/arrow.svg");
|
|
|
|
bracket->setResize(glm::vec2{0.0f, lbl->getFont()->getLetterHeight()});
|
|
|
|
row.addElement(bracket, false);
|
|
|
|
|
2021-11-17 16:35:34 +00:00
|
|
|
const std::string title = it->displayPrompt;
|
2021-10-23 17:08:32 +00:00
|
|
|
|
|
|
|
// OK callback (apply new value to ed).
|
|
|
|
auto updateVal = [ed, originalValue](const std::string& newVal) {
|
|
|
|
ed->setValue(newVal);
|
2021-10-23 18:28:07 +00:00
|
|
|
if (newVal == BadgeComponent::getDisplayName(originalValue))
|
2021-10-23 17:08:32 +00:00
|
|
|
ed->setColor(DEFAULT_TEXTCOLOR);
|
|
|
|
else
|
|
|
|
ed->setColor(TEXTCOLOR_USERMARKED);
|
|
|
|
};
|
|
|
|
|
2021-10-23 20:49:34 +00:00
|
|
|
row.makeAcceptInputHandler([this, title, ed, updateVal] {
|
2021-10-23 17:08:32 +00:00
|
|
|
GuiSettings* s = new GuiSettings(mWindow, title);
|
|
|
|
|
2021-10-23 20:49:34 +00:00
|
|
|
for (auto controller : mControllerBadges) {
|
2021-10-23 17:08:32 +00:00
|
|
|
std::string selectedLabel = ed->getValue();
|
|
|
|
std::string label;
|
|
|
|
ComponentListRow row;
|
|
|
|
|
|
|
|
std::shared_ptr<TextComponent> labelText = std::make_shared<TextComponent>(
|
|
|
|
mWindow, label, Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
|
|
|
|
labelText->setSelectable(true);
|
2021-10-23 18:14:17 +00:00
|
|
|
labelText->setValue(controller.displayName);
|
2021-10-23 17:08:32 +00:00
|
|
|
|
2021-10-23 18:14:17 +00:00
|
|
|
label = controller.displayName;
|
2021-10-23 17:08:32 +00:00
|
|
|
|
|
|
|
row.addElement(labelText, true);
|
|
|
|
|
2021-10-23 18:14:17 +00:00
|
|
|
row.makeAcceptInputHandler([s, updateVal, controller] {
|
|
|
|
updateVal(controller.displayName);
|
2021-10-23 17:08:32 +00:00
|
|
|
delete s;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Select the row that corresponds to the selected label.
|
|
|
|
if (selectedLabel == label)
|
|
|
|
s->addRow(row, true);
|
|
|
|
else
|
|
|
|
s->addRow(row, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If a value is set, then display "Clear entry" as the last entry.
|
|
|
|
if (ed->getValue() != "") {
|
|
|
|
ComponentListRow row;
|
|
|
|
std::shared_ptr<TextComponent> clearText = std::make_shared<TextComponent>(
|
|
|
|
mWindow, ViewController::CROSSEDCIRCLE_CHAR + " CLEAR ENTRY",
|
|
|
|
Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
|
|
|
|
clearText->setSelectable(true);
|
|
|
|
row.addElement(clearText, true);
|
|
|
|
row.makeAcceptInputHandler([s, ed] {
|
|
|
|
ed->setValue("");
|
|
|
|
delete s;
|
|
|
|
});
|
|
|
|
s->addRow(row, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
float aspectValue = 1.778f / Renderer::getScreenAspectRatio();
|
|
|
|
float maxWidthModifier = glm::clamp(0.64f * aspectValue, 0.42f, 0.92f);
|
|
|
|
float maxWidth =
|
|
|
|
static_cast<float>(Renderer::getScreenWidth()) * maxWidthModifier;
|
|
|
|
|
|
|
|
s->setMenuSize(glm::vec2{maxWidth, s->getMenuSize().y});
|
|
|
|
s->setMenuPosition(
|
|
|
|
glm::vec3{(s->getSize().x - maxWidth) / 2.0f, mPosition.y, mPosition.z});
|
|
|
|
mWindow->pushGui(s);
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
}
|
2021-09-04 09:21:55 +00:00
|
|
|
case MD_ALT_EMULATOR: {
|
|
|
|
mInvalidEmulatorEntry = false;
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
ed = std::make_shared<TextComponent>(window, "",
|
2021-07-07 18:03:42 +00:00
|
|
|
Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT),
|
|
|
|
0x777777FF, ALIGN_RIGHT);
|
2020-06-21 12:25:28 +00:00
|
|
|
row.addElement(ed, true);
|
|
|
|
|
|
|
|
auto spacer = std::make_shared<GuiComponent>(mWindow);
|
2021-07-07 18:03:42 +00:00
|
|
|
spacer->setSize(Renderer::getScreenWidth() * 0.005f, 0.0f);
|
2020-06-21 12:25:28 +00:00
|
|
|
row.addElement(spacer, false);
|
|
|
|
|
|
|
|
auto bracket = std::make_shared<ImageComponent>(mWindow);
|
2020-06-21 17:35:43 +00:00
|
|
|
bracket->setImage(":/graphics/arrow.svg");
|
2021-08-17 16:41:45 +00:00
|
|
|
bracket->setResize(glm::vec2{0.0f, lbl->getFont()->getLetterHeight()});
|
2020-06-21 12:25:28 +00:00
|
|
|
row.addElement(bracket, false);
|
|
|
|
|
2021-11-17 16:35:34 +00:00
|
|
|
const std::string title = it->displayPrompt;
|
2020-07-15 15:44:27 +00:00
|
|
|
|
|
|
|
// OK callback (apply new value to ed).
|
2021-09-04 09:21:55 +00:00
|
|
|
auto updateVal = [this, ed, originalValue](const std::string& newVal) {
|
2020-07-15 15:44:27 +00:00
|
|
|
ed->setValue(newVal);
|
2021-09-04 09:21:55 +00:00
|
|
|
if (newVal == originalValue) {
|
2020-07-15 15:44:27 +00:00
|
|
|
ed->setColor(DEFAULT_TEXTCOLOR);
|
2021-09-04 09:21:55 +00:00
|
|
|
}
|
|
|
|
else {
|
2020-07-15 15:44:27 +00:00
|
|
|
ed->setColor(TEXTCOLOR_USERMARKED);
|
2021-09-04 09:21:55 +00:00
|
|
|
mInvalidEmulatorEntry = false;
|
|
|
|
}
|
2020-07-15 15:44:27 +00:00
|
|
|
};
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2021-09-04 09:21:55 +00:00
|
|
|
if (originalValue != "" &&
|
|
|
|
scraperParams.system->getLaunchCommandFromLabel(originalValue) == "") {
|
2021-08-22 13:26:38 +00:00
|
|
|
LOG(LogWarning)
|
2021-09-04 09:21:55 +00:00
|
|
|
<< "GuiMetaDataEd: Invalid alternative emulator \"" << originalValue
|
|
|
|
<< "\" configured for game \"" << mScraperParams.game->getName() << "\"";
|
|
|
|
mInvalidEmulatorEntry = true;
|
2021-08-22 13:26:38 +00:00
|
|
|
}
|
|
|
|
|
2021-09-04 09:21:55 +00:00
|
|
|
if (scraperParams.system->getSystemEnvData()->mLaunchCommands.size() == 1) {
|
|
|
|
lbl->setOpacity(DISABLED_OPACITY);
|
|
|
|
bracket->setOpacity(DISABLED_OPACITY);
|
|
|
|
}
|
2021-07-07 18:03:42 +00:00
|
|
|
|
2021-09-04 09:21:55 +00:00
|
|
|
if (mInvalidEmulatorEntry ||
|
|
|
|
scraperParams.system->getSystemEnvData()->mLaunchCommands.size() > 1) {
|
2021-09-21 17:59:09 +00:00
|
|
|
row.makeAcceptInputHandler([this, title, scraperParams, ed, updateVal,
|
|
|
|
originalValue] {
|
|
|
|
GuiSettings* s = nullptr;
|
2021-09-04 09:21:55 +00:00
|
|
|
|
2021-09-21 17:59:09 +00:00
|
|
|
bool singleEntry =
|
|
|
|
scraperParams.system->getSystemEnvData()->mLaunchCommands.size() == 1;
|
|
|
|
|
|
|
|
if (mInvalidEmulatorEntry && singleEntry)
|
|
|
|
s = new GuiSettings(mWindow, "CLEAR INVALID ENTRY");
|
|
|
|
else
|
|
|
|
s = new GuiSettings(mWindow, title);
|
|
|
|
|
|
|
|
if (!mInvalidEmulatorEntry && ed->getValue() == "" && singleEntry)
|
2021-09-04 09:21:55 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
std::vector<std::pair<std::string, std::string>> launchCommands =
|
|
|
|
scraperParams.system->getSystemEnvData()->mLaunchCommands;
|
|
|
|
|
2021-09-21 17:59:09 +00:00
|
|
|
if (ed->getValue() != "" && mInvalidEmulatorEntry && singleEntry)
|
|
|
|
launchCommands.push_back(std::make_pair(
|
|
|
|
"", ViewController::EXCLAMATION_CHAR + " " + originalValue));
|
2021-09-04 09:21:55 +00:00
|
|
|
else if (ed->getValue() != "")
|
2021-09-21 17:59:09 +00:00
|
|
|
launchCommands.push_back(std::make_pair(
|
|
|
|
"", ViewController::CROSSEDCIRCLE_CHAR + " CLEAR ENTRY"));
|
2021-09-04 09:21:55 +00:00
|
|
|
|
|
|
|
for (auto entry : launchCommands) {
|
2021-10-13 15:22:29 +00:00
|
|
|
if (mInvalidEmulatorEntry && singleEntry &&
|
|
|
|
entry.second !=
|
|
|
|
ViewController::EXCLAMATION_CHAR + " " + originalValue)
|
|
|
|
continue;
|
|
|
|
|
2021-09-04 09:21:55 +00:00
|
|
|
std::string selectedLabel = ed->getValue();
|
|
|
|
std::string label;
|
|
|
|
ComponentListRow row;
|
|
|
|
|
|
|
|
if (entry.second == "")
|
|
|
|
continue;
|
|
|
|
else
|
|
|
|
label = entry.second;
|
|
|
|
|
|
|
|
std::shared_ptr<TextComponent> labelText =
|
2021-10-10 17:02:18 +00:00
|
|
|
std::make_shared<TextComponent>(
|
|
|
|
mWindow, label, Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
|
2021-10-14 20:31:50 +00:00
|
|
|
labelText->setSelectable(true);
|
2021-09-04 09:21:55 +00:00
|
|
|
|
|
|
|
if (scraperParams.system->getAlternativeEmulator() == "" &&
|
|
|
|
scraperParams.system->getSystemEnvData()
|
|
|
|
->mLaunchCommands.front()
|
|
|
|
.second == label)
|
|
|
|
labelText->setValue(labelText->getValue().append(" [SYSTEM-WIDE]"));
|
|
|
|
|
|
|
|
if (scraperParams.system->getAlternativeEmulator() == label)
|
|
|
|
labelText->setValue(labelText->getValue().append(" [SYSTEM-WIDE]"));
|
|
|
|
|
|
|
|
row.addElement(labelText, true);
|
|
|
|
row.makeAcceptInputHandler(
|
|
|
|
[this, s, updateVal, entry, selectedLabel, launchCommands] {
|
|
|
|
if (entry.second == launchCommands.back().second &&
|
|
|
|
launchCommands.back().first == "") {
|
|
|
|
updateVal("");
|
|
|
|
}
|
|
|
|
else if (entry.second != selectedLabel) {
|
|
|
|
updateVal(entry.second);
|
|
|
|
}
|
|
|
|
mInvalidEmulatorEntry = false;
|
|
|
|
delete s;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Select the row that corresponds to the selected label.
|
|
|
|
if (selectedLabel == label)
|
|
|
|
s->addRow(row, true);
|
|
|
|
else
|
|
|
|
s->addRow(row, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
float aspectValue = 1.778f / Renderer::getScreenAspectRatio();
|
2021-10-10 17:02:18 +00:00
|
|
|
float maxWidthModifier = glm::clamp(0.64f * aspectValue, 0.42f, 0.92f);
|
2021-09-04 09:21:55 +00:00
|
|
|
float maxWidth =
|
|
|
|
static_cast<float>(Renderer::getScreenWidth()) * maxWidthModifier;
|
|
|
|
|
|
|
|
s->setMenuSize(glm::vec2{maxWidth, s->getMenuSize().y});
|
2021-10-16 10:23:32 +00:00
|
|
|
s->setMenuPosition(glm::vec3{(s->getSize().x - maxWidth) / 2.0f,
|
|
|
|
mPosition.y, mPosition.z});
|
2021-09-04 09:21:55 +00:00
|
|
|
mWindow->pushGui(s);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
lbl->setOpacity(DISABLED_OPACITY);
|
|
|
|
bracket->setOpacity(DISABLED_OPACITY);
|
|
|
|
}
|
2020-06-21 12:25:28 +00:00
|
|
|
break;
|
|
|
|
}
|
2021-07-07 18:03:42 +00:00
|
|
|
case MD_MULTILINE_STRING:
|
|
|
|
default: {
|
2020-06-21 12:25:28 +00:00
|
|
|
// MD_STRING.
|
2021-07-07 18:03:42 +00:00
|
|
|
ed = std::make_shared<TextComponent>(window, "",
|
|
|
|
Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT),
|
|
|
|
0x777777FF, ALIGN_RIGHT);
|
2020-06-21 12:25:28 +00:00
|
|
|
row.addElement(ed, true);
|
|
|
|
|
|
|
|
auto spacer = std::make_shared<GuiComponent>(mWindow);
|
2021-07-07 18:03:42 +00:00
|
|
|
spacer->setSize(Renderer::getScreenWidth() * 0.005f, 0.0f);
|
2020-06-21 12:25:28 +00:00
|
|
|
row.addElement(spacer, false);
|
|
|
|
|
|
|
|
auto bracket = std::make_shared<ImageComponent>(mWindow);
|
2020-06-21 17:35:43 +00:00
|
|
|
bracket->setImage(":/graphics/arrow.svg");
|
2021-08-17 16:41:45 +00:00
|
|
|
bracket->setResize(glm::vec2{0.0f, lbl->getFont()->getLetterHeight()});
|
2020-06-21 12:25:28 +00:00
|
|
|
row.addElement(bracket, false);
|
|
|
|
|
2021-11-17 16:35:34 +00:00
|
|
|
bool multiLine = it->type == MD_MULTILINE_STRING;
|
|
|
|
const std::string title = it->displayPrompt;
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2020-08-02 09:45:59 +00:00
|
|
|
gamePath = Utils::FileSystem::getStem(mScraperParams.game->getPath());
|
2020-07-15 15:44:27 +00:00
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
// OK callback (apply new value to ed).
|
2021-11-26 22:24:43 +00:00
|
|
|
auto updateVal = [ed, currentKey, originalValue, gamePath,
|
|
|
|
scraperParams](const std::string& newVal) {
|
2020-08-02 09:45:59 +00:00
|
|
|
// If the user has entered a blank game name, then set the name to the ROM
|
|
|
|
// filename (minus the extension).
|
|
|
|
if (currentKey == "name" && newVal == "") {
|
2021-12-17 19:18:47 +00:00
|
|
|
if (scraperParams.game->isArcadeGame()) {
|
2021-11-26 22:24:43 +00:00
|
|
|
ed->setValue(MameNames::getInstance().getCleanName(
|
|
|
|
scraperParams.game->getCleanName()));
|
2021-12-17 19:18:47 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// For the special case where a directory has a supported file extension
|
|
|
|
// and is therefore interpreted as a file, exclude the extension.
|
|
|
|
if (scraperParams.game->getType() == GAME &&
|
|
|
|
Utils::FileSystem::isDirectory(scraperParams.game->getFullPath()))
|
|
|
|
ed->setValue(Utils::FileSystem::getStem(gamePath));
|
|
|
|
else
|
|
|
|
ed->setValue(gamePath);
|
|
|
|
}
|
2020-08-02 09:45:59 +00:00
|
|
|
if (gamePath == originalValue)
|
|
|
|
ed->setColor(DEFAULT_TEXTCOLOR);
|
|
|
|
else
|
|
|
|
ed->setColor(TEXTCOLOR_USERMARKED);
|
|
|
|
}
|
2021-07-07 18:03:42 +00:00
|
|
|
else if (newVal == "" &&
|
|
|
|
(currentKey == "developer" || currentKey == "publisher" ||
|
|
|
|
currentKey == "genre" || currentKey == "players")) {
|
2020-12-17 19:49:20 +00:00
|
|
|
ed->setValue("unknown");
|
|
|
|
if (originalValue == "unknown")
|
|
|
|
ed->setColor(DEFAULT_TEXTCOLOR);
|
|
|
|
else
|
2021-07-07 18:03:42 +00:00
|
|
|
ed->setColor(TEXTCOLOR_USERMARKED);
|
2020-12-17 19:49:20 +00:00
|
|
|
}
|
2020-08-02 09:45:59 +00:00
|
|
|
else {
|
|
|
|
ed->setValue(newVal);
|
|
|
|
if (newVal == originalValue)
|
|
|
|
ed->setColor(DEFAULT_TEXTCOLOR);
|
|
|
|
else
|
2021-07-07 18:03:42 +00:00
|
|
|
ed->setColor(TEXTCOLOR_USERMARKED);
|
2020-08-02 09:45:59 +00:00
|
|
|
}
|
2021-07-07 18:03:42 +00:00
|
|
|
};
|
2020-07-15 15:44:27 +00:00
|
|
|
|
2021-09-17 20:23:41 +00:00
|
|
|
if (Settings::getInstance()->getBool("VirtualKeyboard")) {
|
|
|
|
row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] {
|
|
|
|
mWindow->pushGui(new GuiTextEditKeyboardPopup(
|
|
|
|
mWindow, getHelpStyle(), title, ed->getValue(), updateVal, multiLine,
|
|
|
|
"apply", "APPLY CHANGES?", "", ""));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
row.makeAcceptInputHandler([this, title, ed, updateVal, multiLine] {
|
|
|
|
mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), title,
|
|
|
|
ed->getValue(), updateVal, multiLine,
|
|
|
|
"APPLY", "APPLY CHANGES?"));
|
|
|
|
});
|
|
|
|
}
|
2020-06-21 12:25:28 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(ed);
|
|
|
|
mList->addRow(row);
|
2021-09-04 09:21:55 +00:00
|
|
|
|
2021-11-17 16:35:34 +00:00
|
|
|
if (it->type == MD_ALT_EMULATOR && mInvalidEmulatorEntry == true) {
|
2021-09-21 17:59:09 +00:00
|
|
|
ed->setValue(ViewController::EXCLAMATION_CHAR + " " + originalValue);
|
2021-10-23 17:08:32 +00:00
|
|
|
}
|
2021-11-17 16:35:34 +00:00
|
|
|
else if (it->type == MD_CONTROLLER && mMetaData->get(it->key) != "") {
|
|
|
|
std::string displayName = BadgeComponent::getDisplayName(mMetaData->get(it->key));
|
2021-10-23 17:08:32 +00:00
|
|
|
if (displayName != "unknown")
|
|
|
|
ed->setValue(displayName);
|
|
|
|
else
|
2021-11-17 16:35:34 +00:00
|
|
|
ed->setValue(ViewController::EXCLAMATION_CHAR + " " + mMetaData->get(it->key));
|
2021-10-23 17:08:32 +00:00
|
|
|
}
|
|
|
|
else {
|
2021-11-17 16:35:34 +00:00
|
|
|
ed->setValue(mMetaData->get(it->key));
|
2021-10-23 17:08:32 +00:00
|
|
|
}
|
2021-09-04 09:21:55 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
mEditors.push_back(ed);
|
|
|
|
}
|
|
|
|
|
2020-07-15 15:44:27 +00:00
|
|
|
std::vector<std::shared_ptr<ButtonComponent>> buttons;
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2020-07-28 19:08:17 +00:00
|
|
|
if (!scraperParams.system->hasPlatformId(PlatformIds::PLATFORM_IGNORE))
|
2021-07-07 18:03:42 +00:00
|
|
|
buttons.push_back(std::make_shared<ButtonComponent>(
|
|
|
|
mWindow, "SCRAPE", "scrape", std::bind(&GuiMetaDataEd::fetch, this)));
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2020-09-27 11:14:50 +00:00
|
|
|
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "SAVE", "save metadata", [&] {
|
|
|
|
save();
|
2022-01-04 20:49:22 +00:00
|
|
|
ViewController::getInstance()->onPauseVideo();
|
2020-09-27 11:14:50 +00:00
|
|
|
delete this;
|
|
|
|
}));
|
2020-06-21 12:25:28 +00:00
|
|
|
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CANCEL", "cancel changes",
|
2021-07-07 18:03:42 +00:00
|
|
|
[&] { delete this; }));
|
2020-07-30 18:05:57 +00:00
|
|
|
if (scraperParams.game->getType() == FOLDER) {
|
2020-09-27 08:41:00 +00:00
|
|
|
if (mClearGameFunc) {
|
2021-07-07 18:03:42 +00:00
|
|
|
auto clearSelf = [&] {
|
|
|
|
mClearGameFunc();
|
|
|
|
delete this;
|
|
|
|
};
|
2020-09-27 08:41:00 +00:00
|
|
|
auto clearSelfBtnFunc = [this, clearSelf] {
|
2020-07-30 18:05:57 +00:00
|
|
|
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(),
|
2021-07-07 18:03:42 +00:00
|
|
|
"THIS WILL DELETE ANY MEDIA FILES AND\n"
|
|
|
|
"THE GAMELIST.XML ENTRY FOR THIS FOLDER,\n"
|
|
|
|
"BUT NEITHER THE FOLDER ITSELF OR ANY\n"
|
|
|
|
"CONTENT INSIDE IT WILL BE REMOVED\n"
|
|
|
|
"ARE YOU SURE?",
|
|
|
|
"YES", clearSelf, "NO", nullptr));
|
|
|
|
};
|
|
|
|
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CLEAR", "clear folder",
|
|
|
|
clearSelfBtnFunc));
|
2020-07-30 18:05:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2020-09-27 08:41:00 +00:00
|
|
|
if (mClearGameFunc) {
|
2021-07-07 18:03:42 +00:00
|
|
|
auto clearSelf = [&] {
|
|
|
|
mClearGameFunc();
|
|
|
|
delete this;
|
|
|
|
};
|
2020-09-27 08:41:00 +00:00
|
|
|
auto clearSelfBtnFunc = [this, clearSelf] {
|
|
|
|
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(),
|
2021-07-07 18:03:42 +00:00
|
|
|
"THIS WILL DELETE ANY MEDIA FILES\n"
|
|
|
|
"AND THE GAMELIST.XML ENTRY FOR\n"
|
|
|
|
"THIS GAME, BUT THE GAME FILE\n"
|
|
|
|
"ITSELF WILL NOT BE REMOVED\n"
|
|
|
|
"ARE YOU SURE?",
|
|
|
|
"YES", clearSelf, "NO", nullptr));
|
|
|
|
};
|
|
|
|
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CLEAR", "clear file",
|
|
|
|
clearSelfBtnFunc));
|
2020-09-27 08:41:00 +00:00
|
|
|
}
|
|
|
|
|
2021-12-17 19:18:47 +00:00
|
|
|
// For the special case where a directory has a supported file extension and is therefore
|
|
|
|
// interpreted as a file, don't add the delete button.
|
|
|
|
if (mDeleteGameFunc && !Utils::FileSystem::isDirectory(scraperParams.game->getPath())) {
|
2021-07-07 18:03:42 +00:00
|
|
|
auto deleteFilesAndSelf = [&] {
|
|
|
|
mDeleteGameFunc();
|
|
|
|
delete this;
|
|
|
|
};
|
2020-07-30 18:05:57 +00:00
|
|
|
auto deleteGameBtnFunc = [this, deleteFilesAndSelf] {
|
|
|
|
mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(),
|
2021-07-07 18:03:42 +00:00
|
|
|
"THIS WILL DELETE THE GAME\n"
|
|
|
|
"FILE, ANY MEDIA FILES AND\n"
|
|
|
|
"THE GAMELIST.XML ENTRY\n"
|
|
|
|
"ARE YOU SURE?",
|
|
|
|
"YES", deleteFilesAndSelf, "NO", nullptr));
|
|
|
|
};
|
|
|
|
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "DELETE", "delete game",
|
|
|
|
deleteGameBtnFunc));
|
2020-07-30 18:05:57 +00:00
|
|
|
}
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mButtons = makeButtonGrid(mWindow, buttons);
|
2021-10-14 20:31:50 +00:00
|
|
|
mGrid.setEntry(mButtons, glm::ivec2{0, 5}, true, false, glm::ivec2{2, 1});
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
// Resize + center.
|
2021-07-07 18:03:42 +00:00
|
|
|
float width =
|
|
|
|
static_cast<float>(std::min(static_cast<int>(Renderer::getScreenHeight() * 1.05f),
|
|
|
|
static_cast<int>(Renderer::getScreenWidth() * 0.90f)));
|
2021-10-10 15:07:38 +00:00
|
|
|
|
|
|
|
// Set height explicitly to ten rows for the component list.
|
|
|
|
float height = mList->getRowHeight(0) * 10.0f + mTitle->getSize().y + mSubtitle->getSize().y +
|
|
|
|
mButtons->getSize().y;
|
|
|
|
|
|
|
|
setSize(width, height);
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void GuiMetaDataEd::onSizeChanged()
|
|
|
|
{
|
2021-08-16 16:25:01 +00:00
|
|
|
const float titleSubtitleSpacing = mSize.y * 0.03f;
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2021-10-10 16:15:37 +00:00
|
|
|
mGrid.setRowHeightPerc(0, TITLE_HEIGHT / mSize.y / 2.0f);
|
|
|
|
mGrid.setRowHeightPerc(1, TITLE_HEIGHT / mSize.y / 2.0f);
|
|
|
|
mGrid.setRowHeightPerc(2, titleSubtitleSpacing / mSize.y);
|
|
|
|
mGrid.setRowHeightPerc(3, (titleSubtitleSpacing * 1.2f) / mSize.y);
|
|
|
|
mGrid.setRowHeightPerc(4, ((mList->getRowHeight(0) * 10.0f) + 2.0f) / mSize.y);
|
|
|
|
|
2021-10-14 20:31:50 +00:00
|
|
|
mGrid.setColWidthPerc(1, 0.055f);
|
2021-01-29 17:02:04 +00:00
|
|
|
|
2021-10-10 15:07:38 +00:00
|
|
|
mGrid.setSize(mSize);
|
|
|
|
mBackground.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f});
|
|
|
|
|
|
|
|
setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f,
|
|
|
|
(Renderer::getScreenHeight() - mSize.y) / 2.0f);
|
2021-10-15 18:58:40 +00:00
|
|
|
|
|
|
|
// Add some extra margins to the file/folder name.
|
|
|
|
const float newSizeX = mSize.x * 0.96f;
|
|
|
|
mSubtitle->setSize(newSizeX, mSubtitle->getSize().y);
|
|
|
|
mSubtitle->setPosition((mSize.x - newSizeX) / 2.0f, mSubtitle->getPosition().y);
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void GuiMetaDataEd::save()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
// Remove game from index.
|
|
|
|
mScraperParams.system->getIndex()->removeFromIndex(mScraperParams.game);
|
2017-03-18 17:54:39 +00:00
|
|
|
|
2020-07-26 20:19:29 +00:00
|
|
|
// We need this to handle the special situation where the user sets a game to hidden while
|
|
|
|
// ShowHiddenGames is set to false, meaning it will immediately disappear from the gamelist.
|
2022-01-15 10:50:51 +00:00
|
|
|
bool showHiddenGames{Settings::getInstance()->getBool("ShowHiddenGames")};
|
|
|
|
bool hideGameWhileHidden{false};
|
|
|
|
bool setGameAsCounted{false};
|
|
|
|
int offset{0};
|
2020-07-26 20:19:29 +00:00
|
|
|
|
2021-11-17 16:35:34 +00:00
|
|
|
for (unsigned int i = 0; i < mEditors.size(); ++i) {
|
2022-01-15 10:50:51 +00:00
|
|
|
// The offset is needed to make the editor and metadata fields match up if we're
|
|
|
|
// skipping the custom collections sortname field (which we do if not editing the
|
|
|
|
// game from within a custom collection gamelist).
|
2022-01-15 12:02:45 +00:00
|
|
|
if (mMetaDataDecl.at(i).key == "collectionsortname" && !mIsCustomCollection)
|
2022-01-15 10:50:51 +00:00
|
|
|
offset = 1;
|
|
|
|
|
|
|
|
if (mMetaDataDecl.at(i + offset).isStatistic)
|
2020-06-21 12:25:28 +00:00
|
|
|
continue;
|
2020-05-19 15:52:11 +00:00
|
|
|
|
2022-01-15 12:02:45 +00:00
|
|
|
const std::string& key{mMetaDataDecl.at(i + offset).key};
|
|
|
|
|
|
|
|
if (key == "altemulator" && mInvalidEmulatorEntry == true)
|
2021-09-04 09:21:55 +00:00
|
|
|
continue;
|
|
|
|
|
2022-01-15 12:02:45 +00:00
|
|
|
if (key == "controller" && mEditors.at(i)->getValue() != "") {
|
2021-10-23 18:28:07 +00:00
|
|
|
std::string shortName = BadgeComponent::getShortName(mEditors.at(i)->getValue());
|
2021-10-23 17:08:32 +00:00
|
|
|
if (shortName != "unknown")
|
2022-01-15 12:02:45 +00:00
|
|
|
mMetaData->set(key, shortName);
|
2021-10-23 17:08:32 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-01-15 12:02:45 +00:00
|
|
|
if (!showHiddenGames && key == "hidden" &&
|
2021-07-07 18:03:42 +00:00
|
|
|
mEditors.at(i)->getValue() != mMetaData->get("hidden"))
|
2020-07-26 20:19:29 +00:00
|
|
|
hideGameWhileHidden = true;
|
|
|
|
|
2020-12-31 20:54:32 +00:00
|
|
|
// Check whether the flag to count the entry as a game was set to enabled.
|
2022-01-15 12:02:45 +00:00
|
|
|
if (key == "nogamecount" && mEditors.at(i)->getValue() != mMetaData->get("nogamecount") &&
|
2021-07-07 18:03:42 +00:00
|
|
|
mMetaData->get("nogamecount") == "true") {
|
2020-12-31 20:54:32 +00:00
|
|
|
setGameAsCounted = true;
|
|
|
|
}
|
|
|
|
|
2022-01-15 12:02:45 +00:00
|
|
|
mMetaData->set(key, mEditors.at(i)->getValue());
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-11-08 17:33:28 +00:00
|
|
|
// If hidden games are not shown and the hide flag was set for the entry, then write the
|
2020-07-26 20:19:29 +00:00
|
|
|
// metadata immediately regardless of the SaveGamelistsMode setting. Otherwise the file
|
2021-10-10 15:07:38 +00:00
|
|
|
// will never be written as the game will be filtered from the gamelist. This solution is
|
|
|
|
// not really good as the gamelist will be written twice, but it's a very special and
|
|
|
|
// hopefully rare situation.
|
2020-07-26 20:19:29 +00:00
|
|
|
if (hideGameWhileHidden)
|
2022-01-15 13:12:56 +00:00
|
|
|
GamelistFileParser::updateGamelist(mScraperParams.system);
|
2020-07-26 20:19:29 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Enter game in index.
|
|
|
|
mScraperParams.system->getIndex()->addToIndex(mScraperParams.game);
|
2017-03-18 17:54:39 +00:00
|
|
|
|
2020-09-27 09:19:55 +00:00
|
|
|
// If it's a folder that has been updated, we need to manually sort the gamelist
|
2020-12-23 17:06:30 +00:00
|
|
|
// as CollectionSystemsManager ignores folders.
|
2020-09-27 09:19:55 +00:00
|
|
|
if (mScraperParams.game->getType() == FOLDER)
|
|
|
|
mScraperParams.system->sortSystem(false);
|
|
|
|
|
2021-12-02 18:28:10 +00:00
|
|
|
if (mSavedCallback && !mSavedMediaAndAborted)
|
2020-06-21 12:25:28 +00:00
|
|
|
mSavedCallback();
|
2017-06-12 16:38:59 +00:00
|
|
|
|
2021-02-04 22:58:27 +00:00
|
|
|
if (hideGameWhileHidden) {
|
2021-02-05 16:10:47 +00:00
|
|
|
std::vector<FileData*> hideGames;
|
|
|
|
// If a folder was hidden there may be children inside that we also need to hide.
|
|
|
|
if (mScraperParams.game->getType() == FOLDER) {
|
|
|
|
for (FileData* child : mScraperParams.game->getChildrenRecursive()) {
|
|
|
|
if (!child->getHidden())
|
|
|
|
child->metadata.set("hidden", "true");
|
2021-07-07 18:03:42 +00:00
|
|
|
hideGames.push_back(child);
|
2021-02-05 16:10:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
hideGames.push_back(mScraperParams.game);
|
|
|
|
}
|
|
|
|
for (FileData* hideGame : hideGames) {
|
|
|
|
if (hideGame->getType() == GAME) {
|
|
|
|
// Update disabled auto collections when hiding a game, as otherwise these could
|
|
|
|
// get invalid gamelist cursor positions. A cursor pointing to a removed game
|
|
|
|
// would crash the application upon enabling the collections.
|
2022-01-04 20:21:26 +00:00
|
|
|
CollectionSystemsManager::getInstance()->refreshCollectionSystems(hideGame, true);
|
2021-02-05 16:10:47 +00:00
|
|
|
// Remove the game from the index of all systems.
|
|
|
|
for (SystemData* sys : SystemData::sSystemVector) {
|
|
|
|
std::vector<FileData*> children;
|
|
|
|
for (FileData* child : sys->getRootFolder()->getChildrenRecursive())
|
|
|
|
children.push_back(child->getSourceFileData());
|
|
|
|
if (std::find(children.begin(), children.end(), hideGame) != children.end()) {
|
|
|
|
sys->getIndex()->removeFromIndex(hideGame);
|
|
|
|
// Reload the gamelist as well as the view style may need to change.
|
2022-01-15 12:38:09 +00:00
|
|
|
ViewController::getInstance()->reloadGamelistView(sys);
|
2021-02-05 16:10:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Update all collections where the game is present.
|
2022-01-04 20:21:26 +00:00
|
|
|
CollectionSystemsManager::getInstance()->refreshCollectionSystems(mScraperParams.game);
|
2021-02-04 22:58:27 +00:00
|
|
|
}
|
|
|
|
|
2020-12-31 20:54:32 +00:00
|
|
|
// If game counting was re-enabled for the game, then reactivate it in any custom collections
|
|
|
|
// where it may exist.
|
|
|
|
if (setGameAsCounted)
|
2022-01-04 20:21:26 +00:00
|
|
|
CollectionSystemsManager::getInstance()->reactivateCustomCollectionEntry(
|
|
|
|
mScraperParams.game);
|
2020-12-31 20:54:32 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
mScraperParams.system->onMetaDataSavePoint();
|
2020-09-27 09:19:55 +00:00
|
|
|
|
2020-11-08 17:33:28 +00:00
|
|
|
// If hidden games are not shown and the hide flag was set for the entry, we also need
|
|
|
|
// to re-sort the gamelist as we may need to remove the parent folder if all the entries
|
|
|
|
// within this folder are now hidden.
|
|
|
|
if (hideGameWhileHidden)
|
|
|
|
mScraperParams.system->sortSystem(true);
|
|
|
|
|
2020-09-13 17:20:30 +00:00
|
|
|
// Make sure that the cached background is updated to reflect any possible visible
|
|
|
|
// changes to the gamelist (e.g. the game name).
|
|
|
|
mWindow->invalidateCachedBackground();
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void GuiMetaDataEd::fetch()
|
|
|
|
{
|
2021-07-07 18:03:42 +00:00
|
|
|
GuiGameScraper* scr = new GuiGameScraper(
|
2021-12-02 16:34:30 +00:00
|
|
|
mWindow, mScraperParams, std::bind(&GuiMetaDataEd::fetchDone, this, std::placeholders::_1),
|
|
|
|
mSavedMediaAndAborted);
|
2020-06-21 12:25:28 +00:00
|
|
|
mWindow->pushGui(scr);
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void GuiMetaDataEd::fetchDone(const ScraperSearchResult& result)
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
// Clone the mMetaData object.
|
2022-01-15 12:02:45 +00:00
|
|
|
MetaDataList* metadata{new MetaDataList(*mMetaData)};
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2020-08-05 20:38:44 +00:00
|
|
|
mMediaFilesUpdated = result.savedNewMedia;
|
2022-01-15 12:02:45 +00:00
|
|
|
int offset{0};
|
2020-07-13 18:10:09 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// 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.
|
2021-11-17 16:35:34 +00:00
|
|
|
for (unsigned int i = 0; i < mEditors.size(); ++i) {
|
2022-01-15 12:02:45 +00:00
|
|
|
if (mMetaDataDecl.at(i).key == "collectionsortname" && !mIsCustomCollection)
|
|
|
|
offset = 1;
|
|
|
|
|
|
|
|
const std::string& key{mMetaDataDecl.at(i + offset).key};
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (metadata->get(key) != mEditors[i]->getValue())
|
|
|
|
metadata->set(key, mEditors[i]->getValue());
|
|
|
|
}
|
|
|
|
|
2021-01-26 16:40:37 +00:00
|
|
|
GuiScraperSearch::saveMetadata(result, *metadata, mScraperParams.game);
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2022-01-15 12:02:45 +00:00
|
|
|
offset = 0;
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Update the list with the scraped metadata values.
|
2021-11-17 16:35:34 +00:00
|
|
|
for (unsigned int i = 0; i < mEditors.size(); ++i) {
|
2022-01-15 12:02:45 +00:00
|
|
|
if (mMetaDataDecl.at(i).key == "collectionsortname" && !mIsCustomCollection)
|
|
|
|
offset = 1;
|
|
|
|
|
|
|
|
const std::string& key{mMetaDataDecl.at(i + offset).key};
|
|
|
|
|
2021-10-27 17:23:57 +00:00
|
|
|
if (key == "controller" && metadata->get(key) != "") {
|
|
|
|
std::string displayName = BadgeComponent::getDisplayName(metadata->get(key));
|
|
|
|
if (displayName != "unknown")
|
|
|
|
metadata->set(key, displayName);
|
|
|
|
}
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mEditors.at(i)->getValue() != metadata->get(key)) {
|
2020-07-15 15:44:27 +00:00
|
|
|
if (key == "rating")
|
|
|
|
mEditors.at(i)->setOriginalColor(ICONCOLOR_SCRAPERMARKED);
|
|
|
|
else
|
|
|
|
mEditors.at(i)->setColor(TEXTCOLOR_SCRAPERMARKED);
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
2020-08-08 09:36:43 +00:00
|
|
|
// Save all the keys that should be scraped.
|
2022-01-15 12:02:45 +00:00
|
|
|
if (mMetaDataDecl.at(i + offset).shouldScrape)
|
2020-07-15 15:44:27 +00:00
|
|
|
mEditors.at(i)->setValue(metadata->get(key));
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
delete metadata;
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
2020-06-09 18:03:31 +00:00
|
|
|
void GuiMetaDataEd::close()
|
2014-06-25 16:29:58 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
// Find out if the user made any changes.
|
2022-01-15 10:50:51 +00:00
|
|
|
bool metadataUpdated{false};
|
|
|
|
int offset{0};
|
|
|
|
|
2021-11-17 16:35:34 +00:00
|
|
|
for (unsigned int i = 0; i < mEditors.size(); ++i) {
|
2022-01-15 12:02:45 +00:00
|
|
|
if (mMetaDataDecl.at(i).key == "collectionsortname" && !mIsCustomCollection)
|
2022-01-15 10:50:51 +00:00
|
|
|
offset = 1;
|
2022-01-15 12:02:45 +00:00
|
|
|
|
|
|
|
const std::string& key{mMetaDataDecl.at(i + offset).key};
|
2020-06-21 12:25:28 +00:00
|
|
|
|
2021-09-04 09:21:55 +00:00
|
|
|
if (key == "altemulator" && mInvalidEmulatorEntry == true)
|
|
|
|
continue;
|
|
|
|
|
2022-01-15 10:50:51 +00:00
|
|
|
std::string mMetaDataValue{mMetaData->get(key)};
|
|
|
|
std::string mEditorsValue{mEditors.at(i)->getValue()};
|
|
|
|
|
|
|
|
if (key == "controller" && mEditors.at(i)->getValue() != "") {
|
2021-10-23 18:28:07 +00:00
|
|
|
std::string shortName = BadgeComponent::getShortName(mEditors.at(i)->getValue());
|
2021-10-23 17:08:32 +00:00
|
|
|
if (shortName == "unknown" || mMetaDataValue == shortName)
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mMetaDataValue != mEditorsValue) {
|
2020-07-15 15:44:27 +00:00
|
|
|
metadataUpdated = true;
|
2020-06-21 12:25:28 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
std::function<void()> closeFunc;
|
2021-07-07 18:03:42 +00:00
|
|
|
closeFunc = [this] {
|
2021-12-02 16:34:30 +00:00
|
|
|
if (mMediaFilesUpdated || mSavedMediaAndAborted) {
|
2021-07-07 18:03:42 +00:00
|
|
|
// Always reload the gamelist if media files were updated, even if the user
|
2021-12-02 16:34:30 +00:00
|
|
|
// chose to not save any metadata changes or aborted the scraping. Also manually
|
|
|
|
// unload the game image and marquee, as otherwise they would not get updated
|
|
|
|
// until the user scrolls up and down the gamelist.
|
2021-07-07 18:03:42 +00:00
|
|
|
TextureResource::manualUnload(mScraperParams.game->getImagePath(), false);
|
|
|
|
TextureResource::manualUnload(mScraperParams.game->getMarqueePath(), false);
|
2022-01-15 12:38:09 +00:00
|
|
|
ViewController::getInstance()->reloadGamelistView(mScraperParams.system);
|
2021-11-26 22:01:10 +00:00
|
|
|
// Update all collections where the game is present.
|
2022-01-04 20:21:26 +00:00
|
|
|
CollectionSystemsManager::getInstance()->refreshCollectionSystems(mScraperParams.game);
|
2021-07-07 18:03:42 +00:00
|
|
|
mWindow->invalidateCachedBackground();
|
|
|
|
}
|
2022-01-04 20:49:22 +00:00
|
|
|
ViewController::getInstance()->onPauseVideo();
|
2021-07-07 18:03:42 +00:00
|
|
|
delete this;
|
|
|
|
};
|
2020-09-27 09:41:53 +00:00
|
|
|
|
2020-07-15 15:44:27 +00:00
|
|
|
if (metadataUpdated) {
|
2020-06-21 12:25:28 +00:00
|
|
|
// Changes were made, ask if the user wants to save them.
|
2021-07-07 18:03:42 +00:00
|
|
|
mWindow->pushGui(new GuiMsgBox(
|
|
|
|
mWindow, getHelpStyle(), "SAVE CHANGES?", "YES",
|
|
|
|
[this, closeFunc] {
|
|
|
|
save();
|
|
|
|
closeFunc();
|
|
|
|
},
|
|
|
|
"NO", closeFunc));
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
|
|
|
else {
|
2020-07-13 18:10:09 +00:00
|
|
|
// Always save if the media files have been changed (i.e. newly scraped images).
|
|
|
|
if (mMediaFilesUpdated)
|
|
|
|
save();
|
2020-06-21 12:25:28 +00:00
|
|
|
closeFunc();
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool GuiMetaDataEd::input(InputConfig* config, Input input)
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
if (GuiComponent::input(config, input))
|
|
|
|
return true;
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (input.value != 0 && (config->isMappedTo("b", input))) {
|
|
|
|
close();
|
|
|
|
return true;
|
|
|
|
}
|
2014-06-25 16:29:58 +00:00
|
|
|
|
2020-11-08 12:24:34 +00:00
|
|
|
if (input.value != 0 && (config->isMappedTo("y", input))) {
|
|
|
|
fetch();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return false;
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<HelpPrompt> GuiMetaDataEd::getHelpPrompts()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
std::vector<HelpPrompt> prompts = mGrid.getHelpPrompts();
|
2020-11-08 12:24:34 +00:00
|
|
|
prompts.push_back(HelpPrompt("y", "scrape"));
|
2020-06-21 12:25:28 +00:00
|
|
|
prompts.push_back(HelpPrompt("b", "back"));
|
|
|
|
return prompts;
|
2014-06-25 16:29:58 +00:00
|
|
|
}
|
2020-06-07 18:09:02 +00:00
|
|
|
|
|
|
|
HelpStyle GuiMetaDataEd::getHelpStyle()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
HelpStyle style = HelpStyle();
|
2022-01-04 20:49:22 +00:00
|
|
|
style.applyTheme(ViewController::getInstance()->getState().getSystem()->getTheme(), "system");
|
2020-06-21 12:25:28 +00:00
|
|
|
return style;
|
2020-06-07 18:09:02 +00:00
|
|
|
}
|