Added option to sort folders on top of the gamelists.

This commit is contained in:
Leon Styhre 2020-07-28 19:44:17 +02:00
parent ea59d9f9c4
commit b60c86e40f
7 changed files with 168 additions and 30 deletions

View file

@ -35,7 +35,8 @@ Many bugs have been fixed, and numerous features that were only partially implem
* Custom event scripts can now be enabled or disabled with a menu option
* Help system updated and expanded to the complete application (previously it was only partially implemented)
* Improved input device configuration, and default keyboard mappings are now applied if the keyboard has not been configured by the user
* GUI-configurable option to sort favorite games on the top of the game lists (favorites marked with stars)
* GUI-configurable option to sort favorite games above non-favorite games (favorites marked with stars)
* GUI-configurable option to sort folders on top of the gamelists
* Added new component GuiComplexTextEditPopup to handle changes to configuration file entries and similar
* Speed improvements and optimizations, the application now starts faster and feels more responsive
* Moved all resources to a subdirectory structure and enabled the CMake install prefix variable to generate the resources search path
@ -65,7 +66,7 @@ Many bugs have been fixed, and numerous features that were only partially implem
* The random system selection did not consider the currently selected system
* The random game selection did not consider the currently selected game
* The random game selection traversed folders, i.e. a game could be selected inside a subdirectory and vice versa
* Deleting a game did not delete the game media files or its entry in the gamelist.xml file
* Deleting a game from the metadata editor did not delete the game media files or the entry in the gamelist.xml file
* SystemView didn't properly loop the systems if only two systems were available
* Hidden files still showed up if they had a gamelist.xml entry
* On Unix, adding a hidden folder with a game in it crashed the application on startup

View file

