mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2025-01-18 07:05:39 +00:00
Adding support for Virtual Systems
- Refactoring System Environment data - Added Virtual System Manager class - Added "all", "favorites" and "last played" systems - Added GuiInfoPopup class for notifications - Added Favorites to metadata, as well as a shortcut to toggle favorites - Added warning if enabling systems but themes don't support it - Added "filter by favorites" per system - Adjusted "Go to Random Game" behavior to account for the fact that we now have an "All Games" system - Added "sort by system name" for the collections
This commit is contained in:
parent
c874c506d9
commit
d0cdbf2159
|
@ -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;
|
||||
}
|
||||
|
@ -432,6 +363,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"
|
||||
|
@ -238,6 +239,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");
|
||||
|
@ -364,13 +368,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)
|
||||
|
|
|
@ -149,7 +149,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{
|
||||
|
@ -216,7 +219,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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -95,6 +95,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