From 1d2b9f113be819a7131054bfcae6b81ab1a226a5 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 15 Aug 2020 10:12:19 +0200 Subject: [PATCH] Added the ability to change the ROM directory if no game files were found on startup. --- INSTALL.md | 12 +-- USERGUIDE.md | 20 +++-- .../src/guis/GuiGeneralScreensaverOptions.cpp | 2 +- es-app/src/main.cpp | 90 ++++++++++++++----- es-core/src/guis/GuiComplexTextEditPopup.cpp | 42 +++++---- es-core/src/guis/GuiComplexTextEditPopup.h | 8 +- es-core/src/guis/GuiMsgBox.cpp | 24 +++-- es-core/src/guis/GuiMsgBox.h | 4 +- 8 files changed, 144 insertions(+), 58 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index c57dba215..99792c7bb 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -501,9 +501,9 @@ When ES is first run, a configuration file will be created as `~/.emulationstati This file contains all the settings supported by ES, at their default values. Normally you shouldn't need to modify this file manually, instead you should be able to use the menu inside ES to update all the necessary settings. -The exception would be the ROMDirectory setting as ES won't start if no ROM files are found. +For the ROM directory, you can either change it manually in es_settings.cfg, or use the dialog that is shown on application startup to change the path to your liking. -**Setting the ROM directory:** +**Setting the ROM directory in es_settings.cfg:** By default, ES looks in `~/ROMs` for the ROM files, where they are expected to be grouped into directories corresponding to the game systems, for example: @@ -521,12 +521,14 @@ Here's an example: Keep in mind though that you still need to group the ROMs into directories corresponding to the platform names in es_systems.cfg. -There is also support to add the variable %ESPATH% to the ROM directory setting, this will expand to the path where the ES executable is started from. This is useful for a portable emulator installation, for example on a USB memory stick. +There is also support to add the variable %ESPATH% to the ROM directory setting, this will expand to the path where the ES executable is started from. You would normally not need this, but the option is there, should you require it for some reason. Here is such an example: `` +Note that this complete configuration step can normally be skipped as you're presented with a dialog to change the ROM directory upon application startup if no game files are found. + **Keep in mind that you have to set up your emulator separately from EmulationStation!** **~/.emulationstation/es_input.cfg:** @@ -568,7 +570,7 @@ You can use `--help` or `-h` to view a list of command line options, as shown he --fullscreen-borderless Borderless fullscreen mode (always on top) --vsync [1/on or 0/off] Turn vsync on or off (default is on) --max-vram [size] Max VRAM to use (in mebibytes) before swapping ---gpu-statistics Draw framerate and VRAM usage overlay +--gpu-statistics Display framerate and VRAM usage overlay --force-full Force the UI mode to Full --force-kid Force the UI mode to Kid --force-kiosk Force the UI mode to Kiosk @@ -592,7 +594,7 @@ You can use `--help` or `-h` to view a list of command line options, as shown he --debug Print debug information --vsync [1/on or 0/off] Turn vsync on or off (default is on) --max-vram [size] Max VRAM to use (in mebibytes) before swapping ---gpu-statistics Draw framerate and VRAM usage overlay +--gpu-statistics Display framerate and VRAM usage overlay --force-full Force the UI mode to Full --force-kid Force the UI mode to Kid --force-kiosk Force the UI mode to Kiosk diff --git a/USERGUIDE.md b/USERGUIDE.md index db6d26873..8f031830e 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -26,7 +26,7 @@ A settings file, **es_settings.cfg** will be generated with all the default sett There's a log file in the home directory as well named **es_log.txt**, please refer to this in case of any errors as it should provide information on what went wrong. -After ES finds at least one game file, it will populate that game system and the application will start. If there are no game files, an error messsage will be shown, explaining that you need to install your game files into your ROM directory. Please refer to the game installation procedure below in this document. +After ES finds at least one game file, it will populate that game system and the application will start. If there are no game files, an error messsage will be shown, explaining that you need to install your game files into your ROM directory. You will also be given a choice to change the ROM directory if you don't want to use the default path. Please refer to the game installation procedure below in this document for more information regarding this. ## Input device configuration @@ -53,7 +53,9 @@ The gamelist view is where you browse and start your games, and it's where you w Upon startup with the default settings, ES is set to the gamelist view style to **Automatic**. In this mode the application will look for any game media files (videos and images) and set the view style accordingly. If at least one image is found for any game, the view style **Detailed** will be shown, and if at least one video file is found, the view style **Video** will be selected (superceding the Detailed style). If no game media files are found for a system, the simple **Basic** view will be selected. Note that this automatic selection is applied per game system. -It's possible to manually set a specific gamelist view style in the UI settings entry of the main menu, but this is applied globally regardless of what media files are available per game system. +Also note that the Video view style requires that the theme supports it. If not, the Detailed style will be selected instead. (The default theme rbsimple-DE supports both of these view styles). + +It's possible to manually set a specific gamelist view style in the UI settings entry of the main menu, but this is applied globally regardless of what media files are available per game system. The manual setting also overrides the theme-supported view styles which has the potential of making ES very ugly indeed. In additions to the styles just described, there is a **Grid** view style as well, but as of version 1.0.0 this is very limited and not recommended. Future versions of EmulationStation may update this style to a more useful state. @@ -116,7 +118,7 @@ For some systems though, a more elaborate setup is required, and we will attempt ### Single gamefile installation -Let's start with the simple scenario of a single game file per platform, which is the case for the majority of systems. In this example we're setting up ES to play Nintendo Entertainment System games. +Let's start with the simple scenario of a single ROM game file per platform, which is the case for the majority of systems. In this example we're setting up ES to play Nintendo Entertainment System games. The supported file extensions are listed in [es_systems.cfg_unix](resources/templates/es_systems.cfg_unix) and [es_systems.cfg_windows](resources/templates/es_systems.cfg_windows). @@ -138,7 +140,9 @@ It's required that the ROM files are in one of the supported file extensions, or It's highly recommended to use filenames that are corresponding to the full name of the game, or otherwise you will need to manually feed the scraper the game name when scraping which is very tedious. -The default ROM directory folder is ~/ROMs. On Unix this defaults to /home/\/.emulationstation/ and on Windows it defaults to C:\Users\\\\.emulationstation\ +The default game directory folder is ~/ROMs. On Unix this defaults to /home/\/.emulationstation/ and on Windows it defaults to C:\Users\\\\.emulationstation\ + +If ES can't find any game files during startup, an error message will be displayed with the option to change the ROM directory path. Assuming the default ROM directory is used, we need to create a directory corresponding to the \ tag in es_systems.cfg, in this example it's **nes**. @@ -704,9 +708,9 @@ If enabled, only ROMs that have metadata saved to the gamelist.xml files will be Using this option, you can locate game images in the ROM directory tree. The images are searched inside the directory "\/\/images/" and the filenames must be the same as the ROM names, followed by a dash and the image type. For example "~/ROMs/nes/images/Contra-screenshot.jpg" and "~/ROMs/nes/images/Contra-marquee.jpg". This option is mostly intended for legacy purposes, if you have an existing game collection with this media setup that you would like to open in ES. The scraper will never save files to this directory structure and will instead use the standard media directory logic. It's recommended to keep this option disabled unless you really need it since it slows down the application somewhat. -**Draw GPU statistics overlay** +**Display GPU statistics overlay** -Draws the framerate and VRAM statistics as an overlay. You normally never need to use this. +Displays the framerate and VRAM statistics as an overlay. You normally never need to use this. **Note:** As of version 1.0.0 the VRAM usage statistics is not accurate; this issue will be addressed in future ES versions. **Show "reboot system" menu entry** @@ -977,7 +981,7 @@ You can use **--help** or **-h** to view a list of command line options, as show --fullscreen-borderless Borderless fullscreen mode (always on top) --vsync [1/on or 0/off] Turn vsync on or off (default is on) --max-vram [size] Max VRAM to use (in mebibytes) before swapping ---gpu-statistics Draw framerate and VRAM usage overlay +--gpu-statistics Display framerate and VRAM usage overlay --force-full Force the UI mode to Full --force-kid Force the UI mode to Kid --force-kiosk Force the UI mode to Kiosk @@ -1001,7 +1005,7 @@ You can use **--help** or **-h** to view a list of command line options, as show --debug Print debug information --vsync [1/on or 0/off] Turn vsync on or off (default is on) --max-vram [size] Max VRAM to use (in mebibytes) before swapping ---gpu-statistics Draw framerate and VRAM usage overlay +--gpu-statistics Display framerate and VRAM usage overlay --force-full Force the UI mode to Full --force-kid Force the UI mode to Kid --force-kiosk Force the UI mode to Kiosk diff --git a/es-app/src/guis/GuiGeneralScreensaverOptions.cpp b/es-app/src/guis/GuiGeneralScreensaverOptions.cpp index 72868cbaa..ae227649e 100644 --- a/es-app/src/guis/GuiGeneralScreensaverOptions.cpp +++ b/es-app/src/guis/GuiGeneralScreensaverOptions.cpp @@ -56,7 +56,7 @@ GuiGeneralScreensaverOptions::GuiGeneralScreensaverOptions(Window* window, const mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), "THE \"VIDEO\" SCREENSAVER SHOWS\nVIDEOS FROM YOUR GAMELISTS.\n\nIF YOU DO NOT " "HAVE ANY VIDEOS, THE\nSCREENSAVER WILL DEFAULT TO \"BLACK\"", - "OK", [] { return; })); + "OK", [] { return; }, "", nullptr, "", nullptr, true)); } Settings::getInstance()->setString("ScreenSaverBehavior", screensaver_behavior->getSelected()); diff --git a/es-app/src/main.cpp b/es-app/src/main.cpp index 7a12e5a28..04eb58635 100644 --- a/es-app/src/main.cpp +++ b/es-app/src/main.cpp @@ -18,6 +18,7 @@ #include "guis/GuiDetectDevice.h" #include "guis/GuiMsgBox.h" +#include "guis/GuiComplexTextEditPopup.h" #include "utils/FileSystemUtil.h" #include "utils/StringUtil.h" #include "views/ViewController.h" @@ -53,6 +54,12 @@ bool forceInputConfig = false; +enum returnCode { + NO_ERROR, + NO_SYSTEMS_FILE, + NO_ROMS +}; + #ifdef _WIN64 enum eConsoleType { NO_CONSOLE, @@ -354,7 +361,7 @@ bool verifyHomeFolderExists() // Returns NO_ERRORS if everything is OK. // Otherwise returns either NO_SYSTEMS_FILE or NO_ROMS. -bool loadSystemConfigFile(std::string& errorMsg) +returnCode loadSystemConfigFile(std::string& errorMsg) { if (!SystemData::loadConfig()) { LOG(LogError) << "Could not parse systems configuration file."; @@ -364,19 +371,18 @@ bool loadSystemConfigFile(std::string& errorMsg) "BUT THIS FAILED. HAS EMULATIONSTATION BEEN PROPERLY\n" "INSTALLED AND DO YOU HAVE WRITE PERMISSIONS TO \n" "YOUR HOME DIRECTORY?"; - return true; + return NO_SYSTEMS_FILE; } if (SystemData::sSystemVector.size() == 0) { LOG(LogError) << "No systems found, does at least one system have a game present? " "(Check that the file extensions are supported.)"; errorMsg = "THE SYSTEMS CONFIGURATION FILE EXISTS, BUT NO\n" - "GAME FILES WERE FOUND. PLEASE MAKE SURE THAT\n" - "THE \"ROMDIRECTORY\" SETTING IN ES_SETTINGS.CFG\n" - "IS POINTING TO YOUR ROM DIRECTORY AND THAT YOUR\n" - "GAME FILES ARE USING SUPPORTED FILE EXTENSIONS.\n" - "THE GAME SYSTEMS SUBDIRECTORIES ALSO NEED TO\n" - "MATCH THE PLATFORM TAGS IN ES_SYSTEMS.CFG.\n" + "GAME FILES WERE FOUND. EITHER PLACE YOUR GAMES\n" + "IN THE CURRENTLY CONFIGURED ROM DIRECTORY OR\n" + "CHANGE IT USING THE BUTTON BELOW. MAKE SURE\n" + "THAT YOUR FILE EXTENSIONS AND SYSTEMS DIRECTORY\n" + "NAMES ARE SUPPORTED BY EMULATIONSTATION-DE.\n" "THIS IS THE CURRENTLY CONFIGURED ROM DIRECTORY:\n"; #ifdef _WIN64 errorMsg += Utils::String::replace(FileData::getROMDirectory(), "/", "\\"); @@ -384,10 +390,10 @@ bool loadSystemConfigFile(std::string& errorMsg) errorMsg += FileData::getROMDirectory(); #endif - return true; + return NO_ROMS; } - return false; + return NO_ERROR; } // Called on exit, assuming we get far enough to have the log initialized. @@ -483,8 +489,9 @@ int main(int argc, char* argv[]) } std::string errorMsg; + returnCode returnCodeValue = loadSystemConfigFile(errorMsg); - if (loadSystemConfigFile(errorMsg)) { + if (returnCodeValue) { // Something went terribly wrong. if (errorMsg == "") { LOG(LogError) << "Unknown error occured while parsing system config file."; @@ -498,21 +505,64 @@ int main(int argc, char* argv[]) helpStyle.applyTheme(ViewController::get()-> getState().getSystem()->getTheme(), "system"); - // We can't handle es_systems.cfg file problems inside ES itself, - // so display the error message and then quit. - window.pushGui(new GuiMsgBox(&window, helpStyle, - errorMsg.c_str(), + // If there was an issue with installing the es_systems.cfg file from the + // template directory, then display an error message and let the user quit. + // If there are no game files found, give the option to the user to quit or + // to configure a different ROM directory. The application will need to be + // restarted though, to activate any new ROM directory setting. + if (returnCodeValue == NO_SYSTEMS_FILE) { + window.pushGui(new GuiMsgBox(&window, helpStyle, + errorMsg.c_str(), + "QUIT", [] { + SDL_Event* quit = new SDL_Event(); + quit->type = SDL_QUIT; + SDL_PushEvent(quit); + }, "", nullptr, "", nullptr, true)); + } + else if (returnCodeValue == NO_ROMS) { + auto updateVal = [](const std::string& newROMDirectory) { + Settings::getInstance()->setString("ROMDirectory", newROMDirectory); + Settings::getInstance()->saveFile(); + SDL_Event* quit = new SDL_Event(); + quit->type = SDL_QUIT; + SDL_PushEvent(quit); + }; + + window.pushGui(new GuiMsgBox(&window, helpStyle, errorMsg.c_str(), + "CHANGE ROM DIRECTORY", [&window, &helpStyle, updateVal] { + std::string currentROMDirectory; + #ifdef _WIN64 + currentROMDirectory = + Utils::String::replace(FileData::getROMDirectory(), "/", "\\"); + #else + currentROMDirectory = FileData::getROMDirectory(); + #endif + + window.pushGui(new GuiComplexTextEditPopup( + &window, + helpStyle, + "ENTER ROM DIRECTORY", + "Currently configured directory:", + currentROMDirectory, + currentROMDirectory, + updateVal, + false, + "SAVE AND QUIT", + "SAVE CHANGES?", + "LOAD CURRENT", + "LOAD CURRENTLY CONFIGURED VALUE", + "CLEAR", + "CLEAR (LEAVE BLANK TO RESET TO DEFAULT DIRECTORY)", + true)); + }, "QUIT", [] { SDL_Event* quit = new SDL_Event(); quit->type = SDL_QUIT; SDL_PushEvent(quit); - })); + }, "", nullptr, true)); + } } - std::vector prompts; - prompts.push_back(HelpPrompt("a", "Quit")); - window.setHelpPrompts(prompts, HelpStyle()); - // Dont generate joystick events while we're loading. // (Hopefully fixes "automatically started emulator" bug.) SDL_JoystickEventState(SDL_DISABLE); diff --git a/es-core/src/guis/GuiComplexTextEditPopup.cpp b/es-core/src/guis/GuiComplexTextEditPopup.cpp index 78d4b3c20..41d4bfc0a 100644 --- a/es-core/src/guis/GuiComplexTextEditPopup.cpp +++ b/es-core/src/guis/GuiComplexTextEditPopup.cpp @@ -24,7 +24,12 @@ GuiComplexTextEditPopup::GuiComplexTextEditPopup( const std::function& okCallback, bool multiLine, const char* acceptBtnText, - const char* saveConfirmationText) + const char* saveConfirmationText, + const char* loadBtnText, + const char* loadBtnHelpText, + const char* clearBtnText, + const char* clearBtnHelpText, + bool hideCancelButton) : GuiComponent(window), mHelpStyle(helpstyle), mBackground(window, ":/graphics/frame.png"), @@ -32,7 +37,8 @@ GuiComplexTextEditPopup::GuiComplexTextEditPopup( mMultiLine(multiLine), mInitValue(initValue), mOkCallback(okCallback), - mSaveConfirmationText(saveConfirmationText) + mSaveConfirmationText(saveConfirmationText), + mHideCancelButton(hideCancelButton) { addChild(&mBackground); addChild(&mGrid); @@ -53,13 +59,14 @@ GuiComplexTextEditPopup::GuiComplexTextEditPopup( std::vector< std::shared_ptr > buttons; buttons.push_back(std::make_shared(mWindow, acceptBtnText, acceptBtnText, [this, okCallback] { okCallback(mText->getValue()); delete this; })); - buttons.push_back(std::make_shared(mWindow, "LOAD", "load default", + buttons.push_back(std::make_shared(mWindow, loadBtnText, loadBtnHelpText, [this, infoString2] { mText->setValue(infoString2); mText->setCursor(infoString2.size()); })); - buttons.push_back(std::make_shared(mWindow, "CLEAR", "clear", + buttons.push_back(std::make_shared(mWindow, clearBtnText, clearBtnHelpText, [this] { mText->setValue(""); })); - buttons.push_back(std::make_shared(mWindow, "CANCEL", "discard changes", - [this] { delete this; })); + if (!mHideCancelButton) + buttons.push_back(std::make_shared(mWindow, "CANCEL", "discard changes", + [this] { delete this; })); mButtonGrid = makeButtonGrid(mWindow, buttons); @@ -101,16 +108,18 @@ bool GuiComplexTextEditPopup::input(InputConfig* config, Input input) if (GuiComponent::input(config, input)) return true; + if (!mHideCancelButton) { // Pressing back when not text editing closes us. - if (config->isMappedTo("b", input) && input.value) { - if (mText->getValue() != mInitValue) { - // Changes were made, ask if the user wants to save them. - mWindow->pushGui(new GuiMsgBox(mWindow, mHelpStyle, mSaveConfirmationText, "YES", - [this] { this->mOkCallback(mText->getValue()); delete this; return true; }, - "NO", [this] { delete this; return false; })); - } - else { - delete this; + if (config->isMappedTo("b", input) && input.value) { + if (mText->getValue() != mInitValue) { + // Changes were made, ask if the user wants to save them. + mWindow->pushGui(new GuiMsgBox(mWindow, mHelpStyle, mSaveConfirmationText, "YES", + [this] { this->mOkCallback(mText->getValue()); delete this; return true; }, + "NO", [this] { delete this; return false; })); + } + else { + delete this; + } } } return false; @@ -119,6 +128,7 @@ bool GuiComplexTextEditPopup::input(InputConfig* config, Input input) std::vector GuiComplexTextEditPopup::getHelpPrompts() { std::vector prompts = mGrid.getHelpPrompts(); - prompts.push_back(HelpPrompt("b", "back")); + if (!mHideCancelButton) + prompts.push_back(HelpPrompt("b", "back")); return prompts; } diff --git a/es-core/src/guis/GuiComplexTextEditPopup.h b/es-core/src/guis/GuiComplexTextEditPopup.h index 61480b1d8..09ef600b2 100644 --- a/es-core/src/guis/GuiComplexTextEditPopup.h +++ b/es-core/src/guis/GuiComplexTextEditPopup.h @@ -30,7 +30,12 @@ public: const std::function& okCallback, bool multiLine, const char* acceptBtnText = "OK", - const char* saveConfirmationText = "SAVE CHANGES?"); + const char* saveConfirmationText = "SAVE CHANGES?", + const char* loadBtnText = "LOAD", + const char* loadBtnHelpText = "load default", + const char* clearBtnText = "CLEAR", + const char* clearBtnHelpText = "clear", + bool hideCancelButton = false); bool input(InputConfig* config, Input input) override; void onSizeChanged() override; @@ -50,6 +55,7 @@ private: HelpStyle mHelpStyle; bool mMultiLine; + bool mHideCancelButton; std::string mInitValue; std::function mOkCallback; std::string mSaveConfirmationText; diff --git a/es-core/src/guis/GuiMsgBox.cpp b/es-core/src/guis/GuiMsgBox.cpp index 104f8965b..54bbc09fb 100644 --- a/es-core/src/guis/GuiMsgBox.cpp +++ b/es-core/src/guis/GuiMsgBox.cpp @@ -15,11 +15,13 @@ GuiMsgBox::GuiMsgBox(Window* window, const HelpStyle& helpstyle, const std::string& text, const std::string& name1, const std::function& func1, const std::string& name2, const std::function& func2, - const std::string& name3, const std::function& func3) + const std::string& name3, const std::function& func3, + bool disableBackButton) : GuiComponent(window), mHelpStyle(helpstyle), mBackground(window, ":/graphics/frame.png"), - mGrid(window, Vector2i(1, 2)) + mGrid(window, Vector2i(1, 2)), + mDisableBackButton(disableBackButton) { float width = Renderer::getScreenWidth() * 0.6f; // Max width. float minWidth = Renderer::getScreenWidth() * 0.3f; // Minimum width. @@ -87,9 +89,11 @@ bool GuiMsgBox::input(InputConfig* config, Input input) return true; } - if (mAcceleratorFunc && config->isMappedTo("b", input) && input.value != 0) { - mAcceleratorFunc(); - return true; + if (!mDisableBackButton) { + if (mAcceleratorFunc && config->isMappedTo("b", input) && input.value != 0) { + mAcceleratorFunc(); + return true; + } } return GuiComponent::input(config, input); @@ -119,6 +123,14 @@ void GuiMsgBox::deleteMeAndCall(const std::function& func) std::vector GuiMsgBox::getHelpPrompts() { std::vector prompts = mGrid.getHelpPrompts(); - prompts.push_back(HelpPrompt("b", "Back")); + + // If there is only one button, then remove the "Choose" help symbol + // as there is no way to make a choice. + if (mButtons.size() == 1) + prompts.pop_back(); + + if (!mDisableBackButton) + prompts.push_back(HelpPrompt("b", "Back")); + return prompts; } diff --git a/es-core/src/guis/GuiMsgBox.h b/es-core/src/guis/GuiMsgBox.h index 0f43991e3..7a368b3d0 100644 --- a/es-core/src/guis/GuiMsgBox.h +++ b/es-core/src/guis/GuiMsgBox.h @@ -22,7 +22,8 @@ public: GuiMsgBox(Window* window, const HelpStyle& helpstyle, const std::string& text, const std::string& name1 = "OK", const std::function& func1 = nullptr, const std::string& name2 = "", const std::function& func2 = nullptr, - const std::string& name3 = "", const std::function& func3 = nullptr); + const std::string& name3 = "", const std::function& func3 = nullptr, + bool disableBackButton = false); bool input(InputConfig* config, Input input) override; void onSizeChanged() override; @@ -41,6 +42,7 @@ private: std::vector> mButtons; std::shared_ptr mButtonGrid; std::function mAcceleratorFunc; + bool mDisableBackButton; }; #endif // ES_CORE_GUIS_GUI_MSG_BOX_H