@ -37,6 +37,7 @@ FileData::FileData(
mEnvData(envData),
mSourceFileData(nullptr),
mParent(nullptr),
mOnlyFolders(false),
mDeletionFlag(false),
// Metadata is REALLY set in the constructor!
metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA)
@ -368,6 +369,10 @@ void FileData::removeChild(FileData* file)
void FileData::sort(ComparisonFunction& comparator, bool ascending)
{
mFirstLetterIndex.clear();
mOnlyFolders = false;
bool foldersOnTop = Settings::getInstance()->getBool("FoldersOnTop");
std::vector<FileData*> mChildrenFolders;
std::vector<FileData*> mChildrenOthers;
// Only run this section of code if the setting to show hidden games has been disabled,
// in order to avoid unnecessary processing.
@ -386,32 +391,69 @@ void FileData::sort(ComparisonFunction& comparator, bool ascending)
mChildren.insert(mChildren.end(), mChildrenShown.begin(), mChildrenShown.end());
}
if (foldersOnTop) {
for (unsigned int i = 0; i < mChildren.size(); i++) {
if (mChildren[i]->getType() == FOLDER)
mChildrenFolders.push_back(mChildren[i]);
else
mChildrenOthers.push_back(mChildren[i]);
}
std::stable_sort(mChildrenFolders.begin(), mChildrenFolders.end(), comparator);
std::stable_sort(mChildrenOthers.begin(), mChildrenOthers.end(), comparator);
if (!ascending) {
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 {
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++) {
if (!(foldersOnTop && (*it)->getType() == FOLDER)) {
// Build mFirstLetterIndex.
const char firstChar = toupper((*it)->getSortName().front());
mFirstLetterIndex.push_back(std::string(1, firstChar));
}
// Iterate through any child folders.
if ((*it)->getChildren().size() > 0)
(*it)->sort(comparator, ascending);
}
// If there are only folders in the gamelist, then it makes sense to still
// generate a letter index.
if (mChildrenOthers.size() == 0 && mChildrenFolders.size() > 0) {
for (unsigned int i = 0; i < mChildrenFolders.size(); i++) {
const char firstChar = toupper(mChildrenFolders[i]->getSortName().front());
mFirstLetterIndex.push_back(std::string(1, firstChar));
}
mOnlyFolders = true;
}
// Sort and make each entry unique in mFirstLetterIndex.
std::sort(mFirstLetterIndex.begin(), mFirstLetterIndex.end());
auto last = std::unique(mFirstLetterIndex.begin(), mFirstLetterIndex.end());
mFirstLetterIndex.erase(last, mFirstLetterIndex.end());
if (!ascending)
std::reverse(mChildren.begin(), mChildren.end());
}
void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending)
{
mFirstLetterIndex.clear();
mOnlyFolders = false;
std::vector<FileData*> mChildrenFolders;
std::vector<FileData*> mChildrenFavorites;
std::vector<FileData*> mChildrenOthers;
bool showHiddenGames = Settings::getInstance()->getBool("ShowHiddenGames");
bool foldersOnTop = Settings::getInstance()->getBool("FoldersOnTop");
for (unsigned int i = 0; i < mChildren.size(); i++) {
// Exclude game if it's marked as hidden and the hide setting has been set.
@ -421,7 +463,10 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending
continue;
}
if (mChildren[i]->getFavorite()) {
if (foldersOnTop && mChildren[i]->getType() == FOLDER) {
mChildrenFolders.push_back(mChildren[i]);
}
else if (mChildren[i]->getFavorite()) {
mChildrenFavorites.push_back(mChildren[i]);
}
else {
@ -438,10 +483,25 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending
// probably faster than building a redundant index for all gamelists during sorting.
if (mChildrenOthers.size() == 0 && mChildrenFavorites.size() > 0) {
for (unsigned int i = 0; i < mChildren.size(); i++) {
if (foldersOnTop && mChildren[i]->getType() == FOLDER) {
continue;
}
else {
const char firstChar = toupper(mChildren[i]->getSortName().front());
mFirstLetterIndex.push_back(std::string(1, firstChar));
}
}
}
// If there are only folders in the gamelist, then it also makes sense to generate
// a letter index.
else if (mChildrenOthers.size() == 0 && mChildrenFavorites.size() == 0 &&
mChildrenFolders.size() > 0) {
for (unsigned int i = 0; i < mChildrenFolders.size(); i++) {
const char firstChar = toupper(mChildrenFolders[i]->getSortName().front());
mFirstLetterIndex.push_back(std::string(1, firstChar));
}
mOnlyFolders = true;
}
// Sort and make each entry unique in mFirstLetterIndex.
std::sort(mFirstLetterIndex.begin(), mFirstLetterIndex.end());
@ -454,9 +514,16 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending
mFirstLetterIndex.insert(mFirstLetterIndex.begin(), FAVORITE_CHAR);
// Sort favorite games and the other games separately.
std::stable_sort(mChildrenFolders.begin(), mChildrenFolders.end(), comparator);
std::stable_sort(mChildrenFavorites.begin(), mChildrenFavorites.end(), comparator);
std::stable_sort(mChildrenOthers.begin(), mChildrenOthers.end(), comparator);
// Iterate through any child folders.
for (auto it = mChildrenFolders.cbegin(); it != mChildrenFolders.cend(); it++) {
if ((*it)->getChildren().size() > 0)
(*it)->sortFavoritesOnTop(comparator, ascending);
}
// Iterate through any child folders.
for (auto it = mChildrenFavorites.cbegin(); it != mChildrenFavorites.cend(); it++) {
if ((*it)->getChildren().size() > 0)
@ -470,13 +537,15 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending
}
if (!ascending) {
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(mChildrenFavorites.size() + mChildrenOthers.size());
mChildren.reserve(mChildrenFolders.size() + mChildrenFavorites.size() + mChildrenOthers.size());
mChildren.insert(mChildren.end(), mChildrenFolders.begin(), mChildrenFolders.end());
mChildren.insert(mChildren.end(), mChildrenFavorites.begin(), mChildrenFavorites.end());
mChildren.insert(mChildren.end(), mChildrenOthers.begin(), mChildrenOthers.end());
}

View file

@ -59,6 +59,7 @@ public:
inline SystemEnvironmentData* getSystemEnvData() const { return mEnvData; }
const std::vector<std::string>& getFirstLetterIndex() const
{ return mFirstLetterIndex; };
const bool getOnlyFoldersFlag() { return mOnlyFolders; }
static const std::string getROMDirectory();
static const std::string getMediaDirectory();
const std::string getMediafilePath(std::string subdirectory, std::string mediatype) const;
@ -137,6 +138,7 @@ private:
std::vector<FileData*> mChildren;
std::vector<FileData*> mFilteredChildren;
std::vector<std::string> mFirstLetterIndex;
bool mOnlyFolders;
// Used for flagging a game for deletion from its gamelist.xml file.
bool mDeletionFlag;

View file

@ -39,6 +39,11 @@ GuiGamelistOptions::GuiGamelistOptions(
fromPlaceholder = file->isPlaceHolder();
ComponentListRow row;
// Read the setting for whether folders are sorted on top of the gamelists.
// Also check if the gamelist only contains folders, as generated by the FileData sorting.
mFoldersOnTop = Settings::getInstance()->getBool("FoldersOnTop");
mOnlyHasFolders = file->getParent()->getOnlyFoldersFlag();
// Read the applicable favorite sorting setting depending on whether the
// system is a custom collection or not.
if (CollectionSystemManager::get()->getIsCustomCollection(file->getSystem()))
@ -53,11 +58,21 @@ GuiGamelistOptions::GuiGamelistOptions(
// The letter index is generated in FileData during gamelist sorting.
mFirstLetterIndex = file->getParent()->getFirstLetterIndex();
// Don't include the folder name starting characters if folders are sorted on top
// unless the list only contains folders.
if (!mOnlyHasFolders && mFoldersOnTop && file->getType() == FOLDER) {
mCurrentFirstCharacter = mFirstLetterIndex.front();
}
else {
// Set the quick selector to the first character of the selected game.
if (mFavoritesSorting && file->getFavorite() && mFirstLetterIndex.front() == FAVORITE_CHAR)
if (mFavoritesSorting && file->getFavorite() &&
mFirstLetterIndex.front() == FAVORITE_CHAR) {
mCurrentFirstCharacter = FAVORITE_CHAR;
else
}
else {
mCurrentFirstCharacter = toupper(file->getSortName().front());
}
}
mJumpToLetterList = std::make_shared<LetterList>(mWindow, getHelpStyle(),
"JUMP TO...", false);
@ -178,7 +193,8 @@ GuiGamelistOptions::~GuiGamelistOptions()
}
// Has the user changed the letter using the quick selector?
if (mCurrentFirstCharacter != mJumpToLetterList->getSelected()) {
if ((mFoldersOnTop && getGamelist()->getCursor()->getType() == FOLDER) ||
mCurrentFirstCharacter != mJumpToLetterList->getSelected()) {
if (mJumpToLetterList->getSelected() == FAVORITE_CHAR)
jumpToFirstRow();
else
@ -275,23 +291,47 @@ void GuiGamelistOptions::jumpToLetter()
if (mFavoritesSorting && mFirstLetterIndex.front() == FAVORITE_CHAR) {
if ((char)toupper(files.at(i)->getSortName().front()) ==
letter && !files.at(i)->getFavorite()) {
if (mFoldersOnTop && files.at(i)->getType() == FOLDER) {
continue;
}
else {
getGamelist()->setCursor(files.at(i));
break;
}
}
}
else {
if ((char)toupper(files.at(i)->getSortName().front()) == letter) {
if (!mOnlyHasFolders && mFoldersOnTop && files.at(i)->getType() == FOLDER) {
continue;
}
else {
getGamelist()->setCursor(files.at(i));
break;
}
}
}
}
}
void GuiGamelistOptions::jumpToFirstRow()
{
if (mFoldersOnTop) {
// Get the gamelist.
const std::vector<FileData*>& files = getGamelist()->getCursor()->
getParent()->getChildrenListToDisplay();
// Select the first game that is not a folder.
for (auto it = files.cbegin(); it != files.cend(); it++) {
if ((*it)->getType() == GAME) {
getGamelist()->setCursor(*it);
break;
}
}
}
else {
// Get first row of the gamelist.
getGamelist()->setCursor(getGamelist()->getFirstEntry());
}
}
bool GuiGamelistOptions::input(InputConfig* config, Input input)

View file

@ -50,7 +50,9 @@ private:
SystemData* mSystem;
IGameListView* getGamelist();
bool mFoldersOnTop;
bool mFavoritesSorting;
bool mOnlyHasFolders;
bool fromPlaceholder;
bool mFiltersChanged;
bool mCancelled;

View file

@ -388,12 +388,35 @@ void GuiMenu::openUISettings()
}
});
// Sort favorites on top of the gamelists.
auto favoritesFirstSwitch = std::make_shared<SwitchComponent>(mWindow);
favoritesFirstSwitch->setState(Settings::getInstance()->getBool("FavoritesFirst"));
s->addWithLabel("SORT FAVORITES ON TOP OF GAMELISTS", favoritesFirstSwitch);
s->addSaveFunc([favoritesFirstSwitch] {
if (Settings::getInstance()->setBool("FavoritesFirst", favoritesFirstSwitch->getState()))
// Sort folders on top of the gamelists.
auto folders_on_top = std::make_shared<SwitchComponent>(mWindow);
folders_on_top->setState(Settings::getInstance()->getBool("FoldersOnTop"));
s->addWithLabel("SORT FOLDERS ON TOP OF GAMELISTS", folders_on_top);
s->addSaveFunc([folders_on_top] {
if (Settings::getInstance()->setBool("FoldersOnTop", folders_on_top->getState()))
for (auto it = SystemData::sSystemVector.cbegin(); it !=
SystemData::sSystemVector.cend(); it++) {
if ((*it)->isCollection())
continue;
FileData* rootFolder = (*it)->getRootFolder();
rootFolder->sort(getSortTypeFromString(rootFolder->getSortTypeString()),
Settings::getInstance()->getBool("FavoritesFirst"));
ViewController::get()->reloadGameListView(*it);
// Jump to the first row of the gamelist.
IGameListView* gameList = ViewController::get()->getGameListView((*it)).get();
gameList->setCursor(gameList->getFirstEntry());
}
});
// Sort favorites on top of non-favorites in the gamelists.
auto favorites_first = std::make_shared<SwitchComponent>(mWindow);
favorites_first->setState(Settings::getInstance()->getBool("FavoritesFirst"));
s->addWithLabel("SORT FAVORITE GAMES ABOVE NON-FAVORITES", favorites_first);
s->addSaveFunc([favorites_first] {
if (Settings::getInstance()->setBool("FavoritesFirst", favorites_first->getState()))
for (auto it = SystemData::sSystemVector.cbegin(); it !=
SystemData::sSystemVector.cend(); it++) {
// The favorites and recent gamelists never sort favorites on top.

View file

@ -84,6 +84,7 @@ void Settings::setDefaults()
mStringMap["ThemeSet"] = "";
mStringMap["UIMode"] = "full";
mStringMap["DefaultSortOrder"] = "filename, ascending";
mBoolMap["FoldersOnTop"] = true;
mBoolMap["FavoritesFirst"] = true;
mBoolMap["ForceDisableFilters"] = false;
mBoolMap["QuickSystemSelect"] = true;