Multiple improvements and bugfixes to the custom collections handling.

This commit is contained in:
Leon Styhre 2020-10-20 21:01:24 +02:00
parent 3e6f3487c9
commit 87bd205a3f
7 changed files with 113 additions and 106 deletions

View file

@ -1047,9 +1047,20 @@ The file contents is simply a list of ROM files, such as the following:
Any changes to custom collections (for example adding or removing a game) will be immediately written to the corresponding collection configuration file. Any changes to custom collections (for example adding or removing a game) will be immediately written to the corresponding collection configuration file.
>>>
Note that if you for example copy or migrate a collection from a previous version of EmulationStation or if you're setting up EmulationStation Desktop Edition on a new computer, even though you copy the files into the collections directory, they will not show up in the application. You always need to enable the collection in the menu. ES looks inside the es_settings.cfg file during startup to see which collections should be shown. Note that if you for example copy or migrate a collection from a previous version of EmulationStation or if you're setting up EmulationStation Desktop Edition on a new computer, even though you copy the files into the collections directory, they will not show up in the application. You always need to enable the collection in the menu. ES looks inside the es_settings.cfg file during startup to see which collections should be shown.
If you're migrating from a previous version of EmulationStation that has absolute paths in the collection files, these will be rewritten with the %ROMPATH% variable the first time you make a change to the collection. If you're migrating from a previous version of EmulationStation that has absolute paths in the collection files, these will be rewritten with the %ROMPATH% variable the first time you make a change to the collection.
>>>
It's also possible to add media files to the custom collections entries if they are grouped under the _collections_ system (this is enabled by default). Simply create a directory under your media folder, for example `~/.emulationstation/downloaded_media`, which corresponds to the collection name. For our example it would be _1980s_. The media files themselves should also be named after the collection. This is an example of what this could look like:
```
~/.emulationstation/downloaded_media/1980s/covers/1980s.png
~/.emulationstation/downloaded_media/1980s/videos/1980s.mp4
```
For more details on how to manually copy media files, see the section [Manually copying game media files](USERGUIDE.md#manually-copying-game-media-files) earlier in this guide.
## Themes ## Themes

View file

@ -272,6 +272,11 @@ void CollectionSystemManager::updateSystemsList()
if (rootFolder->getChildren().size() > 0) { if (rootFolder->getChildren().size() > 0) {
rootFolder->sort(rootFolder->getSortTypeFromString(rootFolder-> rootFolder->sort(rootFolder->getSortTypeFromString(rootFolder->
getSortTypeString()), Settings::getInstance()->getBool("FavFirstCustom")); getSortTypeString()), Settings::getInstance()->getBool("FavFirstCustom"));
// Update the custom collections metadata now that the sorting has been done.
std::vector<FileData*> customCollections = rootFolder->getChildren();
for (auto it = customCollections.cbegin(); it != customCollections.cend(); it++)
updateCollectionFolderMetadata((*it)->getSystem());
SystemData::sSystemVector.push_back(mCustomCollectionsBundle); SystemData::sSystemVector.push_back(mCustomCollectionsBundle);
} }
} }
@ -588,17 +593,17 @@ void CollectionSystemManager::setEditMode(std::string collectionName)
// If it's bundled, this needs to be the bundle system. // If it's bundled, this needs to be the bundle system.
mEditingCollectionSystemData = sysData; mEditingCollectionSystemData = sysData;
GuiInfoPopup* s = new GuiInfoPopup(mWindow, "Editing the '" + GuiInfoPopup* s = new GuiInfoPopup(mWindow, "EDITING THE '" +
Utils::String::toUpper(collectionName) + Utils::String::toUpper(collectionName) +
"' Collection. Add/remove games with Y.", 10000); "' COLLECTION, ADD/REMOVE GAMES WITH 'Y'", 10000);
mWindow->setInfoPopup(s); mWindow->setInfoPopup(s);
} }
void CollectionSystemManager::exitEditMode() void CollectionSystemManager::exitEditMode()
{ {
GuiInfoPopup* s = new GuiInfoPopup(mWindow, "Finished editing the '" + GuiInfoPopup* s = new GuiInfoPopup(mWindow, "FINISHED EDITING THE '" +
mEditingCollection + "' Collection.", 4000); Utils::String::toUpper(mEditingCollection) + "' COLLECTION", 4000);
mWindow->setInfoPopup(s); mWindow->setInfoPopup(s);
mIsEditingCustom = false; mIsEditingCustom = false;
@ -685,11 +690,13 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file)
refreshCollectionSystems(file->getSourceFileData()); refreshCollectionSystems(file->getSourceFileData());
} }
if (adding) if (adding)
s = new GuiInfoPopup(mWindow, "Added '" + Utils::String::removeParenthesis(name) + s = new GuiInfoPopup(mWindow, "ADDED '" +
"' to '" + Utils::String::toUpper(sysName) + "'", 4000); Utils::String::toUpper(Utils::String::removeParenthesis(name)) +
"' TO '" + Utils::String::toUpper(sysName) + "'", 4000);
else else
s = new GuiInfoPopup(mWindow, "Removed '" + Utils::String::removeParenthesis(name) + s = new GuiInfoPopup(mWindow, "REMOVED '" +
"' from '" + Utils::String::toUpper(sysName) + "'", 4000); Utils::String::toUpper(Utils::String::removeParenthesis(name)) +
"' FROM '" + Utils::String::toUpper(sysName) + "'", 4000);
mWindow->setInfoPopup(s); mWindow->setInfoPopup(s);
return true; return true;
} }
@ -729,87 +736,42 @@ void CollectionSystemManager::initAutoCollectionSystems()
} }
} }
// This may come in handy if at any point in time in the future we want to // Used to generate a description of the collection, all other metadata fields are hidden.
// automatically generate metadata for a folder.
void CollectionSystemManager::updateCollectionFolderMetadata(SystemData* sys) void CollectionSystemManager::updateCollectionFolderMetadata(SystemData* sys)
{ {
FileData* rootFolder = sys->getRootFolder(); FileData* rootFolder = sys->getRootFolder();
rootFolder->metadata.set("hidemetadata", "true");
std::string desc = "This collection is empty."; std::string desc = "This collection is empty.";
std::string rating = "0"; std::vector<FileData*> gamesList = rootFolder->getChildren();
std::string players = "1"; unsigned int gameCount = gamesList.size();
std::string releasedate = "N/A";
std::string developer = "None";
std::string genre = "None";
std::string video = "";
std::string thumbnail = "";
std::string image = "";
std::unordered_map<std::string, FileData*> games = rootFolder->getChildrenByFilename(); if (gameCount > 0) {
switch (gameCount) {
if (games.size() > 0) { case 1:
std::string games_list = ""; desc = "This collection contains 1 game: '" + gamesList[0]->metadata.get("name") +
int games_counter = 0; " [" + gamesList[0]->getSourceFileData()->getSystem()->getName() + "]'.";
for (std::unordered_map<std::string, FileData*>::const_iterator break;
iter = games.cbegin(); iter != games.cend(); ++iter) { case 2:
desc = "This collection contains 2 games: '" + gamesList[0]->metadata.get("name") +
games_counter++; " [" + gamesList[0]->getSourceFileData()->getSystem()->getName() +
FileData* file = iter->second; "]' and '" + gamesList[1]->metadata.get("name") + " [" +
gamesList[1]->getSourceFileData()->getSystem()->getName() + "]'.";
std::string new_rating = file->metadata.get("rating"); break;
std::string new_releasedate = file->metadata.get("releasedate"); default:
std::string new_developer = file->metadata.get("developer"); desc = "This collection contains " + std::to_string(gameCount) +
std::string new_genre = file->metadata.get("genre"); " games: '" + gamesList[0]->metadata.get("name") +
std::string new_players = file->metadata.get("players"); " [" + gamesList[0]->getSourceFileData()->getSystem()->getName() +
"]', '" + gamesList[1]->metadata.get("name") + " [" +
rating = (new_rating > rating ? (new_rating != "" ? gamesList[1]->getSourceFileData()->getSystem()->getName() + "]' and '" +
new_rating : rating) : rating); gamesList[2]->metadata.get("name") + " [" +
gamesList[2]->getSourceFileData()->getSystem()->getName() + "]'";
players = (new_players > players ? (new_players != "" ? desc += (gameCount == 3 ? "." : ", among others.");
new_players : players) : players); break;
releasedate = (new_releasedate < releasedate ? (new_releasedate != "" ?
new_releasedate : releasedate) : releasedate);
developer = (developer == "None" ? new_developer : (new_developer != developer ?
"Various" : new_developer));
genre = (genre == "None" ? new_genre : (new_genre != genre ?
"Various" : new_genre));
switch (games_counter) {
case 2:
case 3:
games_list += ", ";
case 1:
games_list += "'" + file->getName() + "'";
break;
case 4:
games_list += " among other titles.";
}
}
desc = "This collection contains " + std::to_string(games_counter) +
" games, including " + games_list;
FileData* randomGame = sys->getRandomGame();
if (randomGame) {
video = randomGame->getVideoPath();
thumbnail = randomGame->getThumbnailPath();
image = randomGame->getImagePath();
} }
} }
rootFolder->metadata.set("desc", desc); rootFolder->metadata.set("desc", desc);
rootFolder->metadata.set("rating", rating);
rootFolder->metadata.set("players", players);
rootFolder->metadata.set("genre", genre);
rootFolder->metadata.set("releasedate", releasedate);
rootFolder->metadata.set("developer", developer);
rootFolder->metadata.set("video", video);
rootFolder->metadata.set("thumbnail", thumbnail);
rootFolder->metadata.set("image", image);
} }
void CollectionSystemManager::initCustomCollectionSystems() void CollectionSystemManager::initCustomCollectionSystems()
@ -962,11 +924,10 @@ void CollectionSystemManager::populateCustomCollection(CollectionSystemData* sys
index->addToIndex(newGame); index->addToIndex(newGame);
} }
else { else {
LOG(LogWarning) << "File \"" << gameKey << "\" does not exist, ignoring entry"; LOG(LogWarning) << "File \"" << gameKey <<
"\" does not exist, is hidden, or is not counted as a game, ignoring entry";
} }
} }
updateCollectionFolderMetadata(newSys);
} }
// Functions below to handle System View removal and insertion of collections. // Functions below to handle System View removal and insertion of collections.

