mirror of
				https://github.com/RetroDECK/ES-DE.git
				synced 2025-04-10 19:15:13 +00:00 
			
		
		
		
	Merge pull request #168 from pjft/RetroPie-Virtual-Systems-PR
Adding support for "All", "Favorites" and "Last Played" systems
This commit is contained in:
		
						commit
						7ad62df4df
					
				|  | @ -12,6 +12,7 @@ set(ES_HEADERS | |||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.h | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.h | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemScreenSaver.h | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemManager.h | ||||
| 
 | ||||
|     # GuiComponents | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/components/AsyncReqComponent.h | ||||
|  | @ -30,6 +31,8 @@ set(ES_HEADERS | |||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.h | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperStart.h | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.h | ||||
| 
 | ||||
|     # Scrapers | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.h | ||||
|  | @ -63,6 +66,7 @@ set(ES_SOURCES | |||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.cpp | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.cpp | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemScreenSaver.cpp | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemManager.cpp | ||||
| 
 | ||||
|     # GuiComponents | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/components/AsyncReqComponent.cpp | ||||
|  | @ -80,6 +84,8 @@ set(ES_SOURCES | |||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.cpp | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperStart.cpp | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.cpp | ||||
| 
 | ||||
|     # Scrapers | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.cpp | ||||
|  |  | |||
							
								
								
									
										441
									
								
								es-app/src/CollectionSystemManager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										441
									
								
								es-app/src/CollectionSystemManager.cpp
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,441 @@ | |||
| #include "SystemData.h" | ||||
| #include "Gamelist.h" | ||||
| #include <boost/filesystem.hpp> | ||||
| #include "Util.h" | ||||
| #include <algorithm> | ||||
| #include <fstream> | ||||
| #include <stdlib.h> | ||||
| #include <SDL_joystick.h> | ||||
| #include "Renderer.h" | ||||
| #include "Log.h" | ||||
| #include "InputManager.h" | ||||
| #include <iostream> | ||||
| #include "Settings.h" | ||||
| #include "FileSorts.h" | ||||
| #include "pugixml/src/pugixml.hpp" | ||||
| #include "guis/GuiInfoPopup.h" | ||||
| 
 | ||||
| namespace fs = boost::filesystem; | ||||
| 
 | ||||
| CollectionSystemManager* CollectionSystemManager::sInstance = NULL; | ||||
| 
 | ||||
| CollectionSystemManager* CollectionSystemManager::get() | ||||
| { | ||||
| 	assert(sInstance); | ||||
| 	return sInstance; | ||||
| } | ||||
| 
 | ||||
| void CollectionSystemManager::init(Window* window) | ||||
| { | ||||
| 	assert(!sInstance); | ||||
| 	sInstance = new CollectionSystemManager(window); | ||||
| } | ||||
| 
 | ||||
| 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,    "custom",       "custom",       "filename, ascending",      "custom-collections",      true } | ||||
| 	}; | ||||
| 
 | ||||
| 	// create a map
 | ||||
| 	std::vector<CollectionSystemDecl> tempSystemDecl = std::vector<CollectionSystemDecl>(systemDecls, systemDecls + sizeof(systemDecls) / sizeof(systemDecls[0])); | ||||
| 
 | ||||
| 	for (std::vector<CollectionSystemDecl>::iterator it = tempSystemDecl.begin(); it != tempSystemDecl.end(); ++it ) | ||||
| 	{ | ||||
| 		mCollectionSystemDecls[(*it).name] = (*it); | ||||
| 		//mCollectionSystemDecls.insert(std::make_pair((*it).name,(*it)));
 | ||||
| 	} | ||||
| 
 | ||||
| 	// creating standard environment data
 | ||||
| 	mCollectionEnvData = new SystemEnvironmentData; | ||||
| 	mCollectionEnvData->mStartPath = ""; | ||||
| 	std::vector<std::string> exts; | ||||
| 	mCollectionEnvData->mSearchExtensions = exts; | ||||
| 	mCollectionEnvData->mLaunchCommand = ""; | ||||
| 	std::vector<PlatformIds::PlatformId> allPlatformIds; | ||||
| 	allPlatformIds.push_back(PlatformIds::PLATFORM_IGNORE); | ||||
| 	mCollectionEnvData->mPlatformIds = allPlatformIds; | ||||
| 
 | ||||
| 	// TO DO: Create custom editing help style
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| CollectionSystemManager::~CollectionSystemManager() | ||||
| { | ||||
| 	assert(sInstance == this); | ||||
| 	sInstance = NULL; | ||||
| } | ||||
| 
 | ||||
| void CollectionSystemManager::loadEnabledListFromSettings() | ||||
| { | ||||
| 	// we parse the settings
 | ||||
| 	std::vector<std::string> selected = commaStringToVector(Settings::getInstance()->getString("CollectionSystemsAuto")); | ||||
| 
 | ||||
| 	// iterate the map
 | ||||
| 	for(std::map<std::string, CollectionSystemData>::iterator it = mAllCollectionSystems.begin() ; it != mAllCollectionSystems.end() ; it++ ) | ||||
| 	{ | ||||
| 		it->second.isEnabled = (std::find(selected.begin(), selected.end(), it->first) != selected.end()); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void CollectionSystemManager::initAvailableSystemsList() | ||||
| { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> CollectionSystemManager::getSystemsFromConfig() | ||||
| { | ||||
| 	std::vector<std::string> systems; | ||||
| 	std::string path = SystemData::getConfigPath(false); | ||||
| 
 | ||||
| 	if(!fs::exists(path)) | ||||
| 	{ | ||||
| 		return systems; | ||||
| 	} | ||||
| 
 | ||||
| 	pugi::xml_document doc; | ||||
| 	pugi::xml_parse_result res = doc.load_file(path.c_str()); | ||||
| 
 | ||||
| 	if(!res) | ||||
| 	{ | ||||
| 		return systems; | ||||
| 	} | ||||
| 
 | ||||
| 	//actually read the file
 | ||||
| 	pugi::xml_node systemList = doc.child("systemList"); | ||||
| 
 | ||||
| 	if(!systemList) | ||||
| 	{ | ||||
| 		return systems; | ||||
| 	} | ||||
| 
 | ||||
| 	for(pugi::xml_node system = systemList.child("system"); system; system = system.next_sibling("system")) | ||||
| 	{ | ||||
| 		// theme folder
 | ||||
| 		std::string themeFolder = system.child("theme").text().get(); | ||||
| 		systems.push_back(themeFolder); | ||||
| 	} | ||||
| 	std::sort(systems.begin(), systems.end()); | ||||
| 	return systems; | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> CollectionSystemManager::getSystemsFromTheme() | ||||
| { | ||||
| 	std::vector<std::string> systems; | ||||
| 	auto themeSets = ThemeData::getThemeSets(); | ||||
| 	if(themeSets.empty()) | ||||
| 	{ | ||||
| 		// no theme sets available
 | ||||
| 		return systems; | ||||
| 	} | ||||
| 
 | ||||
| 	auto set = themeSets.find(Settings::getInstance()->getString("ThemeSet")); | ||||
| 	if(set == themeSets.end()) | ||||
| 	{ | ||||
| 		// currently selected theme set is missing, so just pick the first available set
 | ||||
| 		set = themeSets.begin(); | ||||
| 		Settings::getInstance()->setString("ThemeSet", set->first); | ||||
| 	} | ||||
| 
 | ||||
| 	boost::filesystem::path themePath = set->second.path; | ||||
| 
 | ||||
| 	if (fs::exists(themePath)) | ||||
| 	{ | ||||
| 		fs::directory_iterator end_itr; // default construction yields past-the-end
 | ||||
| 		for (fs::directory_iterator itr(themePath); itr != end_itr; ++itr) | ||||
| 		{ | ||||
| 			if (fs::is_directory(itr->status())) | ||||
| 			{ | ||||
| 				//... here you have a directory
 | ||||
| 				std::string folder = itr->path().string(); | ||||
| 				folder = folder.substr(themePath.string().size()+1); | ||||
| 
 | ||||
| 				if(fs::exists(set->second.getThemePath(folder))) | ||||
| 				{ | ||||
| 					systems.push_back(folder); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	std::sort(systems.begin(), systems.end()); | ||||
| 	return systems; | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> CollectionSystemManager::getAutoThemeFolders() | ||||
| { | ||||
| 	std::vector<std::string> systems; | ||||
| 	for(std::map<std::string, CollectionSystemDecl>::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<std::string> 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<std::string> themeSys = getSystemsFromTheme(); | ||||
| 	return std::find(themeSys.begin(), themeSys.end(), folder) != themeSys.end(); | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> CollectionSystemManager::getUnusedSystemsFromTheme() | ||||
| { | ||||
| 	std::vector<std::string> cfgSys = getSystemsFromConfig(); | ||||
| 	std::vector<std::string> themeSys = getSystemsFromTheme(); | ||||
| 	for(auto sysIt = themeSys.begin(); sysIt != themeSys.end(); ) | ||||
| 	{ | ||||
| 		if (std::find(cfgSys.begin(), cfgSys.end(), *sysIt) != cfgSys.end()) | ||||
| 		{ | ||||
| 			sysIt = themeSys.erase(sysIt); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			sysIt++; | ||||
| 		} | ||||
| 	} | ||||
| 	return themeSys; | ||||
| } | ||||
| 
 | ||||
| FileData::SortType CollectionSystemManager::getSortType(std::string desc) { | ||||
| 	std::vector<FileData::SortType> 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() | ||||
| { | ||||
| 	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(); | ||||
| } | ||||
| 
 | ||||
| void CollectionSystemManager::updateSystemsList() | ||||
| { | ||||
| 	// remove all Collection Systems
 | ||||
| 	for(auto sysIt = SystemData::sSystemVector.begin(); sysIt != SystemData::sSystemVector.end(); ) | ||||
| 	{ | ||||
| 		if ((*sysIt)->isCollection()) | ||||
| 		{ | ||||
| 			sysIt = SystemData::sSystemVector.erase(sysIt); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			sysIt++; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// add enabled ones
 | ||||
| 	for(std::map<std::string, CollectionSystemData>::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<std::string, CollectionSystemDecl>::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 ((*sysIt)->isGameSystem()) { | ||||
| 					std::vector<FileData*> 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(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<std::string, FileData*>& 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); | ||||
| 				} | ||||
| 				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" || | ||||
| 					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); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // this deletes collection files from collection systems
 | ||||
| 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
 | ||||
| 	for(auto sysIt = SystemData::sSystemVector.begin(); sysIt != SystemData::sSystemVector.end(); sysIt++) | ||||
| 	{ | ||||
| 		if ((*sysIt)->isCollection()) { | ||||
| 			const std::unordered_map<std::string, FileData*>& 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); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| bool CollectionSystemManager::toggleGameInCollection(FileData* file, std::string collection) | ||||
| { | ||||
| 	if (file->getType() == GAME) | ||||
| 	{ | ||||
| 		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; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| bool CollectionSystemManager::includeFileInAutoCollections(FileData* file) | ||||
| { | ||||
| 	// we exclude non-game files from collections (i.e. "kodi", at least)
 | ||||
| 	// 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"; | ||||
| } | ||||
							
								
								
									
										74
									
								
								es-app/src/CollectionSystemManager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								es-app/src/CollectionSystemManager.h
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <string> | ||||
| #include "FileData.h" | ||||
| #include "Window.h" | ||||
| #include "MetaData.h" | ||||
| #include "PlatformId.h" | ||||
| #include "ThemeData.h" | ||||
| #include "FileFilterIndex.h" | ||||
| #include "SystemData.h" | ||||
| #include "views/ViewController.h" | ||||
| 
 | ||||
| enum CollectionSystemType | ||||
| { | ||||
| 	AUTO_ALL_GAMES, | ||||
| 	AUTO_LAST_PLAYED, | ||||
| 	AUTO_FAVORITES, | ||||
| 	CUSTOM_COLLECTION | ||||
| }; | ||||
| 
 | ||||
| struct CollectionSystemDecl | ||||
| { | ||||
| 	CollectionSystemType type; // type of system
 | ||||
| 	std::string name; | ||||
| 	std::string longName; | ||||
| 	std::string defaultSort; | ||||
| 	std::string themeFolder; | ||||
| 	bool isCustom; | ||||
| }; | ||||
| 
 | ||||
| struct CollectionSystemData | ||||
| { | ||||
| 	SystemData* system; | ||||
| 	CollectionSystemDecl decl; | ||||
| 	bool isEnabled; | ||||
| }; | ||||
| 
 | ||||
| class CollectionSystemManager | ||||
| { | ||||
| public: | ||||
| 	CollectionSystemManager(Window* window); | ||||
| 	~CollectionSystemManager(); | ||||
| 	static void init(Window* window); | ||||
| 	static CollectionSystemManager* get(); | ||||
| 	void loadEnabledListFromSettings(); | ||||
| 	void loadCollectionSystems(); | ||||
| 	void updateCollectionSystems(FileData* file); | ||||
| 	void deleteCollectionFiles(FileData* file); | ||||
| 	inline std::map<std::string, CollectionSystemData> getCollectionSystems() { return mAllCollectionSystems; }; | ||||
| 	void updateSystemsList(); | ||||
| 	bool isThemeAutoCompatible(); | ||||
| 	bool toggleGameInCollection(FileData* file, std::string collection); | ||||
| 
 | ||||
| private: | ||||
| 	static CollectionSystemManager* sInstance; | ||||
| 	std::map<std::string, CollectionSystemDecl> mCollectionSystemDecls; | ||||
| 	SystemEnvironmentData* mCollectionEnvData; | ||||
| 	static FileData::SortType getSortType(std::string desc); | ||||
| 	void initAvailableSystemsList(); | ||||
| 	std::vector<std::string> getSystemsFromConfig(); | ||||
| 	std::vector<std::string> getSystemsFromTheme(); | ||||
| 	std::vector<std::string> getUnusedSystemsFromTheme(); | ||||
| 	std::vector<std::string> getAutoThemeFolders(); | ||||
| 	bool themeFolderExists(std::string folder); | ||||
| 	void loadAutoCollectionSystems(); | ||||
| 	void loadCustomCollectionSystems(); // TO DO NEXT
 | ||||
| 	SystemData* findCollectionSystem(std::string name); | ||||
| 	bool includeFileInAutoCollections(FileData* file); | ||||
| 	std::map<std::string, CollectionSystemData> mAllCollectionSystems; | ||||
| 	std::vector<SystemData*> mAutoCollectionSystems; | ||||
| 	std::vector<SystemData*> mCustomCollectionSystems; | ||||
| 	Window* mWindow; | ||||
| }; | ||||
|  | @ -1,54 +1,21 @@ | |||
| #include "FileData.h" | ||||
| #include "FileSorts.h" | ||||
| #include "views/ViewController.h" | ||||
| #include "SystemData.h" | ||||
| #include "Log.h" | ||||
| #include "AudioManager.h" | ||||
| #include "VolumeControl.h" | ||||
| #include "Util.h" | ||||
| 
 | ||||
| namespace fs = boost::filesystem; | ||||
| 
 | ||||
| std::string removeParenthesis(const std::string& str) | ||||
| { | ||||
| 	// remove anything in parenthesis or brackets
 | ||||
| 	// should be roughly equivalent to the regex replace "\((.*)\)|\[(.*)\]" with ""
 | ||||
| 	// I would love to just use regex, but it's not worth pulling in another boost lib for one function that is used once
 | ||||
| 
 | ||||
| 	std::string ret = str; | ||||
| 	size_t start, end; | ||||
| 
 | ||||
| 	static const int NUM_TO_REPLACE = 2; | ||||
| 	static const char toReplace[NUM_TO_REPLACE*2] = { '(', ')', '[', ']' }; | ||||
| 
 | ||||
| 	bool done = false; | ||||
| 	while(!done) | ||||
| 	{ | ||||
| 		done = true; | ||||
| 		for(int i = 0; i < NUM_TO_REPLACE; i++) | ||||
| 		{ | ||||
| 			end = ret.find_first_of(toReplace[i*2+1]); | ||||
| 			start = ret.find_last_of(toReplace[i*2], end); | ||||
| 
 | ||||
| 			if(start != std::string::npos && end != std::string::npos) | ||||
| 			{ | ||||
| 				ret.erase(start, end - start + 1); | ||||
| 				done = false; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// also strip whitespace
 | ||||
| 	end = ret.find_last_not_of(' '); | ||||
| 	if(end != std::string::npos) | ||||
| 		end++; | ||||
| 
 | ||||
| 	ret = ret.substr(0, end); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| FileData::FileData(FileType type, const fs::path& path, SystemData* system) | ||||
| 	: mType(type), mPath(path), mSystem(system), mParent(NULL), metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA) // metadata is REALLY set in the constructor!
 | ||||
| FileData::FileData(FileType type, const fs::path& path, SystemEnvironmentData* envData, SystemData* system) | ||||
| 	: mType(type), mPath(path), mSystem(system), mEnvData(envData), mSourceFileData(NULL), mParent(NULL), metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA) // metadata is REALLY set in the constructor!
 | ||||
| { | ||||
| 	// metadata needs at least a name field (since that's what getName() will return)
 | ||||
| 	if(metadata.get("name").empty()) | ||||
| 		metadata.set("name", getDisplayName()); | ||||
| 	mSystemName = system->getName(); | ||||
| } | ||||
| 
 | ||||
| FileData::~FileData() | ||||
|  | @ -56,7 +23,9 @@ FileData::~FileData() | |||
| 	if(mParent) | ||||
| 		mParent->removeChild(this); | ||||
| 
 | ||||
| 		mChildren.clear(); | ||||
| 	mSystem->getIndex()->removeFromIndex(this); | ||||
| 
 | ||||
| 	mChildren.clear(); | ||||
| } | ||||
| 
 | ||||
| std::string FileData::getDisplayName() const | ||||
|  | @ -81,6 +50,11 @@ const std::string& FileData::getThumbnailPath() const | |||
| 		return metadata.get("image"); | ||||
| } | ||||
| 
 | ||||
| const std::string& FileData::getName() | ||||
| { | ||||
| 	return metadata.get("name"); | ||||
| } | ||||
| 
 | ||||
| const std::vector<FileData*>& FileData::getChildrenListToDisplay() { | ||||
| 
 | ||||
| 	FileFilterIndex* idx = mSystem->getIndex(); | ||||
|  | @ -134,12 +108,21 @@ std::vector<FileData*> FileData::getFilesRecursive(unsigned int typeMask, bool d | |||
| 	return out; | ||||
| } | ||||
| 
 | ||||
| std::string FileData::getKey() { | ||||
| 	return getFileName(); | ||||
| } | ||||
| 
 | ||||
| FileData* FileData::getSourceFileData() | ||||
| { | ||||
| 	return this; | ||||
| } | ||||
| 
 | ||||
| void FileData::addChild(FileData* file) | ||||
| { | ||||
| 	assert(mType == FOLDER); | ||||
| 	assert(file->getParent() == NULL); | ||||
| 
 | ||||
| 	const std::string key = file->getPath().filename().string(); | ||||
| 	const std::string key = file->getKey(); | ||||
| 	if (mChildrenByFilename.find(key) == mChildrenByFilename.end()) | ||||
| 	{ | ||||
| 		mChildrenByFilename[key] = file; | ||||
|  | @ -152,7 +135,7 @@ void FileData::removeChild(FileData* file) | |||
| { | ||||
| 	assert(mType == FOLDER); | ||||
| 	assert(file->getParent() == this); | ||||
| 	mChildrenByFilename.erase(file->getPath().filename().string()); | ||||
| 	mChildrenByFilename.erase(file->getKey()); | ||||
| 	for(auto it = mChildren.begin(); it != mChildren.end(); it++) | ||||
| 	{ | ||||
| 		if(*it == file) | ||||
|  | @ -169,7 +152,7 @@ void FileData::removeChild(FileData* file) | |||
| 
 | ||||
| void FileData::sort(ComparisonFunction& comparator, bool ascending) | ||||
| { | ||||
| 	std::sort(mChildren.begin(), mChildren.end(), comparator); | ||||
| 	std::stable_sort(mChildren.begin(), mChildren.end(), comparator); | ||||
| 
 | ||||
| 	for(auto it = mChildren.begin(); it != mChildren.end(); it++) | ||||
| 	{ | ||||
|  | @ -185,3 +168,95 @@ void FileData::sort(const SortType& type) | |||
| { | ||||
| 	sort(*type.comparisonFunction, type.ascending); | ||||
| } | ||||
| 
 | ||||
| void FileData::launchGame(Window* window) | ||||
| { | ||||
| 	LOG(LogInfo) << "Attempting to launch game..."; | ||||
| 
 | ||||
| 	AudioManager::getInstance()->deinit(); | ||||
| 	VolumeControl::getInstance()->deinit(); | ||||
| 	window->deinit(); | ||||
| 
 | ||||
| 	std::string command = mEnvData->mLaunchCommand; | ||||
| 
 | ||||
| 	const std::string rom = escapePath(getPath()); | ||||
| 	const std::string basename = getPath().stem().string(); | ||||
| 	const std::string rom_raw = fs::path(getPath()).make_preferred().string(); | ||||
| 
 | ||||
| 	command = strreplace(command, "%ROM%", rom); | ||||
| 	command = strreplace(command, "%BASENAME%", basename); | ||||
| 	command = strreplace(command, "%ROM_RAW%", rom_raw); | ||||
| 
 | ||||
| 	LOG(LogInfo) << "	" << command; | ||||
| 	int exitCode = runSystemCommand(command); | ||||
| 
 | ||||
| 	if(exitCode != 0) | ||||
| 	{ | ||||
| 		LOG(LogWarning) << "...launch terminated with nonzero exit code " << exitCode << "!"; | ||||
| 	} | ||||
| 
 | ||||
| 	window->init(); | ||||
| 	VolumeControl::getInstance()->init(); | ||||
| 	AudioManager::getInstance()->init(); | ||||
| 	window->normalizeNextUpdate(); | ||||
| 
 | ||||
| 	//update number of times the game has been launched
 | ||||
| 
 | ||||
| 	FileData* gameToUpdate = getSourceFileData(); | ||||
| 
 | ||||
| 	int timesPlayed = gameToUpdate->metadata.getInt("playcount") + 1; | ||||
| 	gameToUpdate->metadata.set("playcount", std::to_string(static_cast<long long>(timesPlayed))); | ||||
| 
 | ||||
| 	//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); | ||||
| } | ||||
| 
 | ||||
| CollectionFileData::CollectionFileData(FileData* file, SystemData* system) | ||||
| 	: FileData(file->getType(), file->getPath(), file->getSystemEnvData(), system)/*,
 | ||||
| 	  mSourceFileData(file->getSourceFileData()), | ||||
| 	  mParent(NULL), | ||||
| 	  metadata(file->getSourceFileData()->metadata)*/ | ||||
| { | ||||
| 	// we use this constructor to create a clone of the filedata, and change its system
 | ||||
| 	mSourceFileData = file->getSourceFileData(); | ||||
| 	refreshMetadata(); | ||||
| 	mParent = NULL; | ||||
| 	metadata = mSourceFileData->metadata; | ||||
| 	mSystemName = mSourceFileData->getSystem()->getName(); | ||||
| } | ||||
| 
 | ||||
| CollectionFileData::~CollectionFileData() | ||||
| { | ||||
| 	// need to remove collection file data at the collection object destructor
 | ||||
| 	if(mParent) | ||||
| 		mParent->removeChild(this); | ||||
| 	mParent = NULL; | ||||
| } | ||||
| 
 | ||||
| std::string CollectionFileData::getKey() { | ||||
| 	return getFullPath(); | ||||
| } | ||||
| 
 | ||||
| FileData* CollectionFileData::getSourceFileData() | ||||
| { | ||||
| 	return mSourceFileData; | ||||
| } | ||||
| 
 | ||||
| void CollectionFileData::refreshMetadata() | ||||
| { | ||||
| 	metadata = mSourceFileData->metadata; | ||||
| 	mDirty = true; | ||||
| } | ||||
| 
 | ||||
| const std::string& CollectionFileData::getName() | ||||
| { | ||||
| 	if (mDirty) { | ||||
| 		mCollectionFileName = removeParenthesis(mSourceFileData->metadata.get("name")); | ||||
| 		boost::trim(mCollectionFileName); | ||||
| 		mCollectionFileName += " [" + strToUpper(mSourceFileData->getSystem()->getName()) + "]"; | ||||
| 		mDirty = false; | ||||
| 	} | ||||
| 	return mCollectionFileName; | ||||
| } | ||||
|  | @ -7,6 +7,7 @@ | |||
| #include "MetaData.h" | ||||
| 
 | ||||
| class SystemData; | ||||
| struct SystemEnvironmentData; | ||||
| 
 | ||||
| enum FileType | ||||
| { | ||||
|  | @ -27,24 +28,21 @@ enum FileChangeType | |||
| const char* fileTypeToString(FileType type); | ||||
| FileType stringToFileType(const char* str); | ||||
| 
 | ||||
| // Remove (.*) and [.*] from str
 | ||||
| std::string removeParenthesis(const std::string& str); | ||||
| 
 | ||||
| // A tree node that holds information for a file.
 | ||||
| class FileData | ||||
| { | ||||
| public: | ||||
| 	FileData(FileType type, const boost::filesystem::path& path, SystemData* system); | ||||
| 	FileData(FileType type, const boost::filesystem::path& path, SystemEnvironmentData* envData, SystemData* system); | ||||
| 	virtual ~FileData(); | ||||
| 
 | ||||
| 	inline const std::string& getName() const { return metadata.get("name"); } | ||||
| 	virtual const std::string& getName(); | ||||
| 	inline FileType getType() const { return mType; } | ||||
| 	inline const boost::filesystem::path& getPath() const { return mPath; } | ||||
| 	inline FileData* getParent() const { return mParent; } | ||||
| 	inline const std::unordered_map<std::string, FileData*>& getChildrenByFilename() const { return mChildrenByFilename; } | ||||
| 	inline const std::vector<FileData*>& getChildren() const { return mChildren; } | ||||
| 	inline SystemData* getSystem() const { return mSystem; } | ||||
| 
 | ||||
| 	inline SystemEnvironmentData* getSystemEnvData() const { return mEnvData; } | ||||
| 	virtual const std::string& getThumbnailPath() const; | ||||
| 	virtual const std::string& getVideoPath() const; | ||||
| 	virtual const std::string& getMarqueePath() const; | ||||
|  | @ -57,12 +55,22 @@ public: | |||
| 
 | ||||
| 	inline bool isPlaceHolder() { return mType == PLACEHOLDER; }; | ||||
| 
 | ||||
| 	virtual inline void refreshMetadata() { return; }; | ||||
| 
 | ||||
| 	virtual std::string getKey(); | ||||
| 	inline std::string getFullPath() { return getPath().string(); }; | ||||
| 	inline std::string getFileName() { return getPath().filename().string(); }; | ||||
| 	virtual FileData* getSourceFileData(); | ||||
| 	inline std::string getSystemName() const { return mSystemName; }; | ||||
| 
 | ||||
| 	// Returns our best guess at the "real" name for this file (will attempt to perform MAME name translation)
 | ||||
| 	std::string getDisplayName() const; | ||||
| 
 | ||||
| 	// As above, but also remove parenthesis
 | ||||
| 	std::string getCleanName() const; | ||||
| 
 | ||||
| 	void launchGame(Window* window); | ||||
| 
 | ||||
| 	typedef bool ComparisonFunction(const FileData* a, const FileData* b); | ||||
| 	struct SortType | ||||
| 	{ | ||||
|  | @ -76,15 +84,34 @@ public: | |||
| 
 | ||||
| 	void sort(ComparisonFunction& comparator, bool ascending = true); | ||||
| 	void sort(const SortType& type); | ||||
| 
 | ||||
| 	MetaDataList metadata; | ||||
| 
 | ||||
| protected: | ||||
| 	FileData* mSourceFileData; | ||||
| 	FileData* mParent; | ||||
| 	std::string mSystemName; | ||||
| 
 | ||||
| private: | ||||
| 	FileType mType; | ||||
| 	boost::filesystem::path mPath; | ||||
| 	SystemEnvironmentData* mEnvData; | ||||
| 	SystemData* mSystem; | ||||
| 	FileData* mParent; | ||||
| 	std::unordered_map<std::string,FileData*> mChildrenByFilename; | ||||
| 	std::vector<FileData*> mChildren; | ||||
| 	std::vector<FileData*> mFilteredChildren; | ||||
| }; | ||||
| 
 | ||||
| class CollectionFileData : public FileData | ||||
| { | ||||
| public: | ||||
| 	CollectionFileData(FileData* file, SystemData* system); | ||||
| 	~CollectionFileData(); | ||||
| 	const std::string& getName(); | ||||
| 	void refreshMetadata(); | ||||
| 	FileData* getSourceFileData(); | ||||
| 	std::string getKey(); | ||||
| private: | ||||
| 	// needs to be updated when metadata changes
 | ||||
| 	std::string mCollectionFileName; | ||||
| 	bool mDirty; | ||||
| }; | ||||
|  | @ -4,10 +4,11 @@ | |||
| #define INCLUDE_UNKNOWN false; | ||||
| 
 | ||||
| FileFilterIndex::FileFilterIndex() | ||||
| 	: filterByGenre(false), filterByPlayers(false), filterByPubDev(false), filterByRatings(false) | ||||
| 	: filterByGenre(false), filterByPlayers(false), filterByPubDev(false), filterByRatings(false), filterByFavorites(false) | ||||
| { | ||||
| 	FilterDataDecl filterDecls[] = { | ||||
| 		//type 				//allKeys 				//filteredBy 		//filteredKeys 				//primaryKey 	//hasSecondaryKey 	//secondaryKey 	//menuLabel
 | ||||
| 		{ FAVORITES_FILTER, &favoritesIndexAllKeys, &filterByFavorites,	&favoritesIndexFilteredKeys,"favorite",		false,				"",				"FAVORITES"	}, | ||||
| 		{ GENRE_FILTER, 	&genreIndexAllKeys, 	&filterByGenre,		&genreIndexFilteredKeys, 	"genre",		true,				"genre",		"GENRE"	}, | ||||
| 		{ PLAYER_FILTER, 	&playersIndexAllKeys, 	&filterByPlayers,	&playersIndexFilteredKeys, 	"players",		false,				"",				"PLAYERS"	}, | ||||
| 		{ PUBDEV_FILTER, 	&pubDevIndexAllKeys, 	&filterByPubDev,	&pubDevIndexFilteredKeys, 	"developer",	true,				"publisher",	"PUBLISHER / DEVELOPER"	}, | ||||
|  | @ -23,7 +24,7 @@ FileFilterIndex::~FileFilterIndex() | |||
| 	clearIndex(playersIndexAllKeys); | ||||
| 	clearIndex(pubDevIndexAllKeys); | ||||
| 	clearIndex(ratingsIndexAllKeys); | ||||
| 
 | ||||
| 	clearIndex(favoritesIndexAllKeys); | ||||
| } | ||||
| 
 | ||||
| std::vector<FilterDataDecl>& FileFilterIndex::getFilterDataDecls() | ||||
|  | @ -96,6 +97,13 @@ std::string FileFilterIndex::getIndexableKey(FileData* game, FilterIndexType typ | |||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 		case FAVORITES_FILTER: | ||||
| 		{ | ||||
| 			if (game->getType() != GAME) | ||||
| 				return "FALSE"; | ||||
| 			key = strToUpper(game->metadata.get("favorite")); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	boost::trim(key); | ||||
| 	if (key.empty() || (type == RATINGS_FILTER && key == "0 STARS")) { | ||||
|  | @ -110,6 +118,7 @@ void FileFilterIndex::addToIndex(FileData* game) | |||
| 	managePlayerEntryInIndex(game); | ||||
| 	managePubDevEntryInIndex(game); | ||||
| 	manageRatingsEntryInIndex(game); | ||||
| 	manageFavoritesEntryInIndex(game); | ||||
| } | ||||
| 
 | ||||
| void FileFilterIndex::removeFromIndex(FileData* game) | ||||
|  | @ -118,6 +127,7 @@ void FileFilterIndex::removeFromIndex(FileData* game) | |||
| 	managePlayerEntryInIndex(game, true); | ||||
| 	managePubDevEntryInIndex(game, true); | ||||
| 	manageRatingsEntryInIndex(game, true); | ||||
| 	manageFavoritesEntryInIndex(game, true); | ||||
| } | ||||
| 
 | ||||
| void FileFilterIndex::setFilter(FilterIndexType type, std::vector<std::string>* values) | ||||
|  | @ -160,18 +170,21 @@ void FileFilterIndex::clearAllFilters() | |||
| 
 | ||||
| void FileFilterIndex::debugPrintIndexes() | ||||
| { | ||||
| 	LOG(LogInfo) << "Printing Indexes..."; | ||||
| 	LOG(LogError) << "Printing Indexes..."; | ||||
| 	for (auto x: playersIndexAllKeys) { | ||||
| 		LOG(LogInfo) << "Multiplayer Index: " << x.first << ": " << x.second; | ||||
| 		LOG(LogError) << "Multiplayer Index: " << x.first << ": " << x.second; | ||||
| 	} | ||||
| 	for (auto x: genreIndexAllKeys) { | ||||
| 		LOG(LogInfo) << "Genre Index: " << x.first << ": " << x.second; | ||||
| 		LOG(LogError) << "Genre Index: " << x.first << ": " << x.second; | ||||
| 	} | ||||
| 	for (auto x: ratingsIndexAllKeys) { | ||||
| 		LOG(LogInfo) << "Ratings Index: " << x.first << ": " << x.second; | ||||
| 		LOG(LogError) << "Ratings Index: " << x.first << ": " << x.second; | ||||
| 	} | ||||
| 	for (auto x: pubDevIndexAllKeys) { | ||||
| 		LOG(LogInfo) << "PubDev Index: " << x.first << ": " << x.second; | ||||
| 		LOG(LogError) << "PubDev Index: " << x.first << ": " << x.second; | ||||
| 	} | ||||
| 	for (auto x: favoritesIndexAllKeys) { | ||||
| 		LOG(LogError) << "Favorites Index: " << x.first << ": " << x.second; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | @ -232,10 +245,10 @@ bool FileFilterIndex::showFile(FileData* game) | |||
| 
 | ||||
| bool FileFilterIndex::isKeyBeingFilteredBy(std::string key, FilterIndexType type) | ||||
| { | ||||
| 	const FilterIndexType filterTypes[4] = { PLAYER_FILTER, RATINGS_FILTER, GENRE_FILTER, PUBDEV_FILTER }; | ||||
| 	std::vector<std::string> filterKeysList[4] = { playersIndexFilteredKeys, ratingsIndexFilteredKeys, genreIndexFilteredKeys, pubDevIndexFilteredKeys }; | ||||
| 	const FilterIndexType filterTypes[5] = { FAVORITES_FILTER, PLAYER_FILTER, RATINGS_FILTER, GENRE_FILTER, PUBDEV_FILTER }; | ||||
| 	std::vector<std::string> filterKeysList[5] = { favoritesIndexFilteredKeys, playersIndexFilteredKeys, ratingsIndexFilteredKeys, genreIndexFilteredKeys, pubDevIndexFilteredKeys }; | ||||
| 
 | ||||
| 	for (int i = 0; i < 4; i++) | ||||
| 	for (int i = 0; i < 5; i++) | ||||
| 	{ | ||||
| 		if (filterTypes[i] == type) | ||||
| 		{ | ||||
|  | @ -345,6 +358,19 @@ void FileFilterIndex::manageRatingsEntryInIndex(FileData* game, bool remove) | |||
| 	manageIndexEntry(&ratingsIndexAllKeys, key, remove); | ||||
| } | ||||
| 
 | ||||
| void FileFilterIndex::manageFavoritesEntryInIndex(FileData* game, bool remove) | ||||
| { | ||||
| 	// flag for including unknowns
 | ||||
| 	bool includeUnknown = INCLUDE_UNKNOWN; | ||||
| 	std::string key = getIndexableKey(game, FAVORITES_FILTER, false); | ||||
| 	if (!includeUnknown && key == UNKNOWN_LABEL) { | ||||
| 		// no valid favorites info found
 | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	manageIndexEntry(&favoritesIndexAllKeys, key, remove); | ||||
| } | ||||
| 
 | ||||
| void FileFilterIndex::manageIndexEntry(std::map<std::string, int>* index, std::string key, bool remove) { | ||||
| 	bool includeUnknown = INCLUDE_UNKNOWN; | ||||
| 	if (!includeUnknown && key == UNKNOWN_LABEL) | ||||
|  | @ -354,7 +380,7 @@ void FileFilterIndex::manageIndexEntry(std::map<std::string, int>* index, std::s | |||
| 		if (index->find(key) == index->end()) | ||||
| 		{ | ||||
| 			// this shouldn't happen
 | ||||
| 			LOG(LogError) << "Couldn't find entry in index! " << key; | ||||
| 			LOG(LogInfo) << "Couldn't find entry in index! " << key; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
|  |  | |||
|  | @ -15,7 +15,8 @@ enum FilterIndexType | |||
| 	GENRE_FILTER, | ||||
| 	PLAYER_FILTER, | ||||
| 	PUBDEV_FILTER, | ||||
| 	RATINGS_FILTER | ||||
| 	RATINGS_FILTER, | ||||
| 	FAVORITES_FILTER | ||||
| }; | ||||
| 
 | ||||
| struct FilterDataDecl | ||||
|  | @ -41,10 +42,8 @@ public: | |||
| 	void clearAllFilters(); | ||||
| 	void debugPrintIndexes(); | ||||
| 	bool showFile(FileData* game); | ||||
| 	bool isFiltered() { return (filterByGenre || filterByPlayers || filterByPubDev || filterByRatings); }; | ||||
| 	bool isFiltered() { return (filterByGenre || filterByPlayers || filterByPubDev || filterByRatings || filterByFavorites); }; | ||||
| 	bool isKeyBeingFilteredBy(std::string key, FilterIndexType type); | ||||
| 	std::map<std::string, int>* getGenreAllIndexedKeys() { return &genreIndexAllKeys; }; | ||||
| 	std::vector<std::string>* getGenreFilteredKeys() { return &genreIndexFilteredKeys; }; | ||||
| 	std::vector<FilterDataDecl>& getFilterDataDecls(); | ||||
| private: | ||||
| 	std::vector<FilterDataDecl> filterDataDecl; | ||||
|  | @ -54,6 +53,7 @@ private: | |||
| 	void managePlayerEntryInIndex(FileData* game, bool remove = false); | ||||
| 	void managePubDevEntryInIndex(FileData* game, bool remove = false); | ||||
| 	void manageRatingsEntryInIndex(FileData* game, bool remove = false); | ||||
| 	void manageFavoritesEntryInIndex(FileData* game, bool remove = false); | ||||
| 
 | ||||
| 	void manageIndexEntry(std::map<std::string, int>* index, std::string key, bool remove); | ||||
| 
 | ||||
|  | @ -63,16 +63,19 @@ private: | |||
| 	bool filterByPlayers; | ||||
| 	bool filterByPubDev; | ||||
| 	bool filterByRatings; | ||||
| 	bool filterByFavorites; | ||||
| 
 | ||||
| 	std::map<std::string, int> genreIndexAllKeys; | ||||
| 	std::map<std::string, int> playersIndexAllKeys; | ||||
| 	std::map<std::string, int> pubDevIndexAllKeys; | ||||
| 	std::map<std::string, int> ratingsIndexAllKeys; | ||||
| 	std::map<std::string, int> favoritesIndexAllKeys; | ||||
| 
 | ||||
| 	std::vector<std::string> genreIndexFilteredKeys; | ||||
| 	std::vector<std::string> playersIndexFilteredKeys; | ||||
| 	std::vector<std::string> pubDevIndexFilteredKeys; | ||||
| 	std::vector<std::string> ratingsIndexFilteredKeys; | ||||
| 	std::vector<std::string> favoritesIndexFilteredKeys; | ||||
| 
 | ||||
| 	FileData* mRootFolder; | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,8 +3,8 @@ | |||
| namespace FileSorts | ||||
| { | ||||
| 	const FileData::SortType typesArr[] = { | ||||
| 		FileData::SortType(&compareFileName, true, "filename, ascending"), | ||||
| 		FileData::SortType(&compareFileName, false, "filename, descending"), | ||||
| 		FileData::SortType(&compareName, true, "filename, ascending"), | ||||
| 		FileData::SortType(&compareName, false, "filename, descending"), | ||||
| 
 | ||||
| 		FileData::SortType(&compareRating, true, "rating, ascending"), | ||||
| 		FileData::SortType(&compareRating, false, "rating, descending"), | ||||
|  | @ -28,16 +28,20 @@ namespace FileSorts | |||
| 		FileData::SortType(&compareDeveloper, false, "developer, descending"), | ||||
| 
 | ||||
| 		FileData::SortType(&comparePublisher, true, "publisher, ascending"), | ||||
| 		FileData::SortType(&comparePublisher, false, "publisher, descending") | ||||
| 		FileData::SortType(&comparePublisher, false, "publisher, descending"), | ||||
| 
 | ||||
| 		FileData::SortType(&compareSystem, true, "system, ascending"), | ||||
| 		FileData::SortType(&compareSystem, false, "system, descending") | ||||
| 	}; | ||||
| 
 | ||||
| 	const std::vector<FileData::SortType> SortTypes(typesArr, typesArr + sizeof(typesArr)/sizeof(typesArr[0])); | ||||
| 
 | ||||
| 	//returns if file1 should come before file2
 | ||||
| 	bool compareFileName(const FileData* file1, const FileData* file2) | ||||
| 	bool compareName(const FileData* file1, const FileData* file2) | ||||
| 	{ | ||||
| 		std::string name1 = file1->getName(); | ||||
| 		std::string name2 = file2->getName(); | ||||
| 		// we compare the actual metadata name, as collection files have the system appended which messes up the order
 | ||||
| 		std::string name1 = file1->metadata.get("name"); | ||||
| 		std::string name2 = file2->metadata.get("name"); | ||||
| 		transform(name1.begin(), name1.end(), name1.begin(), ::toupper); | ||||
| 		transform(name2.begin(), name2.end(), name2.begin(), ::toupper); | ||||
| 		return name1.compare(name2) < 0; | ||||
|  | @ -62,9 +66,11 @@ namespace FileSorts | |||
| 	bool compareLastPlayed(const FileData* file1, const FileData* file2) | ||||
| 	{ | ||||
| 		//only games have lastplayed metadata
 | ||||
| 		// since it's stored as a POSIX string (YYYYMMDDTHHMMSS,fffffffff), we can compare as a string
 | ||||
| 		// as it's a lot faster than the time casts and then time comparisons
 | ||||
| 		if(file1->metadata.getType() == GAME_METADATA && file2->metadata.getType() == GAME_METADATA) | ||||
| 		{ | ||||
| 			return (file1)->metadata.getTime("lastplayed") < (file2)->metadata.getTime("lastplayed"); | ||||
| 			return (file1)->metadata.get("lastplayed") < (file2)->metadata.get("lastplayed"); | ||||
| 		} | ||||
| 
 | ||||
| 		return false; | ||||
|  | @ -106,4 +112,13 @@ namespace FileSorts | |||
| 		transform(publisher2.begin(), publisher2.end(), publisher2.begin(), ::toupper); | ||||
| 		return publisher1.compare(publisher2) < 0; | ||||
| 	} | ||||
| 
 | ||||
| 	bool compareSystem(const FileData* file1, const FileData* file2) | ||||
| 	{ | ||||
| 		std::string system1 = file1->getSystemName(); | ||||
| 		std::string system2 = file2->getSystemName(); | ||||
| 		transform(system1.begin(), system1.end(), system1.begin(), ::toupper); | ||||
| 		transform(system2.begin(), system2.end(), system2.begin(), ::toupper); | ||||
| 		return system1.compare(system2) < 0; | ||||
| 	} | ||||
| }; | ||||
|  |  | |||
|  | @ -2,10 +2,11 @@ | |||
| 
 | ||||
| #include <vector> | ||||
| #include "FileData.h" | ||||
| #include "SystemData.h" | ||||
| 
 | ||||
| namespace FileSorts | ||||
| { | ||||
| 	bool compareFileName(const FileData* file1, const FileData* file2); | ||||
| 	bool compareName(const FileData* file1, const FileData* file2); | ||||
| 	bool compareRating(const FileData* file1, const FileData* file2); | ||||
| 	bool compareTimesPlayed(const FileData* file1, const FileData* fil2); | ||||
| 	bool compareLastPlayed(const FileData* file1, const FileData* file2); | ||||
|  | @ -14,6 +15,7 @@ namespace FileSorts | |||
| 	bool compareGenre(const FileData* file1, const FileData* file2); | ||||
| 	bool compareDeveloper(const FileData* file1, const FileData* file2); | ||||
| 	bool comparePublisher(const FileData* file1, const FileData* file2); | ||||
| 	bool compareSystem(const FileData* file1, const FileData* file2); | ||||
| 
 | ||||
| 	extern const std::vector<FileData::SortType> SortTypes; | ||||
| }; | ||||
|  |  | |||
|  | @ -54,7 +54,7 @@ FileData* findOrCreateFile(SystemData* system, const boost::filesystem::path& pa | |||
| 				return NULL; | ||||
| 			} | ||||
| 
 | ||||
| 			FileData* file = new FileData(type, path, system); | ||||
| 			FileData* file = new FileData(type, path, system->getSystemEnvData(), system); | ||||
| 			treeNode->addChild(file); | ||||
| 			return file; | ||||
| 		} | ||||
|  | @ -70,7 +70,7 @@ FileData* findOrCreateFile(SystemData* system, const boost::filesystem::path& pa | |||
| 			} | ||||
| 
 | ||||
| 			// create missing folder
 | ||||
| 			FileData* folder = new FileData(FOLDER, treeNode->getPath().stem() / *path_it, system); | ||||
| 			FileData* folder = new FileData(FOLDER, treeNode->getPath().stem() / *path_it, system->getSystemEnvData(), system); | ||||
| 			treeNode->addChild(folder); | ||||
| 			treeNode = folder; | ||||
| 		} | ||||
|  |  | |||
|  | @ -5,38 +5,39 @@ | |||
| 
 | ||||
| namespace fs = boost::filesystem; | ||||
| 
 | ||||
| MetaDataDecl gameDecls[] = {  | ||||
| 	// key,			type,					default,			statistic,	name in GuiMetaDataEd,	prompt in GuiMetaDataEd
 | ||||
| 	{"name",		MD_STRING,				"", 				false,		"name",					"enter game name"},  | ||||
| 	{"desc",		MD_MULTILINE_STRING,	"", 				false,		"description",			"enter description"}, | ||||
| 	{"image",		MD_PATH,				"", 				false,		"image",				"enter path to image"}, | ||||
| 	{"video",		MD_PATH		,			"", 				false,		"video",				"enter path to video"}, | ||||
| 	{"marquee",		MD_PATH,				"", 				false,		"marquee",				"enter path to marquee"}, | ||||
| 	{"thumbnail",	MD_PATH,				"", 				false,		"thumbnail",			"enter path to thumbnail"}, | ||||
| 	{"rating",		MD_RATING,				"0.000000", 		false,		"rating",				"enter rating"}, | ||||
| 	{"releasedate", MD_DATE,				"not-a-date-time", 	false,		"release date",			"enter release date"}, | ||||
| 	{"developer",	MD_STRING,				"unknown",			false,		"developer",			"enter game developer"}, | ||||
| 	{"publisher",	MD_STRING,				"unknown",			false,		"publisher",			"enter game publisher"}, | ||||
| 	{"genre",		MD_STRING,				"unknown",			false,		"genre",				"enter game genre"}, | ||||
| 	{"players",		MD_INT,					"1",				false,		"players",				"enter number of players"}, | ||||
| 	{"playcount",	MD_INT,					"0",				true,		"play count",			"enter number of times played"}, | ||||
| 	{"lastplayed",	MD_TIME,				"0", 				true,		"last played",			"enter last played date"} | ||||
| MetaDataDecl gameDecls[] = { | ||||
| 	// key,         type,                   default,            statistic,  name in GuiMetaDataEd,  prompt in GuiMetaDataEd
 | ||||
| 	{"name",        MD_STRING,              "",                 false,      "name",                 "enter game name"}, | ||||
| 	{"desc",        MD_MULTILINE_STRING,    "",                 false,      "description",          "enter description"}, | ||||
| 	{"image",       MD_PATH,                "",                 false,      "image",                "enter path to image"}, | ||||
| 	{"video",       MD_PATH     ,           "",                 false,      "video",                "enter path to video"}, | ||||
| 	{"marquee",     MD_PATH,                "",                 false,      "marquee",              "enter path to marquee"}, | ||||
| 	{"thumbnail",   MD_PATH,                "",                 false,      "thumbnail",            "enter path to thumbnail"}, | ||||
| 	{"rating",      MD_RATING,              "0.000000",         false,      "rating",               "enter rating"}, | ||||
| 	{"releasedate", MD_DATE,                "not-a-date-time",  false,      "release date",         "enter release date"}, | ||||
| 	{"developer",   MD_STRING,              "unknown",          false,      "developer",            "enter game developer"}, | ||||
| 	{"publisher",   MD_STRING,              "unknown",          false,      "publisher",            "enter game publisher"}, | ||||
| 	{"genre",       MD_STRING,              "unknown",          false,      "genre",                "enter game genre"}, | ||||
| 	{"players",     MD_INT,                 "1",                false,      "players",              "enter number of players"}, | ||||
| 	{"favorite",    MD_BOOL,                "false",            false,      "favorite",             "enter favorite off/on"}, | ||||
| 	{"playcount",   MD_INT,                 "0",                true,       "play count",           "enter number of times played"}, | ||||
| 	{"lastplayed",  MD_TIME,                "0",                true,       "last played",          "enter last played date"} | ||||
| }; | ||||
| const std::vector<MetaDataDecl> gameMDD(gameDecls, gameDecls + sizeof(gameDecls) / sizeof(gameDecls[0])); | ||||
| 
 | ||||
| MetaDataDecl folderDecls[] = {  | ||||
| 	{"name",		MD_STRING,				"", 	false,		"name",					"enter game name"},  | ||||
| 	{"desc",		MD_MULTILINE_STRING,	"", 	false,		"description",			"enter description"}, | ||||
| 	{"image",		MD_PATH,				"", 	false,		"image",				"enter path to image"}, | ||||
| 	{"thumbnail",	MD_PATH,				"", 	false,		"thumbnail",			"enter path to thumbnail"}, | ||||
| 	{"video",	MD_PATH,				"", 	false,		"video",				"enter path to video"}, | ||||
| 	{"marquee",	MD_PATH,				"", 	false,		"marquee",				"enter path to marquee"}, | ||||
| 	{"rating",		MD_RATING,				"0.000000", 		false,		"rating",				"enter rating"}, | ||||
| 	{"releasedate", MD_DATE,				"not-a-date-time", 	false,		"release date",			"enter release date"}, | ||||
| 	{"developer",	MD_STRING,				"unknown",			false,		"developer",			"enter game developer"}, | ||||
| 	{"publisher",	MD_STRING,				"unknown",			false,		"publisher",			"enter game publisher"}, | ||||
| 	{"genre",		MD_STRING,				"unknown",			false,		"genre",				"enter game genre"}, | ||||
| 	{"players",		MD_INT,					"1",				false,		"players",				"enter number of players"} | ||||
| MetaDataDecl folderDecls[] = { | ||||
| 	{"name",        MD_STRING,              "",                 false,      "name",                 "enter game name"}, | ||||
| 	{"desc",        MD_MULTILINE_STRING,    "",                 false,      "description",          "enter description"}, | ||||
| 	{"image",       MD_PATH,                "",                 false,      "image",                "enter path to image"}, | ||||
| 	{"thumbnail",   MD_PATH,                "",                 false,      "thumbnail",            "enter path to thumbnail"}, | ||||
| 	{"video",       MD_PATH,                "",                 false,      "video",                "enter path to video"}, | ||||
| 	{"marquee",     MD_PATH,                "",                 false,      "marquee",              "enter path to marquee"}, | ||||
| 	{"rating",      MD_RATING,              "0.000000",         false,      "rating",               "enter rating"}, | ||||
| 	{"releasedate", MD_DATE,                "not-a-date-time",  false,      "release date",         "enter release date"}, | ||||
| 	{"developer",   MD_STRING,              "unknown",          false,      "developer",            "enter game developer"}, | ||||
| 	{"publisher",   MD_STRING,              "unknown",          false,      "publisher",            "enter game publisher"}, | ||||
| 	{"genre",       MD_STRING,              "unknown",          false,      "genre",                "enter game genre"}, | ||||
| 	{"players",     MD_INT,                 "1",                false,      "players",              "enter number of players"} | ||||
| }; | ||||
| const std::vector<MetaDataDecl> folderMDD(folderDecls, folderDecls + sizeof(folderDecls) / sizeof(folderDecls[0])); | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ enum MetaDataType | |||
| 	MD_STRING, | ||||
| 	MD_INT, | ||||
| 	MD_FLOAT, | ||||
| 	MD_BOOL, | ||||
| 
 | ||||
| 	//specialized types
 | ||||
| 	MD_MULTILINE_STRING, | ||||
|  |  | |||
|  | @ -5,8 +5,6 @@ | |||
| #include <stdlib.h> | ||||
| #include <SDL_joystick.h> | ||||
| #include "Renderer.h" | ||||
| #include "AudioManager.h" | ||||
| #include "VolumeControl.h" | ||||
| #include "Log.h" | ||||
| #include "InputManager.h" | ||||
| #include <iostream> | ||||
|  | @ -17,45 +15,38 @@ std::vector<SystemData*> SystemData::sSystemVector; | |||
| 
 | ||||
| namespace fs = boost::filesystem; | ||||
| 
 | ||||
| SystemData::SystemData(const std::string& name, const std::string& fullName, const std::string& startPath, const std::vector<std::string>& extensions, | ||||
| 	const std::string& command, const std::vector<PlatformIds::PlatformId>& platformIds, const std::string& themeFolder) | ||||
| SystemData::SystemData(const std::string& name, const std::string& fullName, SystemEnvironmentData* envData, const std::string& themeFolder, bool CollectionSystem) : | ||||
| 	mName(name), mFullName(fullName), mEnvData(envData), mThemeFolder(themeFolder), mIsCollectionSystem(CollectionSystem), mIsGameSystem(true) | ||||
| { | ||||
| 	mName = name; | ||||
| 	mFullName = fullName; | ||||
| 	mStartPath = startPath; | ||||
| 
 | ||||
| 	//expand home symbol if the startpath contains ~
 | ||||
| 	if(mStartPath[0] == '~') | ||||
| 	{ | ||||
| 		mStartPath.erase(0, 1); | ||||
| 		mStartPath.insert(0, getHomePath()); | ||||
| 	} | ||||
| 
 | ||||
| 	mSearchExtensions = extensions; | ||||
| 	mLaunchCommand = command; | ||||
| 	mPlatformIds = platformIds; | ||||
| 	mThemeFolder = themeFolder; | ||||
| 
 | ||||
| 	mFilterIndex = new FileFilterIndex(); | ||||
| 
 | ||||
| 	mRootFolder = new FileData(FOLDER, mStartPath, this); | ||||
| 	mRootFolder->metadata.set("name", mFullName); | ||||
| 	// if it's an actual system, initialize it, if not, just create the data structure
 | ||||
| 	if(!CollectionSystem) | ||||
| 	{ | ||||
| 		mRootFolder = new FileData(FOLDER, mEnvData->mStartPath, mEnvData, this); | ||||
| 		mRootFolder->metadata.set("name", mFullName); | ||||
| 
 | ||||
| 	if(!Settings::getInstance()->getBool("ParseGamelistOnly")) | ||||
| 		populateFolder(mRootFolder); | ||||
| 		if(!Settings::getInstance()->getBool("ParseGamelistOnly")) | ||||
| 			populateFolder(mRootFolder); | ||||
| 
 | ||||
| 	if(!Settings::getInstance()->getBool("IgnoreGamelist")) | ||||
| 		parseGamelist(this); | ||||
| 
 | ||||
| 	mRootFolder->sort(FileSorts::SortTypes.at(0)); | ||||
| 		if(!Settings::getInstance()->getBool("IgnoreGamelist")) | ||||
| 			parseGamelist(this); | ||||
| 
 | ||||
| 		mRootFolder->sort(FileSorts::SortTypes.at(0)); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		// virtual systems are updated afterwards, we're just creating the data structure
 | ||||
| 		mRootFolder = new FileData(FOLDER, "" + name, mEnvData, this); | ||||
| 	} | ||||
| 	setIsGameSystemStatus(); | ||||
| 	loadTheme(); | ||||
| } | ||||
| 
 | ||||
| SystemData::~SystemData() | ||||
| { | ||||
| 	//save changed game data back to xml
 | ||||
| 	if(!Settings::getInstance()->getBool("IgnoreGamelist") && Settings::getInstance()->getBool("SaveGamelistsOnExit")) | ||||
| 	if(!Settings::getInstance()->getBool("IgnoreGamelist") && Settings::getInstance()->getBool("SaveGamelistsOnExit") && !mIsCollectionSystem) | ||||
| 	{ | ||||
| 		updateGamelist(this); | ||||
| 	} | ||||
|  | @ -64,87 +55,12 @@ SystemData::~SystemData() | |||
| 	delete mFilterIndex; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| std::string strreplace(std::string str, const std::string& replace, const std::string& with) | ||||
| void SystemData::setIsGameSystemStatus() | ||||
| { | ||||
| 	size_t pos; | ||||
| 	while((pos = str.find(replace)) != std::string::npos) | ||||
| 		str = str.replace(pos, replace.length(), with.c_str(), with.length()); | ||||
| 
 | ||||
| 	return str; | ||||
| } | ||||
| 
 | ||||
| // plaform-specific escape path function
 | ||||
| // on windows: just puts the path in quotes
 | ||||
| // everything else: assume bash and escape special characters with backslashes
 | ||||
| std::string escapePath(const boost::filesystem::path& path) | ||||
| { | ||||
| #ifdef WIN32 | ||||
| 	// windows escapes stuff by just putting everything in quotes
 | ||||
| 	return '"' + fs::path(path).make_preferred().string() + '"'; | ||||
| #else | ||||
| 	// a quick and dirty way to insert a backslash before most characters that would mess up a bash path
 | ||||
| 	std::string pathStr = path.string(); | ||||
| 
 | ||||
| 	const char* invalidChars = " '\"\\!$^&*(){}[]?;<>"; | ||||
| 	for(unsigned int i = 0; i < pathStr.length(); i++) | ||||
| 	{ | ||||
| 		char c; | ||||
| 		unsigned int charNum = 0; | ||||
| 		do { | ||||
| 			c = invalidChars[charNum]; | ||||
| 			if(pathStr[i] == c) | ||||
| 			{ | ||||
| 				pathStr.insert(i, "\\"); | ||||
| 				i++; | ||||
| 				break; | ||||
| 			} | ||||
| 			charNum++; | ||||
| 		} while(c != '\0'); | ||||
| 	} | ||||
| 
 | ||||
| 	return pathStr; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| void SystemData::launchGame(Window* window, FileData* game) | ||||
| { | ||||
| 	LOG(LogInfo) << "Attempting to launch game..."; | ||||
| 
 | ||||
| 	AudioManager::getInstance()->deinit(); | ||||
| 	VolumeControl::getInstance()->deinit(); | ||||
| 	window->deinit(); | ||||
| 
 | ||||
| 	std::string command = mLaunchCommand; | ||||
| 
 | ||||
| 	const std::string rom = escapePath(game->getPath()); | ||||
| 	const std::string basename = game->getPath().stem().string(); | ||||
| 	const std::string rom_raw = fs::path(game->getPath()).make_preferred().string(); | ||||
| 
 | ||||
| 	command = strreplace(command, "%ROM%", rom); | ||||
| 	command = strreplace(command, "%BASENAME%", basename); | ||||
| 	command = strreplace(command, "%ROM_RAW%", rom_raw); | ||||
| 
 | ||||
| 	LOG(LogInfo) << "	" << command; | ||||
| 	int exitCode = runSystemCommand(command); | ||||
| 
 | ||||
| 	if(exitCode != 0) | ||||
| 	{ | ||||
| 		LOG(LogWarning) << "...launch terminated with nonzero exit code " << exitCode << "!"; | ||||
| 	} | ||||
| 
 | ||||
| 	window->init(); | ||||
| 	VolumeControl::getInstance()->init(); | ||||
| 	AudioManager::getInstance()->init(); | ||||
| 	window->normalizeNextUpdate(); | ||||
| 
 | ||||
| 	//update number of times the game has been launched
 | ||||
| 	int timesPlayed = game->metadata.getInt("playcount") + 1; | ||||
| 	game->metadata.set("playcount", std::to_string(static_cast<long long>(timesPlayed))); | ||||
| 
 | ||||
| 	//update last played time
 | ||||
| 	boost::posix_time::ptime time = boost::posix_time::second_clock::universal_time(); | ||||
| 	game->metadata.setTime("lastplayed", time); | ||||
| 	// we exclude non-game systems from specific operations (i.e. the "RetroPie" system, at least)
 | ||||
| 	// 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
 | ||||
| 	mIsGameSystem = (mName != "retropie"); | ||||
| } | ||||
| 
 | ||||
| void SystemData::populateFolder(FileData* folder) | ||||
|  | @ -187,9 +103,9 @@ void SystemData::populateFolder(FileData* folder) | |||
| 		//see issue #75: https://github.com/Aloshi/EmulationStation/issues/75
 | ||||
| 
 | ||||
| 		isGame = false; | ||||
| 		if(std::find(mSearchExtensions.begin(), mSearchExtensions.end(), extension) != mSearchExtensions.end()) | ||||
| 		if(std::find(mEnvData->mSearchExtensions.begin(), mEnvData->mSearchExtensions.end(), extension) != mEnvData->mSearchExtensions.end()) | ||||
| 		{ | ||||
| 			FileData* newGame = new FileData(GAME, filePath.generic_string(), this); | ||||
| 			FileData* newGame = new FileData(GAME, filePath.generic_string(), mEnvData, this); | ||||
| 			folder->addChild(newGame); | ||||
| 			isGame = true; | ||||
| 		} | ||||
|  | @ -197,7 +113,7 @@ void SystemData::populateFolder(FileData* folder) | |||
| 		//add directories that also do not match an extension as folders
 | ||||
| 		if(!isGame && fs::is_directory(filePath)) | ||||
| 		{ | ||||
| 			FileData* newFolder = new FileData(FOLDER, filePath.generic_string(), this); | ||||
| 			FileData* newFolder = new FileData(FOLDER, filePath.generic_string(), mEnvData, this); | ||||
| 			populateFolder(newFolder); | ||||
| 
 | ||||
| 			//ignore folders that do not contain games
 | ||||
|  | @ -313,7 +229,21 @@ bool SystemData::loadConfig() | |||
| 		boost::filesystem::path genericPath(path); | ||||
| 		path = genericPath.generic_string(); | ||||
| 
 | ||||
| 		SystemData* newSys = new SystemData(name, fullname, path, extensions, cmd, platformIds, themeFolder); | ||||
| 		//expand home symbol if the startpath contains ~
 | ||||
| 		if(path[0] == '~') | ||||
| 		{ | ||||
| 			path.erase(0, 1); | ||||
| 			path.insert(0, getHomePath()); | ||||
| 		} | ||||
| 
 | ||||
| 		//create the system runtime environment data
 | ||||
| 		SystemEnvironmentData* envData = new SystemEnvironmentData; | ||||
| 		envData->mStartPath = path; | ||||
| 		envData->mSearchExtensions = extensions; | ||||
| 		envData->mLaunchCommand = cmd; | ||||
| 		envData->mPlatformIds = platformIds; | ||||
| 
 | ||||
| 		SystemData* newSys = new SystemData(name, fullname, envData, themeFolder); | ||||
| 		if(newSys->getRootFolder()->getChildrenByFilename().size() == 0) | ||||
| 		{ | ||||
| 			LOG(LogWarning) << "System \"" << name << "\" has no games! Ignoring it."; | ||||
|  | @ -322,6 +252,7 @@ bool SystemData::loadConfig() | |||
| 			sSystemVector.push_back(newSys); | ||||
| 		} | ||||
| 	} | ||||
| 	CollectionSystemManager::get()->loadCollectionSystems(); | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
|  | @ -441,6 +372,44 @@ unsigned int SystemData::getGameCount() const | |||
| 	return mRootFolder->getFilesRecursive(GAME).size(); | ||||
| } | ||||
| 
 | ||||
| SystemData* SystemData::getRandomSystem() | ||||
| { | ||||
| 	//  this is a bit brute force. It might be more efficient to just to a while (!gameSystem) do random again...
 | ||||
| 	unsigned int total = 0; | ||||
| 	for(auto it = sSystemVector.begin(); it != sSystemVector.end(); it++) | ||||
| 	{ | ||||
| 		if ((*it)->isGameSystem()) | ||||
| 			total ++; | ||||
| 	} | ||||
| 
 | ||||
| 	// get random number in range
 | ||||
| 	int target = std::round(((double)std::rand() / (double)RAND_MAX) * total); | ||||
| 
 | ||||
| 	for (auto it = sSystemVector.begin(); it != sSystemVector.end(); it++) | ||||
| 	{ | ||||
| 		if ((*it)->isGameSystem()) | ||||
| 		{ | ||||
| 			if (target >= 0) | ||||
| 			{ | ||||
| 				target--; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				return (*it); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| FileData* SystemData::getRandomGame() | ||||
| { | ||||
| 	std::vector<FileData*> list = mRootFolder->getFilesRecursive(GAME, true); | ||||
| 	unsigned int total = list.size(); | ||||
| 	// get random number in range
 | ||||
| 	int target = std::round(((double)std::rand() / (double)RAND_MAX) * total); | ||||
| 	return list.at(target); | ||||
| } | ||||
| 
 | ||||
| unsigned int SystemData::getDisplayedGameCount() const | ||||
| { | ||||
| 	return mRootFolder->getFilesRecursive(GAME, true).size(); | ||||
|  |  | |||
|  | @ -8,23 +8,31 @@ | |||
| #include "PlatformId.h" | ||||
| #include "ThemeData.h" | ||||
| #include "FileFilterIndex.h" | ||||
| #include "CollectionSystemManager.h" | ||||
| 
 | ||||
| struct SystemEnvironmentData | ||||
| { | ||||
| 	std::string mStartPath; | ||||
| 	std::vector<std::string> mSearchExtensions; | ||||
| 	std::string mLaunchCommand; | ||||
| 	std::vector<PlatformIds::PlatformId> mPlatformIds; | ||||
| }; | ||||
| 
 | ||||
| class SystemData | ||||
| { | ||||
| public: | ||||
| 	SystemData(const std::string& name, const std::string& fullName, const std::string& startPath, const std::vector<std::string>& extensions, | ||||
| 		const std::string& command, const std::vector<PlatformIds::PlatformId>& platformIds, const std::string& themeFolder); | ||||
| 	SystemData(const std::string& name, const std::string& fullName, SystemEnvironmentData* envData, const std::string& themeFolder, bool CollectionSystem = false); | ||||
| 	~SystemData(); | ||||
| 
 | ||||
| 	inline FileData* getRootFolder() const { return mRootFolder; }; | ||||
| 	inline const std::string& getName() const { return mName; } | ||||
| 	inline const std::string& getFullName() const { return mFullName; } | ||||
| 	inline const std::string& getStartPath() const { return mStartPath; } | ||||
| 	inline const std::vector<std::string>& getExtensions() const { return mSearchExtensions; } | ||||
| 	inline const std::string& getStartPath() const { return mEnvData->mStartPath; } | ||||
| 	inline const std::vector<std::string>& getExtensions() const { return mEnvData->mSearchExtensions; } | ||||
| 	inline const std::string& getThemeFolder() const { return mThemeFolder; } | ||||
| 
 | ||||
| 	inline const std::vector<PlatformIds::PlatformId>& getPlatformIds() const { return mPlatformIds; } | ||||
| 	inline bool hasPlatformId(PlatformIds::PlatformId id) { return std::find(mPlatformIds.begin(), mPlatformIds.end(), id) != mPlatformIds.end(); } | ||||
| 	inline SystemEnvironmentData* getSystemEnvData() const { return mEnvData; } | ||||
| 	inline const std::vector<PlatformIds::PlatformId>& getPlatformIds() const { return mEnvData->mPlatformIds; } | ||||
| 	inline bool hasPlatformId(PlatformIds::PlatformId id) { if (!mEnvData) return false; return std::find(mEnvData->mPlatformIds.begin(), mEnvData->mPlatformIds.end(), id) != mEnvData->mPlatformIds.end(); } | ||||
| 
 | ||||
| 	inline const std::shared_ptr<ThemeData>& getTheme() const { return mTheme; } | ||||
| 
 | ||||
|  | @ -35,8 +43,6 @@ public: | |||
| 	unsigned int getGameCount() const; | ||||
| 	unsigned int getDisplayedGameCount() const; | ||||
| 
 | ||||
| 	void launchGame(Window* window, FileData* game); | ||||
| 
 | ||||
| 	static void deleteSystems(); | ||||
| 	static bool loadConfig(); //Load the system config file at getConfigPath(). Returns true if no errors were encountered. An example will be written if the file doesn't exist.
 | ||||
| 	static void writeExampleConfig(const std::string& path); | ||||
|  | @ -46,7 +52,8 @@ public: | |||
| 
 | ||||
| 	inline std::vector<SystemData*>::const_iterator getIterator() const { return std::find(sSystemVector.begin(), sSystemVector.end(), this); }; | ||||
| 	inline std::vector<SystemData*>::const_reverse_iterator getRevIterator() const { return std::find(sSystemVector.rbegin(), sSystemVector.rend(), this); }; | ||||
| 
 | ||||
| 	inline bool isCollection() { return mIsCollectionSystem; }; | ||||
| 	inline bool isGameSystem() { return mIsGameSystem; } | ||||
| 	inline SystemData* getNext() const | ||||
| 	{ | ||||
| 		auto it = getIterator(); | ||||
|  | @ -63,22 +70,25 @@ public: | |||
| 		return *it; | ||||
| 	} | ||||
| 
 | ||||
| 	static SystemData* getRandomSystem(); | ||||
| 	FileData* getRandomGame(); | ||||
| 
 | ||||
| 	// Load or re-load theme.
 | ||||
| 	void loadTheme(); | ||||
| 
 | ||||
| 	FileFilterIndex* getIndex() { return mFilterIndex; }; | ||||
| 
 | ||||
| private: | ||||
| 	bool mIsCollectionSystem; | ||||
| 	bool mIsGameSystem; | ||||
| 	std::string mName; | ||||
| 	std::string mFullName; | ||||
| 	std::string mStartPath; | ||||
| 	std::vector<std::string> mSearchExtensions; | ||||
| 	std::string mLaunchCommand; | ||||
| 	std::vector<PlatformIds::PlatformId> mPlatformIds; | ||||
| 	SystemEnvironmentData* mEnvData; | ||||
| 	std::string mThemeFolder; | ||||
| 	std::shared_ptr<ThemeData> mTheme; | ||||
| 
 | ||||
| 	void populateFolder(FileData* folder); | ||||
| 	void setIsGameSystemStatus(); | ||||
| 
 | ||||
| 	FileFilterIndex* mFilterIndex; | ||||
| 
 | ||||
|  |  | |||
|  | @ -148,23 +148,26 @@ void SystemScreenSaver::countVideos() | |||
| 		std::vector<SystemData*>:: iterator it; | ||||
| 		for (it = SystemData::sSystemVector.begin(); it != SystemData::sSystemVector.end(); ++it) | ||||
| 		{ | ||||
| 			pugi::xml_document doc; | ||||
| 			pugi::xml_node root; | ||||
| 			std::string xmlReadPath = (*it)->getGamelistPath(false); | ||||
| 
 | ||||
| 			if(boost::filesystem::exists(xmlReadPath)) | ||||
| 			if (!(*it)->isCollection()) | ||||
| 			{ | ||||
| 				pugi::xml_parse_result result = doc.load_file(xmlReadPath.c_str()); | ||||
| 				if (!result) | ||||
| 					continue; | ||||
| 				root = doc.child("gameList"); | ||||
| 				if (!root) | ||||
| 					continue; | ||||
| 				for(pugi::xml_node fileNode = root.child("game"); fileNode; fileNode = fileNode.next_sibling("game")) | ||||
| 				pugi::xml_document doc; | ||||
| 				pugi::xml_node root; | ||||
| 				std::string xmlReadPath = (*it)->getGamelistPath(false); | ||||
| 
 | ||||
| 				if(boost::filesystem::exists(xmlReadPath)) | ||||
| 				{ | ||||
| 					pugi::xml_node videoNode = fileNode.child("video"); | ||||
| 					if (videoNode) | ||||
| 						++mVideoCount; | ||||
| 					pugi::xml_parse_result result = doc.load_file(xmlReadPath.c_str()); | ||||
| 					if (!result) | ||||
| 						continue; | ||||
| 					root = doc.child("gameList"); | ||||
| 					if (!root) | ||||
| 						continue; | ||||
| 					for(pugi::xml_node fileNode = root.child("game"); fileNode; fileNode = fileNode.next_sibling("game")) | ||||
| 					{ | ||||
| 						pugi::xml_node videoNode = fileNode.child("video"); | ||||
| 						if (videoNode) | ||||
| 							++mVideoCount; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  |  | |||
							
								
								
									
										105
									
								
								es-app/src/guis/GuiCollectionSystemsOptions.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								es-app/src/guis/GuiCollectionSystemsOptions.cpp
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,105 @@ | |||
| #include "guis/GuiCollectionSystemsOptions.h" | ||||
| #include "guis/GuiMsgBox.h" | ||||
| #include "Settings.h" | ||||
| #include "views/ViewController.h" | ||||
| 
 | ||||
| #include "components/TextComponent.h" | ||||
| #include "components/OptionListComponent.h" | ||||
| 
 | ||||
| GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(Window* window) : GuiComponent(window), mMenu(window, "GAME COLLECTION SETTINGS") | ||||
| { | ||||
| 	initializeMenu(); | ||||
| } | ||||
| 
 | ||||
| void GuiCollectionSystemsOptions::initializeMenu() | ||||
| { | ||||
| 	addChild(&mMenu); | ||||
| 
 | ||||
| 	// get virtual systems
 | ||||
| 
 | ||||
| 	addSystemsToMenu(); | ||||
| 
 | ||||
| 	mMenu.addButton("BACK", "back", std::bind(&GuiCollectionSystemsOptions::applySettings, this)); | ||||
| 
 | ||||
| 	mMenu.setPosition((Renderer::getScreenWidth() - mMenu.getSize().x()) / 2, Renderer::getScreenHeight() * 0.15f); | ||||
| } | ||||
| 
 | ||||
| GuiCollectionSystemsOptions::~GuiCollectionSystemsOptions() | ||||
| { | ||||
| 	//mSystemOptions.clear();
 | ||||
| } | ||||
| 
 | ||||
| void GuiCollectionSystemsOptions::addSystemsToMenu() | ||||
| { | ||||
| 
 | ||||
| 	std::map<std::string, CollectionSystemData> vSystems = CollectionSystemManager::get()->getCollectionSystems(); | ||||
| 
 | ||||
| 	autoOptionList = std::make_shared< OptionListComponent<std::string> >(mWindow, "SELECT COLLECTIONS", true); | ||||
| 
 | ||||
| 	// add Systems
 | ||||
| 	ComponentListRow row; | ||||
| 
 | ||||
| 	for(std::map<std::string, CollectionSystemData>::iterator it = vSystems.begin() ; it != vSystems.end() ; it++ ) | ||||
| 	{ | ||||
| 		autoOptionList->add(it->second.decl.longName, it->second.decl.name, it->second.isEnabled); | ||||
| 	} | ||||
| 	mMenu.addWithLabel("AUTOMATIC COLLECTIONS", autoOptionList); | ||||
| } | ||||
| 
 | ||||
| void GuiCollectionSystemsOptions::applySettings() | ||||
| { | ||||
| 	std::string out = commaStringToVector(autoOptionList->getSelectedObjects()); | ||||
| 	std::string prev = Settings::getInstance()->getString("CollectionSystemsAuto"); | ||||
| 	if (out != "" && !CollectionSystemManager::get()->isThemeAutoCompatible()) | ||||
| 	{ | ||||
| 		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; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void GuiCollectionSystemsOptions::updateSettings(std::string newSettings) | ||||
| { | ||||
| 	Settings::getInstance()->setString("CollectionSystemsAuto", newSettings); | ||||
| 	Settings::getInstance()->saveFile(); | ||||
| 	CollectionSystemManager::get()->loadEnabledListFromSettings(); | ||||
| 	CollectionSystemManager::get()->updateSystemsList(); | ||||
| 	ViewController::get()->goToStart(); | ||||
| 	ViewController::get()->reloadAll(); | ||||
| } | ||||
| 
 | ||||
| bool GuiCollectionSystemsOptions::input(InputConfig* config, Input input) | ||||
| { | ||||
| 	bool consumed = GuiComponent::input(config, input); | ||||
| 	if(consumed) | ||||
| 		return true; | ||||
| 
 | ||||
| 	if(config->isMappedTo("b", input) && input.value != 0) | ||||
| 	{ | ||||
| 		applySettings(); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| std::vector<HelpPrompt> GuiCollectionSystemsOptions::getHelpPrompts() | ||||
| { | ||||
| 	std::vector<HelpPrompt> prompts = mMenu.getHelpPrompts(); | ||||
| 	prompts.push_back(HelpPrompt("b", "back")); | ||||
| 	return prompts; | ||||
| } | ||||
							
								
								
									
										31
									
								
								es-app/src/guis/GuiCollectionSystemsOptions.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								es-app/src/guis/GuiCollectionSystemsOptions.h
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include "GuiComponent.h" | ||||
| #include "SystemData.h" | ||||
| #include "components/MenuComponent.h" | ||||
| #include "CollectionSystemManager.h" | ||||
| #include "Log.h" | ||||
| 
 | ||||
| 
 | ||||
| template<typename T> | ||||
| class OptionListComponent; | ||||
| 
 | ||||
| 
 | ||||
| class GuiCollectionSystemsOptions : public GuiComponent | ||||
| { | ||||
| public: | ||||
| 	GuiCollectionSystemsOptions(Window* window); | ||||
| 	~GuiCollectionSystemsOptions(); | ||||
| 	bool input(InputConfig* config, Input input) override; | ||||
| 
 | ||||
| 	virtual std::vector<HelpPrompt> getHelpPrompts() override; | ||||
| 
 | ||||
| private: | ||||
| 	void initializeMenu(); | ||||
| 	void applySettings(); | ||||
| 	void addSystemsToMenu(); | ||||
| 	void updateSettings(std::string newSettings); | ||||
| 	std::shared_ptr< OptionListComponent<std::string> > autoOptionList; | ||||
| 	MenuComponent mMenu; | ||||
| 	SystemData* mSystem; | ||||
| }; | ||||
|  | @ -145,7 +145,7 @@ void GuiFastSelect::updateGameListCursor() | |||
| 
 | ||||
| 	// only skip by letter when the sort mode is alphabetical
 | ||||
| 	const FileData::SortType& sort = FileSorts::SortTypes.at(mSortId); | ||||
| 	if(sort.comparisonFunction != &FileSorts::compareFileName) | ||||
| 	if(sort.comparisonFunction != &FileSorts::compareName) | ||||
| 		return; | ||||
| 
 | ||||
| 	// find the first entry in the list that either exactly matches our target letter or is beyond our target letter
 | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| #include "GuiMetaDataEd.h" | ||||
| #include "views/gamelist/IGameListView.h" | ||||
| #include "views/ViewController.h" | ||||
| #include "CollectionSystemManager.h" | ||||
| 
 | ||||
| GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : GuiComponent(window), | ||||
| 	mSystem(system), mMenu(window, "OPTIONS"), fromPlaceholder(false), mFiltersChanged(false) | ||||
|  | @ -122,7 +123,8 @@ void GuiGamelistOptions::openGamelistFilter() | |||
| void GuiGamelistOptions::openMetaDataEd() | ||||
| { | ||||
| 	// open metadata editor
 | ||||
| 	FileData* file = getGamelist()->getCursor(); | ||||
| 	// get the FileData that hosts the original metadata
 | ||||
| 	FileData* file = getGamelist()->getCursor()->getSourceFileData(); | ||||
| 	ScraperSearchParams p; | ||||
| 	p.game = file; | ||||
| 	p.system = file->getSystem(); | ||||
|  | @ -136,12 +138,13 @@ void GuiGamelistOptions::openMetaDataEd() | |||
| 	else | ||||
| 	{ | ||||
| 		deleteBtnFunc = [this, file] { | ||||
| 			getGamelist()->remove(file); | ||||
| 			CollectionSystemManager::get()->deleteCollectionFiles(file); | ||||
| 			ViewController::get()->getGameListView(file->getSystem()).get()->remove(file, true); | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	mWindow->pushGui(new GuiMetaDataEd(mWindow, &file->metadata, file->metadata.getMDD(), p, file->getPath().filename().string(), | ||||
| 		std::bind(&IGameListView::onFileChanged, getGamelist(), file, FILE_METADATA_CHANGED), deleteBtnFunc)); | ||||
| 		std::bind(&IGameListView::onFileChanged, ViewController::get()->getGameListView(file->getSystem()).get(), file, FILE_METADATA_CHANGED), deleteBtnFunc)); | ||||
| } | ||||
| 
 | ||||
| void GuiGamelistOptions::jumpToLetter() | ||||
|  |  | |||
							
								
								
									
										114
									
								
								es-app/src/guis/GuiInfoPopup.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								es-app/src/guis/GuiInfoPopup.cpp
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,114 @@ | |||
| #include "guis/GuiInfoPopup.h" | ||||
| #include "Renderer.h" | ||||
| #include "components/TextComponent.h" | ||||
| #include "Log.h" | ||||
| 
 | ||||
| GuiInfoPopup::GuiInfoPopup(Window* window, std::string message, int duration) : | ||||
| 	GuiComponent(window), mMessage(message), mDuration(duration), running(true) | ||||
| { | ||||
| 	mFrame = new NinePatchComponent(window); | ||||
| 	float maxWidth = Renderer::getScreenWidth() * 0.9f; | ||||
| 	float maxHeight = Renderer::getScreenHeight() * 0.2f; | ||||
| 
 | ||||
| 	std::shared_ptr<TextComponent> s = std::make_shared<TextComponent>(mWindow, | ||||
| 		"", | ||||
| 		Font::get(FONT_SIZE_MINI), | ||||
| 		0x444444FF, | ||||
| 		ALIGN_CENTER); | ||||
| 
 | ||||
| 	// we do this to force the text container to resize and return an actual expected popup size
 | ||||
| 	s->setSize(0,0); | ||||
| 	s->setText(message); | ||||
| 	mSize = s->getSize(); | ||||
| 
 | ||||
| 	// confirm the size isn't larger than the screen width, otherwise cap it
 | ||||
| 	if (mSize.x() > maxWidth) { | ||||
| 		s->setSize(maxWidth, mSize[1]); | ||||
| 		mSize[0] = maxWidth; | ||||
| 	} | ||||
| 	if (mSize.y() > maxHeight) { | ||||
| 		s->setSize(mSize[0], maxHeight); | ||||
| 		mSize[1] = maxHeight; | ||||
| 	} | ||||
| 
 | ||||
| 	// add a padding to the box
 | ||||
| 	int paddingX = Renderer::getScreenWidth() * 0.03f; | ||||
| 	int paddingY = Renderer::getScreenHeight() * 0.02f; | ||||
| 	mSize[0] = mSize.x() + paddingX; | ||||
| 	mSize[1] = mSize.y() + paddingY; | ||||
| 
 | ||||
| 	float posX = Renderer::getScreenWidth()*0.5f - mSize.x()*0.5f; | ||||
| 	float posY = Renderer::getScreenHeight() * 0.02f; | ||||
| 
 | ||||
| 	setPosition(posX, posY, 0); | ||||
| 
 | ||||
| 	mFrame->setImagePath(":/frame.png"); | ||||
| 	mFrame->fitTo(mSize, Eigen::Vector3f::Zero(), Eigen::Vector2f(-32, -32)); | ||||
| 	addChild(mFrame); | ||||
| 
 | ||||
| 	// we only init the actual time when we first start to render
 | ||||
| 	mStartTime = 0; | ||||
| 
 | ||||
| 	mGrid = new ComponentGrid(window, Eigen::Vector2i(1, 3)); | ||||
| 	mGrid->setSize(mSize); | ||||
| 	mGrid->setEntry(s, Eigen::Vector2i(0, 1), false, true); | ||||
| 	addChild(mGrid); | ||||
| } | ||||
| 
 | ||||
| GuiInfoPopup::~GuiInfoPopup() | ||||
| { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| void GuiInfoPopup::render(const Eigen::Affine3f& parentTrans) | ||||
| { | ||||
| 	// we use identity as we want to render on a specific window position, not on the view
 | ||||
| 	Eigen::Affine3f trans = getTransform() * Eigen::Affine3f::Identity(); | ||||
| 	if(running && updateState()) | ||||
| 	{ | ||||
| 		// if we're still supposed to be rendering it
 | ||||
| 		Renderer::setMatrix(trans); | ||||
| 		renderChildren(trans); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| bool GuiInfoPopup::updateState() | ||||
| { | ||||
| 	int curTime = SDL_GetTicks(); | ||||
| 
 | ||||
| 	// we only init the actual time when we first start to render
 | ||||
| 	if(mStartTime == 0) | ||||
| 	{ | ||||
| 		mStartTime = curTime; | ||||
| 	} | ||||
| 
 | ||||
| 	// compute fade in effect
 | ||||
| 	if (curTime - mStartTime > mDuration) | ||||
| 	{ | ||||
| 		// we're past the popup duration, no need to render
 | ||||
| 		running = false; | ||||
| 		return false; | ||||
| 	} | ||||
| 	else if (curTime < mStartTime) { | ||||
| 		// if SDL reset
 | ||||
| 		running = false; | ||||
| 		return false; | ||||
| 	} | ||||
| 	else if (curTime - mStartTime <= 500) { | ||||
| 		alpha = ((curTime - mStartTime)*255/500); | ||||
| 	} | ||||
| 	else if (curTime - mStartTime < mDuration - 500) | ||||
| 	{ | ||||
| 		alpha = 255; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		alpha = ((-(curTime - mStartTime - mDuration)*255)/500); | ||||
| 	} | ||||
| 	mGrid->setOpacity(alpha); | ||||
| 
 | ||||
| 	// apply fade in effect to popup frame
 | ||||
| 	mFrame->setEdgeColor(0xFFFFFF00 | (unsigned char)(alpha)); | ||||
| 	mFrame->setCenterColor(0xFFFFFF00 | (unsigned char)(alpha)); | ||||
| 	return true; | ||||
| } | ||||
							
								
								
									
										27
									
								
								es-app/src/guis/GuiInfoPopup.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								es-app/src/guis/GuiInfoPopup.h
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include "GuiComponent.h" | ||||
| #include "components/NinePatchComponent.h" | ||||
| #include "components/ComponentGrid.h" | ||||
| #include "Window.h" | ||||
| #include "Log.h" | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class GuiInfoPopup : public GuiComponent, public Window::InfoPopup | ||||
| { | ||||
| public: | ||||
| 	GuiInfoPopup(Window* window, std::string message, int duration); | ||||
| 	~GuiInfoPopup(); | ||||
| 	void render(const Eigen::Affine3f& parentTrans) override; | ||||
| 	inline void stop() { running = false; }; | ||||
| private: | ||||
| 	std::string mMessage; | ||||
| 	int mDuration; | ||||
| 	int alpha; | ||||
| 	bool updateState(); | ||||
| 	int mStartTime; | ||||
| 	ComponentGrid* mGrid; | ||||
| 	NinePatchComponent* mFrame; | ||||
| 	bool running; | ||||
| }; | ||||
|  | @ -7,6 +7,7 @@ | |||
| #include "guis/GuiMsgBox.h" | ||||
| #include "guis/GuiSettings.h" | ||||
| #include "guis/GuiScreensaverOptions.h" | ||||
| #include "guis/GuiCollectionSystemsOptions.h" | ||||
| #include "guis/GuiScraperStart.h" | ||||
| #include "guis/GuiDetectDevice.h" | ||||
| #include "views/ViewController.h" | ||||
|  | @ -243,6 +244,9 @@ GuiMenu::GuiMenu(Window* window) : GuiComponent(window), mMenu(window, "MAIN MEN | |||
| 			mWindow->pushGui(s); | ||||
| 	}); | ||||
| 
 | ||||
| 	addEntry("GAME COLLECTION SETTINGS", 0x777777FF, true, | ||||
| 		[this] { openCollectionSystemSettings(); | ||||
| 		}); | ||||
| 	addEntry("OTHER SETTINGS", 0x777777FF, true, | ||||
| 		[this] { | ||||
| 			auto s = new GuiSettings(mWindow, "OTHER SETTINGS"); | ||||
|  | @ -369,13 +373,20 @@ GuiMenu::GuiMenu(Window* window) : GuiComponent(window), mMenu(window, "MAIN MEN | |||
| 	addChild(&mMenu); | ||||
| 	addChild(&mVersion); | ||||
| 
 | ||||
| 	/*int menuWidth = Renderer::getScreenWidth() * 0.4f;
 | ||||
| 	int menuHeight = Renderer::getScreenHeight() * 0.74f; | ||||
| 	mMenu.setSize(menuWidth, menuHeight);*/ | ||||
| 
 | ||||
| 	setSize(mMenu.getSize()); | ||||
| 	setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, Renderer::getScreenHeight() * 0.15f); | ||||
| 	setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, Renderer::getScreenHeight() * 0.13f); | ||||
| } | ||||
| 
 | ||||
| void GuiMenu::openScreensaverOptions() { | ||||
| 	GuiScreensaverOptions* ggf = new GuiScreensaverOptions(mWindow, "VIDEO SCREENSAVER"); | ||||
| 	mWindow->pushGui(ggf); | ||||
| 	mWindow->pushGui(new GuiScreensaverOptions(mWindow, "VIDEO SCREENSAVER")); | ||||
| } | ||||
| 
 | ||||
| void GuiMenu::openCollectionSystemSettings() { | ||||
| 	mWindow->pushGui(new GuiCollectionSystemsOptions(mWindow)); | ||||
| } | ||||
| 
 | ||||
| void GuiMenu::onSizeChanged() | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ public: | |||
| private: | ||||
| 	void addEntry(const char* name, unsigned int color, bool add_arrow, const std::function<void()>& func); | ||||
| 	void openScreensaverOptions(); | ||||
| 	void openCollectionSystemSettings(); | ||||
| 	MenuComponent mMenu; | ||||
| 	TextComponent mVersion; | ||||
| }; | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ | |||
| #include "components/TextEditComponent.h" | ||||
| #include "components/DateTimeComponent.h" | ||||
| #include "components/RatingComponent.h" | ||||
| #include "components/SwitchComponent.h" | ||||
| #include "guis/GuiTextEditPopup.h" | ||||
| 
 | ||||
| using namespace Eigen; | ||||
|  | @ -59,6 +60,12 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector | |||
| 
 | ||||
| 		switch(iter->type) | ||||
| 		{ | ||||
| 		case MD_BOOL: | ||||
| 			{ | ||||
| 				ed = std::make_shared<SwitchComponent>(window); | ||||
| 				row.addElement(ed, false, true); | ||||
| 				break; | ||||
| 			} | ||||
| 		case MD_RATING: | ||||
| 			{ | ||||
| 				ed = std::make_shared<RatingComponent>(window); | ||||
|  | @ -177,7 +184,6 @@ void GuiMetaDataEd::save() | |||
| 	{ | ||||
| 		if(mMetaDataDecl.at(i).isStatistic) | ||||
| 			continue; | ||||
| 
 | ||||
| 		mMetaData->set(mMetaDataDecl.at(i).key, mEditors.at(i)->getValue()); | ||||
| 	} | ||||
| 
 | ||||
|  | @ -186,6 +192,9 @@ void GuiMetaDataEd::save() | |||
| 
 | ||||
| 	if(mSavedCallback) | ||||
| 		mSavedCallback(); | ||||
| 
 | ||||
| 	// update respective Collection Entries
 | ||||
| 	CollectionSystemManager::get()->updateCollectionSystems(mScraperParams.game); | ||||
| } | ||||
| 
 | ||||
| void GuiMetaDataEd::fetch() | ||||
|  |  | |||
|  | @ -225,6 +225,7 @@ int main(int argc, char* argv[]) | |||
| 	Window window; | ||||
| 	SystemScreenSaver screensaver(&window); | ||||
| 	ViewController::init(&window); | ||||
| 	CollectionSystemManager::init(&window); | ||||
| 	window.pushGui(ViewController::get()); | ||||
| 
 | ||||
| 	if(!scrape_cmdline) | ||||
|  |  | |||
|  | @ -157,7 +157,10 @@ bool SystemView::input(InputConfig* config, Input input) | |||
| 		} | ||||
| 		if (config->isMappedTo("x", input)) | ||||
| 		{ | ||||
| 			ViewController::get()->goToRandomGame(); | ||||
| 			// get random system
 | ||||
| 			// go to system
 | ||||
| 			setCursor(SystemData::getRandomSystem()); | ||||
| 			//ViewController::get()->goToRandomGame();
 | ||||
| 			return true; | ||||
| 		} | ||||
| 	}else{ | ||||
|  | @ -224,7 +227,7 @@ void SystemView::onCursorChanged(const CursorState& state) | |||
| 	setAnimation(infoFadeOut, 0, [this, gameCount] { | ||||
| 		std::stringstream ss; | ||||
| 
 | ||||
| 		if (getSelected()->getName() == "retropie") | ||||
| 		if (!getSelected()->isGameSystem()) | ||||
| 			ss << "CONFIGURATION"; | ||||
| 		// only display a game count if there are at least 2 games
 | ||||
| 		else if(gameCount > 1) | ||||
|  |  | |||
|  | @ -124,7 +124,7 @@ void ViewController::goToRandomGame() | |||
| 	unsigned int total = 0; | ||||
| 	for(auto it = SystemData::sSystemVector.begin(); it != SystemData::sSystemVector.end(); it++) | ||||
| 	{ | ||||
| 		if ((*it)->getName() != "retropie") | ||||
| 		if ((*it)->isGameSystem()) | ||||
| 			total += (*it)->getDisplayedGameCount(); | ||||
| 	} | ||||
| 
 | ||||
|  | @ -133,7 +133,7 @@ void ViewController::goToRandomGame() | |||
| 
 | ||||
| 	for (auto it = SystemData::sSystemVector.begin(); it != SystemData::sSystemVector.end(); it++) | ||||
| 	{ | ||||
| 		if ((*it)->getName() != "retropie") | ||||
| 		if ((*it)->isGameSystem()) | ||||
| 		{ | ||||
| 			if ((target - (int)(*it)->getDisplayedGameCount()) >= 0) | ||||
| 			{ | ||||
|  | @ -228,6 +228,7 @@ void ViewController::launch(FileData* game, Eigen::Vector3f center) | |||
| 
 | ||||
| 	center += mCurrentView->getPosition(); | ||||
| 	stopAnimation(1); // make sure the fade in isn't still playing
 | ||||
| 	mWindow->stopInfoPopup(); // make sure we disable any existing info popup
 | ||||
| 	mLockInput = true; | ||||
| 
 | ||||
| 	std::string transition_style = Settings::getInstance()->getString("TransitionStyle"); | ||||
|  | @ -241,7 +242,7 @@ void ViewController::launch(FileData* game, Eigen::Vector3f center) | |||
| 		}; | ||||
| 		setAnimation(new LambdaAnimation(fadeFunc, 800), 0, [this, game, fadeFunc] | ||||
| 		{ | ||||
| 			game->getSystem()->launchGame(mWindow, game); | ||||
| 			game->launchGame(mWindow); | ||||
| 			mLockInput = false; | ||||
| 			setAnimation(new LambdaAnimation(fadeFunc, 800), 0, nullptr, true); | ||||
| 			this->onFileChanged(game, FILE_METADATA_CHANGED); | ||||
|  | @ -250,7 +251,7 @@ void ViewController::launch(FileData* game, Eigen::Vector3f center) | |||
| 		// move camera to zoom in on center + fade out, launch game, come back in
 | ||||
| 		setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 1500), 0, [this, origCamera, center, game] | ||||
| 		{ | ||||
| 			game->getSystem()->launchGame(mWindow, game); | ||||
| 			game->launchGame(mWindow); | ||||
| 			mCamera = origCamera; | ||||
| 			mLockInput = false; | ||||
| 			setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 600), 0, nullptr, true); | ||||
|  | @ -259,7 +260,7 @@ void ViewController::launch(FileData* game, Eigen::Vector3f center) | |||
| 	} else { | ||||
| 		setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 10), 0, [this, origCamera, center, game] | ||||
| 		{ | ||||
| 			game->getSystem()->launchGame(mWindow, game); | ||||
| 			game->launchGame(mWindow); | ||||
| 			mCamera = origCamera; | ||||
| 			mLockInput = false; | ||||
| 			setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 10), 0, nullptr, true); | ||||
|  | @ -436,14 +437,12 @@ void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme) | |||
| 
 | ||||
| 			if(reloadTheme) | ||||
| 				system->loadTheme(); | ||||
| 
 | ||||
| 			std::shared_ptr<IGameListView> newView = getGameListView(system); | ||||
| 
 | ||||
| 			// to counter having come from a placeholder
 | ||||
| 			if (!cursor->isPlaceHolder()) { | ||||
| 				newView->setCursor(cursor); | ||||
| 			} | ||||
| 
 | ||||
| 			if(isCurrent) | ||||
| 				mCurrentView = newView; | ||||
| 
 | ||||
|  |  | |||
|  | @ -53,9 +53,7 @@ void BasicGameListView::populateList(const std::vector<FileData*>& files) | |||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		// empty list - add a placeholder
 | ||||
| 		FileData* placeholder = new FileData(PLACEHOLDER, "<No Results Found for Current Filter Criteria>", this->mRoot->getSystem()); | ||||
| 		mList.add(placeholder->getName(), placeholder, (placeholder->getType() == PLACEHOLDER)); | ||||
| 		addPlaceholder(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | @ -66,9 +64,7 @@ FileData* BasicGameListView::getCursor() | |||
| 
 | ||||
| void BasicGameListView::setCursor(FileData* cursor) | ||||
| { | ||||
| 	if (cursor->isPlaceHolder()) | ||||
| 		return; | ||||
| 	if(!mList.setCursor(cursor)) | ||||
| 	if(!mList.setCursor(cursor) && (!cursor->isPlaceHolder())) | ||||
| 	{ | ||||
| 		populateList(cursor->getParent()->getChildrenListToDisplay()); | ||||
| 		mList.setCursor(cursor); | ||||
|  | @ -95,17 +91,26 @@ void BasicGameListView::setCursor(FileData* cursor) | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| void BasicGameListView::addPlaceholder() | ||||
| { | ||||
| 	// empty list - add a placeholder
 | ||||
| 	FileData* placeholder = new FileData(PLACEHOLDER, "<No Entries Found>", this->mRoot->getSystem()->getSystemEnvData(), this->mRoot->getSystem()); | ||||
| 	mList.add(placeholder->getName(), placeholder, (placeholder->getType() == PLACEHOLDER)); | ||||
| } | ||||
| 
 | ||||
| void BasicGameListView::launch(FileData* game) | ||||
| { | ||||
| 	ViewController::get()->launch(game); | ||||
| } | ||||
| 
 | ||||
| void BasicGameListView::remove(FileData *game) | ||||
| void BasicGameListView::remove(FileData *game, bool deleteFile) | ||||
| { | ||||
| 	boost::filesystem::remove(game->getPath());  // actually delete the file on the filesystem
 | ||||
| 	if (deleteFile) | ||||
| 		boost::filesystem::remove(game->getPath());  // actually delete the file on the filesystem
 | ||||
| 	FileData* parent = game->getParent(); | ||||
| 	if (getCursor() == game)                     // Select next element in list, or prev if none
 | ||||
| 	{ | ||||
| 		std::vector<FileData*> siblings = game->getParent()->getChildren(); | ||||
| 		std::vector<FileData*> siblings = parent->getChildrenListToDisplay(); | ||||
| 		auto gameIter = std::find(siblings.begin(), siblings.end(), game); | ||||
| 		auto gamePos = std::distance(siblings.begin(), gameIter); | ||||
| 		if (gameIter != siblings.end()) | ||||
|  | @ -118,8 +123,13 @@ void BasicGameListView::remove(FileData *game) | |||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	mList.remove(game); | ||||
| 	if(mList.size() == 0) | ||||
| 	{ | ||||
| 		addPlaceholder(); | ||||
| 	} | ||||
| 	delete game;                                 // remove before repopulating (removes from parent)
 | ||||
| 	onFileChanged(game, FILE_REMOVED);           // update the view, with game removed
 | ||||
| 	onFileChanged(parent, FILE_REMOVED);           // update the view, with game removed
 | ||||
| } | ||||
| 
 | ||||
| std::vector<HelpPrompt> BasicGameListView::getHelpPrompts() | ||||
|  | @ -133,5 +143,7 @@ std::vector<HelpPrompt> 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")); | ||||
| 	return prompts; | ||||
| } | ||||
|  |  | |||
|  | @ -23,7 +23,8 @@ public: | |||
| 
 | ||||
| protected: | ||||
| 	virtual void populateList(const std::vector<FileData*>& files) override; | ||||
| 	virtual void remove(FileData* game) override; | ||||
| 	virtual void remove(FileData* game, bool deleteFile) override; | ||||
| 	virtual void addPlaceholder(); | ||||
| 
 | ||||
| 	TextListComponent<FileData*> mList; | ||||
| }; | ||||
|  |  | |||
|  | @ -32,7 +32,7 @@ public: | |||
| 	virtual void setCursor(FileData*) = 0; | ||||
| 
 | ||||
| 	virtual bool input(InputConfig* config, Input input) override; | ||||
| 	virtual void remove(FileData* game) = 0; | ||||
| 	virtual void remove(FileData* game, bool deleteFile) = 0; | ||||
| 
 | ||||
| 	virtual const char* getName() const = 0; | ||||
| 	virtual void launch(FileData* game) = 0; | ||||
|  |  | |||
|  | @ -3,7 +3,9 @@ | |||
| #include "Window.h" | ||||
| #include "views/ViewController.h" | ||||
| #include "Sound.h" | ||||
| #include "Log.h" | ||||
| #include "Settings.h" | ||||
| #include "CollectionSystemManager.h" | ||||
| 
 | ||||
| ISimpleGameListView::ISimpleGameListView(Window* window, FileData* root) : IGameListView(window, root), | ||||
| 	mHeaderText(window), mHeaderImage(window), mBackground(window) | ||||
|  | @ -127,10 +129,31 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) | |||
| 			} | ||||
| 		}else if (config->isMappedTo("x", input)) | ||||
| 		{ | ||||
| 			ViewController::get()->goToRandomGame(); | ||||
| 			// go to random system game
 | ||||
| 			setCursor(mRoot->getSystem()->getRandomGame()); | ||||
| 			//ViewController::get()->goToRandomGame();
 | ||||
| 			return true; | ||||
| 		}else if (config->isMappedTo("y", input)) | ||||
| 		{ | ||||
| 			if(Settings::getInstance()->getString("CollectionSystemsAuto").find("favorites") != std::string::npos && mRoot->getSystem()->isGameSystem()) | ||||
| 			{ | ||||
| 				if(CollectionSystemManager::get()->toggleGameInCollection(getCursor(), "favorites")) | ||||
| 				{ | ||||
| 					return true; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return IGameListView::input(config, input); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -96,6 +96,7 @@ void Settings::setDefaults() | |||
| 	mBoolMap["CaptionsCompatibility"] = true; | ||||
| 	// Audio out device for Video playback using OMX player.
 | ||||
| 	mStringMap["OMXAudioDev"] = "both"; | ||||
| 	mStringMap["CollectionSystemsAuto"] = ""; | ||||
| 
 | ||||
| 	// Audio out device for volume control
 | ||||
| 	#ifdef _RPI_ | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| #include "Util.h" | ||||
| #include "resources/ResourceManager.h" | ||||
| #include "platform.h" | ||||
| #include <boost/algorithm/string.hpp> | ||||
| 
 | ||||
| namespace fs = boost::filesystem; | ||||
| 
 | ||||
|  | @ -208,3 +209,103 @@ boost::posix_time::ptime string_to_ptime(const std::string& str, const std::stri | |||
| 
 | ||||
| 	return time; | ||||
| } | ||||
| 
 | ||||
| std::string strreplace(std::string str, const std::string& replace, const std::string& with) | ||||
| { | ||||
| 	size_t pos; | ||||
| 	while((pos = str.find(replace)) != std::string::npos) | ||||
| 		str = str.replace(pos, replace.length(), with.c_str(), with.length()); | ||||
| 
 | ||||
| 	return str; | ||||
| } | ||||
| 
 | ||||
| // plaform-specific escape path function
 | ||||
| // on windows: just puts the path in quotes
 | ||||
| // everything else: assume bash and escape special characters with backslashes
 | ||||
| std::string escapePath(const boost::filesystem::path& path) | ||||
| { | ||||
| #ifdef WIN32 | ||||
| 	// windows escapes stuff by just putting everything in quotes
 | ||||
| 	return '"' + fs::path(path).make_preferred().string() + '"'; | ||||
| #else | ||||
| 	// a quick and dirty way to insert a backslash before most characters that would mess up a bash path
 | ||||
| 	std::string pathStr = path.string(); | ||||
| 
 | ||||
| 	const char* invalidChars = " '\"\\!$^&*(){}[]?;<>"; | ||||
| 	for(unsigned int i = 0; i < pathStr.length(); i++) | ||||
| 	{ | ||||
| 		char c; | ||||
| 		unsigned int charNum = 0; | ||||
| 		do { | ||||
| 			c = invalidChars[charNum]; | ||||
| 			if(pathStr[i] == c) | ||||
| 			{ | ||||
| 				pathStr.insert(i, "\\"); | ||||
| 				i++; | ||||
| 				break; | ||||
| 			} | ||||
| 			charNum++; | ||||
| 		} while(c != '\0'); | ||||
| 	} | ||||
| 
 | ||||
| 	return pathStr; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| std::string removeParenthesis(const std::string& str) | ||||
| { | ||||
| 	// remove anything in parenthesis or brackets
 | ||||
| 	// should be roughly equivalent to the regex replace "\((.*)\)|\[(.*)\]" with ""
 | ||||
| 	// I would love to just use regex, but it's not worth pulling in another boost lib for one function that is used once
 | ||||
| 
 | ||||
| 	std::string ret = str; | ||||
| 	size_t start, end; | ||||
| 
 | ||||
| 	static const int NUM_TO_REPLACE = 2; | ||||
| 	static const char toReplace[NUM_TO_REPLACE*2] = { '(', ')', '[', ']' }; | ||||
| 
 | ||||
| 	bool done = false; | ||||
| 	while(!done) | ||||
| 	{ | ||||
| 		done = true; | ||||
| 		for(int i = 0; i < NUM_TO_REPLACE; i++) | ||||
| 		{ | ||||
| 			end = ret.find_first_of(toReplace[i*2+1]); | ||||
| 			start = ret.find_last_of(toReplace[i*2], end); | ||||
| 
 | ||||
| 			if(start != std::string::npos && end != std::string::npos) | ||||
| 			{ | ||||
| 				ret.erase(start, end - start + 1); | ||||
| 				done = false; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// also strip whitespace
 | ||||
| 	end = ret.find_last_not_of(' '); | ||||
| 	if(end != std::string::npos) | ||||
| 		end++; | ||||
| 
 | ||||
| 	ret = ret.substr(0, end); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> commaStringToVector(std::string commaString) | ||||
| { | ||||
| 	// from a comma separated string, get a vector of strings
 | ||||
| 	std::vector<std::string> strs; | ||||
| 	boost::split(strs, commaString, boost::is_any_of(",")); | ||||
| 	return strs; | ||||
| } | ||||
| 
 | ||||
| std::string commaStringToVector(std::vector<std::string> stringVector) | ||||
| { | ||||
| 	std::string out = ""; | ||||
| 	// from a vector of system names get comma separated string
 | ||||
| 	for(std::vector<std::string>::iterator it = stringVector.begin() ; it != stringVector.end() ; it++ ) | ||||
| 	{ | ||||
| 		out = out + (out == "" ? "" : ",") + (*it); | ||||
| 	} | ||||
| 	return out; | ||||
| } | ||||
|  | @ -34,3 +34,16 @@ boost::filesystem::path makeRelativePath(const boost::filesystem::path& path, co | |||
| boost::filesystem::path resolvePath(const boost::filesystem::path& path, const boost::filesystem::path& relativeTo, bool allowHome); | ||||
| 
 | ||||
| boost::posix_time::ptime string_to_ptime(const std::string& str, const std::string& fmt = "%Y%m%dT%H%M%S%F%q"); | ||||
| 
 | ||||
| std::string escapePath(const boost::filesystem::path& path); | ||||
| 
 | ||||
| std::string strreplace(std::string str, const std::string& replace, const std::string& with); | ||||
| 
 | ||||
| // Remove (.*) and [.*] from str
 | ||||
| std::string removeParenthesis(const std::string& str); | ||||
| 
 | ||||
| // split a comma-separated string into a vector
 | ||||
| std::vector<std::string> commaStringToVector(std::string commaString); | ||||
| 
 | ||||
| // turn a vector of strings into a comma-separated string
 | ||||
| std::string commaStringToVector(std::vector<std::string> stringVector); | ||||
|  | @ -10,7 +10,7 @@ | |||
| #include "components/ImageComponent.h" | ||||
| 
 | ||||
| Window::Window() : mNormalizeNextUpdate(false), mFrameTimeElapsed(0), mFrameCountElapsed(0), mAverageDeltaTime(10), | ||||
| 	mAllowSleep(true), mSleeping(false), mTimeSinceLastInput(0), mScreenSaver(NULL), mRenderScreenSaver(false) | ||||
| 	mAllowSleep(true), mSleeping(false), mTimeSinceLastInput(0), mScreenSaver(NULL), mRenderScreenSaver(false), mInfoPopup(NULL) | ||||
| { | ||||
| 	mHelp = new HelpComponent(this); | ||||
| 	mBackgroundOverlay = new ImageComponent(this); | ||||
|  | @ -129,7 +129,7 @@ void Window::input(InputConfig* config, Input input) | |||
| 					} | ||||
| 					return; | ||||
| 				} | ||||
| 				else if(config->isMappedTo("start", input)) | ||||
| 				else if(config->isMappedTo("start", input) && input.value != 0) | ||||
| 				{ | ||||
| 					// launch game!
 | ||||
| 					cancelScreenSaver(); | ||||
|  | @ -138,10 +138,10 @@ void Window::input(InputConfig* config, Input input) | |||
| 					mSleeping = true; | ||||
| 				} | ||||
| 			} | ||||
| 			else if(input.value != 0) | ||||
| 			/*else if(input.value != 0)
 | ||||
| 			{ | ||||
| 				return; | ||||
| 			} | ||||
| 			}*/ | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | @ -258,6 +258,11 @@ void Window::render() | |||
| 	// Always call the screensaver render function regardless of whether the screensaver is active
 | ||||
| 	// or not because it may perform a fade on transition
 | ||||
| 	renderScreenSaver(); | ||||
| 
 | ||||
| 	if(!mRenderScreenSaver && mInfoPopup) | ||||
| 	{ | ||||
| 		mInfoPopup->render(transform); | ||||
| 	} | ||||
| 	 | ||||
| 	if(mTimeSinceLastInput >= screensaverTime && screensaverTime != 0) | ||||
| 	{ | ||||
|  |  | |||
|  | @ -25,6 +25,13 @@ public: | |||
| 		virtual void launchGame() = 0; | ||||
| 	}; | ||||
| 
 | ||||
| 	class InfoPopup { | ||||
| 	public: | ||||
| 		virtual void render(const Eigen::Affine3f& parentTrans) = 0; | ||||
| 		virtual void stop() = 0; | ||||
| 		virtual ~InfoPopup() {}; | ||||
| 	}; | ||||
| 
 | ||||
| 	Window(); | ||||
| 	~Window(); | ||||
| 
 | ||||
|  | @ -53,6 +60,8 @@ public: | |||
| 	void setHelpPrompts(const std::vector<HelpPrompt>& prompts, const HelpStyle& style); | ||||
| 
 | ||||
| 	void setScreenSaver(ScreenSaver* screenSaver) { mScreenSaver = screenSaver; } | ||||
| 	void setInfoPopup(InfoPopup* infoPopup) { delete mInfoPopup; mInfoPopup = infoPopup; } | ||||
| 	inline void stopInfoPopup() { if (mInfoPopup) mInfoPopup->stop(); }; | ||||
| 
 | ||||
| 	void startScreenSaver(); | ||||
| 	void cancelScreenSaver(); | ||||
|  | @ -68,6 +77,7 @@ private: | |||
| 	HelpComponent* mHelp; | ||||
| 	ImageComponent* mBackgroundOverlay; | ||||
| 	ScreenSaver*	mScreenSaver; | ||||
| 	InfoPopup*		mInfoPopup; | ||||
| 	bool			mRenderScreenSaver; | ||||
| 
 | ||||
| 	std::vector<GuiComponent*> mGuiStack; | ||||
|  |  | |||
|  | @ -46,7 +46,7 @@ float MenuComponent::getButtonGridHeight() const | |||
| 
 | ||||
| void MenuComponent::updateSize() | ||||
| { | ||||
| 	const float maxHeight = Renderer::getScreenHeight() * 0.7f; | ||||
| 	const float maxHeight = Renderer::getScreenHeight() * 0.75f; | ||||
| 	float height = TITLE_HEIGHT + mList->getTotalRowHeight() + getButtonGridHeight() + 2; | ||||
| 	if(height > maxHeight) | ||||
| 	{ | ||||
|  |  | |||
|  | @ -47,6 +47,23 @@ void SwitchComponent::setState(bool state) | |||
| 	onStateChanged(); | ||||
| } | ||||
| 
 | ||||
| std::string SwitchComponent::getValue() const | ||||
| { | ||||
| 	return mState ?  "true" : "false"; | ||||
| } | ||||
| 
 | ||||
| void SwitchComponent::setValue(const std::string& statestring) | ||||
| { | ||||
| 	if (statestring == "true") | ||||
| 	{ | ||||
| 		mState = true; | ||||
| 	}else | ||||
| 	{ | ||||
| 		mState = false; | ||||
| 	} | ||||
| 	onStateChanged(); | ||||
| } | ||||
| 
 | ||||
| void SwitchComponent::onStateChanged() | ||||
| { | ||||
| 	mImage.setImage(mState ? ":/on.svg" : ":/off.svg"); | ||||
|  |  | |||
|  | @ -16,6 +16,8 @@ public: | |||
| 
 | ||||
| 	bool getState() const; | ||||
| 	void setState(bool state); | ||||
| 	std::string getValue() const; | ||||
| 	void setValue(const std::string& statestring) override; | ||||
| 
 | ||||
| 	virtual std::vector<HelpPrompt> getHelpPrompts() override; | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ | |||
| 
 | ||||
| class TextCache; | ||||
| 
 | ||||
| #define FONT_SIZE_MINI ((unsigned int)(0.030f * Renderer::getScreenHeight())) | ||||
| #define FONT_SIZE_SMALL ((unsigned int)(0.035f * Renderer::getScreenHeight())) | ||||
| #define FONT_SIZE_MEDIUM ((unsigned int)(0.045f * Renderer::getScreenHeight())) | ||||
| #define FONT_SIZE_LARGE ((unsigned int)(0.085f * Renderer::getScreenHeight())) | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Jools Wills
						Jools Wills