From 77978ee83acb7f5402259796ff55575ce031c4ed Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 8 Jan 2021 20:30:21 +0100 Subject: [PATCH] Fixed multiple issues related to the gamelist sorting options. --- CHANGELOG.md | 1 + INSTALL.md | 2 +- USERGUIDE.md | 23 ++- es-app/src/FileData.cpp | 61 ++++---- es-app/src/FileData.h | 18 +-- es-app/src/FileSorts.cpp | 197 +++++++++++++++++++------ es-app/src/FileSorts.h | 18 ++- es-app/src/MetaData.cpp | 4 +- es-app/src/guis/GuiGamelistOptions.cpp | 6 +- es-app/src/guis/GuiMenu.cpp | 13 +- 10 files changed, 229 insertions(+), 114 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b59419e68..1d5301289 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -97,6 +97,7 @@ Many bugs have been fixed, and numerous features that were only partially implem * Fixed an issue where SVG images would sometimes be cut off slightly on the right side (e.g. logos on the system view carousel) * The scraper didn't handle error conditions correctly * The metadata editor insisted that changes were made although nothing was updated. Note: The editor will still ask for save confirmations after automatically rounding fractional game ratings to half-star values, but any time such a rounding has taken place, the rating stars will be colored green in the metadata editor to nofity the user +* Sorting by number of players did not work properly for games with ranges such as 1-2 or 1-8 * Restart and power-off menu entries not working on any of the tested operating systems * Toggling the screensaver didn't work as expected * The setting to enable or disable audio for the video screensaver only worked on Raspberry Pi diff --git a/INSTALL.md b/INSTALL.md index 3775ba0da..85e3d9d91 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -245,7 +245,7 @@ Assuming the default installation prefix /usr/local has been used, this is the d /usr/local/share/pixmaps/emulationstation.svg ``` -Be aware that if using the GNOME desktop environment, /usr/local/share/pixmaps/emulationstation.svg must exist in order for any ES-DE icon to be shown in the Dash and task switcher. In for instance KDE this is not required and there will be an application icon shown even if just running from the emulationstation-de source tree. +Be aware that if using the GNOME desktop environment, /usr/local/share/pixmaps/emulationstation.svg must exist in order for the ES-DE icon to be shown in the Dash and task switcher. ES-DE will look in the following locations for the resources, in the listed order: diff --git a/USERGUIDE.md b/USERGUIDE.md index 91cd4bc39..e3573d5f8 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -750,7 +750,7 @@ Sets the user interface mode for the application to _Full, Kiosk_ or _Kid_. See **Default sort order** -The order in which to sort your gamelists. This can be overriden per game system using the game options menu, but that override will only be persistent during the application session. +The order in which to sort your gamelists. This can be overriden per game system using the game options menu, but that override will only be persistent during the application session. The _System_ sorting can not be selected here as it's only applicable to collection systems. **Menu opening effect** _(OpenGL renderer only)_ @@ -956,7 +956,7 @@ This gives you a choice between _Normal_ and _Borderless_ modes. With the border **When to save game metadata** -The metadata for a game is updated by scraping and by manual editing it using the metadata editor, but also when launching a game, as the play count and last played date is then updated. This setting enables you to define when to write such metadata changes to the gamelist.xml files. Setting the option to _Never_ will disable writing to these files altogether, except for some special conditions such as when a game is manually deleted using the metadata editor, or when scraping using the multi-scraper (the multi-scraper will always save any updates immediately to the gamelist.xml files). In theory _On exit_ will give some performance gains, but it's normally recommended to leave the setting at its default value which is _Always_. Note that with the settings set to _Never_, any updates such as the last played date will still be shown on screen, but during the next application startup, any values previously saved to the gamelist.xml files will be read in again. As well, when changing this setting to _Always_ from either of the two other options, any pending changes will be immediately written to the gamelist.xml files. +The metadata for a game is updated by scraping and by manual editing (using the metadata editor), but also when launching it as this updates the _Times played_ counter and the _Last played_ date. This setting enables you to define when to write such metadata changes to the gamelist.xml files. Setting the option to _Never_ will disable writing to these files altogether, except for some special conditions such as when a game is manually deleted using the metadata editor, or when scraping using the multi-scraper (the multi-scraper will always save any updates immediately to the gamelist.xml files). In theory _On exit_ will give some performance gains, but it's normally recommended to leave the setting at its default value which is _Always_. Note that with the settings set to _Never_, any updates such as the _Last played_ date will still be shown on screen, but during the next application startup, any values previously saved to the gamelist.xml files will be read in again. As well, when changing this setting to _Always_ from either of the two other options, any pending changes will be immediately written to the gamelist.xml files. **Game media directory** @@ -1048,7 +1048,22 @@ This provides the ability to jump to a certain letter using a quick selector. If ### Sort games by -This is the sort order for the gamelist. The default sorting shown here is taken from the setting **Default sort order** under **UI settings** in the main menu. Any sorting that is applied in the game options menu will be persistent throughout the program session and it can be set individually per game system and custom collection. +This is the sort order for the gamelist. The default sorting shown here is taken from the setting **Default sort order** under **UI settings** in the main menu. Any sorting that is applied via the game options menu will be persistent throughout the program session, and it can be set individually per game system and per collection. + +Sorting can be applied using the following metadata, in either ascending or descending order: + +* Filename +* Rating +* Release date +* Developer +* Publisher +* Genre +* Players +* Times played +* Last played +* System _(Only for collections)_ + +The secondary sorting is always in ascending filename order. ### Filter gamelist @@ -1181,7 +1196,7 @@ This option will hide most metadata fields in the gamelist view. The intention i Here you can override the launch command for the game, for example to use a different emulator than the default one for the game system. Very useful for MAME/arcade games. -**Play count** _(files only)_ +**Times played** _(files only)_ A statistics counter that tracks how many times you have played the game. You normally don't need to touch this, but if you want to, the possibility is there. diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index 6da9555da..d2bc730bf 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -443,7 +443,7 @@ void FileData::removeChild(FileData* file) assert(false); } -void FileData::sort(ComparisonFunction& comparator, bool ascending, +void FileData::sort(ComparisonFunction& comparator, std::pair& gameCount) { mOnlyFolders = true; @@ -480,7 +480,7 @@ void FileData::sort(ComparisonFunction& comparator, bool ascending, std::pair tempGameCount = {}; for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) { if ((*it)->getChildren().size() > 0) - (*it)->sort(comparator, ascending, gameCount); + (*it)->sort(comparator, gameCount); tempGameCount.first += gameCount.first; tempGameCount.second += gameCount.second; gameCount = {}; @@ -500,37 +500,35 @@ void FileData::sort(ComparisonFunction& comparator, bool ascending, } } - // If descending sorting is requested, always perform a ascending sort by filename first. - // This adds a slight (probably negligible) overhead but it will avoid strange sorting - // issues where the secondary sorting is reversed for some sort types. - if (!ascending) + // If the requested sorting is not by filename, then sort in ascending filename order + // as a first step, in order to get a correct secondary sorting. + if (getSortTypeFromString("filename, ascending").comparisonFunction != comparator && + getSortTypeFromString("filename, descending").comparisonFunction != comparator) { + std::stable_sort(mChildrenFolders.begin(), mChildrenFolders.end(), + getSortTypeFromString("filename, ascending").comparisonFunction); std::stable_sort(mChildrenOthers.begin(), mChildrenOthers.end(), getSortTypeFromString("filename, ascending").comparisonFunction); + } if (foldersOnTop && mOnlyFolders) std::stable_sort(mChildrenFolders.begin(), mChildrenFolders.end(), comparator); std::stable_sort(mChildrenOthers.begin(), mChildrenOthers.end(), comparator); - if (!ascending) { - if (foldersOnTop && mOnlyFolders) - std::reverse(mChildrenFolders.begin(), mChildrenFolders.end()); - std::reverse(mChildrenOthers.begin(), mChildrenOthers.end()); - } - mChildren.erase(mChildren.begin(), mChildren.end()); mChildren.reserve(mChildrenFolders.size() + mChildrenOthers.size()); mChildren.insert(mChildren.end(), mChildrenFolders.begin(), mChildrenFolders.end()); mChildren.insert(mChildren.end(), mChildrenOthers.begin(), mChildrenOthers.end()); } else { - if (!ascending) + // If the requested sorting is not by filename, then sort in ascending filename order + // as a first step, in order to get a correct secondary sorting. + if (getSortTypeFromString("filename, ascending").comparisonFunction != comparator && + getSortTypeFromString("filename, descending").comparisonFunction != comparator) std::stable_sort(mChildren.begin(), mChildren.end(), getSortTypeFromString("filename, ascending").comparisonFunction); std::stable_sort(mChildren.begin(), mChildren.end(), comparator); - if (!ascending) - std::reverse(mChildren.begin(), mChildren.end()); } for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) { @@ -550,14 +548,14 @@ void FileData::sort(ComparisonFunction& comparator, bool ascending, // Iterate through any child folders. if ((*it)->getChildren().size() > 0) - (*it)->sort(comparator, ascending, gameCount); + (*it)->sort(comparator, gameCount); } if (mSystem->isGroupedCustomCollection()) mGameCount = gameCount; } -void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending, +void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, std::pair& gameCount) { mOnlyFolders = true; @@ -579,7 +577,7 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending std::pair tempGameCount = {}; for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) { if ((*it)->getChildren().size() > 0) - (*it)->sortFavoritesOnTop(comparator, ascending, gameCount); + (*it)->sortFavoritesOnTop(comparator, gameCount); tempGameCount.first += gameCount.first; tempGameCount.second += gameCount.second; gameCount = {}; @@ -644,10 +642,10 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending getSortTypeFromString("filename, ascending").comparisonFunction); } - // If descending sorting is requested, always perform a ascending sort by filename first. - // This adds a slight (probably negligible) overhead but it will avoid strange sorting - // issues where the secondary sorting is reversed for some sort types. - if (!ascending) { + // If the requested sorting is not by filename, then sort in ascending filename order + // as a first step, in order to get a correct secondary sorting. + if (getSortTypeFromString("filename, ascending").comparisonFunction != comparator && + getSortTypeFromString("filename, descending").comparisonFunction != comparator) { std::stable_sort(mChildrenFolders.begin(), mChildrenFolders.end(), getSortTypeFromString("filename, ascending").comparisonFunction); std::stable_sort(mChildrenFavoritesFolders.begin(), mChildrenFavoritesFolders.end(), @@ -671,13 +669,13 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending for (auto it = mChildrenFavoritesFolders.cbegin(); it != mChildrenFavoritesFolders.cend(); it++) { if ((*it)->getChildren().size() > 0) - (*it)->sortFavoritesOnTop(comparator, ascending, gameCount); + (*it)->sortFavoritesOnTop(comparator, gameCount); } // Iterate through any child folders. for (auto it = mChildrenFolders.cbegin(); it != mChildrenFolders.cend(); it++) { if ((*it)->getChildren().size() > 0) - (*it)->sortFavoritesOnTop(comparator, ascending, gameCount); + (*it)->sortFavoritesOnTop(comparator, gameCount); } // If folders are not sorted on top, mChildrenFavoritesFolders and mChildrenFolders @@ -686,19 +684,10 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending if (mChildrenFavoritesFolders.size() == 0 && mChildrenFolders.size() == 0) { for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) { if ((*it)->getChildren().size() > 0) - (*it)->sortFavoritesOnTop(comparator, ascending, gameCount); + (*it)->sortFavoritesOnTop(comparator, gameCount); } } - if (!ascending) { - if (foldersOnTop && mOnlyFolders) { - std::reverse(mChildrenFavoritesFolders.begin(), mChildrenFavoritesFolders.end()); - std::reverse(mChildrenFolders.begin(), mChildrenFolders.end()); - } - std::reverse(mChildrenFavorites.begin(), mChildrenFavorites.end()); - std::reverse(mChildrenOthers.begin(), mChildrenOthers.end()); - } - // Combine the individually sorted favorite games and other games vectors. mChildren.erase(mChildren.begin(), mChildren.end()); mChildren.reserve(mChildrenFavoritesFolders.size() + mChildrenFolders.size() + @@ -715,9 +704,9 @@ void FileData::sort(const SortType& type, bool mFavoritesOnTop) mGameCount = std::make_pair(0, 0); if (mFavoritesOnTop) - sortFavoritesOnTop(*type.comparisonFunction, type.ascending, mGameCount); + sortFavoritesOnTop(*type.comparisonFunction, mGameCount); else - sort(*type.comparisonFunction, type.ascending, mGameCount); + sort(*type.comparisonFunction, mGameCount); } void FileData::countGames(std::pair& gameCount) diff --git a/es-app/src/FileData.h b/es-app/src/FileData.h index 6efdb31ec..6b3133f5c 100644 --- a/es-app/src/FileData.h +++ b/es-app/src/FileData.h @@ -101,21 +101,13 @@ public: typedef bool ComparisonFunction(const FileData* a, const FileData* b); struct SortType { ComparisonFunction* comparisonFunction; - bool ascending; std::string description; - - SortType( - ComparisonFunction* sortFunction, - bool sortAscending, - const std::string& sortDescription) - : comparisonFunction(sortFunction), - ascending(sortAscending), - description(sortDescription) {} + SortType(ComparisonFunction* sortFunction, const std::string& sortDescription) + : comparisonFunction(sortFunction), description(sortDescription) {} }; - void sort(ComparisonFunction& comparator, bool ascending, - std::pair& gameCount); - void sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending, + void sort(ComparisonFunction& comparator, std::pair& gameCount); + void sortFavoritesOnTop(ComparisonFunction& comparator, std::pair& gameCount); void sort(const SortType& type, bool mFavoritesOnTop = false); MetaDataList metadata; @@ -124,7 +116,6 @@ public: inline void setSortTypeString(std::string typestring) { mSortTypeString = typestring; } inline std::string getSortTypeString() { return mSortTypeString; } - // Return sort type based on a string description. FileData::SortType getSortTypeFromString(std::string desc); protected: @@ -147,7 +138,6 @@ private: bool mHasFolders; // Used for flagging a game for deletion from its gamelist.xml file. bool mDeletionFlag; - }; class CollectionFileData : public FileData diff --git a/es-app/src/FileSorts.cpp b/es-app/src/FileSorts.cpp index 7f70dccbf..dc2f8e739 100644 --- a/es-app/src/FileSorts.cpp +++ b/es-app/src/FileSorts.cpp @@ -11,83 +11,78 @@ #include "utils/StringUtil.h" +#include +#include + namespace FileSorts { const FileData::SortType typesArr[] = { - FileData::SortType(&compareName, true, "filename, ascending"), - FileData::SortType(&compareName, false, "filename, descending"), + FileData::SortType(&compareName, "filename, ascending"), + FileData::SortType(&compareNameDescending, "filename, descending"), - FileData::SortType(&compareRating, true, "rating, ascending"), - FileData::SortType(&compareRating, false, "rating, descending"), + FileData::SortType(&compareRating, "rating, ascending"), + FileData::SortType(&compareRatingDescending, "rating, descending"), - FileData::SortType(&compareTimesPlayed, true, "times played, ascending"), - FileData::SortType(&compareTimesPlayed, false, "times played, descending"), + FileData::SortType(&compareReleaseDate, "release date, ascending"), + FileData::SortType(&compareReleaseDateDescending, "release date, descending"), - FileData::SortType(&compareLastPlayed, true, "last played, ascending"), - FileData::SortType(&compareLastPlayed, false, "last played, descending"), + FileData::SortType(&compareDeveloper, "developer, ascending"), + FileData::SortType(&compareDeveloperDescending, "developer, descending"), - FileData::SortType(&compareNumPlayers, true, "number players, ascending"), - FileData::SortType(&compareNumPlayers, false, "number players, descending"), + FileData::SortType(&comparePublisher, "publisher, ascending"), + FileData::SortType(&comparePublisherDescending, "publisher, descending"), - FileData::SortType(&compareReleaseDate, true, "release date, ascending"), - FileData::SortType(&compareReleaseDate, false, "release date, descending"), + FileData::SortType(&compareGenre, "genre, ascending"), + FileData::SortType(&compareGenreDescending, "genre, descending"), - FileData::SortType(&compareGenre, true, "genre, ascending"), - FileData::SortType(&compareGenre, false, "genre, descending"), + FileData::SortType(&compareNumPlayers, "players, ascending"), + FileData::SortType(&compareNumPlayersDescending, "players, descending"), - FileData::SortType(&compareDeveloper, true, "developer, ascending"), - FileData::SortType(&compareDeveloper, false, "developer, descending"), + FileData::SortType(&compareTimesPlayed, "times played, ascending"), + FileData::SortType(&compareTimesPlayedDescending, "times played, descending"), - FileData::SortType(&comparePublisher, true, "publisher, ascending"), - FileData::SortType(&comparePublisher, false, "publisher, descending"), + FileData::SortType(&compareLastPlayed, "last played, ascending"), + FileData::SortType(&compareLastPlayedDescending, "last played, descending"), - FileData::SortType(&compareSystem, true, "system, ascending"), - FileData::SortType(&compareSystem, false, "system, descending") + FileData::SortType(&compareSystem, "system, ascending"), + FileData::SortType(&compareSystemDescending, "system, descending") }; const std::vector SortTypes(typesArr, typesArr + sizeof(typesArr)/sizeof(typesArr[0])); - // Returns if file1 should come before file2. bool compareName(const FileData* file1, const FileData* file2) { // We compare the actual metadata name, as collection files have the system // appended which messes up the order. std::string name1 = Utils::String::toUpper(file1->metadata.get("sortname")); std::string name2 = Utils::String::toUpper(file2->metadata.get("sortname")); - if (name1.empty()){ + if (name1.empty()) name1 = Utils::String::toUpper(file1->metadata.get("name")); - } - if (name2.empty()){ + if (name2.empty()) name2 = Utils::String::toUpper(file2->metadata.get("name")); - } return name1.compare(name2) < 0; } + bool compareNameDescending(const FileData* file1, const FileData* file2) + { + std::string name1 = Utils::String::toUpper(file1->metadata.get("sortname")); + std::string name2 = Utils::String::toUpper(file2->metadata.get("sortname")); + if (name1.empty()) + name1 = Utils::String::toUpper(file1->metadata.get("name")); + if (name2.empty()) + name2 = Utils::String::toUpper(file2->metadata.get("name")); + return name1.compare(name2) > 0; + } + bool compareRating(const FileData* file1, const FileData* file2) { return file1->metadata.getFloat("rating") < file2->metadata.getFloat("rating"); } - bool compareTimesPlayed(const FileData* file1, const FileData* file2) + bool compareRatingDescending(const FileData* file1, const FileData* file2) { - //only games have playcount metadata - if (file1->metadata.getType() == GAME_METADATA && file2->metadata.getType() == GAME_METADATA) - return (file1)->metadata.getInt("playcount") < (file2)->metadata.getInt("playcount"); - - return false; - } - - bool compareLastPlayed(const FileData* file1, const FileData* file2) - { - // Since it's stored as an ISO string (YYYYMMDDTHHMMSS), we can compare as a string - // which is a lot faster than the time casts and the time comparisons. - return (file1)->metadata.get("lastplayed") < (file2)->metadata.get("lastplayed"); - } - - bool compareNumPlayers(const FileData* file1, const FileData* file2) - { - return (file1)->metadata.getInt("players") < (file2)->metadata.getInt("players"); + return file1->metadata.getFloat("rating") > file2->metadata.getFloat("rating"); } bool compareReleaseDate(const FileData* file1, const FileData* file2) @@ -97,11 +92,9 @@ namespace FileSorts return (file1)->metadata.get("releasedate") < (file2)->metadata.get("releasedate"); } - bool compareGenre(const FileData* file1, const FileData* file2) + bool compareReleaseDateDescending(const FileData* file1, const FileData* file2) { - std::string genre1 = Utils::String::toUpper(file1->metadata.get("genre")); - std::string genre2 = Utils::String::toUpper(file2->metadata.get("genre")); - return genre1.compare(genre2) < 0; + return (file1)->metadata.get("releasedate") > (file2)->metadata.get("releasedate"); } bool compareDeveloper(const FileData* file1, const FileData* file2) @@ -111,6 +104,13 @@ namespace FileSorts return developer1.compare(developer2) < 0; } + bool compareDeveloperDescending(const FileData* file1, const FileData* file2) + { + std::string developer1 = Utils::String::toUpper(file1->metadata.get("developer")); + std::string developer2 = Utils::String::toUpper(file2->metadata.get("developer")); + return developer1.compare(developer2) > 0; + } + bool comparePublisher(const FileData* file1, const FileData* file2) { std::string publisher1 = Utils::String::toUpper(file1->metadata.get("publisher")); @@ -118,10 +118,113 @@ namespace FileSorts return publisher1.compare(publisher2) < 0; } + bool comparePublisherDescending(const FileData* file1, const FileData* file2) + { + std::string publisher1 = Utils::String::toUpper(file1->metadata.get("publisher")); + std::string publisher2 = Utils::String::toUpper(file2->metadata.get("publisher")); + return publisher1.compare(publisher2) > 0; + } + + bool compareGenre(const FileData* file1, const FileData* file2) + { + std::string genre1 = Utils::String::toUpper(file1->metadata.get("genre")); + std::string genre2 = Utils::String::toUpper(file2->metadata.get("genre")); + return genre1.compare(genre2) < 0; + } + + bool compareGenreDescending(const FileData* file1, const FileData* file2) + { + std::string genre1 = Utils::String::toUpper(file1->metadata.get("genre")); + std::string genre2 = Utils::String::toUpper(file2->metadata.get("genre")); + return genre1.compare(genre2) > 0; + } + + bool compareNumPlayers(const FileData* file1, const FileData* file2) + { + std::string file1Players = (file1)->metadata.get("players"); + std::string file2Players = (file2)->metadata.get("players"); + unsigned int file1Int = 0; + unsigned int file2Int = 0; + size_t dashPos; + // If there is a range of players such as '1-4' then capture the number after the dash. + dashPos = file1Players.find("-"); + if (dashPos != std::string::npos) + file1Players = file1Players.substr(dashPos + 1, file1Players.size() - dashPos - 1); + dashPos = file2Players.find("-"); + if (dashPos != std::string::npos) + file2Players = file2Players.substr(dashPos + 1, file2Players.size() - dashPos - 1); + // Any non-numeric value will end up as zero. + if (!file1Players.empty() && + std::all_of(file1Players.begin(), file1Players.end(), ::isdigit)) + file1Int = stoi(file1Players); + if (!file2Players.empty() && + std::all_of(file2Players.begin(), file2Players.end(), ::isdigit)) + file2Int = stoi(file2Players); + return file1Int < file2Int; + } + + bool compareNumPlayersDescending(const FileData* file1, const FileData* file2) + { + std::string file1Players = (file1)->metadata.get("players"); + std::string file2Players = (file2)->metadata.get("players"); + unsigned int file1Int = 0; + unsigned int file2Int = 0; + size_t dashPos; + dashPos = file1Players.find("-"); + if (dashPos != std::string::npos) + file1Players = file1Players.substr(dashPos + 1, file1Players.size() - dashPos - 1); + dashPos = file2Players.find("-"); + if (dashPos != std::string::npos) + file2Players = file2Players.substr(dashPos + 1, file2Players.size() - dashPos - 1); + if (!file1Players.empty() && + std::all_of(file1Players.begin(), file1Players.end(), ::isdigit)) + file1Int = stoi(file1Players); + if (!file2Players.empty() && + std::all_of(file2Players.begin(), file2Players.end(), ::isdigit)) + file2Int = stoi(file2Players); + return file1Int > file2Int; + } + + bool compareTimesPlayed(const FileData* file1, const FileData* file2) + { + // Only games have playcount metadata. + if (file1->metadata.getType() == GAME_METADATA && + file2->metadata.getType() == GAME_METADATA) + return (file1)->metadata.getInt("playcount") < (file2)->metadata.getInt("playcount"); + return false; + } + + bool compareTimesPlayedDescending(const FileData* file1, const FileData* file2) + { + if (file1->metadata.getType() == GAME_METADATA && + file2->metadata.getType() == GAME_METADATA) + return (file1)->metadata.getInt("playcount") > (file2)->metadata.getInt("playcount"); + return false; + } + + bool compareLastPlayed(const FileData* file1, const FileData* file2) + { + // Since it's stored as an ISO string (YYYYMMDDTHHMMSS), we can compare as a string + // which is a lot faster than the time casts and the time comparisons. + return (file1)->metadata.get("lastplayed") > (file2)->metadata.get("lastplayed"); + } + + bool compareLastPlayedDescending(const FileData* file1, const FileData* file2) + { + return (file1)->metadata.get("lastplayed") < (file2)->metadata.get("lastplayed"); + } + bool compareSystem(const FileData* file1, const FileData* file2) { std::string system1 = Utils::String::toUpper(file1->getSystemName()); std::string system2 = Utils::String::toUpper(file2->getSystemName()); return system1.compare(system2) < 0; } + + bool compareSystemDescending(const FileData* file1, const FileData* file2) + { + std::string system1 = Utils::String::toUpper(file1->getSystemName()); + std::string system2 = Utils::String::toUpper(file2->getSystemName()); + return system1.compare(system2) > 0; + } }; diff --git a/es-app/src/FileSorts.h b/es-app/src/FileSorts.h index 42f1cc237..dbcb04ce4 100644 --- a/es-app/src/FileSorts.h +++ b/es-app/src/FileSorts.h @@ -17,15 +17,25 @@ namespace FileSorts { bool compareName(const FileData* file1, const FileData* file2); + bool compareNameDescending(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); - bool compareNumPlayers(const FileData* file1, const FileData* file2); + bool compareRatingDescending(const FileData* file1, const FileData* file2); bool compareReleaseDate(const FileData* file1, const FileData* file2); - bool compareGenre(const FileData* file1, const FileData* file2); + bool compareReleaseDateDescending(const FileData* file1, const FileData* file2); bool compareDeveloper(const FileData* file1, const FileData* file2); + bool compareDeveloperDescending(const FileData* file1, const FileData* file2); bool comparePublisher(const FileData* file1, const FileData* file2); + bool comparePublisherDescending(const FileData* file1, const FileData* file2); + bool compareGenre(const FileData* file1, const FileData* file2); + bool compareGenreDescending(const FileData* file1, const FileData* file2); + bool compareNumPlayers(const FileData* file1, const FileData* file2); + bool compareNumPlayersDescending(const FileData* file1, const FileData* file2); + bool compareTimesPlayed(const FileData* file1, const FileData* fil2); + bool compareTimesPlayedDescending(const FileData* file1, const FileData* fil2); + bool compareLastPlayed(const FileData* file1, const FileData* file2); + bool compareLastPlayedDescending(const FileData* file1, const FileData* file2); bool compareSystem(const FileData* file1, const FileData* file2); + bool compareSystemDescending(const FileData* file1, const FileData* file2); extern const std::vector SortTypes; }; diff --git a/es-app/src/MetaData.cpp b/es-app/src/MetaData.cpp index 2b052fda9..a8d2a696f 100644 --- a/es-app/src/MetaData.cpp +++ b/es-app/src/MetaData.cpp @@ -24,7 +24,7 @@ MetaDataDecl gameDecls[] = { {"developer", MD_STRING, "unknown", false, "developer", "enter game developer", true}, {"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher", true}, {"genre", MD_STRING, "unknown", false, "genre", "enter game genre", true}, -{"players", MD_INT, "unknown", false, "players", "enter number of players", true}, +{"players", MD_STRING, "unknown", false, "players", "enter number of players", true}, {"favorite", MD_BOOL, "false", false, "favorite", "enter favorite off/on", false}, {"completed", MD_BOOL, "false", false, "completed", "enter completed off/on", false}, {"kidgame", MD_BOOL, "false", false, "kidgame", "enter kidgame off/on", false}, @@ -50,7 +50,7 @@ MetaDataDecl folderDecls[] = { {"developer", MD_STRING, "unknown", false, "developer", "enter game developer", true}, {"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher", true}, {"genre", MD_STRING, "unknown", false, "genre", "enter game genre", true}, -{"players", MD_INT, "unknown", false, "players", "enter number of players", true}, +{"players", MD_STRING, "unknown", false, "players", "enter number of players", true}, {"favorite", MD_BOOL, "false", false, "favorite", "enter favorite off/on", false}, {"completed", MD_BOOL, "false", false, "completed", "enter completed off/on", false}, {"hidden", MD_BOOL, "false", false, "hidden", "enter hidden off/on", false}, diff --git a/es-app/src/guis/GuiGamelistOptions.cpp b/es-app/src/guis/GuiGamelistOptions.cpp index 6279c004b..611ebddca 100644 --- a/es-app/src/guis/GuiGamelistOptions.cpp +++ b/es-app/src/guis/GuiGamelistOptions.cpp @@ -122,8 +122,12 @@ GuiGamelistOptions::GuiGamelistOptions( root = mSystem->getRootFolder(); std::string sortType = root->getSortTypeString(); + unsigned int numSortTypes = FileSorts::SortTypes.size(); + // If it's not a collection, then hide the System sort options. + if (!root->getSystem()->isCollection()) + numSortTypes -= 2; - for (unsigned int i = 0; i add(sort.description, &sort, 1); diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 8004cc865..e893cf78e 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -254,9 +254,12 @@ void GuiMenu::openUISettings() std::string sortOrder; auto default_sort_order = std::make_shared (mWindow, getHelpStyle(), "DEFAULT SORT ORDER", false); - for (auto it = FileSorts::SortTypes.cbegin(); it != FileSorts::SortTypes.cend(); it++) { - if (it->description == Settings::getInstance()->getString("DefaultSortOrder")) { - sortOrder = it->description; + // Exclude the System sort options. + unsigned int numSortTypes = FileSorts::SortTypes.size() - 2; + for (unsigned int i = 0; i < numSortTypes; i++) { + if (FileSorts::SortTypes[i].description == + Settings::getInstance()->getString("DefaultSortOrder")) { + sortOrder = FileSorts::SortTypes[i].description; break; } } @@ -264,8 +267,8 @@ void GuiMenu::openUISettings() // sort order 'filename, ascending'. if (sortOrder == "") sortOrder = Settings::getInstance()->getDefaultString("DefaultSortOrder"); - for (auto it = FileSorts::SortTypes.cbegin(); it != FileSorts::SortTypes.cend(); it++) { - const FileData::SortType& sort = *it; + for (unsigned int i = 0; i < numSortTypes; i++) { + const FileData::SortType& sort = FileSorts::SortTypes[i]; if (sort.description == sortOrder) default_sort_order->add(sort.description, &sort, true); else