View file

@ -132,6 +132,8 @@ void GuiCollectionSystemsOptions::addEntry(const char* name, unsigned int color,
} }
void GuiCollectionSystemsOptions::createCollection(std::string inName) { void GuiCollectionSystemsOptions::createCollection(std::string inName) {
if (CollectionSystemManager::get()->isEditing())
CollectionSystemManager::get()->exitEditMode();
std::string name = CollectionSystemManager::get()->getValidNewCollectionName(inName); std::string name = CollectionSystemManager::get()->getValidNewCollectionName(inName);
SystemData* newSys = CollectionSystemManager::get()->addNewCustomCollection(name); SystemData* newSys = CollectionSystemManager::get()->addNewCustomCollection(name);
CollectionSystemManager::get()->saveCustomCollection(newSys); CollectionSystemManager::get()->saveCustomCollection(newSys);

View file

@ -204,6 +204,14 @@ GuiGamelistOptions::~GuiGamelistOptions()
// Notify that the root folder was sorted (refresh). // Notify that the root folder was sorted (refresh).
getGamelist()->onFileChanged(root, FILE_SORTED); getGamelist()->onFileChanged(root, FILE_SORTED);
if (mSystem->isCollection() && mSystem->getFullName() == "collections") {
// Update the custom collections metadata now that we have changed the sorting.
std::vector<FileData*> customCollections = root->getChildren();
for (auto it = customCollections.cbegin(); it != customCollections.cend(); it++)
CollectionSystemManager::get()->
updateCollectionFolderMetadata((*it)->getSystem());
ViewController::get()->reloadGameListView(mSystem);
}
} }
// Has the user changed the letter using the quick selector? // Has the user changed the letter using the quick selector?
@ -270,12 +278,12 @@ void GuiGamelistOptions::openMetaDataEd()
clearGameBtnFunc = [this, file] { clearGameBtnFunc = [this, file] {
if (file->getType() == FOLDER) { if (file->getType() == FOLDER) {
LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the folder '" << LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the folder \"" <<
file->getFullPath() << "'"; file->getFullPath() << "\"";
} }
else { else {
LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the file '" << LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the file \"" <<
file->getFullPath() << "'"; file->getFullPath() << "\"";
} }
ViewController::get()->getGameListView(file->getSystem()).get()->removeMedia(file); ViewController::get()->getGameListView(file->getSystem()).get()->removeMedia(file);
@ -298,8 +306,8 @@ void GuiGamelistOptions::openMetaDataEd()
}; };
deleteGameBtnFunc = [this, file] { deleteGameBtnFunc = [this, file] {
LOG(LogInfo) << "Deleting the game file '" << file->getFullPath() << LOG(LogInfo) << "Deleting the game file \"" << file->getFullPath() <<
"', all its media files and its gamelist.xml entry."; "\", all its media files and its gamelist.xml entry.";
CollectionSystemManager::get()->deleteCollectionFiles(file); CollectionSystemManager::get()->deleteCollectionFiles(file);
ViewController::get()->getGameListView( ViewController::get()->getGameListView(
file->getSystem()).get()->removeMedia(file); file->getSystem()).get()->removeMedia(file);

View file

@ -362,7 +362,7 @@ void GuiMenu::openUISettings()
defaultSortOrder->add(sort.description, &sort, false); defaultSortOrder->add(sort.description, &sort, false);
} }
s->addWithLabel("DEFAULT SORT ORDER", defaultSortOrder); s->addWithLabel("DEFAULT SORT ORDER", defaultSortOrder);
s->addSaveFunc([defaultSortOrder, sortOrder] { s->addSaveFunc([defaultSortOrder, sortOrder, this] {
std::string selectedSortOrder = defaultSortOrder.get()->getSelected()->description; std::string selectedSortOrder = defaultSortOrder.get()->getSelected()->description;
if (selectedSortOrder != sortOrder) { if (selectedSortOrder != sortOrder) {
Settings::getInstance()->setString("DefaultSortOrder", selectedSortOrder); Settings::getInstance()->setString("DefaultSortOrder", selectedSortOrder);
@ -374,11 +374,21 @@ void GuiMenu::openUISettings()
(*it)->sortSystem(); (*it)->sortSystem();
// Update the metadata for any custom collections.
if ((*it)->isCollection() && (*it)->getFullName() == "collections") {
std::vector<FileData*> customCollections =
(*it)->getRootFolder()->getChildren();
for (auto it2 = customCollections.cbegin();
it2 != customCollections.cend(); it2++)
CollectionSystemManager::get()->
updateCollectionFolderMetadata((*it2)->getSystem());
}
// Jump to the first row of the gamelist. // Jump to the first row of the gamelist.
IGameListView* gameList = ViewController::get()->getGameListView((*it)).get(); IGameListView* gameList = ViewController::get()->getGameListView((*it)).get();
gameList->setCursor(gameList->getFirstEntry()); gameList->setCursor(gameList->getFirstEntry());
} }
mWindow->invalidateCachedBackground();
} }
}); });

View file

@ -179,7 +179,8 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
} }
else if (config->isMappedTo("y", input) && else if (config->isMappedTo("y", input) &&
!UIModeController::getInstance()->isUIModeKid()) { !UIModeController::getInstance()->isUIModeKid()) {
if (mRoot->getSystem()->isGameSystem()) { if (mRoot->getSystem()->isGameSystem() &&
getCursor()->getParent()->getPath() != "collections") {
if (getCursor()->getType() == GAME || getCursor()->getType() == FOLDER) if (getCursor()->getType() == GAME || getCursor()->getType() == FOLDER)
NavigationSounds::getInstance()->playThemeNavigationSound(FAVORITESOUND); NavigationSounds::getInstance()->playThemeNavigationSound(FAVORITESOUND);
// When marking or unmarking a game as favorite, don't jump to the new position // When marking or unmarking a game as favorite, don't jump to the new position
@ -201,7 +202,8 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
favoritesSorting = Settings::getInstance()->getBool("FavoritesFirst"); favoritesSorting = Settings::getInstance()->getBool("FavoritesFirst");
if (favoritesSorting && static_cast<std::string>( if (favoritesSorting && static_cast<std::string>(
mRoot->getSystem()->getName()) != "recent") { mRoot->getSystem()->getName()) != "recent" &&
!CollectionSystemManager::get()->isEditing()) {
FileData* entryToSelect; FileData* entryToSelect;
// Add favorite flag. // Add favorite flag.
if (!getCursor()->getFavorite()) { if (!getCursor()->getFavorite()) {
@ -260,23 +262,29 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
setCursor(entryToSelect); setCursor(entryToSelect);
} }
// Marking folders as favorites is only cosmetic as they're not sorted // Marking folders as favorites don't make them part of any collections,
// differently and they're not part of any collections. So it makes more // so it makes more sense to handle it here than to add the function to
// sense to do it here than to add the function to CollectionSystemManager. // CollectionSystemManager.
if (entryToUpdate->getType() == FOLDER) { if (entryToUpdate->getType() == FOLDER) {
GuiInfoPopup* s; GuiInfoPopup* s;
MetaDataList* md = &entryToUpdate->getSourceFileData()->metadata; if (CollectionSystemManager::get()->isEditing()) {
if (md->get("favorite") == "false") { s = new GuiInfoPopup(mWindow,
md->set("favorite", "true"); "CAN'T ADD FOLDERS TO CUSTOM COLLECTIONS", 4000);
s = new GuiInfoPopup(mWindow, "Marked folder '" +
Utils::String::removeParenthesis(entryToUpdate->getName()) +
"' as favorite", 4000);
} }
else { else {
md->set("favorite", "false"); MetaDataList* md = &entryToUpdate->getSourceFileData()->metadata;
s = new GuiInfoPopup(mWindow, "Removed favorite marking for folder '" + if (md->get("favorite") == "false") {
Utils::String::removeParenthesis(entryToUpdate->getName()) + md->set("favorite", "true");
"'", 4000); s = new GuiInfoPopup(mWindow, "MARKED FOLDER '" +
Utils::String::toUpper(Utils::String::removeParenthesis(
entryToUpdate->getName())) + "' AS FAVORITE", 4000);
}
else {
md->set("favorite", "false");
s = new GuiInfoPopup(mWindow, "REMOVED FAVORITE MARKING FOR FOLDER '" +
Utils::String::toUpper(Utils::String::removeParenthesis(
entryToUpdate->getName())) + "'", 4000);
}
} }
mWindow->setInfoPopup(s); mWindow->setInfoPopup(s);
@ -299,6 +307,13 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
return true; return true;
} }
else if (CollectionSystemManager::get()->isEditing() &&
entryToUpdate->metadata.get("nogamecount") == "true") {
GuiInfoPopup* s = new GuiInfoPopup(mWindow,
"CAN'T ADD ENTRIES THAT ARE NOT COUNTED "
"AS GAMES TO CUSTOM COLLECTIONS", 4000);
mWindow->setInfoPopup(s);
}
else if (CollectionSystemManager::get()->toggleGameInCollection(entryToUpdate)) { else if (CollectionSystemManager::get()->toggleGameInCollection(entryToUpdate)) {
// Jump to the first entry in the gamelist if the last favorite was unmarked. // Jump to the first entry in the gamelist if the last favorite was unmarked.
if (foldersOnTop && removedLastFavorite && if (foldersOnTop && removedLastFavorite &&

View file

@ -3,7 +3,7 @@
<view name="system"> <view name="system">
<text name="info1" extra="true"> <text name="info1" extra="true">
<text>Add your own custom collections here.</text> <text>Find your own custom collections here.</text>
</text> </text>
</view> </view>
</theme> </theme>