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:
pjft 2017-06-12 17:38:59 +01:00
parent c874c506d9
commit d0cdbf2159
40 changed files with 1428 additions and 285 deletions

View file

@ -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

View 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";
}

View 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;
};

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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
{

View file

@ -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;

View file

@ -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;
}
};

View file

@ -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;
};

View file

@ -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;
}

View file

@ -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]));

View file

@ -13,6 +13,7 @@ enum MetaDataType
MD_STRING,
MD_INT,
MD_FLOAT,
MD_BOOL,
//specialized types
MD_MULTILINE_STRING,

View file

@ -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();

View file

@ -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;

View file

@ -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;
}
}
}
}

View 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;
}

View 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;
};

View file

@ -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

View file

@ -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()

View 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;
}

View 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;
};

View file

@ -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()

View file

@ -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;
};

View file

@ -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()

View file

@ -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)

View file

@ -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)

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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;

View file

@ -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);
}

View file

@ -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_

View file

@ -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;
}

View file

@ -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);

View file

@ -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)
{

View file

@ -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;

View file

@ -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)
{

View file

@ -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");

View file

@ -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;

View file

@ -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()))