mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-26 16:15:39 +00:00
Merge pull request #115 from pjft/RetroPie-master-filter-rebase
Adding generic gamelist filter funcionality for ES
This commit is contained in:
commit
a909f10b2d
|
@ -10,6 +10,7 @@ set(ES_HEADERS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.h
|
||||||
|
|
||||||
# GuiComponents
|
# GuiComponents
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/AsyncReqComponent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/components/AsyncReqComponent.h
|
||||||
|
@ -26,6 +27,7 @@ set(ES_HEADERS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperStart.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperStart.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h
|
||||||
|
|
||||||
# Scrapers
|
# Scrapers
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.h
|
||||||
|
@ -57,6 +59,7 @@ set(ES_SOURCES
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/VolumeControl.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.cpp
|
||||||
|
|
||||||
# GuiComponents
|
# GuiComponents
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/AsyncReqComponent.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/components/AsyncReqComponent.cpp
|
||||||
|
@ -72,6 +75,7 @@ set(ES_SOURCES
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiSettings.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperStart.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperStart.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp
|
||||||
|
|
||||||
# Scrapers
|
# Scrapers
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.cpp
|
||||||
|
|
|
@ -81,6 +81,26 @@ const std::string& FileData::getThumbnailPath() const
|
||||||
return metadata.get("image");
|
return metadata.get("image");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<FileData*>& FileData::getChildrenListToDisplay() {
|
||||||
|
|
||||||
|
FileFilterIndex* idx = mSystem->getIndex();
|
||||||
|
if (idx->isFiltered()) {
|
||||||
|
mFilteredChildren.clear();
|
||||||
|
for(auto it = mChildren.begin(); it != mChildren.end(); it++)
|
||||||
|
{
|
||||||
|
if (idx->showFile((*it))) {
|
||||||
|
mFilteredChildren.push_back(*it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFilteredChildren;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return mChildren;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const std::string& FileData::getVideoPath() const
|
const std::string& FileData::getVideoPath() const
|
||||||
{
|
{
|
||||||
return metadata.get("video");
|
return metadata.get("video");
|
||||||
|
@ -91,19 +111,22 @@ const std::string& FileData::getMarqueePath() const
|
||||||
return metadata.get("marquee");
|
return metadata.get("marquee");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<FileData*> FileData::getFilesRecursive(unsigned int typeMask, bool displayedOnly) const
|
||||||
std::vector<FileData*> FileData::getFilesRecursive(unsigned int typeMask) const
|
|
||||||
{
|
{
|
||||||
std::vector<FileData*> out;
|
std::vector<FileData*> out;
|
||||||
|
FileFilterIndex* idx = mSystem->getIndex();
|
||||||
|
|
||||||
for(auto it = mChildren.begin(); it != mChildren.end(); it++)
|
for(auto it = mChildren.begin(); it != mChildren.end(); it++)
|
||||||
{
|
{
|
||||||
if((*it)->getType() & typeMask)
|
if((*it)->getType() & typeMask)
|
||||||
out.push_back(*it);
|
{
|
||||||
|
if (!displayedOnly || !idx->isFiltered() || idx->showFile(*it))
|
||||||
|
out.push_back(*it);
|
||||||
|
}
|
||||||
|
|
||||||
if((*it)->getChildren().size() > 0)
|
if((*it)->getChildren().size() > 0)
|
||||||
{
|
{
|
||||||
std::vector<FileData*> subchildren = (*it)->getFilesRecursive(typeMask);
|
std::vector<FileData*> subchildren = (*it)->getFilesRecursive(typeMask, displayedOnly);
|
||||||
out.insert(out.end(), subchildren.cbegin(), subchildren.cend());
|
out.insert(out.end(), subchildren.cbegin(), subchildren.cend());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,8 @@ class SystemData;
|
||||||
enum FileType
|
enum FileType
|
||||||
{
|
{
|
||||||
GAME = 1, // Cannot have children.
|
GAME = 1, // Cannot have children.
|
||||||
FOLDER = 2
|
FOLDER = 2,
|
||||||
|
PLACEHOLDER = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
enum FileChangeType
|
enum FileChangeType
|
||||||
|
@ -48,11 +49,14 @@ public:
|
||||||
virtual const std::string& getVideoPath() const;
|
virtual const std::string& getVideoPath() const;
|
||||||
virtual const std::string& getMarqueePath() const;
|
virtual const std::string& getMarqueePath() const;
|
||||||
|
|
||||||
std::vector<FileData*> getFilesRecursive(unsigned int typeMask) const;
|
const std::vector<FileData*>& getChildrenListToDisplay();
|
||||||
|
std::vector<FileData*> getFilesRecursive(unsigned int typeMask, bool displayedOnly = false) const;
|
||||||
|
|
||||||
void addChild(FileData* file); // Error if mType != FOLDER
|
void addChild(FileData* file); // Error if mType != FOLDER
|
||||||
void removeChild(FileData* file); //Error if mType != FOLDER
|
void removeChild(FileData* file); //Error if mType != FOLDER
|
||||||
|
|
||||||
|
inline bool isPlaceHolder() { return mType == PLACEHOLDER; };
|
||||||
|
|
||||||
// Returns our best guess at the "real" name for this file (will attempt to perform MAME name translation)
|
// Returns our best guess at the "real" name for this file (will attempt to perform MAME name translation)
|
||||||
std::string getDisplayName() const;
|
std::string getDisplayName() const;
|
||||||
|
|
||||||
|
@ -82,4 +86,5 @@ private:
|
||||||
FileData* mParent;
|
FileData* mParent;
|
||||||
std::unordered_map<std::string,FileData*> mChildrenByFilename;
|
std::unordered_map<std::string,FileData*> mChildrenByFilename;
|
||||||
std::vector<FileData*> mChildren;
|
std::vector<FileData*> mChildren;
|
||||||
|
std::vector<FileData*> mFilteredChildren;
|
||||||
};
|
};
|
||||||
|
|
377
es-app/src/FileFilterIndex.cpp
Normal file
377
es-app/src/FileFilterIndex.cpp
Normal file
|
@ -0,0 +1,377 @@
|
||||||
|
#include "FileFilterIndex.h"
|
||||||
|
|
||||||
|
#define UNKNOWN_LABEL "UNKNOWN"
|
||||||
|
#define INCLUDE_UNKNOWN false;
|
||||||
|
|
||||||
|
FileFilterIndex::FileFilterIndex()
|
||||||
|
: filterByGenre(false), filterByPlayers(false), filterByPubDev(false), filterByRatings(false)
|
||||||
|
{
|
||||||
|
FilterDataDecl filterDecls[] = {
|
||||||
|
//type //allKeys //filteredBy //filteredKeys //primaryKey //hasSecondaryKey //secondaryKey //menuLabel
|
||||||
|
{ 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" },
|
||||||
|
{ RATINGS_FILTER, &ratingsIndexAllKeys, &filterByRatings, &ratingsIndexFilteredKeys, "rating", false, "", "RATING" }
|
||||||
|
};
|
||||||
|
|
||||||
|
filterDataDecl = std::vector<FilterDataDecl>(filterDecls, filterDecls + sizeof(filterDecls) / sizeof(filterDecls[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
FileFilterIndex::~FileFilterIndex()
|
||||||
|
{
|
||||||
|
clearIndex(genreIndexAllKeys);
|
||||||
|
clearIndex(playersIndexAllKeys);
|
||||||
|
clearIndex(pubDevIndexAllKeys);
|
||||||
|
clearIndex(ratingsIndexAllKeys);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<FilterDataDecl>& FileFilterIndex::getFilterDataDecls()
|
||||||
|
{
|
||||||
|
return filterDataDecl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FileFilterIndex::getIndexableKey(FileData* game, FilterIndexType type, bool getSecondary)
|
||||||
|
{
|
||||||
|
std::string key = "";
|
||||||
|
switch(type)
|
||||||
|
{
|
||||||
|
case GENRE_FILTER:
|
||||||
|
{
|
||||||
|
key = strToUpper(game->metadata.get("genre"));
|
||||||
|
boost::trim(key);
|
||||||
|
if (getSecondary && !key.empty()) {
|
||||||
|
std::istringstream f(key);
|
||||||
|
std::string newKey;
|
||||||
|
getline(f, newKey, '/');
|
||||||
|
if (!newKey.empty() && newKey != key)
|
||||||
|
{
|
||||||
|
key = newKey;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
key = std::string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PLAYER_FILTER:
|
||||||
|
{
|
||||||
|
if (getSecondary)
|
||||||
|
break;
|
||||||
|
|
||||||
|
key = game->metadata.get("players");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PUBDEV_FILTER:
|
||||||
|
{
|
||||||
|
key = strToUpper(game->metadata.get("publisher"));
|
||||||
|
boost::trim(key);
|
||||||
|
|
||||||
|
if ((getSecondary && !key.empty()) || (!getSecondary && key.empty()))
|
||||||
|
key = strToUpper(game->metadata.get("developer"));
|
||||||
|
else
|
||||||
|
key = strToUpper(game->metadata.get("publisher"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RATINGS_FILTER:
|
||||||
|
{
|
||||||
|
int ratingNumber = 0;
|
||||||
|
if (!getSecondary)
|
||||||
|
{
|
||||||
|
std::string ratingString = game->metadata.get("rating");
|
||||||
|
if (!ratingString.empty()) {
|
||||||
|
try {
|
||||||
|
ratingNumber = boost::math::iround(std::stod(ratingString)*5);
|
||||||
|
if (ratingNumber < 0)
|
||||||
|
ratingNumber = 0;
|
||||||
|
|
||||||
|
key = std::to_string(ratingNumber) + " STARS";
|
||||||
|
}
|
||||||
|
catch (int e)
|
||||||
|
{
|
||||||
|
LOG(LogError) << "Error parsing Rating (invalid value, expected decimal): " << ratingString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boost::trim(key);
|
||||||
|
if (key.empty() || (type == RATINGS_FILTER && key == "0 STARS")) {
|
||||||
|
key = UNKNOWN_LABEL;
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileFilterIndex::addToIndex(FileData* game)
|
||||||
|
{
|
||||||
|
manageGenreEntryInIndex(game);
|
||||||
|
managePlayerEntryInIndex(game);
|
||||||
|
managePubDevEntryInIndex(game);
|
||||||
|
manageRatingsEntryInIndex(game);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileFilterIndex::removeFromIndex(FileData* game)
|
||||||
|
{
|
||||||
|
manageGenreEntryInIndex(game, true);
|
||||||
|
managePlayerEntryInIndex(game, true);
|
||||||
|
managePubDevEntryInIndex(game, true);
|
||||||
|
manageRatingsEntryInIndex(game, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileFilterIndex::setFilter(FilterIndexType type, std::vector<std::string>* values)
|
||||||
|
{
|
||||||
|
// test if it exists before setting
|
||||||
|
if(type == NONE)
|
||||||
|
{
|
||||||
|
clearAllFilters();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (std::vector<FilterDataDecl>::iterator it = filterDataDecl.begin(); it != filterDataDecl.end(); ++it ) {
|
||||||
|
if ((*it).type == type)
|
||||||
|
{
|
||||||
|
FilterDataDecl filterData = (*it);
|
||||||
|
*(filterData.filteredByRef) = values->size() > 0;
|
||||||
|
filterData.currentFilteredKeys->clear();
|
||||||
|
for (std::vector<std::string>::iterator vit = values->begin(); vit != values->end(); ++vit ) {
|
||||||
|
// check if exists
|
||||||
|
if (filterData.allIndexKeys->find(*vit) != filterData.allIndexKeys->end()) {
|
||||||
|
filterData.currentFilteredKeys->push_back(std::string(*vit));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileFilterIndex::clearAllFilters()
|
||||||
|
{
|
||||||
|
for (std::vector<FilterDataDecl>::iterator it = filterDataDecl.begin(); it != filterDataDecl.end(); ++it ) {
|
||||||
|
FilterDataDecl filterData = (*it);
|
||||||
|
*(filterData.filteredByRef) = false;
|
||||||
|
filterData.currentFilteredKeys->clear();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileFilterIndex::debugPrintIndexes()
|
||||||
|
{
|
||||||
|
LOG(LogInfo) << "Printing Indexes...";
|
||||||
|
for (auto x: playersIndexAllKeys) {
|
||||||
|
LOG(LogInfo) << "Multiplayer Index: " << x.first << ": " << x.second;
|
||||||
|
}
|
||||||
|
for (auto x: genreIndexAllKeys) {
|
||||||
|
LOG(LogInfo) << "Genre Index: " << x.first << ": " << x.second;
|
||||||
|
}
|
||||||
|
for (auto x: ratingsIndexAllKeys) {
|
||||||
|
LOG(LogInfo) << "Ratings Index: " << x.first << ": " << x.second;
|
||||||
|
}
|
||||||
|
for (auto x: pubDevIndexAllKeys) {
|
||||||
|
LOG(LogInfo) << "PubDev Index: " << x.first << ": " << x.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileFilterIndex::showFile(FileData* game)
|
||||||
|
{
|
||||||
|
// this shouldn't happen, but just in case let's get it out of the way
|
||||||
|
if (!isFiltered())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// if folder, needs further inspection - i.e. see if folder contains at least one element
|
||||||
|
// that should be shown
|
||||||
|
if (game->getType() == FOLDER) {
|
||||||
|
std::vector<FileData*> children = game->getChildren();
|
||||||
|
// iterate through all of the children, until there's a match
|
||||||
|
|
||||||
|
for (std::vector<FileData*>::iterator it = children.begin(); it != children.end(); ++it ) {
|
||||||
|
if (showFile(*it))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool keepGoing = false;
|
||||||
|
|
||||||
|
for (std::vector<FilterDataDecl>::iterator it = filterDataDecl.begin(); it != filterDataDecl.end(); ++it ) {
|
||||||
|
FilterDataDecl filterData = (*it);
|
||||||
|
if(*(filterData.filteredByRef)) {
|
||||||
|
// try to find a match
|
||||||
|
std::string key = getIndexableKey(game, filterData.type, false);
|
||||||
|
keepGoing = isKeyBeingFilteredBy(key, filterData.type);
|
||||||
|
|
||||||
|
// if we didn't find a match, try for secondary keys - i.e. publisher and dev, or first genre
|
||||||
|
if (!keepGoing) {
|
||||||
|
if (!filterData.hasSecondaryKey)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string secKey = getIndexableKey(game, filterData.type, true);
|
||||||
|
if (secKey != UNKNOWN_LABEL)
|
||||||
|
{
|
||||||
|
keepGoing = isKeyBeingFilteredBy(secKey, filterData.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if still nothing, then it's not a match
|
||||||
|
if (!keepGoing)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return keepGoing;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 };
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (filterTypes[i] == type) {
|
||||||
|
for (std::vector<std::string>::iterator it = filterKeysList[i].begin(); it != filterKeysList[i].end(); ++it ) {
|
||||||
|
if (key == (*it))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileFilterIndex::manageGenreEntryInIndex(FileData* game, bool remove)
|
||||||
|
{
|
||||||
|
|
||||||
|
std::string key = getIndexableKey(game, GENRE_FILTER, false);
|
||||||
|
|
||||||
|
// flag for including unknowns
|
||||||
|
bool includeUnknown = INCLUDE_UNKNOWN;
|
||||||
|
|
||||||
|
// only add unknown in pubdev IF both dev and pub are empty
|
||||||
|
if (!includeUnknown && (key == UNKNOWN_LABEL || key == "BIOS")) {
|
||||||
|
// no valid genre info found
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
manageIndexEntry(&genreIndexAllKeys, key, remove);
|
||||||
|
|
||||||
|
key = getIndexableKey(game, GENRE_FILTER, true);
|
||||||
|
if (!includeUnknown && key == UNKNOWN_LABEL)
|
||||||
|
{
|
||||||
|
manageIndexEntry(&genreIndexAllKeys, key, remove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileFilterIndex::managePlayerEntryInIndex(FileData* game, bool remove)
|
||||||
|
{
|
||||||
|
// flag for including unknowns
|
||||||
|
bool includeUnknown = INCLUDE_UNKNOWN;
|
||||||
|
std::string key = getIndexableKey(game, PLAYER_FILTER, false);
|
||||||
|
|
||||||
|
// only add unknown in pubdev IF both dev and pub are empty
|
||||||
|
if (!includeUnknown && key == UNKNOWN_LABEL) {
|
||||||
|
// no valid player info found
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
manageIndexEntry(&playersIndexAllKeys, key, remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileFilterIndex::managePubDevEntryInIndex(FileData* game, bool remove)
|
||||||
|
{
|
||||||
|
std::string pub = getIndexableKey(game, PUBDEV_FILTER, false);
|
||||||
|
std::string dev = getIndexableKey(game, PUBDEV_FILTER, true);
|
||||||
|
|
||||||
|
// flag for including unknowns
|
||||||
|
bool includeUnknown = INCLUDE_UNKNOWN;
|
||||||
|
bool unknownPub = false;
|
||||||
|
bool unknownDev = false;
|
||||||
|
|
||||||
|
if (pub == UNKNOWN_LABEL) {
|
||||||
|
unknownPub = true;
|
||||||
|
}
|
||||||
|
if (dev == UNKNOWN_LABEL) {
|
||||||
|
unknownDev = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!includeUnknown && unknownDev && unknownPub) {
|
||||||
|
// no valid rating info found
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unknownDev && unknownPub) {
|
||||||
|
// if no info at all
|
||||||
|
manageIndexEntry(&pubDevIndexAllKeys, pub, remove);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!unknownDev) {
|
||||||
|
// if no info at all
|
||||||
|
manageIndexEntry(&pubDevIndexAllKeys, dev, remove);
|
||||||
|
}
|
||||||
|
if (!unknownPub) {
|
||||||
|
// if no info at all
|
||||||
|
manageIndexEntry(&pubDevIndexAllKeys, pub, remove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileFilterIndex::manageRatingsEntryInIndex(FileData* game, bool remove)
|
||||||
|
{
|
||||||
|
std::string key = getIndexableKey(game, RATINGS_FILTER, false);
|
||||||
|
|
||||||
|
// flag for including unknowns
|
||||||
|
bool includeUnknown = INCLUDE_UNKNOWN;
|
||||||
|
|
||||||
|
if (!includeUnknown && key == UNKNOWN_LABEL) {
|
||||||
|
// no valid rating info found
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
manageIndexEntry(&ratingsIndexAllKeys, 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)
|
||||||
|
return;
|
||||||
|
if (remove) {
|
||||||
|
// removing entry
|
||||||
|
if (index->find(key) == index->end())
|
||||||
|
{
|
||||||
|
// this shouldn't happen
|
||||||
|
LOG(LogError) << "Couldn't find entry in index! " << key;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(index->at(key))--;
|
||||||
|
if(index->at(key) <= 0) {
|
||||||
|
index->erase(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// adding entry
|
||||||
|
if (index->find(key) == index->end())
|
||||||
|
{
|
||||||
|
(*index)[key] = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(index->at(key))++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileFilterIndex::clearIndex(std::map<std::string, int> indexMap)
|
||||||
|
{
|
||||||
|
indexMap.clear();
|
||||||
|
}
|
79
es-app/src/FileFilterIndex.h
Normal file
79
es-app/src/FileFilterIndex.h
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include "FileData.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include <boost/math/special_functions/round.hpp>
|
||||||
|
#include <boost/algorithm/string.hpp>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include "Util.h"
|
||||||
|
|
||||||
|
enum FilterIndexType
|
||||||
|
{
|
||||||
|
NONE,
|
||||||
|
GENRE_FILTER,
|
||||||
|
PLAYER_FILTER,
|
||||||
|
PUBDEV_FILTER,
|
||||||
|
RATINGS_FILTER
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FilterDataDecl
|
||||||
|
{
|
||||||
|
FilterIndexType type; // type of filter
|
||||||
|
std::map<std::string, int>* allIndexKeys; // all possible filters for this type
|
||||||
|
bool* filteredByRef; // is it filtered by this type
|
||||||
|
std::vector<std::string>* currentFilteredKeys; // current keys being filtered for
|
||||||
|
std::string primaryKey; // primary key in metadata
|
||||||
|
bool hasSecondaryKey; // has secondary key for comparison
|
||||||
|
std::string secondaryKey; // what's the secondary key
|
||||||
|
std::string menuLabel; // text to show in menu
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileFilterIndex
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FileFilterIndex();
|
||||||
|
~FileFilterIndex();
|
||||||
|
void addToIndex(FileData* game);
|
||||||
|
void removeFromIndex(FileData* game);
|
||||||
|
void setFilter(FilterIndexType type, std::vector<std::string>* values);
|
||||||
|
void clearAllFilters();
|
||||||
|
void debugPrintIndexes();
|
||||||
|
bool showFile(FileData* game);
|
||||||
|
bool isFiltered() { return (filterByGenre || filterByPlayers || filterByPubDev || filterByRatings); };
|
||||||
|
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;
|
||||||
|
std::string getIndexableKey(FileData* game, FilterIndexType type, bool getSecondary);
|
||||||
|
|
||||||
|
void manageGenreEntryInIndex(FileData* game, bool remove = false);
|
||||||
|
void managePlayerEntryInIndex(FileData* game, bool remove = false);
|
||||||
|
void managePubDevEntryInIndex(FileData* game, bool remove = false);
|
||||||
|
void manageRatingsEntryInIndex(FileData* game, bool remove = false);
|
||||||
|
|
||||||
|
void manageIndexEntry(std::map<std::string, int>* index, std::string key, bool remove);
|
||||||
|
|
||||||
|
void clearIndex(std::map<std::string, int> indexMap);
|
||||||
|
|
||||||
|
bool filterByGenre;
|
||||||
|
bool filterByPlayers;
|
||||||
|
bool filterByPubDev;
|
||||||
|
bool filterByRatings;
|
||||||
|
|
||||||
|
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::vector<std::string> genreIndexFilteredKeys;
|
||||||
|
std::vector<std::string> playersIndexFilteredKeys;
|
||||||
|
std::vector<std::string> pubDevIndexFilteredKeys;
|
||||||
|
std::vector<std::string> ratingsIndexFilteredKeys;
|
||||||
|
|
||||||
|
FileData* mRootFolder;
|
||||||
|
|
||||||
|
};
|
|
@ -141,6 +141,14 @@ void parseGamelist(SystemData* system)
|
||||||
file->metadata.set("name", defaultName);
|
file->metadata.set("name", defaultName);
|
||||||
|
|
||||||
file->metadata.resetChangedFlag();
|
file->metadata.resetChangedFlag();
|
||||||
|
|
||||||
|
// index if it's a game!
|
||||||
|
if(type == GAME)
|
||||||
|
{
|
||||||
|
FileFilterIndex* index = system->getIndex();
|
||||||
|
index->addToIndex(file);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,8 @@ SystemData::SystemData(const std::string& name, const std::string& fullName, con
|
||||||
mPlatformIds = platformIds;
|
mPlatformIds = platformIds;
|
||||||
mThemeFolder = themeFolder;
|
mThemeFolder = themeFolder;
|
||||||
|
|
||||||
|
mFilterIndex = new FileFilterIndex();
|
||||||
|
|
||||||
mRootFolder = new FileData(FOLDER, mStartPath, this);
|
mRootFolder = new FileData(FOLDER, mStartPath, this);
|
||||||
mRootFolder->metadata.set("name", mFullName);
|
mRootFolder->metadata.set("name", mFullName);
|
||||||
|
|
||||||
|
@ -59,6 +61,7 @@ SystemData::~SystemData()
|
||||||
}
|
}
|
||||||
|
|
||||||
delete mRootFolder;
|
delete mRootFolder;
|
||||||
|
delete mFilterIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -429,6 +432,11 @@ unsigned int SystemData::getGameCount() const
|
||||||
return mRootFolder->getFilesRecursive(GAME).size();
|
return mRootFolder->getFilesRecursive(GAME).size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned int SystemData::getDisplayedGameCount() const
|
||||||
|
{
|
||||||
|
return mRootFolder->getFilesRecursive(GAME, true).size();
|
||||||
|
}
|
||||||
|
|
||||||
void SystemData::loadTheme()
|
void SystemData::loadTheme()
|
||||||
{
|
{
|
||||||
mTheme = std::make_shared<ThemeData>();
|
mTheme = std::make_shared<ThemeData>();
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "MetaData.h"
|
#include "MetaData.h"
|
||||||
#include "PlatformId.h"
|
#include "PlatformId.h"
|
||||||
#include "ThemeData.h"
|
#include "ThemeData.h"
|
||||||
|
#include "FileFilterIndex.h"
|
||||||
|
|
||||||
class SystemData
|
class SystemData
|
||||||
{
|
{
|
||||||
|
@ -32,6 +33,7 @@ public:
|
||||||
std::string getThemePath() const;
|
std::string getThemePath() const;
|
||||||
|
|
||||||
unsigned int getGameCount() const;
|
unsigned int getGameCount() const;
|
||||||
|
unsigned int getDisplayedGameCount() const;
|
||||||
|
|
||||||
void launchGame(Window* window, FileData* game);
|
void launchGame(Window* window, FileData* game);
|
||||||
|
|
||||||
|
@ -64,6 +66,8 @@ public:
|
||||||
// Load or re-load theme.
|
// Load or re-load theme.
|
||||||
void loadTheme();
|
void loadTheme();
|
||||||
|
|
||||||
|
FileFilterIndex* getIndex() { return mFilterIndex; };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string mName;
|
std::string mName;
|
||||||
std::string mFullName;
|
std::string mFullName;
|
||||||
|
@ -76,5 +80,7 @@ private:
|
||||||
|
|
||||||
void populateFolder(FileData* folder);
|
void populateFolder(FileData* folder);
|
||||||
|
|
||||||
|
FileFilterIndex* mFilterIndex;
|
||||||
|
|
||||||
FileData* mRootFolder;
|
FileData* mRootFolder;
|
||||||
};
|
};
|
||||||
|
|
112
es-app/src/guis/GuiGamelistFilter.cpp
Normal file
112
es-app/src/guis/GuiGamelistFilter.cpp
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
#include "guis/GuiGamelistFilter.h"
|
||||||
|
#include "guis/GuiMsgBox.h"
|
||||||
|
#include "views/ViewController.h"
|
||||||
|
|
||||||
|
#include "components/TextComponent.h"
|
||||||
|
#include "components/OptionListComponent.h"
|
||||||
|
|
||||||
|
GuiGamelistFilter::GuiGamelistFilter(Window* window, SystemData* system) : GuiComponent(window), mMenu(window, "FILTER GAMELIST BY"), mSystem(system)
|
||||||
|
{
|
||||||
|
initializeMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuiGamelistFilter::initializeMenu()
|
||||||
|
{
|
||||||
|
addChild(&mMenu);
|
||||||
|
|
||||||
|
// get filters from system
|
||||||
|
|
||||||
|
mFilterIndex = mSystem->getIndex();
|
||||||
|
|
||||||
|
ComponentListRow row;
|
||||||
|
|
||||||
|
// show filtered menu
|
||||||
|
row.elements.clear();
|
||||||
|
row.addElement(std::make_shared<TextComponent>(mWindow, "RESET ALL FILTERS", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
||||||
|
row.makeAcceptInputHandler(std::bind(&GuiGamelistFilter::resetAllFilters, this));
|
||||||
|
mMenu.addRow(row);
|
||||||
|
row.elements.clear();
|
||||||
|
|
||||||
|
addFiltersToMenu();
|
||||||
|
|
||||||
|
mMenu.addButton("BACK", "back", std::bind(&GuiGamelistFilter::applyFilters, this));
|
||||||
|
|
||||||
|
mMenu.setPosition((Renderer::getScreenWidth() - mMenu.getSize().x()) / 2, Renderer::getScreenHeight() * 0.15f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuiGamelistFilter::resetAllFilters()
|
||||||
|
{
|
||||||
|
mFilterIndex->clearAllFilters();
|
||||||
|
for (std::map<FilterIndexType, std::shared_ptr< OptionListComponent<std::string> >>::iterator it = mFilterOptions.begin(); it != mFilterOptions.end(); ++it ) {
|
||||||
|
std::shared_ptr< OptionListComponent<std::string> > optionList = it->second;
|
||||||
|
optionList->selectNone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GuiGamelistFilter::~GuiGamelistFilter()
|
||||||
|
{
|
||||||
|
mFilterOptions.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuiGamelistFilter::addFiltersToMenu()
|
||||||
|
{
|
||||||
|
std::vector<FilterDataDecl> decls = mFilterIndex->getFilterDataDecls();
|
||||||
|
for (std::vector<FilterDataDecl>::iterator it = decls.begin(); it != decls.end(); ++it ) {
|
||||||
|
|
||||||
|
FilterIndexType type = (*it).type; // type of filter
|
||||||
|
std::map<std::string, int>* allKeys = (*it).allIndexKeys; // all possible filters for this type
|
||||||
|
std::vector<std::string>* allFilteredKeys = (*it).currentFilteredKeys; // current keys being filtered for
|
||||||
|
std::string menuLabel = (*it).menuLabel; // text to show in menu
|
||||||
|
std::shared_ptr< OptionListComponent<std::string> > optionList;
|
||||||
|
|
||||||
|
|
||||||
|
// add filters (with first one selected)
|
||||||
|
ComponentListRow row;
|
||||||
|
|
||||||
|
// add genres
|
||||||
|
optionList = std::make_shared< OptionListComponent<std::string> >(mWindow, menuLabel, true);
|
||||||
|
for(auto it: *allKeys)
|
||||||
|
{
|
||||||
|
optionList->add(it.first, it.first, mFilterIndex->isKeyBeingFilteredBy(it.first, type));
|
||||||
|
}
|
||||||
|
if (allKeys->size() > 0)
|
||||||
|
mMenu.addWithLabel(menuLabel, optionList);
|
||||||
|
|
||||||
|
mFilterOptions[type] = optionList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuiGamelistFilter::applyFilters()
|
||||||
|
{
|
||||||
|
std::vector<FilterDataDecl> decls = mFilterIndex->getFilterDataDecls();
|
||||||
|
for (std::map<FilterIndexType, std::shared_ptr< OptionListComponent<std::string> >>::iterator it = mFilterOptions.begin(); it != mFilterOptions.end(); ++it ) {
|
||||||
|
std::shared_ptr< OptionListComponent<std::string> > optionList = it->second;
|
||||||
|
std::vector<std::string> filters = optionList->getSelectedObjects();
|
||||||
|
mFilterIndex->setFilter(it->first, &filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GuiGamelistFilter::input(InputConfig* config, Input input)
|
||||||
|
{
|
||||||
|
bool consumed = GuiComponent::input(config, input);
|
||||||
|
if(consumed)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if(config->isMappedTo("b", input) && input.value != 0)
|
||||||
|
{
|
||||||
|
applyFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<HelpPrompt> GuiGamelistFilter::getHelpPrompts()
|
||||||
|
{
|
||||||
|
std::vector<HelpPrompt> prompts = mMenu.getHelpPrompts();
|
||||||
|
prompts.push_back(HelpPrompt("b", "back"));
|
||||||
|
return prompts;
|
||||||
|
}
|
34
es-app/src/guis/GuiGamelistFilter.h
Normal file
34
es-app/src/guis/GuiGamelistFilter.h
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GuiComponent.h"
|
||||||
|
#include "SystemData.h"
|
||||||
|
#include "components/MenuComponent.h"
|
||||||
|
#include "FileFilterIndex.h"
|
||||||
|
#include "Log.h"
|
||||||
|
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class OptionListComponent;
|
||||||
|
|
||||||
|
|
||||||
|
class GuiGamelistFilter : public GuiComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GuiGamelistFilter(Window* window, SystemData* system);
|
||||||
|
~GuiGamelistFilter();
|
||||||
|
bool input(InputConfig* config, Input input) override;
|
||||||
|
|
||||||
|
virtual std::vector<HelpPrompt> getHelpPrompts() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void initializeMenu();
|
||||||
|
void applyFilters();
|
||||||
|
void resetAllFilters();
|
||||||
|
void addFiltersToMenu();
|
||||||
|
|
||||||
|
std::map<FilterIndexType, std::shared_ptr< OptionListComponent<std::string> >> mFilterOptions;
|
||||||
|
|
||||||
|
MenuComponent mMenu;
|
||||||
|
SystemData* mSystem;
|
||||||
|
FileFilterIndex* mFilterIndex;
|
||||||
|
};
|
|
@ -4,35 +4,21 @@
|
||||||
#include "views/ViewController.h"
|
#include "views/ViewController.h"
|
||||||
|
|
||||||
GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : GuiComponent(window),
|
GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : GuiComponent(window),
|
||||||
mSystem(system),
|
mSystem(system), mMenu(window, "OPTIONS"), fromPlaceholder(false), mFiltersChanged(false)
|
||||||
mMenu(window, "OPTIONS")
|
|
||||||
{
|
{
|
||||||
addChild(&mMenu);
|
addChild(&mMenu);
|
||||||
|
|
||||||
// jump to letter
|
// check it's not a placeholder folder - if it is, only show "Filter Options"
|
||||||
char curChar = toupper(getGamelist()->getCursor()->getName()[0]);
|
FileData* file = getGamelist()->getCursor();
|
||||||
if(curChar < 'A' || curChar > 'Z')
|
fromPlaceholder = file->isPlaceHolder();
|
||||||
curChar = 'A';
|
bool isFiltered = system->getIndex()->isFiltered();
|
||||||
|
|
||||||
mJumpToLetterList = std::make_shared<LetterList>(mWindow, "JUMP TO LETTER", false);
|
|
||||||
for(char c = 'A'; c <= 'Z'; c++)
|
|
||||||
mJumpToLetterList->add(std::string(1, c), c, c == curChar);
|
|
||||||
|
|
||||||
ComponentListRow row;
|
ComponentListRow row;
|
||||||
row.addElement(std::make_shared<TextComponent>(mWindow, "JUMP TO LETTER", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
|
||||||
row.addElement(mJumpToLetterList, false);
|
// show filtered menu
|
||||||
row.input_handler = [&](InputConfig* config, Input input) {
|
row.elements.clear();
|
||||||
if(config->isMappedTo("a", input) && input.value)
|
row.addElement(std::make_shared<TextComponent>(mWindow, "FILTER GAMELIST", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
||||||
{
|
row.addElement(makeArrow(mWindow), false);
|
||||||
jumpToLetter();
|
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openGamelistFilter, this));
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if(mJumpToLetterList->input(config, input))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
mMenu.addRow(row);
|
mMenu.addRow(row);
|
||||||
|
|
||||||
row.elements.clear();
|
row.elements.clear();
|
||||||
|
@ -48,23 +34,53 @@ GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : Gui
|
||||||
};
|
};
|
||||||
mMenu.addRow(row);
|
mMenu.addRow(row);
|
||||||
|
|
||||||
// sort list by
|
if (!fromPlaceholder) {
|
||||||
mListSort = std::make_shared<SortList>(mWindow, "SORT GAMES BY", false);
|
|
||||||
for(unsigned int i = 0; i < FileSorts::SortTypes.size(); i++)
|
if (!isFiltered) {
|
||||||
{
|
// jump to letter
|
||||||
const FileData::SortType& sort = FileSorts::SortTypes.at(i);
|
row.elements.clear();
|
||||||
mListSort->add(sort.description, &sort, i == 0); // TODO - actually make the sort type persistent
|
char curChar = toupper(getGamelist()->getCursor()->getName()[0]);
|
||||||
|
if(curChar < 'A' || curChar > 'Z')
|
||||||
|
curChar = 'A';
|
||||||
|
|
||||||
|
mJumpToLetterList = std::make_shared<LetterList>(mWindow, "JUMP TO LETTER", false);
|
||||||
|
for(char c = 'A'; c <= 'Z'; c++)
|
||||||
|
mJumpToLetterList->add(std::string(1, c), c, c == curChar);
|
||||||
|
|
||||||
|
row.addElement(std::make_shared<TextComponent>(mWindow, "JUMP TO LETTER", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
||||||
|
row.addElement(mJumpToLetterList, false);
|
||||||
|
row.input_handler = [&](InputConfig* config, Input input) {
|
||||||
|
if(config->isMappedTo("a", input) && input.value)
|
||||||
|
{
|
||||||
|
jumpToLetter();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if(mJumpToLetterList->input(config, input))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
mMenu.addRow(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort list by
|
||||||
|
mListSort = std::make_shared<SortList>(mWindow, "SORT GAMES BY", false);
|
||||||
|
for(unsigned int i = 0; i < FileSorts::SortTypes.size(); i++)
|
||||||
|
{
|
||||||
|
const FileData::SortType& sort = FileSorts::SortTypes.at(i);
|
||||||
|
mListSort->add(sort.description, &sort, i == 0); // TODO - actually make the sort type persistent
|
||||||
|
}
|
||||||
|
|
||||||
|
mMenu.addWithLabel("SORT GAMES BY", mListSort);
|
||||||
|
|
||||||
|
row.elements.clear();
|
||||||
|
row.addElement(std::make_shared<TextComponent>(mWindow, "EDIT THIS GAME'S METADATA", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
||||||
|
row.addElement(makeArrow(mWindow), false);
|
||||||
|
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openMetaDataEd, this));
|
||||||
|
mMenu.addRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
mMenu.addWithLabel("SORT GAMES BY", mListSort);
|
|
||||||
|
|
||||||
// edit game metadata
|
|
||||||
row.elements.clear();
|
|
||||||
row.addElement(std::make_shared<TextComponent>(mWindow, "EDIT THIS GAME'S METADATA", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
|
||||||
row.addElement(makeArrow(mWindow), false);
|
|
||||||
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openMetaDataEd, this));
|
|
||||||
mMenu.addRow(row);
|
|
||||||
|
|
||||||
// center the menu
|
// center the menu
|
||||||
setSize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
|
setSize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
|
||||||
mMenu.setPosition((mSize.x() - mMenu.getSize().x()) / 2, (mSize.y() - mMenu.getSize().y()) / 2);
|
mMenu.setPosition((mSize.x() - mMenu.getSize().x()) / 2, (mSize.y() - mMenu.getSize().y()) / 2);
|
||||||
|
@ -73,11 +89,34 @@ GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : Gui
|
||||||
GuiGamelistOptions::~GuiGamelistOptions()
|
GuiGamelistOptions::~GuiGamelistOptions()
|
||||||
{
|
{
|
||||||
// apply sort
|
// apply sort
|
||||||
FileData* root = getGamelist()->getCursor()->getSystem()->getRootFolder();
|
if (!fromPlaceholder) {
|
||||||
root->sort(*mListSort->getSelected()); // will also recursively sort children
|
FileData* root = getGamelist()->getCursor()->getSystem()->getRootFolder();
|
||||||
|
root->sort(*mListSort->getSelected()); // will also recursively sort children
|
||||||
|
|
||||||
// notify that the root folder was sorted
|
// notify that the root folder was sorted
|
||||||
getGamelist()->onFileChanged(root, FILE_SORTED);
|
getGamelist()->onFileChanged(root, FILE_SORTED);
|
||||||
|
}
|
||||||
|
if (mFiltersChanged)
|
||||||
|
{
|
||||||
|
if (!fromPlaceholder) {
|
||||||
|
FileData* root = getGamelist()->getCursor()->getSystem()->getRootFolder();
|
||||||
|
getGamelist()->onFileChanged(root, FILE_SORTED);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// only reload full view if we came from a placeholder
|
||||||
|
// as we need to re-display the remaining elements for whatever new
|
||||||
|
// game is selected
|
||||||
|
ViewController::get()->reloadGameListView(mSystem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuiGamelistOptions::openGamelistFilter()
|
||||||
|
{
|
||||||
|
mFiltersChanged = true;
|
||||||
|
GuiGamelistFilter* ggf = new GuiGamelistFilter(mWindow, mSystem);
|
||||||
|
mWindow->pushGui(ggf);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuiGamelistOptions::openMetaDataEd()
|
void GuiGamelistOptions::openMetaDataEd()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "GuiComponent.h"
|
#include "GuiComponent.h"
|
||||||
#include "components/MenuComponent.h"
|
#include "components/MenuComponent.h"
|
||||||
#include "components/OptionListComponent.h"
|
#include "components/OptionListComponent.h"
|
||||||
|
#include "GuiGamelistFilter.h"
|
||||||
#include "FileSorts.h"
|
#include "FileSorts.h"
|
||||||
|
|
||||||
class IGameListView;
|
class IGameListView;
|
||||||
|
@ -15,6 +16,7 @@ public:
|
||||||
virtual std::vector<HelpPrompt> getHelpPrompts() override;
|
virtual std::vector<HelpPrompt> getHelpPrompts() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void openGamelistFilter();
|
||||||
void openMetaDataEd();
|
void openMetaDataEd();
|
||||||
void jumpToLetter();
|
void jumpToLetter();
|
||||||
|
|
||||||
|
@ -28,4 +30,6 @@ private:
|
||||||
|
|
||||||
SystemData* mSystem;
|
SystemData* mSystem;
|
||||||
IGameListView* getGamelist();
|
IGameListView* getGamelist();
|
||||||
|
bool fromPlaceholder;
|
||||||
|
bool mFiltersChanged;
|
||||||
};
|
};
|
||||||
|
|
|
@ -170,6 +170,9 @@ void GuiMetaDataEd::onSizeChanged()
|
||||||
|
|
||||||
void GuiMetaDataEd::save()
|
void GuiMetaDataEd::save()
|
||||||
{
|
{
|
||||||
|
// remove game from index
|
||||||
|
mScraperParams.system->getIndex()->removeFromIndex(mScraperParams.game);
|
||||||
|
|
||||||
for(unsigned int i = 0; i < mEditors.size(); i++)
|
for(unsigned int i = 0; i < mEditors.size(); i++)
|
||||||
{
|
{
|
||||||
if(mMetaDataDecl.at(i).isStatistic)
|
if(mMetaDataDecl.at(i).isStatistic)
|
||||||
|
@ -178,6 +181,9 @@ void GuiMetaDataEd::save()
|
||||||
mMetaData->set(mMetaDataDecl.at(i).key, mEditors.at(i)->getValue());
|
mMetaData->set(mMetaDataDecl.at(i).key, mEditors.at(i)->getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enter game in index
|
||||||
|
mScraperParams.system->getIndex()->addToIndex(mScraperParams.game);
|
||||||
|
|
||||||
if(mSavedCallback)
|
if(mSavedCallback)
|
||||||
mSavedCallback();
|
mSavedCallback();
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,7 +125,7 @@ void ViewController::goToRandomGame()
|
||||||
for(auto it = SystemData::sSystemVector.begin(); it != SystemData::sSystemVector.end(); it++)
|
for(auto it = SystemData::sSystemVector.begin(); it != SystemData::sSystemVector.end(); it++)
|
||||||
{
|
{
|
||||||
if ((*it)->getName() != "retropie")
|
if ((*it)->getName() != "retropie")
|
||||||
total += (*it)->getGameCount();
|
total += (*it)->getDisplayedGameCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
// get random number in range
|
// get random number in range
|
||||||
|
@ -135,14 +135,14 @@ void ViewController::goToRandomGame()
|
||||||
{
|
{
|
||||||
if ((*it)->getName() != "retropie")
|
if ((*it)->getName() != "retropie")
|
||||||
{
|
{
|
||||||
if ((target - (int)(*it)->getGameCount()) >= 0)
|
if ((target - (int)(*it)->getDisplayedGameCount()) >= 0)
|
||||||
{
|
{
|
||||||
target -= (int)(*it)->getGameCount();
|
target -= (int)(*it)->getDisplayedGameCount();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
goToGameList(*it);
|
goToGameList(*it);
|
||||||
std::vector<FileData*> list = (*it)->getRootFolder()->getFilesRecursive(GAME);
|
std::vector<FileData*> list = (*it)->getRootFolder()->getFilesRecursive(GAME, true);
|
||||||
getGameListView(*it)->setCursor(list.at(target));
|
getGameListView(*it)->setCursor(list.at(target));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -401,7 +401,11 @@ void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
|
||||||
system->loadTheme();
|
system->loadTheme();
|
||||||
|
|
||||||
std::shared_ptr<IGameListView> newView = getGameListView(system);
|
std::shared_ptr<IGameListView> newView = getGameListView(system);
|
||||||
newView->setCursor(cursor);
|
|
||||||
|
// to counter having come from a placeholder
|
||||||
|
if (!cursor->isPlaceHolder()) {
|
||||||
|
newView->setCursor(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
if(isCurrent)
|
if(isCurrent)
|
||||||
mCurrentView = newView;
|
mCurrentView = newView;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "ThemeData.h"
|
#include "ThemeData.h"
|
||||||
#include "SystemData.h"
|
#include "SystemData.h"
|
||||||
#include "Settings.h"
|
#include "Settings.h"
|
||||||
|
#include "FileFilterIndex.h"
|
||||||
|
|
||||||
BasicGameListView::BasicGameListView(Window* window, FileData* root)
|
BasicGameListView::BasicGameListView(Window* window, FileData* root)
|
||||||
: ISimpleGameListView(window, root), mList(window)
|
: ISimpleGameListView(window, root), mList(window)
|
||||||
|
@ -13,7 +14,7 @@ BasicGameListView::BasicGameListView(Window* window, FileData* root)
|
||||||
mList.setPosition(0, mSize.y() * 0.2f);
|
mList.setPosition(0, mSize.y() * 0.2f);
|
||||||
addChild(&mList);
|
addChild(&mList);
|
||||||
|
|
||||||
populateList(root->getChildren());
|
populateList(root->getChildrenListToDisplay());
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasicGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
|
void BasicGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
|
||||||
|
@ -38,12 +39,20 @@ void BasicGameListView::onFileChanged(FileData* file, FileChangeType change)
|
||||||
void BasicGameListView::populateList(const std::vector<FileData*>& files)
|
void BasicGameListView::populateList(const std::vector<FileData*>& files)
|
||||||
{
|
{
|
||||||
mList.clear();
|
mList.clear();
|
||||||
|
if (files.size() > 0)
|
||||||
mHeaderText.setText(files.at(0)->getSystem()->getFullName());
|
|
||||||
|
|
||||||
for(auto it = files.begin(); it != files.end(); it++)
|
|
||||||
{
|
{
|
||||||
mList.add((*it)->getName(), *it, ((*it)->getType() == FOLDER));
|
mHeaderText.setText(files.at(0)->getSystem()->getFullName());
|
||||||
|
|
||||||
|
for(auto it = files.begin(); it != files.end(); it++)
|
||||||
|
{
|
||||||
|
mList.add((*it)->getName(), *it, ((*it)->getType() == FOLDER));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,9 +63,11 @@ FileData* BasicGameListView::getCursor()
|
||||||
|
|
||||||
void BasicGameListView::setCursor(FileData* cursor)
|
void BasicGameListView::setCursor(FileData* cursor)
|
||||||
{
|
{
|
||||||
|
if (cursor->isPlaceHolder())
|
||||||
|
return;
|
||||||
if(!mList.setCursor(cursor))
|
if(!mList.setCursor(cursor))
|
||||||
{
|
{
|
||||||
populateList(cursor->getParent()->getChildren());
|
populateList(cursor->getParent()->getChildrenListToDisplay());
|
||||||
mList.setCursor(cursor);
|
mList.setCursor(cursor);
|
||||||
|
|
||||||
// update our cursor stack in case our cursor just got set to some folder we weren't in before
|
// update our cursor stack in case our cursor just got set to some folder we weren't in before
|
||||||
|
|
|
@ -10,7 +10,7 @@ GridGameListView::GridGameListView(Window* window, FileData* root) : ISimpleGame
|
||||||
mGrid.setSize(mSize.x(), mSize.y() * 0.8f);
|
mGrid.setSize(mSize.x(), mSize.y() * 0.8f);
|
||||||
addChild(&mGrid);
|
addChild(&mGrid);
|
||||||
|
|
||||||
populateList(root->getChildren());
|
populateList(root->getChildrenListToDisplay());
|
||||||
}
|
}
|
||||||
|
|
||||||
FileData* GridGameListView::getCursor()
|
FileData* GridGameListView::getCursor()
|
||||||
|
@ -22,7 +22,7 @@ void GridGameListView::setCursor(FileData* file)
|
||||||
{
|
{
|
||||||
if(!mGrid.setCursor(file))
|
if(!mGrid.setCursor(file))
|
||||||
{
|
{
|
||||||
populateList(file->getParent()->getChildren());
|
populateList(file->getParent()->getChildrenListToDisplay());
|
||||||
mGrid.setCursor(file);
|
mGrid.setCursor(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,8 +47,15 @@ void ISimpleGameListView::onFileChanged(FileData* file, FileChangeType change)
|
||||||
// we could be tricky here to be efficient;
|
// we could be tricky here to be efficient;
|
||||||
// but this shouldn't happen very often so we'll just always repopulate
|
// but this shouldn't happen very often so we'll just always repopulate
|
||||||
FileData* cursor = getCursor();
|
FileData* cursor = getCursor();
|
||||||
populateList(cursor->getParent()->getChildren());
|
if (!cursor->isPlaceHolder()) {
|
||||||
setCursor(cursor);
|
populateList(cursor->getParent()->getChildrenListToDisplay());
|
||||||
|
setCursor(cursor);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
populateList(mRoot->getChildrenListToDisplay());
|
||||||
|
setCursor(cursor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ISimpleGameListView::input(InputConfig* config, Input input)
|
bool ISimpleGameListView::input(InputConfig* config, Input input)
|
||||||
|
@ -67,7 +74,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
|
||||||
if(cursor->getChildren().size() > 0)
|
if(cursor->getChildren().size() > 0)
|
||||||
{
|
{
|
||||||
mCursorStack.push(cursor);
|
mCursorStack.push(cursor);
|
||||||
populateList(cursor->getChildren());
|
populateList(cursor->getChildrenListToDisplay());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -248,7 +248,9 @@ void VideoGameListView::updateInfoPanel()
|
||||||
thumbnail_path.insert(0, getHomePath());
|
thumbnail_path.insert(0, getHomePath());
|
||||||
}
|
}
|
||||||
if (!mVideo.setVideo(video_path))
|
if (!mVideo.setVideo(video_path))
|
||||||
|
{
|
||||||
mVideo.setDefaultVideo();
|
mVideo.setDefaultVideo();
|
||||||
|
}
|
||||||
mVideoPlaying = true;
|
mVideoPlaying = true;
|
||||||
|
|
||||||
mVideo.setImage(thumbnail_path);
|
mVideo.setImage(thumbnail_path);
|
||||||
|
|
|
@ -81,11 +81,12 @@ bool ComponentList::input(InputConfig* config, Input input)
|
||||||
}else if(config->isMappedTo("down", input))
|
}else if(config->isMappedTo("down", input))
|
||||||
{
|
{
|
||||||
return listInput(input.value != 0 ? 1 : 0);
|
return listInput(input.value != 0 ? 1 : 0);
|
||||||
|
|
||||||
}else if(config->isMappedTo("pageup", input))
|
}else if(config->isMappedTo("pageup", input))
|
||||||
{
|
{
|
||||||
return listInput(input.value != 0 ? -7 : 0);
|
return listInput(input.value != 0 ? -6 : 0);
|
||||||
}else if(config->isMappedTo("pagedown", input)){
|
}else if(config->isMappedTo("pagedown", input)){
|
||||||
return listInput(input.value != 0 ? 7 : 0);
|
return listInput(input.value != 0 ? 6 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -251,6 +251,24 @@ public:
|
||||||
onSelectedChanged();
|
onSelectedChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void selectAll()
|
||||||
|
{
|
||||||
|
for(unsigned int i = 0; i < mEntries.size(); i++)
|
||||||
|
{
|
||||||
|
mEntries.at(i).selected = true;
|
||||||
|
}
|
||||||
|
onSelectedChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectNone()
|
||||||
|
{
|
||||||
|
for(unsigned int i = 0; i < mEntries.size(); i++)
|
||||||
|
{
|
||||||
|
mEntries.at(i).selected = false;
|
||||||
|
}
|
||||||
|
onSelectedChanged();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
unsigned int getSelectedId()
|
unsigned int getSelectedId()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue