diff --git a/es-app/src/CollectionSystemManager.cpp b/es-app/src/CollectionSystemManager.cpp index eb8761ecb..5896a11a3 100644 --- a/es-app/src/CollectionSystemManager.cpp +++ b/es-app/src/CollectionSystemManager.cpp @@ -1,6 +1,8 @@ #include "SystemData.h" #include "Gamelist.h" #include +#include +#include #include "Util.h" #include #include @@ -11,14 +13,71 @@ #include "InputManager.h" #include #include "Settings.h" -#include "FileSorts.h" #include "pugixml/src/pugixml.hpp" #include "guis/GuiInfoPopup.h" namespace fs = boost::filesystem; +std::string myCollectionsName = "collections"; +/* Handling the getting, initialization, deinitialization, saving and deletion of + * a CollectionSystemManager Instance */ CollectionSystemManager* CollectionSystemManager::sInstance = NULL; +CollectionSystemManager::CollectionSystemManager(Window* window) : mWindow(window) +{ + CollectionSystemDecl systemDecls[] = { + //type name long name //default sort // theme folder // isCustom + { AUTO_ALL_GAMES, "all", "all games", "filename, ascending", "auto-allgames", false }, + { AUTO_LAST_PLAYED, "recent", "last played", "last played, descending", "auto-lastplayed", false }, + { AUTO_FAVORITES, "favorites", "favorites", "filename, ascending", "auto-favorites", false }, + { CUSTOM_COLLECTION, myCollectionsName, "collections", "filename, ascending", "custom-collections", true } + }; + + // create a map + std::vector tempSystemDecl = std::vector(systemDecls, systemDecls + sizeof(systemDecls) / sizeof(systemDecls[0])); + + for (std::vector::iterator it = tempSystemDecl.begin(); it != tempSystemDecl.end(); ++it ) + { + mCollectionSystemDeclsIndex[(*it).name] = (*it); + } + + // creating standard environment data + mCollectionEnvData = new SystemEnvironmentData; + mCollectionEnvData->mStartPath = ""; + std::vector exts; + mCollectionEnvData->mSearchExtensions = exts; + mCollectionEnvData->mLaunchCommand = ""; + std::vector allPlatformIds; + allPlatformIds.push_back(PlatformIds::PLATFORM_IGNORE); + mCollectionEnvData->mPlatformIds = allPlatformIds; + + std::string path = getCollectionsFolder(); + if(!fs::exists(path)) + fs::create_directory(path); + + mIsEditingCustom = false; + mEditingCollection = "Favorites"; + mEditingCollectionSystemData = NULL; + mCustomCollectionsBundle = NULL; +} + +CollectionSystemManager::~CollectionSystemManager() +{ + assert(sInstance == this); + removeCollectionsFromDisplayedSystems(); + + // iterate the map + for(std::map::iterator it = mCustomCollectionSystemsData.begin() ; it != mCustomCollectionSystemsData.end() ; it++ ) + { + if (it->second.isPopulated) + { + saveCustomCollection(it->second.system); + } + delete it->second.system; + } + sInstance = NULL; +} + CollectionSystemManager* CollectionSystemManager::get() { assert(sInstance); @@ -31,62 +90,732 @@ void CollectionSystemManager::init(Window* window) sInstance = new CollectionSystemManager(window); } -CollectionSystemManager::CollectionSystemManager(Window* window) : mWindow(window) +void CollectionSystemManager::deinit() { - CollectionSystemDecl systemDecls[] = { - //type name long name //default sort // theme folder // isCustom - { AUTO_ALL_GAMES, "all", "all games", "filename, ascending", "auto-allgames", false }, - { AUTO_LAST_PLAYED, "recent", "last played", "last played, descending", "auto-lastplayed", false }, - { AUTO_FAVORITES, "favorites", "favorites", "filename, ascending", "auto-favorites", false }, - { CUSTOM_COLLECTION, "custom", "custom", "filename, ascending", "custom-collections", true } - }; - - // create a map - std::vector tempSystemDecl = std::vector(systemDecls, systemDecls + sizeof(systemDecls) / sizeof(systemDecls[0])); - - for (std::vector::iterator it = tempSystemDecl.begin(); it != tempSystemDecl.end(); ++it ) + if (sInstance) { - mCollectionSystemDecls[(*it).name] = (*it); - //mCollectionSystemDecls.insert(std::make_pair((*it).name,(*it))); + delete sInstance; } - - // creating standard environment data - mCollectionEnvData = new SystemEnvironmentData; - mCollectionEnvData->mStartPath = ""; - std::vector exts; - mCollectionEnvData->mSearchExtensions = exts; - mCollectionEnvData->mLaunchCommand = ""; - std::vector allPlatformIds; - allPlatformIds.push_back(PlatformIds::PLATFORM_IGNORE); - mCollectionEnvData->mPlatformIds = allPlatformIds; - - // TO DO: Create custom editing help style - } -CollectionSystemManager::~CollectionSystemManager() +void CollectionSystemManager::saveCustomCollection(SystemData* sys) { - assert(sInstance == this); - sInstance = NULL; + std::string name = sys->getName(); + std::unordered_map games = sys->getRootFolder()->getChildrenByFilename(); + bool found = mCustomCollectionSystemsData.find(name) != mCustomCollectionSystemsData.end(); + if (found) { + CollectionSystemData sysData = mCustomCollectionSystemsData.at(name); + if (sysData.needsSave) + { + std::ofstream configFile; + configFile.open(getCustomCollectionConfigPath(name)); + for(std::unordered_map::iterator iter = games.begin(); iter != games.end(); ++iter) + { + std::string path = iter->first; + configFile << path << std::endl; + } + configFile.close(); + } + } + else + { + LOG(LogError) << "Couldn't find collection to save! " << name; + } } +/* Methods to load all Collections into memory, and handle enabling the active ones */ +// loads all Collection Systems +void CollectionSystemManager::loadCollectionSystems() +{ + initAutoCollectionSystems(); + CollectionSystemDecl decl = mCollectionSystemDeclsIndex[myCollectionsName]; + mCustomCollectionsBundle = createNewCollectionEntry(decl.name, decl, false); + // we will also load custom systems here + initCustomCollectionSystems(); + if(Settings::getInstance()->getString("CollectionSystemsAuto") != "" || Settings::getInstance()->getString("CollectionSystemsCustom") != "") + { + // Now see which ones are enabled + loadEnabledListFromSettings(); + // add to the main System Vector, and create Views as needed + updateSystemsList(); + } +} + +// loads settings void CollectionSystemManager::loadEnabledListFromSettings() { - // we parse the settings - std::vector selected = commaStringToVector(Settings::getInstance()->getString("CollectionSystemsAuto")); + // we parse the auto collection settings list + std::vector autoSelected = commaStringToVector(Settings::getInstance()->getString("CollectionSystemsAuto")); // iterate the map - for(std::map::iterator it = mAllCollectionSystems.begin() ; it != mAllCollectionSystems.end() ; it++ ) + for(std::map::iterator it = mAutoCollectionSystemsData.begin() ; it != mAutoCollectionSystemsData.end() ; it++ ) { - it->second.isEnabled = (std::find(selected.begin(), selected.end(), it->first) != selected.end()); + it->second.isEnabled = (std::find(autoSelected.begin(), autoSelected.end(), it->first) != autoSelected.end()); + } + + // we parse the custom collection settings list + std::vector customSelected = commaStringToVector(Settings::getInstance()->getString("CollectionSystemsCustom")); + + // iterate the map + for(std::map::iterator it = mCustomCollectionSystemsData.begin() ; it != mCustomCollectionSystemsData.end() ; it++ ) + { + it->second.isEnabled = (std::find(customSelected.begin(), customSelected.end(), it->first) != customSelected.end()); } } -void CollectionSystemManager::initAvailableSystemsList() +// updates enabled system list in System View +void CollectionSystemManager::updateSystemsList() { + // remove all Collection Systems + removeCollectionsFromDisplayedSystems(); + // add custom enabled ones + addEnabledCollectionsToDisplayedSystems(&mCustomCollectionSystemsData); + if(Settings::getInstance()->getBool("SortAllSystems")) + { + // sort custom individual systems with other systems + std::sort(SystemData::sSystemVector.begin(), SystemData::sSystemVector.end(), systemSort); + + // move RetroPie system to end, before auto collections + for(auto sysIt = SystemData::sSystemVector.begin(); sysIt != SystemData::sSystemVector.end(); ) + { + if ((*sysIt)->getName() == "retropie") + { + SystemData* retroPieSystem = (*sysIt); + sysIt = SystemData::sSystemVector.erase(sysIt); + SystemData::sSystemVector.push_back(retroPieSystem); + break; + } + else + { + sysIt++; + } + } + } + + if(mCustomCollectionsBundle->getRootFolder()->getChildren().size() > 0) + { + mCustomCollectionsBundle->getRootFolder()->sort(getSortTypeFromString(mCollectionSystemDeclsIndex[myCollectionsName].defaultSort)); + SystemData::sSystemVector.push_back(mCustomCollectionsBundle); + } + + // add auto enabled ones + addEnabledCollectionsToDisplayedSystems(&mAutoCollectionSystemsData); + + // create views for collections, before reload + for(auto sysIt = SystemData::sSystemVector.begin(); sysIt != SystemData::sSystemVector.end(); sysIt++) + { + if ((*sysIt)->isCollection()) + { + ViewController::get()->getGameListView((*sysIt)); + } + } + + // if we were editing a custom collection, and it's no longer enabled, exit edit mode + if(mIsEditingCustom && !mEditingCollectionSystemData->isEnabled) + { + exitEditMode(); + } } +/* Methods to manage collection files related to a source FileData */ +// updates all collection files related to the source file +void CollectionSystemManager::refreshCollectionSystems(FileData* file) +{ + if (!file->getSystem()->isGameSystem()) + return; + + std::map allCollections; + allCollections.insert(mAutoCollectionSystemsData.begin(), mAutoCollectionSystemsData.end()); + allCollections.insert(mCustomCollectionSystemsData.begin(), mCustomCollectionSystemsData.end()); + + for(auto sysDataIt = allCollections.begin(); sysDataIt != allCollections.end(); sysDataIt++) + { + updateCollectionSystem(file, sysDataIt->second); + } +} + +void CollectionSystemManager::updateCollectionSystem(FileData* file, CollectionSystemData sysData) +{ + if (sysData.isPopulated) + { + // collection files use the full path as key, to avoid clashes + std::string key = file->getFullPath(); + + SystemData* curSys = sysData.system; + const std::unordered_map& children = curSys->getRootFolder()->getChildrenByFilename(); + bool found = children.find(key) != children.end(); + FileData* rootFolder = curSys->getRootFolder(); + FileFilterIndex* fileIndex = curSys->getIndex(); + std::string name = curSys->getName(); + + if (found) { + // if we found it, we need to update it + FileData* collectionEntry = children.at(key); + // remove from index, so we can re-index metadata after refreshing + fileIndex->removeFromIndex(collectionEntry); + collectionEntry->refreshMetadata(); + // found and we are removing + if (name == "favorites" && file->metadata.get("favorite") == "false") { + // need to check if still marked as favorite, if not remove + ViewController::get()->getGameListView(curSys).get()->remove(collectionEntry, false); + } + else + { + // re-index with new metadata + fileIndex->addToIndex(collectionEntry); + ViewController::get()->onFileChanged(collectionEntry, FILE_METADATA_CHANGED); + } + } + else + { + // we didn't find it here - we need to check if we should add it + if (name == "recent" && file->metadata.get("playcount") > "0" && includeFileInAutoCollections(file) || + name == "favorites" && file->metadata.get("favorite") == "true") { + CollectionFileData* newGame = new CollectionFileData(file, curSys); + rootFolder->addChild(newGame); + fileIndex->addToIndex(newGame); + ViewController::get()->onFileChanged(file, FILE_METADATA_CHANGED); + ViewController::get()->getGameListView(curSys)->onFileChanged(newGame, FILE_METADATA_CHANGED); + } + } + rootFolder->sort(getSortTypeFromString(mCollectionSystemDeclsIndex[name].defaultSort)); + ViewController::get()->onFileChanged(rootFolder, FILE_SORTED); + } +} + +// deletes all collection files from collection systems related to the source file +void CollectionSystemManager::deleteCollectionFiles(FileData* file) +{ + // collection files use the full path as key, to avoid clashes + std::string key = file->getFullPath(); + // find games in collection systems + std::map allCollections; + allCollections.insert(mAutoCollectionSystemsData.begin(), mAutoCollectionSystemsData.end()); + allCollections.insert(mCustomCollectionSystemsData.begin(), mCustomCollectionSystemsData.end()); + + for(auto sysDataIt = allCollections.begin(); sysDataIt != allCollections.end(); sysDataIt++) + { + if (sysDataIt->second.isPopulated) + { + const std::unordered_map& children = (sysDataIt->second.system)->getRootFolder()->getChildrenByFilename(); + + bool found = children.find(key) != children.end(); + if (found) { + sysDataIt->second.needsSave = true; + FileData* collectionEntry = children.at(key); + ViewController::get()->getGameListView(sysDataIt->second.system).get()->remove(collectionEntry, false); + } + } + } +} + +// returns whether the current theme is compatible with Automatic or Custom Collections +bool CollectionSystemManager::isThemeGenericCollectionCompatible(bool genericCustomCollections) +{ + std::vector cfgSys = getCollectionThemeFolders(genericCustomCollections); + for(auto sysIt = cfgSys.begin(); sysIt != cfgSys.end(); sysIt++) + { + if(!themeFolderExists(*sysIt)) + return false; + } + return true; +} + +bool CollectionSystemManager::isThemeCustomCollectionCompatible(std::vector stringVector) +{ + if (isThemeGenericCollectionCompatible(true)) + return true; + + // get theme path + auto themeSets = ThemeData::getThemeSets(); + auto set = themeSets.find(Settings::getInstance()->getString("ThemeSet")); + if(set != themeSets.end()) + { + std::string defaultThemeFilePath = set->second.path.string() + "/theme.xml"; + if (fs::exists(defaultThemeFilePath)) + { + return true; + } + } + + for(auto sysIt = stringVector.begin(); sysIt != stringVector.end(); sysIt++) + { + if(!themeFolderExists(*sysIt)) + return false; + } + return true; +} + +std::string CollectionSystemManager::getValidNewCollectionName(std::string inName, int index) +{ + // filter name - [^A-Za-z0-9\[\]\(\)\s] + using namespace boost::xpressive; + std::string name; + sregex regexp = sregex::compile("[^A-Za-z0-9\\-\\[\\]\\(\\)\\s']"); + if (index == 0) + { + name = regex_replace(inName, regexp, ""); + if (name == "") + { + name = "New Collection"; + } + } + else + { + name = inName + " (" + std::to_string(index) + ")"; + } + if(name != inName) + { + LOG(LogInfo) << "Had to change name, from: " << inName << " to: " << name; + } + // get used systems in es_systems.cfg + std::vector systemsInUse = getSystemsFromConfig(); + // get folders assigned to custom collections + std::vector autoSys = getCollectionThemeFolders(false); + // get folder assigned to custom collections + std::vector customSys = getCollectionThemeFolders(true); + // get folders assigned to user collections + std::vector userSys = getUserCollectionThemeFolders(); + // add them all to the list of systems in use + systemsInUse.insert(systemsInUse.end(), autoSys.begin(), autoSys.end()); + systemsInUse.insert(systemsInUse.end(), customSys.begin(), customSys.end()); + systemsInUse.insert(systemsInUse.end(), userSys.begin(), userSys.end()); + for(auto sysIt = systemsInUse.begin(); sysIt != systemsInUse.end(); sysIt++) + { + if (*sysIt == name) + { + if(index > 0) { + name = name.substr(0, name.size()-4); + } + return getValidNewCollectionName(name, index+1); + } + } + // if it matches one of the custom collections reserved names + if (mCollectionSystemDeclsIndex.find(name) != mCollectionSystemDeclsIndex.end()) + return getValidNewCollectionName(name, index+1); + return name; +} + +void CollectionSystemManager::setEditMode(std::string collectionName) +{ + if (mCustomCollectionSystemsData.find(collectionName) == mCustomCollectionSystemsData.end()) + { + LOG(LogError) << "Tried to edit a non-existing collection: " << collectionName; + return; + } + mIsEditingCustom = true; + mEditingCollection = collectionName; + + CollectionSystemData* sysData = &(mCustomCollectionSystemsData.at(mEditingCollection)); + if (!sysData->isPopulated) + { + populateCustomCollection(sysData); + } + // if it's bundled, this needs to be the bundle system + mEditingCollectionSystemData = sysData; + + GuiInfoPopup* s = new GuiInfoPopup(mWindow, "Editing the '" + strToUpper(collectionName) + "' Collection. Add/remove games with Y.", 10000); + mWindow->setInfoPopup(s); +} + +void CollectionSystemManager::exitEditMode() +{ + GuiInfoPopup* s = new GuiInfoPopup(mWindow, "Finished editing the '" + mEditingCollection + "' Collection.", 4000); + mWindow->setInfoPopup(s); + mIsEditingCustom = false; + mEditingCollection = "Favorites"; +} + +// adds or removes a game from a specific collection +bool CollectionSystemManager::toggleGameInCollection(FileData* file) +{ + if (file->getType() == GAME) + { + GuiInfoPopup* s; + bool adding = true; + std::string name = file->getName(); + std::string sysName = mEditingCollection; + if (mIsEditingCustom) + { + SystemData* sysData = mEditingCollectionSystemData->system; + mEditingCollectionSystemData->needsSave = true; + if (!mEditingCollectionSystemData->isPopulated) + { + populateCustomCollection(mEditingCollectionSystemData); + } + std::string key = file->getFullPath(); + FileData* rootFolder = sysData->getRootFolder(); + const std::unordered_map& children = rootFolder->getChildrenByFilename(); + bool found = children.find(key) != children.end(); + FileFilterIndex* fileIndex = sysData->getIndex(); + std::string name = sysData->getName(); + + SystemData* systemViewToUpdate = getSystemToView(sysData); + + if (found) { + adding = false; + // if we found it, we need to remove it + FileData* collectionEntry = children.at(key); + // remove from index + fileIndex->removeFromIndex(collectionEntry); + // remove from bundle index as well, if needed + if(systemViewToUpdate != sysData) + { + systemViewToUpdate->getIndex()->removeFromIndex(collectionEntry); + } + ViewController::get()->getGameListView(systemViewToUpdate).get()->remove(collectionEntry, false); + } + else + { + // we didn't find it here, we should add it + CollectionFileData* newGame = new CollectionFileData(file, sysData); + rootFolder->addChild(newGame); + fileIndex->addToIndex(newGame); + ViewController::get()->getGameListView(systemViewToUpdate)->onFileChanged(newGame, FILE_METADATA_CHANGED); + rootFolder->sort(getSortTypeFromString(mEditingCollectionSystemData->decl.defaultSort)); + ViewController::get()->onFileChanged(systemViewToUpdate->getRootFolder(), FILE_SORTED); + // add to bundle index as well, if needed + if(systemViewToUpdate != sysData) + { + systemViewToUpdate->getIndex()->addToIndex(newGame); + } + } + updateCollectionFolderMetadata(sysData); + } + else + { + MetaDataList* md = &file->getSourceFileData()->metadata; + std::string value = md->get("favorite"); + if (value == "false") + { + md->set("favorite", "true"); + } + else + { + adding = false; + md->set("favorite", "false"); + } + refreshCollectionSystems(file->getSourceFileData()); + } + if (adding) + { + s = new GuiInfoPopup(mWindow, "Added '" + removeParenthesis(name) + "' to '" + strToUpper(sysName) + "'", 4000); + } + else + { + s = new GuiInfoPopup(mWindow, "Removed '" + removeParenthesis(name) + "' from '" + strToUpper(sysName) + "'", 4000); + } + mWindow->setInfoPopup(s); + return true; + } + return false; +} + +SystemData* CollectionSystemManager::getSystemToView(SystemData* sys) +{ + SystemData* systemToView = sys; + FileData* rootFolder = sys->getRootFolder(); + + FileData* bundleRootFolder = mCustomCollectionsBundle->getRootFolder(); + const std::unordered_map& bundleChildren = bundleRootFolder->getChildrenByFilename(); + + // is the rootFolder bundled in the "My Collections" system? + bool sysFoundInBundle = bundleChildren.find(rootFolder->getKey()) != bundleChildren.end(); + + if (sysFoundInBundle && sys->isCollection()) + { + systemToView = mCustomCollectionsBundle; + } + return systemToView; +} + +/* Handles loading a collection system, creating an empty one, and populating on demand */ +// loads Automatic Collection systems (All, Favorites, Last Played) +void CollectionSystemManager::initAutoCollectionSystems() +{ + for(std::map::iterator it = mCollectionSystemDeclsIndex.begin() ; it != mCollectionSystemDeclsIndex.end() ; it++ ) + { + CollectionSystemDecl sysDecl = it->second; + if (!sysDecl.isCustom) + { + createNewCollectionEntry(sysDecl.name, sysDecl); + } + } +} + +// this may come in handy if at any point in time in the future we want to +// automatically generate metadata for a folder +void CollectionSystemManager::updateCollectionFolderMetadata(SystemData* sys) +{ + FileData* rootFolder = sys->getRootFolder(); + + std::string desc = "This collection is empty."; + std::string rating = "0"; + std::string players = "1"; + std::string releasedate = "N/A"; + std::string developer = "None"; + std::string genre = "None"; + std::string video = ""; + std::string thumbnail = ""; + + std::unordered_map games = rootFolder->getChildrenByFilename(); + + if(games.size() > 0) + { + std::string games_list = ""; + int games_counter = 0; + for(std::unordered_map::iterator iter = games.begin(); iter != games.end(); ++iter) + { + games_counter++; + FileData* file = iter->second; + + std::string new_rating = file->metadata.get("rating"); + std::string new_releasedate = file->metadata.get("releasedate"); + std::string new_developer = file->metadata.get("developer"); + std::string new_genre = file->metadata.get("genre"); + std::string new_players = file->metadata.get("players"); + + rating = (new_rating > rating ? (new_rating != "" ? new_rating : rating) : rating); + players = (new_players > players ? (new_players != "" ? new_players : players) : players); + releasedate = (new_releasedate < releasedate ? (new_releasedate != "" ? new_releasedate : releasedate) : releasedate); + developer = (developer == "None" ? new_developer : (new_developer != developer ? "Various" : new_developer)); + genre = (genre == "None" ? new_genre : (new_genre != genre ? "Various" : new_genre)); + + switch(games_counter) + { + case 2: + case 3: + games_list += ", "; + case 1: + games_list += "'" + file->getName() + "'"; + break; + case 4: + games_list += " among other titles."; + } + } + + desc = "This collection contains " + std::to_string(games_counter) + " games, including " + games_list; + + FileData* randomGame = sys->getRandomGame(); + + video = randomGame->getVideoPath(); + thumbnail = randomGame->getThumbnailPath(); + } + + + rootFolder->metadata.set("desc", desc); + rootFolder->metadata.set("rating", rating); + rootFolder->metadata.set("players", players); + rootFolder->metadata.set("genre", genre); + rootFolder->metadata.set("releasedate", releasedate); + rootFolder->metadata.set("developer", developer); + rootFolder->metadata.set("video", video); + rootFolder->metadata.set("image", thumbnail); +} + +void CollectionSystemManager::initCustomCollectionSystems() +{ + std::vector systems = getCollectionsFromConfigFolder(); + for (auto nameIt = systems.begin(); nameIt != systems.end(); nameIt++) + { + addNewCustomCollection(*nameIt); + } +} + +SystemData* CollectionSystemManager::getAllGamesCollection() +{ + CollectionSystemData* allSysData = &mAutoCollectionSystemsData["all"]; + if (!allSysData->isPopulated) + { + populateAutoCollection(allSysData); + } + return allSysData->system; +} + +SystemData* CollectionSystemManager::addNewCustomCollection(std::string name) +{ + CollectionSystemDecl decl = mCollectionSystemDeclsIndex[myCollectionsName]; + decl.themeFolder = name; + decl.name = name; + decl.longName = name; + return createNewCollectionEntry(name, decl); +} + +// creates a new, empty Collection system, based on the name and declaration +SystemData* CollectionSystemManager::createNewCollectionEntry(std::string name, CollectionSystemDecl sysDecl, bool index) +{ + SystemData* newSys = new SystemData(name, sysDecl.longName, mCollectionEnvData, sysDecl.themeFolder, true); + + CollectionSystemData newCollectionData; + newCollectionData.system = newSys; + newCollectionData.decl = sysDecl; + newCollectionData.isEnabled = false; + newCollectionData.isPopulated = false; + newCollectionData.needsSave = false; + + if (index) + { + if (!sysDecl.isCustom) + { + mAutoCollectionSystemsData[name] = newCollectionData; + } + else + { + mCustomCollectionSystemsData[name] = newCollectionData; + } + } + + return newSys; +} + +// populates an Automatic Collection System +void CollectionSystemManager::populateAutoCollection(CollectionSystemData* sysData) +{ + SystemData* newSys = sysData->system; + CollectionSystemDecl sysDecl = sysData->decl; + FileData* rootFolder = newSys->getRootFolder(); + FileFilterIndex* index = newSys->getIndex(); + for(auto sysIt = SystemData::sSystemVector.begin(); sysIt != SystemData::sSystemVector.end(); sysIt++) + { + // we won't iterate all collections + if ((*sysIt)->isGameSystem() && !(*sysIt)->isCollection()) { + std::vector files = (*sysIt)->getRootFolder()->getFilesRecursive(GAME); + for(auto gameIt = files.begin(); gameIt != files.end(); gameIt++) + { + bool include = includeFileInAutoCollections((*gameIt)); + switch(sysDecl.type) { + case AUTO_LAST_PLAYED: + include = include && (*gameIt)->metadata.get("playcount") > "0"; + break; + case AUTO_FAVORITES: + // we may still want to add files we don't want in auto collections in "favorites" + include = (*gameIt)->metadata.get("favorite") == "true"; + break; + } + + if (include) { + CollectionFileData* newGame = new CollectionFileData(*gameIt, newSys); + rootFolder->addChild(newGame); + index->addToIndex(newGame); + } + } + } + } + rootFolder->sort(getSortTypeFromString(sysDecl.defaultSort)); + sysData->isPopulated = true; +} + +// populates a Custom Collection System +void CollectionSystemManager::populateCustomCollection(CollectionSystemData* sysData) +{ + SystemData* newSys = sysData->system; + sysData->isPopulated = true; + CollectionSystemDecl sysDecl = sysData->decl; + std::string path = getCustomCollectionConfigPath(newSys->getName()); + + if(!fs::exists(path)) + { + LOG(LogInfo) << "Couldn't find custom collection config file at " << path; + return; + } + LOG(LogInfo) << "Loading custom collection config file at " << path; + + FileData* rootFolder = newSys->getRootFolder(); + FileFilterIndex* index = newSys->getIndex(); + + // get Configuration for this Custom System + std::ifstream input(path); + + // get all files map + std::unordered_map allFilesMap = getAllGamesCollection()->getRootFolder()->getChildrenByFilename(); + + // iterate list of files in config file + + for(std::string gameKey; getline(input, gameKey); ) + { + std::unordered_map::iterator it = allFilesMap.find(gameKey); + if (it != allFilesMap.end()) { + CollectionFileData* newGame = new CollectionFileData(it->second, newSys); + rootFolder->addChild(newGame); + index->addToIndex(newGame); + } + else + { + LOG(LogInfo) << "Couldn't find game referenced at '" << gameKey << "' for system config '" << path << "'"; + } + } + rootFolder->sort(getSortTypeFromString(sysDecl.defaultSort)); + updateCollectionFolderMetadata(newSys); +} + +/* Handle System View removal and insertion of Collections */ +void CollectionSystemManager::removeCollectionsFromDisplayedSystems() +{ + // remove all Collection Systems + for(auto sysIt = SystemData::sSystemVector.begin(); sysIt != SystemData::sSystemVector.end(); ) + { + if ((*sysIt)->isCollection()) + { + sysIt = SystemData::sSystemVector.erase(sysIt); + } + else + { + sysIt++; + } + } + + // remove all custom collections in bundle + // this should not delete the objects from memory! + FileData* customRoot = mCustomCollectionsBundle->getRootFolder(); + std::vector mChildren = customRoot->getChildren(); + for(auto it = mChildren.begin(); it != mChildren.end(); it++) + { + customRoot->removeChild(*it); + } + // clear index + mCustomCollectionsBundle->getIndex()->resetIndex(); + // remove view so it's re-created as needed + ViewController::get()->removeGameListView(mCustomCollectionsBundle); +} + +void CollectionSystemManager::addEnabledCollectionsToDisplayedSystems(std::map* colSystemData) +{ + // add auto enabled ones + for(std::map::iterator it = colSystemData->begin() ; it != colSystemData->end() ; it++ ) + { + if(it->second.isEnabled) + { + // check if populated, otherwise populate + if (!it->second.isPopulated) + { + if(it->second.decl.isCustom) + { + populateCustomCollection(&(it->second)); + } + else + { + populateAutoCollection(&(it->second)); + } + } + // check if it has its own view + if(!it->second.decl.isCustom || themeFolderExists(it->first) || !Settings::getInstance()->getBool("UseCustomCollectionsSystem")) + { + // exists theme folder, or we chose not to bundle it under the custom-collections system + // so we need to create a view + SystemData::sSystemVector.push_back(it->second.system); + } + else + { + FileData* newSysRootFolder = it->second.system->getRootFolder(); + mCustomCollectionsBundle->getRootFolder()->addChild(newSysRootFolder); + mCustomCollectionsBundle->getIndex()->importIndex(it->second.system->getIndex()); + } + } + } +} + +/* Auxiliary methods to get available custom collection possibilities */ std::vector CollectionSystemManager::getSystemsFromConfig() { std::vector systems; @@ -123,9 +852,11 @@ std::vector CollectionSystemManager::getSystemsFromConfig() return systems; } +// gets all folders from the current theme path std::vector CollectionSystemManager::getSystemsFromTheme() { std::vector systems; + auto themeSets = ThemeData::getThemeSets(); if(themeSets.empty()) { @@ -141,7 +872,7 @@ std::vector CollectionSystemManager::getSystemsFromTheme() Settings::getInstance()->setString("ThemeSet", set->first); } - boost::filesystem::path themePath = set->second.path; + fs::path themePath = set->second.path; if (fs::exists(themePath)) { @@ -165,44 +896,27 @@ std::vector CollectionSystemManager::getSystemsFromTheme() return systems; } -std::vector CollectionSystemManager::getAutoThemeFolders() -{ - std::vector systems; - for(std::map::iterator it = mCollectionSystemDecls.begin() ; it != mCollectionSystemDecls.end() ; it++ ) - { - CollectionSystemDecl sysDecl = it->second; - if (!sysDecl.isCustom) - { - systems.push_back(sysDecl.themeFolder); - } - } - return systems; -} - -bool CollectionSystemManager::isThemeAutoCompatible() -{ - std::vector cfgSys = getAutoThemeFolders(); - for(auto sysIt = cfgSys.begin(); sysIt != cfgSys.end(); sysIt++) - { - if(!themeFolderExists(*sysIt)) - return false; - } - return true; -} - -bool CollectionSystemManager::themeFolderExists(std::string folder) -{ - std::vector themeSys = getSystemsFromTheme(); - return std::find(themeSys.begin(), themeSys.end(), folder) != themeSys.end(); -} - +// returns the unused folders from current theme path std::vector CollectionSystemManager::getUnusedSystemsFromTheme() { - std::vector cfgSys = getSystemsFromConfig(); + // get used systems in es_systems.cfg + std::vector systemsInUse = getSystemsFromConfig(); + // get available folders in theme std::vector themeSys = getSystemsFromTheme(); + // get folders assigned to custom collections + std::vector autoSys = getCollectionThemeFolders(false); + // get folder assigned to custom collections + std::vector customSys = getCollectionThemeFolders(true); + // get folders assigned to user collections + std::vector userSys = getUserCollectionThemeFolders(); + // add them all to the list of systems in use + systemsInUse.insert(systemsInUse.end(), autoSys.begin(), autoSys.end()); + systemsInUse.insert(systemsInUse.end(), customSys.begin(), customSys.end()); + systemsInUse.insert(systemsInUse.end(), userSys.begin(), userSys.end()); + for(auto sysIt = themeSys.begin(); sysIt != themeSys.end(); ) { - if (std::find(cfgSys.begin(), cfgSys.end(), *sysIt) != cfgSys.end()) + if (std::find(systemsInUse.begin(), systemsInUse.end(), *sysIt) != systemsInUse.end()) { sysIt = themeSys.erase(sysIt); } @@ -214,228 +928,97 @@ std::vector CollectionSystemManager::getUnusedSystemsFromTheme() return themeSys; } -FileData::SortType CollectionSystemManager::getSortType(std::string desc) { - std::vector SortTypes = FileSorts::SortTypes; - // find it - for(unsigned int i = 0; i < FileSorts::SortTypes.size(); i++) - { - const FileData::SortType& sort = FileSorts::SortTypes.at(i); - if(sort.description == desc) - { - return sort; - } - } - // if not found default to name, ascending - return FileSorts::SortTypes.at(0); -} - -void CollectionSystemManager::loadCollectionSystems() +// returns which collection config files exist in the user folder +std::vector CollectionSystemManager::getCollectionsFromConfigFolder() { - loadAutoCollectionSystems(); - // we will also load custom systems here in the future - loadCustomCollectionSystems(); - // Now see which ones are enabled - loadEnabledListFromSettings(); - // add to the main System Vector, and create Views as needed - updateSystemsList(); -} + std::vector systems; + fs::path configPath = getCollectionsFolder(); -void CollectionSystemManager::updateSystemsList() -{ - // remove all Collection Systems - for(auto sysIt = SystemData::sSystemVector.begin(); sysIt != SystemData::sSystemVector.end(); ) + if (fs::exists(configPath)) { - if ((*sysIt)->isCollection()) + fs::directory_iterator end_itr; // default construction yields past-the-end + for (fs::directory_iterator itr(configPath); itr != end_itr; ++itr) { - sysIt = SystemData::sSystemVector.erase(sysIt); - } - else - { - sysIt++; - } - } - - // add enabled ones - for(std::map::iterator it = mAllCollectionSystems.begin() ; it != mAllCollectionSystems.end() ; it++ ) - { - if(it->second.isEnabled) - { - SystemData::sSystemVector.push_back(it->second.system); - } - } - - // remove disabled gamelist views - // create gamelist views if needed - // iterate the map -} - -void CollectionSystemManager::loadAutoCollectionSystems() -{ - for(std::map::iterator it = mCollectionSystemDecls.begin() ; it != mCollectionSystemDecls.end() ; it++ ) - { - CollectionSystemDecl sysDecl = it->second; - if (!sysDecl.isCustom && !findCollectionSystem(sysDecl.name)) - { - SystemData* newSys = new SystemData(sysDecl.name, sysDecl.longName, mCollectionEnvData, sysDecl.themeFolder, true); - - FileData* rootFolder = newSys->getRootFolder(); - FileFilterIndex* index = newSys->getIndex(); - for(auto sysIt = SystemData::sSystemVector.begin(); sysIt != SystemData::sSystemVector.end(); sysIt++) + if (fs::is_regular_file(itr->status())) { - if ((*sysIt)->isGameSystem()) { - std::vector files = (*sysIt)->getRootFolder()->getFilesRecursive(GAME); - for(auto gameIt = files.begin(); gameIt != files.end(); gameIt++) - { - bool include = includeFileInAutoCollections((*gameIt)); - switch(sysDecl.type) { - case AUTO_LAST_PLAYED: - include = include && (*gameIt)->metadata.get("playcount") > "0"; - break; - case AUTO_FAVORITES: - // we may still want to add files we don't want in auto collections in "favorites" - include = (*gameIt)->metadata.get("favorite") == "true"; - break; - } + // it's a file + std::string file = itr->path().string(); + std::string filename = file.substr(configPath.string().size()); - if (include) { - CollectionFileData* newGame = new CollectionFileData(*gameIt, newSys); - rootFolder->addChild(newGame); - index->addToIndex(newGame); - } - } - } - } - rootFolder->sort(getSortType(sysDecl.defaultSort)); - mAutoCollectionSystems.push_back(newSys); - - CollectionSystemData newCollectionData; - newCollectionData.system = newSys; - newCollectionData.decl = sysDecl; - newCollectionData.isEnabled = false; - mAllCollectionSystems[sysDecl.name] = newCollectionData; - } - } -} - -void CollectionSystemManager::loadCustomCollectionSystems() -{ - // Load custom systems into memory - // Check unassigned theme folders - // Check settings string for selected/enabled systems, if there are any that aren't in the theme - // Check saved preferences for each of those and load them if we can - // Load settings to see which systems are Enabled -} - -SystemData* CollectionSystemManager::findCollectionSystem(std::string name) -{ - for(auto sysIt = SystemData::sSystemVector.begin(); sysIt != SystemData::sSystemVector.end(); sysIt++) - { - if ((*sysIt)->getName() == name) { - // found it! - return (*sysIt); - } - } - return NULL; -} - -// this updates all collection files related to the argument file -void CollectionSystemManager::updateCollectionSystems(FileData* file) -{ - // collection files use the full path as key, to avoid clashes - std::string key = file->getFullPath(); - // find games in collection systems - for(auto sysIt = SystemData::sSystemVector.begin(); sysIt != SystemData::sSystemVector.end(); sysIt++) - { - if ((*sysIt)->isCollection()) { - const std::unordered_map& children = (*sysIt)->getRootFolder()->getChildrenByFilename(); - bool found = children.find(key) != children.end(); - FileData* rootFolder = (*sysIt)->getRootFolder(); - FileFilterIndex* fileIndex = (*sysIt)->getIndex(); - std::string name = (*sysIt)->getName(); - if (found) { - // if we found it, we need to update it - FileData* collectionEntry = children.at(key); - // remove from index, so we can re-index metadata after refreshing - fileIndex->removeFromIndex(collectionEntry); - collectionEntry->refreshMetadata(); - if (name == "favorites" && file->metadata.get("favorite") == "false") { - // need to check if still marked as favorite, if not remove - ViewController::get()->getGameListView((*sysIt)).get()->remove(collectionEntry, false); - ViewController::get()->onFileChanged((*sysIt)->getRootFolder(), FILE_REMOVED); + // need to confirm filename matches config format + if (boost::algorithm::ends_with(filename, ".cfg") && boost::algorithm::starts_with(filename, "custom-") && filename != "custom-.cfg") + { + filename = filename.substr(7, filename.size()-11); + systems.push_back(filename); } else { - // re-index with new metadata - fileIndex->addToIndex(collectionEntry); - ViewController::get()->onFileChanged(collectionEntry, FILE_METADATA_CHANGED); + LOG(LogInfo) << "Found non-collection config file in collections folder: " << filename; } } - else - { - // we didn't find it here - we need to check if we should add it - if (name == "recent" && file->metadata.get("playcount") > "0" || - name == "favorites" && file->metadata.get("favorite") == "true") { - CollectionFileData* newGame = new CollectionFileData(file, (*sysIt)); - rootFolder->addChild(newGame); - fileIndex->addToIndex(newGame); - ViewController::get()->onFileChanged(file, FILE_METADATA_CHANGED); - ViewController::get()->getGameListView((*sysIt))->onFileChanged(newGame, FILE_METADATA_CHANGED); - } - } - rootFolder->sort(getSortType(mCollectionSystemDecls[name].defaultSort)); - ViewController::get()->onFileChanged(rootFolder, FILE_SORTED); } } + return systems; } -// this deletes collection files from collection systems -void CollectionSystemManager::deleteCollectionFiles(FileData* file) +// returns the theme folders for Automatic Collections (All, Favorites, Last Played) or generic Custom Collections folder +std::vector CollectionSystemManager::getCollectionThemeFolders(bool custom) { - // collection files use the full path as key, to avoid clashes - std::string key = file->getFullPath(); - // find games in collection systems - for(auto sysIt = SystemData::sSystemVector.begin(); sysIt != SystemData::sSystemVector.end(); sysIt++) + std::vector systems; + for(std::map::iterator it = mCollectionSystemDeclsIndex.begin() ; it != mCollectionSystemDeclsIndex.end() ; it++ ) { - if ((*sysIt)->isCollection()) { - const std::unordered_map& children = (*sysIt)->getRootFolder()->getChildrenByFilename(); - - bool found = children.find(key) != children.end(); - if (found) { - FileData* collectionEntry = children.at(key); - ViewController::get()->getGameListView((*sysIt)).get()->remove(collectionEntry, false); - } + CollectionSystemDecl sysDecl = it->second; + if (sysDecl.isCustom == custom) + { + systems.push_back(sysDecl.themeFolder); } } + return systems; } -bool CollectionSystemManager::toggleGameInCollection(FileData* file, std::string collection) +// returns the theme folders in use for the user-defined Custom Collections +std::vector CollectionSystemManager::getUserCollectionThemeFolders() { - if (file->getType() == GAME) + std::vector systems; + for(std::map::iterator it = mCustomCollectionSystemsData.begin() ; it != mCustomCollectionSystemsData.end() ; it++ ) { - GuiInfoPopup* s; - - MetaDataList* md = &file->getSourceFileData()->metadata; - std::string value = md->get("favorite"); - if (value == "false") - { - md->set("favorite", "true"); - s = new GuiInfoPopup(mWindow, "Added '" + removeParenthesis(file->getName()) + "' to 'Favorites'", 4000); - }else - { - md->set("favorite", "false"); - s = new GuiInfoPopup(mWindow, "Removed '" + removeParenthesis(file->getName()) + "' from 'Favorites'", 4000); - } - mWindow->setInfoPopup(s); - updateCollectionSystems(file->getSourceFileData()); - return true; + systems.push_back(it->second.decl.themeFolder); } - return false; + return systems; +} + +// returns whether a specific folder exists in the theme +bool CollectionSystemManager::themeFolderExists(std::string folder) +{ + std::vector themeSys = getSystemsFromTheme(); + return std::find(themeSys.begin(), themeSys.end(), folder) != themeSys.end(); } bool CollectionSystemManager::includeFileInAutoCollections(FileData* file) { - // we exclude non-game files from collections (i.e. "kodi", at least) + // we exclude non-game files from collections (i.e. "kodi", entries from non-game systems) // if/when there are more in the future, maybe this can be a more complex method, with a proper list // but for now a simple string comparison is more performant - return file->getName() != "kodi"; + return file->getName() != "kodi" && file->getSystem()->isGameSystem(); +} + + +std::string getCustomCollectionConfigPath(std::string collectionName) +{ + fs::path path = getCollectionsFolder() + "custom-" + collectionName + ".cfg"; + return path.generic_string(); +} + +std::string getCollectionsFolder() +{ + return getHomePath() + "/.emulationstation/collections/"; +} + +bool systemSort(SystemData* sys1, SystemData* sys2) +{ + std::string name1 = sys1->getName(); + std::string name2 = sys2->getName(); + transform(name1.begin(), name1.end(), name1.begin(), ::toupper); + transform(name2.begin(), name2.end(), name2.begin(), ::toupper); + return name1.compare(name2) < 0; } \ No newline at end of file diff --git a/es-app/src/CollectionSystemManager.h b/es-app/src/CollectionSystemManager.h index 9482786d2..87993d4bf 100644 --- a/es-app/src/CollectionSystemManager.h +++ b/es-app/src/CollectionSystemManager.h @@ -34,6 +34,8 @@ struct CollectionSystemData SystemData* system; CollectionSystemDecl decl; bool isEnabled; + bool isPopulated; + bool needsSave; }; class CollectionSystemManager @@ -41,34 +43,73 @@ class CollectionSystemManager public: CollectionSystemManager(Window* window); ~CollectionSystemManager(); - static void init(Window* window); + static CollectionSystemManager* get(); - void loadEnabledListFromSettings(); + static void init(Window* window); + static void deinit(); + void saveCustomCollection(SystemData* sys); + void loadCollectionSystems(); - void updateCollectionSystems(FileData* file); - void deleteCollectionFiles(FileData* file); - inline std::map getCollectionSystems() { return mAllCollectionSystems; }; + void loadEnabledListFromSettings(); void updateSystemsList(); - bool isThemeAutoCompatible(); - bool toggleGameInCollection(FileData* file, std::string collection); + + void refreshCollectionSystems(FileData* file); + void updateCollectionSystem(FileData* file, CollectionSystemData sysData); + void deleteCollectionFiles(FileData* file); + + inline std::map getAutoCollectionSystems() { return mAutoCollectionSystemsData; }; + inline std::map getCustomCollectionSystems() { return mCustomCollectionSystemsData; }; + inline SystemData* getCustomCollectionsBundle() { return mCustomCollectionsBundle; }; + std::vector getUnusedSystemsFromTheme(); + SystemData* addNewCustomCollection(std::string name); + + bool isThemeGenericCollectionCompatible(bool genericCustomCollections); + bool isThemeCustomCollectionCompatible(std::vector stringVector); + std::string getValidNewCollectionName(std::string name, int index = 0); + + void setEditMode(std::string collectionName); + void exitEditMode(); + inline bool isEditing() { return mIsEditingCustom; }; + inline std::string getEditingCollection() { std::string res = mEditingCollection; return res; }; + bool toggleGameInCollection(FileData* file); + + SystemData* getSystemToView(SystemData* sys); + void updateCollectionFolderMetadata(SystemData* sys); private: static CollectionSystemManager* sInstance; - std::map mCollectionSystemDecls; SystemEnvironmentData* mCollectionEnvData; - static FileData::SortType getSortType(std::string desc); - void initAvailableSystemsList(); + std::map mCollectionSystemDeclsIndex; + std::map mAutoCollectionSystemsData; + std::map mCustomCollectionSystemsData; + Window* mWindow; + bool mIsEditingCustom; + std::string mEditingCollection; + CollectionSystemData* mEditingCollectionSystemData; + + void initAutoCollectionSystems(); + void initCustomCollectionSystems(); + SystemData* getAllGamesCollection(); + SystemData* createNewCollectionEntry(std::string name, CollectionSystemDecl sysDecl, bool index = true); + void populateAutoCollection(CollectionSystemData* sysData); + void populateCustomCollection(CollectionSystemData* sysData); + + void removeCollectionsFromDisplayedSystems(); + void addEnabledCollectionsToDisplayedSystems(std::map* colSystemData); + std::vector getSystemsFromConfig(); std::vector getSystemsFromTheme(); - std::vector getUnusedSystemsFromTheme(); - std::vector getAutoThemeFolders(); + std::vector getCollectionsFromConfigFolder(); + std::vector getCollectionThemeFolders(bool custom); + std::vector getUserCollectionThemeFolders(); + bool themeFolderExists(std::string folder); - void loadAutoCollectionSystems(); - void loadCustomCollectionSystems(); // TO DO NEXT - SystemData* findCollectionSystem(std::string name); + bool includeFileInAutoCollections(FileData* file); - std::map mAllCollectionSystems; - std::vector mAutoCollectionSystems; - std::vector mCustomCollectionSystems; - Window* mWindow; + + SystemData* mCustomCollectionsBundle; }; + +std::string getCustomCollectionConfigPath(std::string collectionName); +std::string getCollectionsFolder(); +bool systemSort(SystemData* sys1, SystemData* sys2); diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index 8352b6c52..892c701a3 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -23,7 +23,8 @@ FileData::~FileData() if(mParent) mParent->removeChild(this); - mSystem->getIndex()->removeFromIndex(this); + if(mType == GAME) + mSystem->getIndex()->removeFromIndex(this); mChildren.clear(); } @@ -57,7 +58,7 @@ const std::string& FileData::getName() const std::vector& FileData::getChildrenListToDisplay() { - FileFilterIndex* idx = mSystem->getIndex(); + FileFilterIndex* idx = CollectionSystemManager::get()->getSystemToView(mSystem)->getIndex(); if (idx->isFiltered()) { mFilteredChildren.clear(); for(auto it = mChildren.begin(); it != mChildren.end(); it++) @@ -140,6 +141,7 @@ void FileData::removeChild(FileData* file) { if(*it == file) { + file->mParent = NULL; mChildren.erase(it); return; } @@ -210,14 +212,11 @@ void FileData::launchGame(Window* window) //update last played time boost::posix_time::ptime time = boost::posix_time::second_clock::universal_time(); gameToUpdate->metadata.setTime("lastplayed", time); - CollectionSystemManager::get()->updateCollectionSystems(gameToUpdate); + CollectionSystemManager::get()->refreshCollectionSystems(gameToUpdate); } CollectionFileData::CollectionFileData(FileData* file, SystemData* system) - : FileData(file->getType(), file->getPath(), file->getSystemEnvData(), system)/*, - mSourceFileData(file->getSourceFileData()), - mParent(NULL), - metadata(file->getSourceFileData()->metadata)*/ + : FileData(file->getSourceFileData()->getType(), file->getSourceFileData()->getPath(), file->getSourceFileData()->getSystemEnvData(), system) { // we use this constructor to create a clone of the filedata, and change its system mSourceFileData = file->getSourceFileData(); @@ -259,4 +258,20 @@ const std::string& CollectionFileData::getName() mDirty = false; } return mCollectionFileName; +} + +// returns Sort Type based on a string description +FileData::SortType getSortTypeFromString(std::string desc) { + std::vector SortTypes = FileSorts::SortTypes; + // find it + for(unsigned int i = 0; i < FileSorts::SortTypes.size(); i++) + { + const FileData::SortType& sort = FileSorts::SortTypes.at(i); + if(sort.description == desc) + { + return sort; + } + } + // if not found default to name, ascending + return FileSorts::SortTypes.at(0); } \ No newline at end of file diff --git a/es-app/src/FileData.h b/es-app/src/FileData.h index 425f7f981..0ce34f9bb 100644 --- a/es-app/src/FileData.h +++ b/es-app/src/FileData.h @@ -114,4 +114,6 @@ private: // needs to be updated when metadata changes std::string mCollectionFileName; bool mDirty; -}; \ No newline at end of file +}; + +FileData::SortType getSortTypeFromString(std::string desc); \ No newline at end of file diff --git a/es-app/src/FileFilterIndex.cpp b/es-app/src/FileFilterIndex.cpp index 3f60539d8..0f35db14b 100644 --- a/es-app/src/FileFilterIndex.cpp +++ b/es-app/src/FileFilterIndex.cpp @@ -20,11 +20,7 @@ FileFilterIndex::FileFilterIndex() FileFilterIndex::~FileFilterIndex() { - clearIndex(genreIndexAllKeys); - clearIndex(playersIndexAllKeys); - clearIndex(pubDevIndexAllKeys); - clearIndex(ratingsIndexAllKeys); - clearIndex(favoritesIndexAllKeys); + resetIndex(); } std::vector& FileFilterIndex::getFilterDataDecls() @@ -32,6 +28,50 @@ std::vector& FileFilterIndex::getFilterDataDecls() return filterDataDecl; } +void FileFilterIndex::importIndex(FileFilterIndex* indexToImport) +{ + struct IndexImportStructure + { + std::map* destinationIndex; + std::map* sourceIndex; + }; + + IndexImportStructure indexStructDecls[] = { + { &genreIndexAllKeys, &(indexToImport->genreIndexAllKeys) }, + { &playersIndexAllKeys, &(indexToImport->playersIndexAllKeys) }, + { &pubDevIndexAllKeys, &(indexToImport->pubDevIndexAllKeys) }, + { &ratingsIndexAllKeys, &(indexToImport->ratingsIndexAllKeys) }, + { &favoritesIndexAllKeys, &(indexToImport->favoritesIndexAllKeys) } + }; + + std::vector indexImportDecl = std::vector(indexStructDecls, indexStructDecls + sizeof(indexStructDecls) / sizeof(indexStructDecls[0])); + + for (std::vector::iterator indexesIt = indexImportDecl.begin(); indexesIt != indexImportDecl.end(); ++indexesIt ) + { + for (std::map::iterator sourceIt = (*indexesIt).sourceIndex->begin(); sourceIt != (*indexesIt).sourceIndex->end(); ++sourceIt ) + { + if ((*indexesIt).destinationIndex->find((*sourceIt).first) == (*indexesIt).destinationIndex->end()) + { + // entry doesn't exist + (*((*indexesIt).destinationIndex))[(*sourceIt).first] = (*sourceIt).second; + } + else + { + (*((*indexesIt).destinationIndex))[(*sourceIt).first] += (*sourceIt).second; + } + } + } +} +void FileFilterIndex::resetIndex() +{ + clearAllFilters(); + clearIndex(genreIndexAllKeys); + clearIndex(playersIndexAllKeys); + clearIndex(pubDevIndexAllKeys); + clearIndex(ratingsIndexAllKeys); + clearIndex(favoritesIndexAllKeys); +} + std::string FileFilterIndex::getIndexableKey(FileData* game, FilterIndexType type, bool getSecondary) { std::string key = ""; @@ -170,21 +210,21 @@ void FileFilterIndex::clearAllFilters() void FileFilterIndex::debugPrintIndexes() { - LOG(LogError) << "Printing Indexes..."; + LOG(LogInfo) << "Printing Indexes..."; for (auto x: playersIndexAllKeys) { - LOG(LogError) << "Multiplayer Index: " << x.first << ": " << x.second; + LOG(LogInfo) << "Multiplayer Index: " << x.first << ": " << x.second; } for (auto x: genreIndexAllKeys) { - LOG(LogError) << "Genre Index: " << x.first << ": " << x.second; + LOG(LogInfo) << "Genre Index: " << x.first << ": " << x.second; } for (auto x: ratingsIndexAllKeys) { - LOG(LogError) << "Ratings Index: " << x.first << ": " << x.second; + LOG(LogInfo) << "Ratings Index: " << x.first << ": " << x.second; } for (auto x: pubDevIndexAllKeys) { - LOG(LogError) << "PubDev Index: " << x.first << ": " << x.second; + LOG(LogInfo) << "PubDev Index: " << x.first << ": " << x.second; } for (auto x: favoritesIndexAllKeys) { - LOG(LogError) << "Favorites Index: " << x.first << ": " << x.second; + LOG(LogInfo) << "Favorites Index: " << x.first << ": " << x.second; } } diff --git a/es-app/src/FileFilterIndex.h b/es-app/src/FileFilterIndex.h index d1e62d948..886588311 100644 --- a/es-app/src/FileFilterIndex.h +++ b/es-app/src/FileFilterIndex.h @@ -45,6 +45,9 @@ public: bool isFiltered() { return (filterByGenre || filterByPlayers || filterByPubDev || filterByRatings || filterByFavorites); }; bool isKeyBeingFilteredBy(std::string key, FilterIndexType type); std::vector& getFilterDataDecls(); + + void importIndex(FileFilterIndex* indexToImport); + void resetIndex(); private: std::vector filterDataDecl; std::string getIndexableKey(FileData* game, FilterIndexType type, bool getSecondary); diff --git a/es-app/src/SystemData.cpp b/es-app/src/SystemData.cpp index afb426c95..29e09e975 100644 --- a/es-app/src/SystemData.cpp +++ b/es-app/src/SystemData.cpp @@ -426,8 +426,11 @@ FileData* SystemData::getRandomGame() { std::vector list = mRootFolder->getFilesRecursive(GAME, true); unsigned int total = list.size(); + int target = 0; // get random number in range - int target = std::round(((double)std::rand() / (double)RAND_MAX) * (total - 1)); + if (total == 0) + return NULL; + target = std::round(((double)std::rand() / (double)RAND_MAX) * (total - 1)); return list.at(target); } diff --git a/es-app/src/guis/GuiCollectionSystemsOptions.cpp b/es-app/src/guis/GuiCollectionSystemsOptions.cpp index 23833a114..d3487542b 100644 --- a/es-app/src/guis/GuiCollectionSystemsOptions.cpp +++ b/es-app/src/guis/GuiCollectionSystemsOptions.cpp @@ -1,8 +1,11 @@ #include "guis/GuiCollectionSystemsOptions.h" #include "guis/GuiMsgBox.h" +#include "guis/GuiTextEditPopup.h" #include "Settings.h" #include "views/ViewController.h" +#include "guis/GuiSettings.h" +#include "Util.h" #include "components/TextComponent.h" #include "components/OptionListComponent.h" @@ -15,66 +18,172 @@ void GuiCollectionSystemsOptions::initializeMenu() { addChild(&mMenu); - // get virtual systems + // get collections addSystemsToMenu(); + // add "Create New Custom Collection from Theme" + + std::vector unusedFolders = CollectionSystemManager::get()->getUnusedSystemsFromTheme(); + if (unusedFolders.size() > 0) + { + addEntry("CREATE NEW CUSTOM COLLECTION FROM THEME", 0x777777FF, true, + [this, unusedFolders] { + auto s = new GuiSettings(mWindow, "SELECT THEME FOLDER"); + std::shared_ptr< OptionListComponent > folderThemes = std::make_shared< OptionListComponent >(mWindow, "SELECT THEME FOLDER", true); + + // add Custom Systems + for(auto it = unusedFolders.begin() ; it != unusedFolders.end() ; it++ ) + { + ComponentListRow row; + std::string name = *it; + + std::function createCollectionCall = [name, this, s] { + createCollection(name); + }; + row.makeAcceptInputHandler(createCollectionCall); + + auto themeFolder = std::make_shared(mWindow, strToUpper(name), Font::get(FONT_SIZE_SMALL), 0x777777FF); + row.addElement(themeFolder, true); + s->addRow(row); + } + mWindow->pushGui(s); + }); + } + + ComponentListRow row; + row.addElement(std::make_shared(mWindow, "CREATE NEW CUSTOM COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); + auto createCustomCollection = [this](const std::string& newVal) { + std::string name = newVal; + // we need to store the first Gui and remove it, as it'll be deleted by the actual Gui + Window* window = mWindow; + GuiComponent* topGui = window->peekGui(); + window->removeGui(topGui); + createCollection(name); + }; + row.makeAcceptInputHandler([this, createCustomCollection] { + mWindow->pushGui(new GuiTextEditPopup(mWindow, "New Collection Name", "", createCustomCollection, false)); + }); + + mMenu.addRow(row); + + bundleCustomCollections = std::make_shared(mWindow); + bundleCustomCollections->setState(Settings::getInstance()->getBool("UseCustomCollectionsSystem")); + mMenu.addWithLabel("GROUP UNTHEMED CUSTOM COLLECTIONS", bundleCustomCollections); + + sortAllSystemsSwitch = std::make_shared(mWindow); + sortAllSystemsSwitch->setState(Settings::getInstance()->getBool("SortAllSystems")); + mMenu.addWithLabel("SORT CUSTOM COLLECTIONS AND SYSTEMS", sortAllSystemsSwitch); + + if(CollectionSystemManager::get()->isEditing()) + { + row.elements.clear(); + row.addElement(std::make_shared(mWindow, "FINISH EDITING '" + strToUpper(CollectionSystemManager::get()->getEditingCollection()) + "' COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); + row.makeAcceptInputHandler(std::bind(&GuiCollectionSystemsOptions::exitEditMode, this)); + mMenu.addRow(row); + } + mMenu.addButton("BACK", "back", std::bind(&GuiCollectionSystemsOptions::applySettings, this)); mMenu.setPosition((Renderer::getScreenWidth() - mMenu.getSize().x()) / 2, Renderer::getScreenHeight() * 0.15f); } +void GuiCollectionSystemsOptions::addEntry(const char* name, unsigned int color, bool add_arrow, const std::function& func) +{ + std::shared_ptr font = Font::get(FONT_SIZE_MEDIUM); + + // populate the list + ComponentListRow row; + row.addElement(std::make_shared(mWindow, name, font, color), true); + + if(add_arrow) + { + std::shared_ptr bracket = makeArrow(mWindow); + row.addElement(bracket, false); + } + + row.makeAcceptInputHandler(func); + + mMenu.addRow(row); +} + +void GuiCollectionSystemsOptions::createCollection(std::string inName) { + std::string name = CollectionSystemManager::get()->getValidNewCollectionName(inName); + SystemData* newSys = CollectionSystemManager::get()->addNewCustomCollection(name); + customOptionList->add(name, name, true); + std::string outAuto = vectorToCommaString(autoOptionList->getSelectedObjects()); + std::string outCustom = vectorToCommaString(customOptionList->getSelectedObjects()); + updateSettings(outAuto, outCustom); + ViewController::get()->goToSystemView(newSys); + + Window* window = mWindow; + CollectionSystemManager::get()->setEditMode(name); + while(window->peekGui() && window->peekGui() != ViewController::get()) + delete window->peekGui(); + return; +} + +void GuiCollectionSystemsOptions::exitEditMode() +{ + CollectionSystemManager::get()->exitEditMode(); + applySettings(); +} + GuiCollectionSystemsOptions::~GuiCollectionSystemsOptions() { - //mSystemOptions.clear(); + } void GuiCollectionSystemsOptions::addSystemsToMenu() { - std::map vSystems = CollectionSystemManager::get()->getCollectionSystems(); + std::map autoSystems = CollectionSystemManager::get()->getAutoCollectionSystems(); autoOptionList = std::make_shared< OptionListComponent >(mWindow, "SELECT COLLECTIONS", true); - // add Systems - ComponentListRow row; - - for(std::map::iterator it = vSystems.begin() ; it != vSystems.end() ; it++ ) + // add Auto Systems + for(std::map::iterator it = autoSystems.begin() ; it != autoSystems.end() ; it++ ) { autoOptionList->add(it->second.decl.longName, it->second.decl.name, it->second.isEnabled); } - mMenu.addWithLabel("AUTOMATIC COLLECTIONS", autoOptionList); + mMenu.addWithLabel("AUTOMATIC GAME COLLECTIONS", autoOptionList); + + std::map customSystems = CollectionSystemManager::get()->getCustomCollectionSystems(); + + customOptionList = std::make_shared< OptionListComponent >(mWindow, "SELECT COLLECTIONS", true); + + // add Custom Systems + for(std::map::iterator it = customSystems.begin() ; it != customSystems.end() ; it++ ) + { + customOptionList->add(it->second.decl.longName, it->second.decl.name, it->second.isEnabled); + } + mMenu.addWithLabel("CUSTOM GAME COLLECTIONS", customOptionList); } void GuiCollectionSystemsOptions::applySettings() { - std::string out = commaStringToVector(autoOptionList->getSelectedObjects()); - std::string prev = Settings::getInstance()->getString("CollectionSystemsAuto"); - if (out != "" && !CollectionSystemManager::get()->isThemeAutoCompatible()) + std::string outAuto = vectorToCommaString(autoOptionList->getSelectedObjects()); + std::string prevAuto = Settings::getInstance()->getString("CollectionSystemsAuto"); + std::string outCustom = vectorToCommaString(customOptionList->getSelectedObjects()); + std::string prevCustom = Settings::getInstance()->getString("CollectionSystemsCustom"); + bool outSort = sortAllSystemsSwitch->getState(); + bool prevSort = Settings::getInstance()->getBool("SortAllSystems"); + bool outBundle = bundleCustomCollections->getState(); + bool prevBundle = Settings::getInstance()->getBool("UseCustomCollectionsSystem"); + bool needUpdateSettings = prevAuto != outAuto || prevCustom != outCustom || outSort != prevSort || outBundle != prevBundle; + if (needUpdateSettings) { - mWindow->pushGui(new GuiMsgBox(mWindow, - "Your theme does not support game collections. Please update your theme, or ensure that you use a theme that contains the folders:\n\n• auto-favorites\n• auto-lastplayed\n• auto-allgames\n\nDo you still want to enable collections?", - "YES", [this, out, prev] { - if (prev != out) - { - updateSettings(out); - } - delete this; }, - "NO", [this] { delete this; })); - } - else - { - if (prev != out) - { - updateSettings(out); - } - delete this; + updateSettings(outAuto, outCustom); } + delete this; } -void GuiCollectionSystemsOptions::updateSettings(std::string newSettings) +void GuiCollectionSystemsOptions::updateSettings(std::string newAutoSettings, std::string newCustomSettings) { - Settings::getInstance()->setString("CollectionSystemsAuto", newSettings); + Settings::getInstance()->setString("CollectionSystemsAuto", newAutoSettings); + Settings::getInstance()->setString("CollectionSystemsCustom", newCustomSettings); + Settings::getInstance()->setBool("SortAllSystems", sortAllSystemsSwitch->getState()); + Settings::getInstance()->setBool("UseCustomCollectionsSystem", bundleCustomCollections->getState()); Settings::getInstance()->saveFile(); CollectionSystemManager::get()->loadEnabledListFromSettings(); CollectionSystemManager::get()->updateSystemsList(); diff --git a/es-app/src/guis/GuiCollectionSystemsOptions.h b/es-app/src/guis/GuiCollectionSystemsOptions.h index 739aecd1b..39748e7c8 100644 --- a/es-app/src/guis/GuiCollectionSystemsOptions.h +++ b/es-app/src/guis/GuiCollectionSystemsOptions.h @@ -4,6 +4,7 @@ #include "SystemData.h" #include "components/MenuComponent.h" #include "CollectionSystemManager.h" +#include "components/SwitchComponent.h" #include "Log.h" @@ -24,8 +25,14 @@ private: void initializeMenu(); void applySettings(); void addSystemsToMenu(); - void updateSettings(std::string newSettings); + void addEntry(const char* name, unsigned int color, bool add_arrow, const std::function& func); + void updateSettings(std::string newAutoSettings, std::string newCustomSettings); + void createCollection(std::string inName); + void exitEditMode(); std::shared_ptr< OptionListComponent > autoOptionList; + std::shared_ptr< OptionListComponent > customOptionList; + std::shared_ptr sortAllSystemsSwitch; + std::shared_ptr bundleCustomCollections; MenuComponent mMenu; SystemData* mSystem; }; diff --git a/es-app/src/guis/GuiGamelistOptions.cpp b/es-app/src/guis/GuiGamelistOptions.cpp index 9fc36694a..ef48600cd 100644 --- a/es-app/src/guis/GuiGamelistOptions.cpp +++ b/es-app/src/guis/GuiGamelistOptions.cpp @@ -1,5 +1,6 @@ #include "GuiGamelistOptions.h" #include "GuiMetaDataEd.h" +#include "Util.h" #include "views/gamelist/IGameListView.h" #include "views/ViewController.h" #include "CollectionSystemManager.h" @@ -54,6 +55,33 @@ GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : Gui } mMenu.addWithLabel("SORT GAMES BY", mListSort); + } + // show filtered menu + row.elements.clear(); + row.addElement(std::make_shared(mWindow, "FILTER GAMELIST", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); + row.addElement(makeArrow(mWindow), false); + row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openGamelistFilter, this)); + mMenu.addRow(row); + + std::map customCollections = CollectionSystemManager::get()->getCustomCollectionSystems(); + if((customCollections.find(system->getName()) != customCollections.end() && CollectionSystemManager::get()->getEditingCollection() != system->getName()) || + CollectionSystemManager::get()->getCustomCollectionsBundle()->getName() == system->getName()) + { + row.elements.clear(); + row.addElement(std::make_shared(mWindow, "ADD/REMOVE GAMES TO THIS GAME COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); + row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::startEditMode, this)); + mMenu.addRow(row); + } + + if(CollectionSystemManager::get()->isEditing()) + { + row.elements.clear(); + row.addElement(std::make_shared(mWindow, "FINISH EDITING '" + strToUpper(CollectionSystemManager::get()->getEditingCollection()) + "' COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); + row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::exitEditMode, this)); + mMenu.addRow(row); + } + + if (!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); @@ -62,13 +90,6 @@ GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : Gui mMenu.addRow(row); } - // show filtered menu - row.elements.clear(); - row.addElement(std::make_shared(mWindow, "FILTER GAMELIST", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); - row.addElement(makeArrow(mWindow), false); - row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openGamelistFilter, this)); - mMenu.addRow(row); - // center the menu setSize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight()); mMenu.setPosition((mSize.x() - mMenu.getSize().x()) / 2, (mSize.y() - mMenu.getSize().y()) / 2); @@ -78,7 +99,7 @@ GuiGamelistOptions::~GuiGamelistOptions() { // apply sort if (!fromPlaceholder) { - FileData* root = getGamelist()->getCursor()->getSystem()->getRootFolder(); + FileData* root = mSystem->getRootFolder(); root->sort(*mListSort->getSelected()); // will also recursively sort children // notify that the root folder was sorted @@ -86,17 +107,10 @@ GuiGamelistOptions::~GuiGamelistOptions() } if (mFiltersChanged) { - if (!fromPlaceholder) { - FileData* root = getGamelist()->getCursor()->getSystem()->getRootFolder(); - getGamelist()->onFileChanged(root, FILE_SORTED); - } - else - { - // only reload full view if we came from a placeholder - // as we need to re-display the remaining elements for whatever new - // game is selected - ViewController::get()->reloadGameListView(mSystem); - } + // only reload full view if we came from a placeholder + // as we need to re-display the remaining elements for whatever new + // game is selected + ViewController::get()->reloadGameListView(mSystem); } } @@ -107,6 +121,34 @@ void GuiGamelistOptions::openGamelistFilter() mWindow->pushGui(ggf); } +void GuiGamelistOptions::startEditMode() +{ + std::string editingSystem = mSystem->getName(); + // need to check if we're editing the collections bundle, as we will want to edit the selected collection within + if(editingSystem == CollectionSystemManager::get()->getCustomCollectionsBundle()->getName()) + { + FileData* file = getGamelist()->getCursor(); + // do we have the cursor on a specific collection? + if (file->getType() == FOLDER) + { + editingSystem = file->getName(); + } + else + { + // we are inside a specific collection. We want to edit that one. + editingSystem = file->getSystem()->getName(); + } + } + CollectionSystemManager::get()->setEditMode(editingSystem); + delete this; +} + +void GuiGamelistOptions::exitEditMode() +{ + CollectionSystemManager::get()->exitEditMode(); + delete this; +} + void GuiGamelistOptions::openMetaDataEd() { // open metadata editor diff --git a/es-app/src/guis/GuiGamelistOptions.h b/es-app/src/guis/GuiGamelistOptions.h index 8088997d1..04854e0c1 100644 --- a/es-app/src/guis/GuiGamelistOptions.h +++ b/es-app/src/guis/GuiGamelistOptions.h @@ -19,6 +19,8 @@ public: private: void openGamelistFilter(); void openMetaDataEd(); + void startEditMode(); + void exitEditMode(); void jumpToLetter(); MenuComponent mMenu; diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 696e444e2..8973cc0f0 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -234,7 +234,10 @@ GuiMenu::GuiMenu(Window* window) : GuiComponent(window), mMenu(window, "MAIN MEN Settings::getInstance()->setString("ThemeSet", theme_set->getSelected()); if(needReload) + { + CollectionSystemManager::get()->updateSystemsList(); ViewController::get()->reloadAll(); // TODO - replace this with some sort of signal-based implementation + } }); } diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index e6a722ce0..45f31b8c6 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -194,7 +194,7 @@ void GuiMetaDataEd::save() mSavedCallback(); // update respective Collection Entries - CollectionSystemManager::get()->updateCollectionSystems(mScraperParams.game); + CollectionSystemManager::get()->refreshCollectionSystems(mScraperParams.game); } void GuiMetaDataEd::fetch() diff --git a/es-app/src/main.cpp b/es-app/src/main.cpp index 414f2b145..058121e65 100644 --- a/es-app/src/main.cpp +++ b/es-app/src/main.cpp @@ -376,6 +376,7 @@ int main(int argc, char* argv[]) delete window.peekGui(); window.deinit(); + CollectionSystemManager::deinit(); SystemData::deleteSystems(); LOG(LogInfo) << "EmulationStation cleanly shutting down."; diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index cb7b66a49..c3cb1484d 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -271,6 +271,17 @@ void ViewController::launch(FileData* game, Eigen::Vector3f center) } } +void ViewController::removeGameListView(SystemData* system) +{ + //if we already made one, return that one + auto exists = mGameListViews.find(system); + if(exists != mGameListViews.end()) + { + exists->second.reset(); + mGameListViews.erase(system); + } +} + std::shared_ptr ViewController::getGameListView(SystemData* system) { //if we already made one, return that one diff --git a/es-app/src/views/ViewController.h b/es-app/src/views/ViewController.h index 623796ac8..94ed9f562 100644 --- a/es-app/src/views/ViewController.h +++ b/es-app/src/views/ViewController.h @@ -77,6 +77,7 @@ public: std::shared_ptr getGameListView(SystemData* system); std::shared_ptr getSystemListView(); + void removeGameListView(SystemData* system); private: ViewController(Window* window); diff --git a/es-app/src/views/gamelist/BasicGameListView.cpp b/es-app/src/views/gamelist/BasicGameListView.cpp index 1202da118..38c47ea96 100644 --- a/es-app/src/views/gamelist/BasicGameListView.cpp +++ b/es-app/src/views/gamelist/BasicGameListView.cpp @@ -42,10 +42,9 @@ void BasicGameListView::onFileChanged(FileData* file, FileChangeType change) void BasicGameListView::populateList(const std::vector& files) { mList.clear(); + mHeaderText.setText(mRoot->getSystem()->getFullName()); if (files.size() > 0) { - mHeaderText.setText(files.at(0)->getSystem()->getFullName()); - for(auto it = files.begin(); it != files.end(); it++) { mList.add((*it)->getName(), *it, ((*it)->getType() == FOLDER)); @@ -143,7 +142,10 @@ std::vector BasicGameListView::getHelpPrompts() prompts.push_back(HelpPrompt("b", "back")); prompts.push_back(HelpPrompt("select", "options")); prompts.push_back(HelpPrompt("x", "random")); - if(Settings::getInstance()->getString("CollectionSystemsAuto").find("favorites") != std::string::npos && mRoot->getSystem()->isGameSystem()) - prompts.push_back(HelpPrompt("y", "favorite")); + if(mRoot->getSystem()->isGameSystem()) + { + const char* prompt = CollectionSystemManager::get()->getEditingCollection().c_str(); + prompts.push_back(HelpPrompt("y", prompt)); + } return prompts; } diff --git a/es-app/src/views/gamelist/ISimpleGameListView.cpp b/es-app/src/views/gamelist/ISimpleGameListView.cpp index 3b468f445..63325b520 100644 --- a/es-app/src/views/gamelist/ISimpleGameListView.cpp +++ b/es-app/src/views/gamelist/ISimpleGameListView.cpp @@ -93,6 +93,8 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) { mCursorStack.push(cursor); populateList(cursor->getChildrenListToDisplay()); + FileData* cursor = getCursor(); + setCursor(cursor); } } @@ -107,7 +109,12 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) Sound::getFromTheme(getTheme(), getName(), "back")->play(); }else{ onFocusLost(); - ViewController::get()->goToSystemView(getCursor()->getSystem()); + SystemData* systemToView = getCursor()->getSystem(); + if (systemToView->isCollection()) + { + systemToView = CollectionSystemManager::get()->getSystemToView(systemToView); + } + ViewController::get()->goToSystemView(systemToView); } return true; @@ -130,14 +137,17 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) }else if (config->isMappedTo("x", input)) { // go to random system game - setCursor(mRoot->getSystem()->getRandomGame()); - //ViewController::get()->goToRandomGame(); + FileData* randomGame = getCursor()->getSystem()->getRandomGame(); + if (randomGame) + { + setCursor(randomGame); + } return true; }else if (config->isMappedTo("y", input)) { - if(Settings::getInstance()->getString("CollectionSystemsAuto").find("favorites") != std::string::npos && mRoot->getSystem()->isGameSystem()) + if(mRoot->getSystem()->isGameSystem()) { - if(CollectionSystemManager::get()->toggleGameInCollection(getCursor(), "favorites")) + if(CollectionSystemManager::get()->toggleGameInCollection(getCursor())) { return true; } diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index bca120639..936d5b84f 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -99,6 +99,9 @@ void Settings::setDefaults() // Audio out device for Video playback using OMX player. mStringMap["OMXAudioDev"] = "both"; mStringMap["CollectionSystemsAuto"] = ""; + mStringMap["CollectionSystemsCustom"] = ""; + mBoolMap["SortAllSystems"] = false; + mBoolMap["UseCustomCollectionsSystem"] = true; // Audio out device for volume control #ifdef _RPI_ diff --git a/es-core/src/Util.cpp b/es-core/src/Util.cpp index 3a323ff0b..cc6981e85 100644 --- a/es-core/src/Util.cpp +++ b/es-core/src/Util.cpp @@ -296,12 +296,14 @@ std::vector commaStringToVector(std::string commaString) // from a comma separated string, get a vector of strings std::vector strs; boost::split(strs, commaString, boost::is_any_of(",")); + std::sort(strs.begin(), strs.end()); return strs; } -std::string commaStringToVector(std::vector stringVector) +std::string vectorToCommaString(std::vector stringVector) { std::string out = ""; + std::sort(stringVector.begin(), stringVector.end()); // from a vector of system names get comma separated string for(std::vector::iterator it = stringVector.begin() ; it != stringVector.end() ; it++ ) { diff --git a/es-core/src/Util.h b/es-core/src/Util.h index f75d15214..66568d823 100644 --- a/es-core/src/Util.h +++ b/es-core/src/Util.h @@ -46,4 +46,4 @@ std::string removeParenthesis(const std::string& str); std::vector commaStringToVector(std::string commaString); // turn a vector of strings into a comma-separated string -std::string commaStringToVector(std::vector stringVector); \ No newline at end of file +std::string vectorToCommaString(std::vector stringVector); \ No newline at end of file