diff --git a/src/FileData.h b/src/FileData.h index a0c7abde8..ebb44b9b9 100644 --- a/src/FileData.h +++ b/src/FileData.h @@ -9,9 +9,9 @@ class FileData { public: virtual ~FileData() { }; - virtual bool isFolder() = 0; - virtual std::string getName() = 0; - virtual std::string getPath() = 0; + virtual bool isFolder() const = 0; + virtual const std::string & getName() const = 0; + virtual const std::string & getPath() const = 0; }; #endif diff --git a/src/FolderData.cpp b/src/FolderData.cpp index 2bc937cca..ac9e08477 100644 --- a/src/FolderData.cpp +++ b/src/FolderData.cpp @@ -1,19 +1,29 @@ #include "FolderData.h" #include "SystemData.h" +#include "GameData.h" #include #include -bool FolderData::isFolder() { return true; } -std::string FolderData::getName() { return mName; } -std::string FolderData::getPath() { return mPath; } + +std::map FolderData::sortStateNameMap; + +bool FolderData::isFolder() const { return true; } +const std::string & FolderData::getName() const { return mName; } +const std::string & FolderData::getPath() const { return mPath; } unsigned int FolderData::getFileCount() { return mFileVector.size(); } -FileData* FolderData::getFile(unsigned int i) { return mFileVector.at(i); } + FolderData::FolderData(SystemData* system, std::string path, std::string name) + : mSystem(system), mPath(path), mName(name) { - mSystem = system; - mPath = path; - mName = name; + //first created folder data initializes the list + if (sortStateNameMap.empty()) { + sortStateNameMap[compareFileName] = "file name"; + sortStateNameMap[compareRating] = "rating"; + sortStateNameMap[compareUserRating] = "user rating"; + sortStateNameMap[compareTimesPlayed] = "times played"; + sortStateNameMap[compareLastPlayed] = "last time played"; + } } FolderData::~FolderData() @@ -31,8 +41,24 @@ void FolderData::pushFileData(FileData* file) mFileVector.push_back(file); } +//sort this folder and any subfolders +void FolderData::sort(ComparisonFunction & comparisonFunction, bool ascending) +{ + std::sort(mFileVector.begin(), mFileVector.end(), comparisonFunction); + + for(unsigned int i = 0; i < mFileVector.size(); i++) + { + if(mFileVector.at(i)->isFolder()) + ((FolderData*)mFileVector.at(i))->sort(comparisonFunction, ascending); + } + + if (!ascending) { + std::reverse(mFileVector.begin(), mFileVector.end()); + } +} + //returns if file1 should come before file2 -bool filesort(FileData* file1, FileData* file2) +bool FolderData::compareFileName(const FileData* file1, const FileData* file2) { std::string name1 = file1->getName(); std::string name2 = file2->getName(); @@ -43,29 +69,118 @@ bool filesort(FileData* file1, FileData* file2) { if(toupper(name1[i]) != toupper(name2[i])) { - if(toupper(name1[i]) < toupper(name2[i])) - { - return true; - }else{ - return false; - } + return toupper(name1[i]) < toupper(name2[i]); } } - if(name1.length() < name2.length()) - return true; - else - return false; + return name1.length() < name2.length(); } -//sort this folder and any subfolders -void FolderData::sort() +bool FolderData::compareRating(const FileData* file1, const FileData* file2) { - std::sort(mFileVector.begin(), mFileVector.end(), filesort); - - for(unsigned int i = 0; i < mFileVector.size(); i++) - { - if(mFileVector.at(i)->isFolder()) - ((FolderData*)mFileVector.at(i))->sort(); + //we need game data. try to cast + const GameData * game1 = dynamic_cast(file1); + const GameData * game2 = dynamic_cast(file2); + if (game1 != nullptr && game2 != nullptr) { + return game1->getRating() < game2->getRating(); } + return false; } + +bool FolderData::compareUserRating(const FileData* file1, const FileData* file2) +{ + //we need game data. try to cast + const GameData * game1 = dynamic_cast(file1); + const GameData * game2 = dynamic_cast(file2); + if (game1 != nullptr && game2 != nullptr) { + return game1->getUserRating() < game2->getUserRating(); + } + return false; +} + +bool FolderData::compareTimesPlayed(const FileData* file1, const FileData* file2) +{ + //we need game data. try to cast + const GameData * game1 = dynamic_cast(file1); + const GameData * game2 = dynamic_cast(file2); + if (game1 != nullptr && game2 != nullptr) { + return game1->getTimesPlayed() < game2->getTimesPlayed(); + } + return false; +} + +bool FolderData::compareLastPlayed(const FileData* file1, const FileData* file2) +{ + //we need game data. try to cast + const GameData * game1 = dynamic_cast(file1); + const GameData * game2 = dynamic_cast(file2); + if (game1 != nullptr && game2 != nullptr) { + return game1->getLastPlayed() < game2->getLastPlayed(); + } + return false; +} + +std::string FolderData::getSortStateName(ComparisonFunction & comparisonFunction, bool ascending) +{ + std::string temp = sortStateNameMap[comparisonFunction]; + if (ascending) { + temp.append(" (ascending)"); + } + else { + temp.append(" (descending)"); + } + return temp; +} + +FileData* FolderData::getFile(unsigned int i) const +{ + return mFileVector.at(i); +} + +std::vector FolderData::getFiles(bool onlyFiles) const +{ + std::vector temp; + //now check if a child is a folder and get those children in turn + std::vector::const_iterator fdit = mFileVector.cbegin(); + while(fdit != mFileVector.cend()) { + //dynamically try to cast to FolderData type + FolderData * folder = dynamic_cast(*fdit); + if (folder != nullptr) { + //add this only when user wanted it + if (!onlyFiles) { + temp.push_back(*fdit); + } + } + else { + temp.push_back(*fdit); + } + ++fdit; + } + return temp; +} + +std::vector FolderData::getFilesRecursive(bool onlyFiles) const +{ + std::vector temp; + //now check if a child is a folder and get those children in turn + std::vector::const_iterator fdit = mFileVector.cbegin(); + while(fdit != mFileVector.cend()) { + //dynamically try to cast to FolderData type + FolderData * folder = dynamic_cast(*fdit); + if (folder != nullptr) { + //add this onyl when user wanted it + if (!onlyFiles) { + temp.push_back(*fdit); + } + //worked. Is actual folder data. recurse + std::vector children = folder->getFilesRecursive(onlyFiles); + //insert children into return vector + temp.insert(temp.end(), children.cbegin(), children.cend()); + } + else { + temp.push_back(*fdit); + } + ++fdit; + } + return temp; +} \ No newline at end of file diff --git a/src/FolderData.h b/src/FolderData.h index e453ac5e7..ecbd844fb 100644 --- a/src/FolderData.h +++ b/src/FolderData.h @@ -1,28 +1,54 @@ #ifndef _FOLDER_H_ #define _FOLDER_H_ -#include "FileData.h" +#include #include +#include "FileData.h" + + class SystemData; //This class lets us hold a vector of FileDatas within under a common name. class FolderData : public FileData { +public: + typedef bool ComparisonFunction(const FileData* a, const FileData* b); + struct SortState + { + ComparisonFunction & comparisonFunction; + bool ascending; + std::string description; + + SortState(ComparisonFunction & sortFunction, bool sortAscending, const std::string & sortDescription) : comparisonFunction(sortFunction), ascending(sortAscending), description(sortDescription) {} + }; + +private: + static std::map sortStateNameMap; + public: FolderData(SystemData* system, std::string path, std::string name); ~FolderData(); - bool isFolder(); - std::string getName(); - std::string getPath(); + bool isFolder() const; + const std::string & getName() const; + const std::string & getPath() const; unsigned int getFileCount(); - FileData* getFile(unsigned int i); + FileData* getFile(unsigned int i) const; + std::vector getFiles(bool onlyFiles = false) const; + std::vector getFilesRecursive(bool onlyFiles = false) const; void pushFileData(FileData* file); - void sort(); + void sort(ComparisonFunction & comparisonFunction = compareFileName, bool ascending = true); + static bool compareFileName(const FileData* file1, const FileData* file2); + static bool compareRating(const FileData* file1, const FileData* file2); + static bool compareUserRating(const FileData* file1, const FileData* file2); + static bool compareTimesPlayed(const FileData* file1, const FileData* file2); + static bool compareLastPlayed(const FileData* file1, const FileData* file2); + static std::string getSortStateName(ComparisonFunction & comparisonFunction = compareFileName, bool ascending = true); + private: SystemData* mSystem; std::string mPath; diff --git a/src/GameData.cpp b/src/GameData.cpp index bb656be01..ef8384dfd 100644 --- a/src/GameData.cpp +++ b/src/GameData.cpp @@ -2,23 +2,110 @@ #include #include -bool GameData::isFolder() { return false; } -std::string GameData::getName() { return mName; } -std::string GameData::getPath() { return mPath; } -std::string GameData::getDescription() { return mDescription; } -std::string GameData::getImagePath() { return mImagePath; } + +const std::string GameData::xmlTagGameList = "gameList"; +const std::string GameData::xmlTagGame = "game"; +const std::string GameData::xmlTagName = "name"; +const std::string GameData::xmlTagPath = "path"; +const std::string GameData::xmlTagDescription = "desc"; +const std::string GameData::xmlTagImagePath = "image"; +const std::string GameData::xmlTagRating = "rating"; +const std::string GameData::xmlTagUserRating = "userrating"; +const std::string GameData::xmlTagTimesPlayed = "timesplayed"; +const std::string GameData::xmlTagLastPlayed = "lastplayed"; + GameData::GameData(SystemData* system, std::string path, std::string name) + : mSystem(system), mPath(path), mName(name), mRating(0.0f), mUserRating(0.0f), mTimesPlayed(0), mLastPlayed(0) { - mSystem = system; - mPath = path; - mName = name; - - mDescription = ""; - mImagePath = ""; } -std::string GameData::getBashPath() +bool GameData::isFolder() const +{ + return false; +} + +const std::string & GameData::getName() const +{ + return mName; +} + +void GameData::setName(const std::string & name) +{ + mName = name; +} + +const std::string & GameData::getPath() const +{ + return mPath; +} + +void GameData::setPath(const std::string & path) +{ + mPath = path; +} + +const std::string & GameData::getDescription() const +{ + return mDescription; +} + +void GameData::setDescription(const std::string & description) +{ + mDescription = description; +} + +const std::string & GameData::getImagePath() const +{ + return mImagePath; +} + +void GameData::setImagePath(const std::string & imagePath) +{ + mImagePath = imagePath; +} + +float GameData::getRating() const +{ + return mRating; +} + +void GameData::setRating(float rating) +{ + mRating = rating; +} + +float GameData::getUserRating() const +{ + return mUserRating; +} + +void GameData::setUserRating(float rating) +{ + mUserRating = rating; +} + +size_t GameData::getTimesPlayed() const +{ + return mTimesPlayed; +} + +void GameData::setTimesPlayed(size_t timesPlayed) +{ + mTimesPlayed = timesPlayed; +} + +std::time_t GameData::getLastPlayed() const +{ + return mLastPlayed; +} + +void GameData::setLastPlayed(std::time_t lastPlayed) +{ + mLastPlayed = lastPlayed; +} + +std::string GameData::getBashPath() const { //a quick and dirty way to insert a backslash before most characters that would mess up a bash path std::string path = mPath; @@ -44,18 +131,8 @@ std::string GameData::getBashPath() } //returns the boost::filesystem stem of our path - e.g. for "/foo/bar.rom" returns "bar" -std::string GameData::getBaseName() +std::string GameData::getBaseName() const { boost::filesystem::path path(mPath); return path.stem().string(); } - -void GameData::set(std::string name, std::string description, std::string imagePath) -{ - if(!name.empty()) - mName = name; - if(!description.empty()) - mDescription = description; - if(!imagePath.empty() && boost::filesystem::exists(imagePath)) - mImagePath = imagePath; -} diff --git a/src/GameData.h b/src/GameData.h index ad0db2038..ebfca5e69 100644 --- a/src/GameData.h +++ b/src/GameData.h @@ -2,6 +2,8 @@ #define _GAMEDATA_H_ #include +#include + #include "FileData.h" #include "SystemData.h" @@ -9,19 +11,49 @@ class GameData : public FileData { public: + //static tag names for reading/writing XML documents. This might fail in PUGIXML_WCHAR_MODE + //TODO: The class should have member to read fromXML() and write toXML() probably... + static const std::string xmlTagGameList; + static const std::string xmlTagGame; + static const std::string xmlTagName; + static const std::string xmlTagPath; + static const std::string xmlTagDescription; + static const std::string xmlTagImagePath; + static const std::string xmlTagRating; + static const std::string xmlTagUserRating; + static const std::string xmlTagTimesPlayed; + static const std::string xmlTagLastPlayed; + GameData(SystemData* system, std::string path, std::string name); - void set(std::string name = "", std::string description = "", std::string imagePath = ""); + const std::string & getName() const; + void setName(const std::string & name); - std::string getName(); - std::string getPath(); - std::string getBashPath(); - std::string getBaseName(); + const std::string & getPath() const; + void setPath(const std::string & path); - std::string getDescription(); - std::string getImagePath(); + const std::string & getDescription() const; + void setDescription(const std::string & description); - bool isFolder(); + const std::string & getImagePath() const; + void setImagePath(const std::string & imagePath); + + float getRating() const; + void setRating(float rating); + + float getUserRating() const; + void setUserRating(float rating); + + size_t getTimesPlayed() const; + void setTimesPlayed(size_t timesPlayed); + + std::time_t getLastPlayed() const; + void setLastPlayed(std::time_t lastPlayed); + + std::string getBashPath() const; + std::string getBaseName() const; + + bool isFolder() const; private: SystemData* mSystem; std::string mPath; @@ -30,6 +62,10 @@ private: //extra data std::string mDescription; std::string mImagePath; + float mRating; + float mUserRating; + size_t mTimesPlayed; + std::time_t mLastPlayed; }; #endif diff --git a/src/GuiComponent.cpp b/src/GuiComponent.cpp index 88830b9d0..3b3057764 100644 --- a/src/GuiComponent.cpp +++ b/src/GuiComponent.cpp @@ -102,6 +102,19 @@ Vector2u GuiComponent::getSize() return mSize; } +void GuiComponent::setSize(Vector2u size) +{ + mSize = size; + onSizeChanged(); +} + +void GuiComponent::setSize(unsigned int w, unsigned int h) +{ + mSize.x = w; + mSize.y = h; + onSizeChanged(); +} + //Children stuff. void GuiComponent::addChild(GuiComponent* cmp) { @@ -166,4 +179,4 @@ unsigned char GuiComponent::getOpacity() void GuiComponent::setOpacity(unsigned char opacity) { mOpacity = opacity; -} \ No newline at end of file +} diff --git a/src/GuiComponent.h b/src/GuiComponent.h index 6e09b5383..fe441b171 100644 --- a/src/GuiComponent.h +++ b/src/GuiComponent.h @@ -36,6 +36,9 @@ public: virtual void onOffsetChanged() {}; Vector2u getSize(); + void setSize(Vector2u size); + void setSize(unsigned int w, unsigned int h); + virtual void onSizeChanged() {}; void setParent(GuiComponent* parent); GuiComponent* getParent(); diff --git a/src/InputManager.cpp b/src/InputManager.cpp index 168787a9f..fb76e48b1 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -423,6 +423,9 @@ void InputManager::loadDefaultConfig() cfg->mapInput("mastervolup", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_PLUS, 1, true)); cfg->mapInput("mastervoldown", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_MINUS, 1, true)); + + cfg->mapInput("sortordernext", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F7, 1, true)); + cfg->mapInput("sortorderprevious", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F8, 1, true)); } void InputManager::writeConfig() diff --git a/src/Settings.cpp b/src/Settings.cpp index 52111668d..8319a3b8c 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -33,6 +33,8 @@ void Settings::setDefaults() mBoolMap["WINDOWED"] = false; mIntMap["DIMTIME"] = 30*1000; + + mIntMap["GameListSortIndex"] = 0; } template diff --git a/src/SystemData.cpp b/src/SystemData.cpp index 2e983e5e4..041b47716 100644 --- a/src/SystemData.cpp +++ b/src/SystemData.cpp @@ -50,6 +50,10 @@ SystemData::SystemData(std::string name, std::string descName, std::string start SystemData::~SystemData() { + //save changed game data back to xml + if(!Settings::getInstance()->getBool("IGNOREGAMELIST")) { + updateGamelist(this); + } delete mRootFolder; } @@ -90,6 +94,10 @@ void SystemData::launchGame(Window* window, GameData* game) window->init(); VolumeControl::getInstance()->init(); AudioManager::getInstance()->init(); + + //update number of times the game has been launched and the time + game->setTimesPlayed(game->getTimesPlayed() + 1); + game->setLastPlayed(std::time(nullptr)); } void SystemData::populateFolder(FolderData* folder) diff --git a/src/XMLReader.cpp b/src/XMLReader.cpp index d1e19b8ae..d8a8f0eea 100644 --- a/src/XMLReader.cpp +++ b/src/XMLReader.cpp @@ -117,19 +117,19 @@ void parseGamelist(SystemData* system) return; } - pugi::xml_node root = doc.child("gameList"); + pugi::xml_node root = doc.child(GameData::xmlTagGameList.c_str()); if(!root) { - LOG(LogError) << "Could not find node in gamelist \"" << xmlpath << "\"!"; + LOG(LogError) << "Could not find <" << GameData::xmlTagGameList << "> node in gamelist \"" << xmlpath << "\"!"; return; } - for(pugi::xml_node gameNode = root.child("game"); gameNode; gameNode = gameNode.next_sibling("game")) + for(pugi::xml_node gameNode = root.child(GameData::xmlTagGame.c_str()); gameNode; gameNode = gameNode.next_sibling(GameData::xmlTagGame.c_str())) { - pugi::xml_node pathNode = gameNode.child("path"); + pugi::xml_node pathNode = gameNode.child(GameData::xmlTagPath.c_str()); if(!pathNode) { - LOG(LogError) << " node contains no child!"; + LOG(LogError) << "<" << GameData::xmlTagGame << "> node contains no <" << GameData::xmlTagPath << "> child!"; continue; } @@ -152,13 +152,17 @@ void parseGamelist(SystemData* system) //actually gather the information in the XML doc, then pass it to the game's set method std::string newName, newDesc, newImage; - if(gameNode.child("name")) - newName = gameNode.child("name").text().get(); - if(gameNode.child("desc")) - newDesc = gameNode.child("desc").text().get(); - if(gameNode.child("image")) + if(gameNode.child(GameData::xmlTagName.c_str())) { - newImage = gameNode.child("image").text().get(); + game->setName(gameNode.child(GameData::xmlTagName.c_str()).text().get()); + } + if(gameNode.child(GameData::xmlTagDescription.c_str())) + { + game->setDescription(gameNode.child(GameData::xmlTagDescription.c_str()).text().get()); + } + if(gameNode.child(GameData::xmlTagImagePath.c_str())) + { + newImage = gameNode.child(GameData::xmlTagImagePath.c_str()).text().get(); //expand "." if(newImage[0] == '.') @@ -168,15 +172,148 @@ void parseGamelist(SystemData* system) newImage.insert(0, pathname.parent_path().string() ); } - //if the image doesn't exist, forget it - if(!boost::filesystem::exists(newImage)) - newImage = ""; + //if the image exist, set it + if(boost::filesystem::exists(newImage)) + { + game->setImagePath(newImage); + } } - game->set(newName, newDesc, newImage); - - }else{ + //get rating and the times played from the XML doc + if(gameNode.child(GameData::xmlTagRating.c_str())) + { + float rating; + std::istringstream(gameNode.child(GameData::xmlTagRating.c_str()).text().get()) >> rating; + game->setRating(rating); + } + if(gameNode.child(GameData::xmlTagUserRating.c_str())) + { + float userRating; + std::istringstream(gameNode.child(GameData::xmlTagUserRating.c_str()).text().get()) >> userRating; + game->setUserRating(userRating); + } + if(gameNode.child(GameData::xmlTagTimesPlayed.c_str())) + { + size_t timesPlayed; + std::istringstream(gameNode.child(GameData::xmlTagTimesPlayed.c_str()).text().get()) >> timesPlayed; + game->setTimesPlayed(timesPlayed); + } + if(gameNode.child(GameData::xmlTagLastPlayed.c_str())) + { + std::time_t lastPlayed; + std::istringstream(gameNode.child(GameData::xmlTagLastPlayed.c_str()).text().get()) >> lastPlayed; + game->setLastPlayed(lastPlayed); + } + } + else{ LOG(LogWarning) << "Game at \"" << path << "\" does not exist!"; } } } + +void addGameDataNode(pugi::xml_node & parent, const GameData * game) +{ + //create game and add to parent node + pugi::xml_node newGame = parent.append_child(GameData::xmlTagGame.c_str()); + //add values + if (!game->getPath().empty()) { + pugi::xml_node pathNode = newGame.append_child(GameData::xmlTagPath.c_str()); + pathNode.text().set(game->getPath().c_str()); + } + if (!game->getName().empty()) { + pugi::xml_node nameNode = newGame.append_child(GameData::xmlTagName.c_str()); + nameNode.text().set(game->getName().c_str()); + } + if (!game->getDescription().empty()) { + pugi::xml_node descriptionNode = newGame.append_child(GameData::xmlTagDescription.c_str()); + descriptionNode.text().set(game->getDescription().c_str()); + } + if (!game->getImagePath().empty()) { + pugi::xml_node imagePathNode = newGame.append_child(GameData::xmlTagImagePath.c_str()); + imagePathNode.text().set(game->getImagePath().c_str()); + } + //all other values are added regardless of their value + pugi::xml_node ratingNode = newGame.append_child(GameData::xmlTagRating.c_str()); + ratingNode.text().set(std::to_string((long double)game->getRating()).c_str()); + + pugi::xml_node userRatingNode = newGame.append_child(GameData::xmlTagUserRating.c_str()); + userRatingNode.text().set(std::to_string((long double)game->getUserRating()).c_str()); + + pugi::xml_node timesPlayedNode = newGame.append_child(GameData::xmlTagTimesPlayed.c_str()); + timesPlayedNode.text().set(std::to_string((unsigned long long)game->getTimesPlayed()).c_str()); + + pugi::xml_node lastPlayedNode = newGame.append_child(GameData::xmlTagLastPlayed.c_str()); + lastPlayedNode.text().set(std::to_string((unsigned long long)game->getLastPlayed()).c_str()); +} + +void updateGamelist(SystemData* system) +{ + //We do this by reading the XML again, adding changes and then writing it back, + //because there might be information missing in our systemdata which would then miss in the new XML. + //We have the complete information for every game though, so we can simply remove a game + //we already have in the system from the XML, and then add it back from its GameData information... + + std::string xmlpath = system->getGamelistPath(); + if(xmlpath.empty()) { + return; + } + + LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\" before writing..."; + + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(xmlpath.c_str()); + + if(!result) { + LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n " << result.description(); + return; + } + + pugi::xml_node root = doc.child(GameData::xmlTagGameList.c_str()); + if(!root) { + LOG(LogError) << "Could not find <" << GameData::xmlTagGameList << "> node in gamelist \"" << xmlpath << "\"!"; + return; + } + + //now we have all the information from the XML. now iterate through all our games and add information from there + FolderData * rootFolder = system->getRootFolder(); + if (rootFolder != nullptr) { + //get only files, no folders + std::vector files = rootFolder->getFilesRecursive(true); + //iterate through all files, checking if they're already in the XML + std::vector::const_iterator fit = files.cbegin(); + while(fit != files.cend()) { + //try to cast to gamedata + const GameData * game = dynamic_cast(*fit); + if (game != nullptr) { + //worked. check if this games' path can be found somewhere in the XML + for(pugi::xml_node gameNode = root.child(GameData::xmlTagGame.c_str()); gameNode; gameNode = gameNode.next_sibling(GameData::xmlTagGame.c_str())) { + //get path from game node + pugi::xml_node pathNode = gameNode.child(GameData::xmlTagPath.c_str()); + if(!pathNode) + { + LOG(LogError) << "<" << GameData::xmlTagGame << "> node contains no <" << GameData::xmlTagPath << "> child!"; + continue; + } + //check path + if (pathNode.text().get() == game->getPath()) { + //found the game. remove it. it will be added again later with updated values + root.remove_child(gameNode); + //break node search loop + break; + } + } + //either the game content was removed, because it needs to be updated, + //or didn't exist in the first place, so just add it + addGameDataNode(root, game); + } + ++fit; + } + //now write the file + if (!doc.save_file(xmlpath.c_str())) { + LOG(LogError) << "Error saving XML file \"" << xmlpath << "\"!"; + } + } + else { + LOG(LogError) << "Found no root folder for system \"" << system->getName() << "\"!"; + } +} diff --git a/src/XMLReader.h b/src/XMLReader.h index 403c96fcf..ea3b4399b 100644 --- a/src/XMLReader.h +++ b/src/XMLReader.h @@ -7,4 +7,7 @@ class SystemData; //Loads gamelist.xml data into a SystemData. void parseGamelist(SystemData* system); +//Writes changes to SystemData back to a previously loaded gamelist.xml. +void updateGamelist(SystemData* system); + #endif diff --git a/src/components/GuiFastSelect.cpp b/src/components/GuiFastSelect.cpp index 008f4b972..fee1ba337 100644 --- a/src/components/GuiFastSelect.cpp +++ b/src/components/GuiFastSelect.cpp @@ -7,17 +7,15 @@ const std::string GuiFastSelect::LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const int GuiFastSelect::SCROLLSPEED = 100; const int GuiFastSelect::SCROLLDELAY = 507; -GuiFastSelect::GuiFastSelect(Window* window, GuiGameList* parent, TextListComponent* list, char startLetter, GuiBoxData data, - int textcolor, std::shared_ptr & scrollsound, Font* font) : GuiComponent(window) +GuiFastSelect::GuiFastSelect(Window* window, GuiGameList* parent, TextListComponent* list, char startLetter, ThemeComponent * theme) + : GuiComponent(window), mParent(parent), mList(list), mTheme(theme) { mLetterID = LETTERS.find(toupper(startLetter)); if(mLetterID == std::string::npos) mLetterID = 0; - mParent = parent; - mList = list; - mScrollSound = scrollsound; - mFont = font; + mScrollSound = mTheme->getSound("menuScroll"); + mTextColor = mTheme->getColor("fastSelect"); mScrolling = false; mScrollTimer = 0; @@ -25,9 +23,7 @@ GuiFastSelect::GuiFastSelect(Window* window, GuiGameList* parent, TextListCompon unsigned int sw = Renderer::getScreenWidth(), sh = Renderer::getScreenHeight(); mBox = new GuiBox(window, (int)(sw * 0.2f), (int)(sh * 0.2f), (int)(sw * 0.6f), (int)(sh * 0.6f)); - mBox->setData(data); - - mTextColor = textcolor; + mBox->setData(mTheme->getBoxData()); } GuiFastSelect::~GuiFastSelect() @@ -41,11 +37,14 @@ void GuiFastSelect::render() unsigned int sw = Renderer::getScreenWidth(), sh = Renderer::getScreenHeight(); if(!mBox->hasBackground()) - Renderer::drawRect((int)(sw * 0.2f), (int)(sh * 0.2f), (int)(sw * 0.6f), (int)(sh * 0.6f), 0x000FF0FF); + Renderer::drawRect((int)(sw * 0.3f), (int)(sh * 0.3f), (int)(sw * 0.4f), (int)(sh * 0.4f), 0x000FF0AA); mBox->render(); - Renderer::drawCenteredText(LETTERS.substr(mLetterID, 1), 0, (int)(sh * 0.5f - (mFont->getHeight() * 0.5f)), mTextColor, mFont); + Renderer::drawCenteredText(LETTERS.substr(mLetterID, 1), 0, (int)(sh * 0.5f - (mTheme->getFastSelectFont()->getHeight() * 0.5f)), mTextColor, mTheme->getFastSelectFont()); + Renderer::drawCenteredText("Sort order:", 0, (int)(sh * 0.6f - (mTheme->getDescriptionFont()->getHeight() * 0.5f)), mTextColor, mTheme->getDescriptionFont()); + std::string sortString = "<- " + mParent->getSortState().description + " ->"; + Renderer::drawCenteredText(sortString, 0, (int)(sh * 0.6f + (mTheme->getDescriptionFont()->getHeight() * 0.5f)), mTextColor, mTheme->getDescriptionFont()); } bool GuiFastSelect::input(InputConfig* config, Input input) @@ -64,6 +63,19 @@ bool GuiFastSelect::input(InputConfig* config, Input input) return true; } + if(config->isMappedTo("left", input) && input.value != 0) + { + mParent->setPreviousSortIndex(); + mScrollSound->play(); + return true; + } + else if(config->isMappedTo("right", input) && input.value != 0) + { + mParent->setNextSortIndex(); + mScrollSound->play(); + return true; + } + if((config->isMappedTo("up", input) || config->isMappedTo("down", input)) && input.value == 0) { mScrolling = false; diff --git a/src/components/GuiFastSelect.h b/src/components/GuiFastSelect.h index 67032e1f1..81ae2ec4d 100644 --- a/src/components/GuiFastSelect.h +++ b/src/components/GuiFastSelect.h @@ -5,6 +5,7 @@ #include "../SystemData.h" #include "../FolderData.h" #include "../Sound.h" +#include "ThemeComponent.h" #include "TextListComponent.h" #include "GuiBox.h" @@ -13,8 +14,7 @@ class GuiGameList; class GuiFastSelect : public GuiComponent { public: - GuiFastSelect(Window* window, GuiGameList* parent, TextListComponent* list, char startLetter, GuiBoxData data, - int textcolor, std::shared_ptr & scrollsound, Font* font); + GuiFastSelect(Window* window, GuiGameList* parent, TextListComponent* list, char startLetter, ThemeComponent * theme); ~GuiFastSelect(); bool input(InputConfig* config, Input input); @@ -41,7 +41,7 @@ private: bool mScrolling; std::shared_ptr mScrollSound; - Font* mFont; + ThemeComponent * mTheme; }; #endif diff --git a/src/components/GuiGameList.cpp b/src/components/GuiGameList.cpp index f2f8875dd..c354a0478 100644 --- a/src/components/GuiGameList.cpp +++ b/src/components/GuiGameList.cpp @@ -7,6 +7,10 @@ #include "../Log.h" #include "../Settings.h" + +std::vector GuiGameList::sortStates; + + Vector2i GuiGameList::getImagePos() { return Vector2i((int)(Renderer::getScreenWidth() * mTheme->getFloat("gameImageOffsetX")), (int)(Renderer::getScreenHeight() * mTheme->getFloat("gameImageOffsetY"))); @@ -26,8 +30,23 @@ GuiGameList::GuiGameList(Window* window) : GuiComponent(window), mScreenshot(window), mDescription(window), mDescContainer(window), - mTransitionImage(window, 0, 0, "", Renderer::getScreenWidth(), Renderer::getScreenHeight(), true) + mTransitionImage(window, 0, 0, "", Renderer::getScreenWidth(), Renderer::getScreenHeight(), true), + sortStateIndex(Settings::getInstance()->getInt("GameListSortIndex")) { + //first object initializes the vector + if (sortStates.empty()) { + sortStates.push_back(FolderData::SortState(FolderData::compareFileName, true, "file name, ascending")); + sortStates.push_back(FolderData::SortState(FolderData::compareFileName, false, "file name, descending")); + sortStates.push_back(FolderData::SortState(FolderData::compareRating, true, "database rating, ascending")); + sortStates.push_back(FolderData::SortState(FolderData::compareRating, false, "database rating, descending")); + sortStates.push_back(FolderData::SortState(FolderData::compareUserRating, true, "your rating, ascending")); + sortStates.push_back(FolderData::SortState(FolderData::compareUserRating, false, "your rating, descending")); + sortStates.push_back(FolderData::SortState(FolderData::compareTimesPlayed, true, "played least often")); + sortStates.push_back(FolderData::SortState(FolderData::compareTimesPlayed, false, "played most often")); + sortStates.push_back(FolderData::SortState(FolderData::compareLastPlayed, true, "played least recently")); + sortStates.push_back(FolderData::SortState(FolderData::compareLastPlayed, false, "played most recently")); + } + mImageAnimation.addChild(&mScreenshot); mDescContainer.addChild(&mDescription); @@ -175,6 +194,16 @@ bool GuiGameList::input(InputConfig* config, Input input) } } + //change sort order + if(config->isMappedTo("sortordernext", input) && input.value != 0) { + setNextSortIndex(); + //std::cout << "Sort order is " << FolderData::getSortStateName(sortStates.at(sortStateIndex).comparisonFunction, sortStates.at(sortStateIndex).ascending) << std::endl; + } + else if(config->isMappedTo("sortorderprevious", input) && input.value != 0) { + setPreviousSortIndex(); + //std::cout << "Sort order is " << FolderData::getSortStateName(sortStates.at(sortStateIndex).comparisonFunction, sortStates.at(sortStateIndex).ascending) << std::endl; + } + //open the "start menu" if(config->isMappedTo("menu", input) && input.value != 0) { @@ -185,7 +214,7 @@ bool GuiGameList::input(InputConfig* config, Input input) //open the fast select menu if(config->isMappedTo("select", input) && input.value != 0) { - mWindow->pushGui(new GuiFastSelect(mWindow, this, &mList, mList.getSelectedObject()->getName()[0], mTheme->getBoxData(), mTheme->getColor("fastSelect"), mTheme->getSound("menuScroll"), mTheme->getFastSelectFont())); + mWindow->pushGui(new GuiFastSelect(mWindow, this, &mList, mList.getSelectedObject()->getName()[0], mTheme)); return true; } @@ -204,6 +233,52 @@ bool GuiGameList::input(InputConfig* config, Input input) return false; } +const FolderData::SortState & GuiGameList::getSortState() const +{ + return sortStates.at(sortStateIndex); +} + +void GuiGameList::setSortIndex(size_t index) +{ + //make the index valid + if (index >= sortStates.size()) { + index = 0; + } + if (index != sortStateIndex) { + //get sort state from vector and sort list + sortStateIndex = index; + sort(sortStates.at(sortStateIndex).comparisonFunction, sortStates.at(sortStateIndex).ascending); + } + //save new index to settings + Settings::getInstance()->setInt("GameListSortIndex", sortStateIndex); +} + +void GuiGameList::setNextSortIndex() +{ + //make the index wrap around + if ((sortStateIndex - 1) >= sortStates.size()) { + setSortIndex(0); + } + setSortIndex(sortStateIndex + 1); +} + +void GuiGameList::setPreviousSortIndex() +{ + //make the index wrap around + if (((int)sortStateIndex - 1) < 0) { + setSortIndex(sortStates.size() - 1); + } + setSortIndex(sortStateIndex - 1); +} + +void GuiGameList::sort(FolderData::ComparisonFunction & comparisonFunction, bool ascending) +{ + //resort list and update it + mFolder->sort(comparisonFunction, ascending); + updateList(); + updateDetailData(); +} + void GuiGameList::updateList() { mList.clear(); diff --git a/src/components/GuiGameList.h b/src/components/GuiGameList.h index 610900ff8..7b2b38660 100644 --- a/src/components/GuiGameList.h +++ b/src/components/GuiGameList.h @@ -18,6 +18,9 @@ //It has a TextListComponent child that handles the game list, a ThemeComponent that handles the theming system, and an ImageComponent for game images. class GuiGameList : public GuiComponent { + static std::vector sortStates; + size_t sortStateIndex; + public: GuiGameList(Window* window); virtual ~GuiGameList(); @@ -33,6 +36,12 @@ public: void updateDetailData(); + const FolderData::SortState & getSortState() const; + void setSortIndex(size_t index); + void setNextSortIndex(); + void setPreviousSortIndex(); + void sort(FolderData::ComparisonFunction & comparisonFunction = FolderData::compareFileName, bool ascending = true); + static GuiGameList* create(Window* window); bool isDetailed() const;