From e56fdf3df65f67b2aac12e14701226bd5cafe33f Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 20 Sep 2020 20:25:32 +0200 Subject: [PATCH] Improved handling and sorting of folders. --- es-app/src/FileData.cpp | 95 +++++++++++++++---- es-app/src/FileData.h | 1 + es-app/src/guis/GuiGamelistOptions.cpp | 10 +- .../views/gamelist/ISimpleGameListView.cpp | 16 +++- 4 files changed, 99 insertions(+), 23 deletions(-) diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index e7ed790ea..69d4790c0 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -151,6 +151,17 @@ const std::vector FileData::getChildrenRecursive() const return childrenRecursive; } +bool FileData::viewHasOnlyFolders() +{ + bool onlyFolders = true; + std::vector entrySiblings = this->getParent()->getChildren(); + for (auto it = entrySiblings.cbegin(); it != entrySiblings.cend(); it++) { + if ((*it)->getType() != FOLDER) + onlyFolders = false; + } + return onlyFolders; +} + const std::string FileData::getROMDirectory() { std::string romDirSetting = Settings::getInstance()->getString("ROMDirectory"); @@ -463,6 +474,13 @@ 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) + 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); @@ -479,6 +497,10 @@ void FileData::sort(ComparisonFunction& comparator, bool ascending) mChildren.insert(mChildren.end(), mChildrenOthers.begin(), mChildrenOthers.end()); } else { + if (!ascending) + 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()); @@ -523,6 +545,7 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending mFirstLetterIndex.clear(); mOnlyFolders = true; std::vector mChildrenFolders; + std::vector mChildrenFavoritesFolders; std::vector mChildrenFavorites; std::vector mChildrenOthers; bool showHiddenGames = Settings::getInstance()->getBool("ShowHiddenGames"); @@ -538,7 +561,11 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending } if (foldersOnTop && mChildren[i]->getType() == FOLDER) { - mChildrenFolders.push_back(mChildren[i]); + if (!mChildren[i]->getFavorite()) + mChildrenFolders.push_back(mChildren[i]); + else + mChildrenFavoritesFolders.push_back(mChildren[i]); + hasFolders = true; } else if (mChildren[i]->getFavorite()) { @@ -555,6 +582,19 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending mOnlyFolders = false; } + // If there are favorite folders and this is a mixed list, then don't handle these + // separately but instead merge them into the same vector. This is a quite wasteful + // approach but the scenario where a user has a mixed folder and files list and marks + // some folders as favorites is probably a rare situation. + if (!mOnlyFolders && mChildrenFavoritesFolders.size() > 0) { + mChildrenFolders.insert(mChildrenFolders.end(), mChildrenFavoritesFolders.begin(), + mChildrenFavoritesFolders.end()); + mChildrenFavoritesFolders.erase(mChildrenFavoritesFolders.begin(), + mChildrenFavoritesFolders.end()); + std::stable_sort(mChildrenFolders.begin(), mChildrenFolders.end(), + getSortTypeFromString("filename, ascending").comparisonFunction); + } + // If there are only favorites in the gamelist, it makes sense to still generate // a letter index. For instance to be able to quick jump in the 'favorites' // collection. Doing this additional work here only for the applicable gamelists is @@ -584,49 +624,70 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending auto last = std::unique(mFirstLetterIndex.begin(), mFirstLetterIndex.end()); mFirstLetterIndex.erase(last, mFirstLetterIndex.end()); + // If there were at least one favorite folder in the gamelist, insert the favorite + // unicode character in the first position. + if (foldersOnTop && mOnlyFolders && mChildrenFavoritesFolders.size() > 0) + mFirstLetterIndex.insert(mFirstLetterIndex.begin(), FAVORITE_CHAR); // If there were at least one favorite in the gamelist, insert the favorite // unicode character in the first position. - if (mChildrenOthers.size() > 0 && mChildrenFavorites.size() > 0) + else if (mChildrenOthers.size() > 0 && mChildrenFavorites.size() > 0) mFirstLetterIndex.insert(mFirstLetterIndex.begin(), FAVORITE_CHAR); // If it's a mixed list and folders are sorted on top, add a folder icon to the index. if (foldersOnTop && hasFolders && !mOnlyFolders) mFirstLetterIndex.insert(mFirstLetterIndex.begin(), FOLDER_CHAR); + // 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) { + std::stable_sort(mChildrenFolders.begin(), mChildrenFolders.end(), + getSortTypeFromString("filename, ascending").comparisonFunction); + std::stable_sort(mChildrenFavoritesFolders.begin(), mChildrenFavoritesFolders.end(), + getSortTypeFromString("filename, ascending").comparisonFunction); + std::stable_sort(mChildrenFavorites.begin(), mChildrenFavorites.end(), + getSortTypeFromString("filename, ascending").comparisonFunction); + std::stable_sort(mChildrenOthers.begin(), mChildrenOthers.end(), + getSortTypeFromString("filename, ascending").comparisonFunction); + } + // Sort favorite games and the other games separately. - if (foldersOnTop && mOnlyFolders) + if (foldersOnTop && mOnlyFolders) { + std::stable_sort(mChildrenFavoritesFolders.begin(), + mChildrenFavoritesFolders.end(), comparator); 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 favorite folders. + for (auto it = mChildrenFavoritesFolders.cbegin(); it != + mChildrenFavoritesFolders.cend(); it++) { + if ((*it)->getChildren().size() > 0) + (*it)->sortFavoritesOnTop(comparator, ascending); + } + // 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) - (*it)->sortFavoritesOnTop(comparator, ascending); - } - - // Iterate through any child folders. - for (auto it = mChildrenOthers.cbegin(); it != mChildrenOthers.cend(); it++) { - if ((*it)->getChildren().size() > 0) - (*it)->sortFavoritesOnTop(comparator, ascending); - } - if (!ascending) { - if (foldersOnTop && mOnlyFolders) + 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(mChildrenFolders.size() + mChildrenFavorites.size() + mChildrenOthers.size()); + mChildren.reserve(mChildrenFavoritesFolders.size() + mChildrenFolders.size() + + mChildrenFavorites.size() + mChildrenOthers.size()); + mChildren.insert(mChildren.end(), mChildrenFavoritesFolders.begin(), + mChildrenFavoritesFolders.end()); mChildren.insert(mChildren.end(), mChildrenFolders.begin(), mChildrenFolders.end()); mChildren.insert(mChildren.end(), mChildrenFavorites.begin(), mChildrenFavorites.end()); mChildren.insert(mChildren.end(), mChildrenOthers.begin(), mChildrenOthers.end()); diff --git a/es-app/src/FileData.h b/es-app/src/FileData.h index f2aecfe32..3ce89b3f2 100644 --- a/es-app/src/FileData.h +++ b/es-app/src/FileData.h @@ -64,6 +64,7 @@ public: const std::vector& getFirstLetterIndex() const { return mFirstLetterIndex; }; const bool getOnlyFoldersFlag() { return mOnlyFolders; } + bool viewHasOnlyFolders(); static const std::string getROMDirectory(); static const std::string getMediaDirectory(); const std::string getMediafilePath(std::string subdirectory, std::string mediatype) const; diff --git a/es-app/src/guis/GuiGamelistOptions.cpp b/es-app/src/guis/GuiGamelistOptions.cpp index 3e9bae9fa..9c2d4e40d 100644 --- a/es-app/src/guis/GuiGamelistOptions.cpp +++ b/es-app/src/guis/GuiGamelistOptions.cpp @@ -339,7 +339,7 @@ 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) { + if (!mOnlyHasFolders && mFoldersOnTop && files.at(i)->getType() == FOLDER) { continue; } else { @@ -368,9 +368,13 @@ void GuiGamelistOptions::jumpToFirstRow() // Get the gamelist. const std::vector& files = getGamelist()->getCursor()-> getParent()->getChildrenListToDisplay(); - // Select the first game that is not a folder. + // Select the first game that is not a folder, unless it's a folder-only list in + // which case the first line overall is selected. for (auto it = files.cbegin(); it != files.cend(); it++) { - if ((*it)->getType() == GAME) { + if (!mOnlyHasFolders && mFoldersOnTop && (*it)->getType() == FOLDER) { + continue; + } + else { getGamelist()->setCursor(*it); break; } diff --git a/es-app/src/views/gamelist/ISimpleGameListView.cpp b/es-app/src/views/gamelist/ISimpleGameListView.cpp index e6af0b047..0a8f274e5 100644 --- a/es-app/src/views/gamelist/ISimpleGameListView.cpp +++ b/es-app/src/views/gamelist/ISimpleGameListView.cpp @@ -189,6 +189,11 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) bool favoritesSorting; bool removedLastFavorite = false; bool foldersOnTop = Settings::getInstance()->getBool("FoldersOnTop"); + // If the current list only contains folders, then treat it as if the folders + // are not sorted on top, this way the logic should work exactly as for mixed + // lists or files-only lists. + if (getCursor()->getType() == FOLDER && foldersOnTop == true) + foldersOnTop = !getCursor()->viewHasOnlyFolders(); if (CollectionSystemManager::get()->getIsCustomCollection(mRoot->getSystem())) favoritesSorting = Settings::getInstance()->getBool("FavFirstCustom"); @@ -208,6 +213,11 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) // If we are on the favorite marking boundary, select the next entry. else if (getCursor()->getFavorite() != getPreviousEntry()->getFavorite()) entryToSelect = getNextEntry(); + // If we mark the second entry as favorite and the first entry is not a + // favorite, then select this entry if they are of the same type. + else if (getPreviousEntry() == getFirstEntry() && + getCursor()->getType() == getPreviousEntry()->getType()) + entryToSelect = getPreviousEntry(); // For all other scenarios try to select the next entry, and if it doesn't // exist, select the previous entry. else @@ -271,9 +281,9 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) mWindow->setInfoPopup(s); entryToUpdate->getSourceFileData()->getSystem()->onMetaDataSavePoint(); - if (!Settings::getInstance()->getBool("FoldersOnTop")) - mRoot->sort(mRoot->getSortTypeFromString(mRoot->getSortTypeString()), - Settings::getInstance()->getBool("FavoritesFirst")); + getCursor()->getParent()->sort( + mRoot->getSortTypeFromString(mRoot->getSortTypeString()), + Settings::getInstance()->getBool("FavoritesFirst")); ViewController::get()->onFileChanged(getCursor(), FILE_METADATA_CHANGED);