diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e9aad13c..9d15d8510 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,6 +127,7 @@ set(ES_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/InputManager.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Log.h ${CMAKE_CURRENT_SOURCE_DIR}/src/MathExp.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.h ${CMAKE_CURRENT_SOURCE_DIR}/src/platform.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Renderer.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Settings.h @@ -169,6 +170,7 @@ set(ES_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/Log.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/MathExp.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/platform.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Renderer_draw_gl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Renderer_init.cpp diff --git a/src/FileData.h b/src/FileData.h index ebb44b9b9..de927a081 100644 --- a/src/FileData.h +++ b/src/FileData.h @@ -10,8 +10,8 @@ class FileData public: virtual ~FileData() { }; virtual bool isFolder() const = 0; - virtual const std::string & getName() const = 0; - virtual const std::string & getPath() 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 ac9e08477..451ffbc33 100644 --- a/src/FolderData.cpp +++ b/src/FolderData.cpp @@ -82,7 +82,7 @@ bool FolderData::compareRating(const FileData* file1, const FileData* file2) const GameData * game1 = dynamic_cast(file1); const GameData * game2 = dynamic_cast(file2); if (game1 != nullptr && game2 != nullptr) { - return game1->getRating() < game2->getRating(); + return const_cast(game1)->metadata()->getFloat("rating") < const_cast(game2)->metadata()->getFloat("rating"); } return false; } @@ -93,7 +93,7 @@ bool FolderData::compareUserRating(const FileData* file1, const FileData* file2) const GameData * game1 = dynamic_cast(file1); const GameData * game2 = dynamic_cast(file2); if (game1 != nullptr && game2 != nullptr) { - return game1->getUserRating() < game2->getUserRating(); + return const_cast(game1)->metadata()->getFloat("userrating") < const_cast(game2)->metadata()->getFloat("userrating"); } return false; } @@ -104,7 +104,7 @@ bool FolderData::compareTimesPlayed(const FileData* file1, const FileData* file2 const GameData * game1 = dynamic_cast(file1); const GameData * game2 = dynamic_cast(file2); if (game1 != nullptr && game2 != nullptr) { - return game1->getTimesPlayed() < game2->getTimesPlayed(); + return const_cast(game1)->metadata()->getInt("playcount") < const_cast(game2)->metadata()->getInt("playcount"); } return false; } @@ -115,7 +115,7 @@ bool FolderData::compareLastPlayed(const FileData* file1, const FileData* file2) const GameData * game1 = dynamic_cast(file1); const GameData * game2 = dynamic_cast(file2); if (game1 != nullptr && game2 != nullptr) { - return game1->getLastPlayed() < game2->getLastPlayed(); + return const_cast(game1)->metadata()->getTime("lastplayed") < const_cast(game2)->metadata()->getTime("lastplayed"); } return false; } @@ -183,4 +183,4 @@ std::vector FolderData::getFilesRecursive(bool onlyFiles) const ++fdit; } return temp; -} \ No newline at end of file +} diff --git a/src/GameData.cpp b/src/GameData.cpp index ef8384dfd..590fedd89 100644 --- a/src/GameData.cpp +++ b/src/GameData.cpp @@ -1,23 +1,14 @@ #include "GameData.h" #include #include +#include +#include - -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) +GameData::GameData(SystemData* system, std::string path) + : mSystem(system), mPath(path), mBaseName(boost::filesystem::path(path).stem().string()), mMetaData(MetaDataList::getDefaultGameMDD()) { + if(mMetaData.get("name").empty()) + mMetaData.set("name", mBaseName); } bool GameData::isFolder() const @@ -25,90 +16,20 @@ bool GameData::isFolder() const return false; } -const std::string & GameData::getName() const +const std::string& GameData::getName() const { - return mName; + return mMetaData.get("name"); } -void GameData::setName(const std::string & name) -{ - mName = name; -} - -const std::string & GameData::getPath() const +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; + std::string path = getPath(); const char* invalidChars = " '\"\\!$^&*(){}[]?;<>"; for(unsigned int i = 0; i < path.length(); i++) @@ -133,6 +54,26 @@ std::string GameData::getBashPath() const //returns the boost::filesystem stem of our path - e.g. for "/foo/bar.rom" returns "bar" std::string GameData::getBaseName() const { - boost::filesystem::path path(mPath); - return path.stem().string(); + return mBaseName; +} + +void GameData::incTimesPlayed() +{ + int timesPlayed = metadata()->getInt("playcount"); + timesPlayed++; + + std::stringstream ss(); + metadata()->set("playcount", std::to_string(static_cast(timesPlayed))); +} + +void GameData::lastPlayedNow() +{ + std::stringstream ss; + ss << std::time(nullptr); + metadata()->set("lastplayed", ss.str()); +} + +MetaDataList* GameData::metadata() +{ + return &mMetaData; } diff --git a/src/GameData.h b/src/GameData.h index ebfca5e69..e67ec2e1d 100644 --- a/src/GameData.h +++ b/src/GameData.h @@ -2,70 +2,44 @@ #define _GAMEDATA_H_ #include -#include #include "FileData.h" #include "SystemData.h" +#include "MetaData.h" //This class holds information about a game: at the least, its name, system, and path. Additional information is optional and read by other classes. 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); - GameData(SystemData* system, std::string path, std::string name); - - const std::string & getName() const; - void setName(const std::string & name); - - const std::string & getPath() const; - void setPath(const std::string & path); - - const std::string & getDescription() const; - void setDescription(const std::string & description); - - 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); + const std::string& getName() const override; + const std::string& getPath() const override; + + void incTimesPlayed(); + void lastPlayedNow(); std::string getBashPath() const; std::string getBaseName() const; - bool isFolder() const; + bool isFolder() const override; + + MetaDataList* metadata(); + private: SystemData* mSystem; - std::string mPath; - std::string mName; + const std::string mPath; + const std::string mBaseName; //extra data - std::string mDescription; + /*std::string mDescription; std::string mImagePath; float mRating; float mUserRating; size_t mTimesPlayed; - std::time_t mLastPlayed; + std::time_t mLastPlayed;*/ + + MetaDataList mMetaData; }; #endif diff --git a/src/GuiComponent.cpp b/src/GuiComponent.cpp index 4eb5d69f5..06f55d287 100644 --- a/src/GuiComponent.cpp +++ b/src/GuiComponent.cpp @@ -157,3 +157,12 @@ const Eigen::Affine3f GuiComponent::getTransform() mTransform.translate(mPosition); return mTransform; } + +void GuiComponent::setValue(const std::string& value) +{ +} + +std::string GuiComponent::getValue() const +{ + return ""; +} diff --git a/src/GuiComponent.h b/src/GuiComponent.h index 23c371ac8..bda2406f1 100644 --- a/src/GuiComponent.h +++ b/src/GuiComponent.h @@ -19,10 +19,10 @@ public: //Called when time passes. Default implementation also calls update(deltaTime) on children - so you should probably call GuiComponent::update(deltaTime) at some point. virtual void update(int deltaTime); - //Called when it's time to render. By default, just calls renderChildren(transform). + //Called when it's time to render. By default, just calls renderChildren(parentTrans * getTransform()). //You probably want to override this like so: //1. Calculate the new transform that your control will draw at with Eigen::Affine3f t = parentTrans * getTransform(). - //2. Set the renderer to use that new transform as the model matrix - Renderer::setModelMatrix(t.data()); + //2. Set the renderer to use that new transform as the model matrix - Renderer::setMatrix(t); //3. Draw your component. //4. Tell your children to render, based on your component's transform - renderChildren(t). virtual void render(const Eigen::Affine3f& parentTrans); @@ -51,6 +51,9 @@ public: const Eigen::Affine3f getTransform(); + virtual std::string getValue() const; + virtual void setValue(const std::string& value); + protected: void renderChildren(const Eigen::Affine3f& transform) const; diff --git a/src/MetaData.cpp b/src/MetaData.cpp new file mode 100644 index 000000000..3f88f83ca --- /dev/null +++ b/src/MetaData.cpp @@ -0,0 +1,126 @@ +#include "MetaData.h" +#include "components/TextComponent.h" +#include "Log.h" + +MetaDataList::MetaDataList() +{ +} + +MetaDataList::MetaDataList(const std::vector& mdd) +{ + for(auto iter = mdd.begin(); iter != mdd.end(); iter++) + set(iter->key, iter->defaultValue); +} + +std::vector MetaDataList::getDefaultGameMDD() +{ + MetaDataDecl decls[] = { + {"name", MD_STRING, ""}, + {"desc", MD_STRING, ""}, + {"image", MD_IMAGE_PATH, ""}, + {"rating", MD_RATING, "0"}, + {"timesplayed", MD_INT, "0"}, + {"playcount", MD_TIME, "0"} + }; + + std::vector mdd(decls, decls + sizeof(decls) / sizeof(decls[0])); + return mdd; +} + +MetaDataList MetaDataList::createFromXML(const std::vector& mdd, pugi::xml_node node) +{ + MetaDataList mdl; + + for(auto iter = mdd.begin(); iter != mdd.end(); iter++) + { + pugi::xml_node md = node.child(iter->key.c_str()); + if(md) + { + mdl.set(iter->key, md.text().get()); + }else{ + mdl.set(iter->key, iter->defaultValue); + } + } + + return mdl; +} + +void MetaDataList::appendToXML(pugi::xml_node parent, const std::vector& ignoreDefaults) const +{ + for(auto iter = mMap.begin(); iter != mMap.end(); iter++) + { + bool write = true; + for(auto mddIter = ignoreDefaults.begin(); mddIter != ignoreDefaults.end(); mddIter++) + { + if(mddIter->key == iter->first) + { + if(iter->second == mddIter->defaultValue) + write = false; + + break; + } + } + + if(write) + parent.append_child(iter->first.c_str()).text().set(iter->second.c_str()); + } +} + +void MetaDataList::set(const std::string& key, const std::string& value) +{ + mMap[key] = value; +} + +const std::string& MetaDataList::get(const std::string& key) const +{ + return mMap.at(key); +} + +int MetaDataList::getInt(const std::string& key) const +{ + return atoi(get(key).c_str()); +} + +float MetaDataList::getFloat(const std::string& key) const +{ + return (float)atof(get(key).c_str()); +} + +std::time_t MetaDataList::getTime(const std::string& key) const +{ + return (std::time_t) atoi(get(key).c_str()); +} + +GuiComponent* MetaDataList::makeDisplay(Window* window, MetaDataType as) +{ + switch(as) + { + default: + TextComponent* comp = new TextComponent(window); + return comp; + } +} + +GuiComponent* MetaDataList::makeEditor(Window* window, MetaDataType as) +{ + switch(as) + { + default: + TextComponent* comp = new TextComponent(window); + return comp; + } +} + +GuiComponent* MetaDataList::makeDisplay(Window* window, MetaDataDecl from) +{ + GuiComponent* comp = makeDisplay(window, from.type); + comp->setValue(get(from.key)); + return comp; +} + +GuiComponent* MetaDataList::makeEditor(Window* window, MetaDataDecl from) +{ + GuiComponent* comp = makeEditor(window, from.type); + comp->setValue(get(from.key)); + return comp; +} diff --git a/src/MetaData.h b/src/MetaData.h new file mode 100644 index 000000000..ccc0218c6 --- /dev/null +++ b/src/MetaData.h @@ -0,0 +1,73 @@ +#pragma once + +#include "pugiXML/pugixml.hpp" +#include +#include +#include "GuiComponent.h" +#include + +enum MetaDataType +{ + //generic types + MD_STRING, + MD_INT, + MD_FLOAT, + + //specialized types + MD_IMAGE_PATH, + MD_RATING, + MD_TIME +}; + +struct MetaDataDecl +{ + std::string key; + MetaDataType type; + std::string defaultValue; +}; + +class MetaDataList +{ +public: + static std::vector getDefaultGameMDD(); + + static MetaDataList createFromXML(const std::vector& mdd, pugi::xml_node node); + + //MetaDataDecl required to set our defaults. + MetaDataList(const std::vector& mdd); + + void set(const std::string& key, const std::string& value); + const std::string& get(const std::string& key) const; + int getInt(const std::string& key) const; + float getFloat(const std::string& key) const; + std::time_t getTime(const std::string& key) const; + + GuiComponent* makeDisplay(Window* window, MetaDataType as); + GuiComponent* makeDisplay(Window* window, MetaDataDecl from); + + GuiComponent* makeEditor(Window* window, MetaDataType as); + GuiComponent* makeEditor(Window* window, MetaDataDecl from); + + void appendToXML(pugi::xml_node parent, const std::vector& ignoreDefaults = std::vector()) const; + +private: + MetaDataList(); + + std::map mMap; +}; + + + +//options for storing metadata... +//store internally everything as a string - this is all going to be read to/from XML anyway, after all +//store using individual get/set functions ala Settings - this is a fair amount of work but the most explicit, for better or worse + +//let's think about some of the special types we would like to support... +//image paths, sound paths, ratings, play counts +//these get represented behind-the-scenes as strings, floats, and integers, and are eventually saved as strings +//the only specialty is how they're edited and viewed, really + +//so we need... +//to be able to iterate through the available metadata +//create components designed to either DISPLAY or EDIT a given piece of metadata +//save and load metadata diff --git a/src/SystemData.cpp b/src/SystemData.cpp index 1a498f82f..ab99eafae 100644 --- a/src/SystemData.cpp +++ b/src/SystemData.cpp @@ -96,8 +96,8 @@ void SystemData::launchGame(Window* window, GameData* game) window->normalizeNextUpdate(); //update number of times the game has been launched and the time - game->setTimesPlayed(game->getTimesPlayed() + 1); - game->setLastPlayed(std::time(nullptr)); + game->incTimesPlayed(); + game->lastPlayedNow(); } void SystemData::populateFolder(FolderData* folder) @@ -145,7 +145,7 @@ void SystemData::populateFolder(FolderData* folder) //if it matches, add it if(chkExt == extension) { - GameData* newGame = new GameData(this, filePath.generic_string(), filePath.stem().string()); + GameData* newGame = new GameData(this, filePath.generic_string()); folder->pushFileData(newGame); isGame = true; break; diff --git a/src/XMLReader.cpp b/src/XMLReader.cpp index 517ae8509..22c9ed282 100644 --- a/src/XMLReader.cpp +++ b/src/XMLReader.cpp @@ -90,11 +90,7 @@ GameData* createGameFromPath(std::string gameAbsPath, SystemData* system) loops++; } - - //find gameName - std::string gameName = gamePath.substr(separator + 1, gamePath.find(".", separator) - separator - 1); - - GameData* game = new GameData(system, gameAbsPath, gameName); + GameData* game = new GameData(system, gameAbsPath); folder->pushFileData(game); return game; } @@ -117,19 +113,19 @@ void parseGamelist(SystemData* system) return; } - pugi::xml_node root = doc.child(GameData::xmlTagGameList.c_str()); + pugi::xml_node root = doc.child("gameList"); if(!root) { - LOG(LogError) << "Could not find <" << GameData::xmlTagGameList << "> node in gamelist \"" << xmlpath << "\"!"; + LOG(LogError) << "Could not find node in gamelist \"" << xmlpath << "\"!"; return; } - for(pugi::xml_node gameNode = root.child(GameData::xmlTagGame.c_str()); gameNode; gameNode = gameNode.next_sibling(GameData::xmlTagGame.c_str())) + for(pugi::xml_node gameNode = root.child("game"); gameNode; gameNode = gameNode.next_sibling("game")) { - pugi::xml_node pathNode = gameNode.child(GameData::xmlTagPath.c_str()); + pugi::xml_node pathNode = gameNode.child("path"); if(!pathNode) { - LOG(LogError) << "<" << GameData::xmlTagGame << "> node contains no <" << GameData::xmlTagPath << "> child!"; + LOG(LogError) << " node contains no child!"; continue; } @@ -137,11 +133,18 @@ void parseGamelist(SystemData* system) boost::filesystem::path gamePath(pathNode.text().get()); std::string path = gamePath.generic_string(); - //expand "." + //expand '.' if(path[0] == '.') { path.erase(0, 1); - path.insert(0, system->getRootFolder()->getPath()); + path.insert(0, boost::filesystem::path(xmlpath).parent_path().generic_string()); + } + + //expand '~' + if(path[0] == '~') + { + path.erase(0, 1); + path.insert(0, getHomePath()); } if(boost::filesystem::exists(path)) @@ -151,103 +154,36 @@ void parseGamelist(SystemData* system) if(game == NULL) game = createGameFromPath(path, system); - //actually gather the information in the XML doc, then pass it to the game's set method - std::string newName, newDesc, newImage; + //load the metadata + *(game->metadata()) = MetaDataList::createFromXML(MetaDataList::getDefaultGameMDD(), gameNode); - if(gameNode.child(GameData::xmlTagName.c_str())) - { - 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] == '.') - { - newImage.erase(0, 1); - boost::filesystem::path pathname(xmlpath); - newImage.insert(0, pathname.parent_path().generic_string() ); - } - - //if the image exist, set it - if(boost::filesystem::exists(newImage)) - { - game->setImagePath(newImage); - } - } - - //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{ + //make sure name gets set if one didn't exist + if(game->metadata()->get("name").empty()) + game->metadata()->set("name", game->getBaseName()); + }else{ LOG(LogWarning) << "Game at \"" << path << "\" does not exist!"; } } } -void addGameDataNode(pugi::xml_node & parent, const GameData * game) +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()); - //store path with generic directory seperators - boost::filesystem::path gamePath(game->getPath()); - pathNode.text().set(gamePath.generic_string().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 newGame = parent.append_child("game"); - 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()); + //write metadata + const_cast(game)->metadata()->appendToXML(newGame, MetaDataList::getDefaultGameMDD()); + + if(newGame.children().begin() == newGame.child("name") //first element is name + && ++newGame.children().begin() == newGame.children().end() //theres only one element + && newGame.child("name").text().get() == game->getBaseName()) //the name is the default + { + //if the only info is the default name, don't bother with this node + parent.remove_child(newGame); + }else{ + //there's something useful in there so we'll keep the node, add the path + newGame.prepend_child("path").text().set(game->getPath().c_str()); + } } void updateGamelist(SystemData* system) @@ -258,56 +194,64 @@ void updateGamelist(SystemData* system) //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()) { + 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) { + 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 << "\"!"; + pugi::xml_node root = doc.child("gameList"); + if(!root) + { + LOG(LogError) << "Could not find 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) { + 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()) { + while(fit != files.cend()) + { //try to cast to gamedata const GameData * game = dynamic_cast(*fit); - if (game != nullptr) { + 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())) { + for(pugi::xml_node gameNode = root.child("game"); gameNode; gameNode = gameNode.next_sibling("game")) + { //get path from game node - pugi::xml_node pathNode = gameNode.child(GameData::xmlTagPath.c_str()); + pugi::xml_node pathNode = gameNode.child("path"); if(!pathNode) { - LOG(LogError) << "<" << GameData::xmlTagGame << "> node contains no <" << GameData::xmlTagPath << "> child!"; + LOG(LogError) << " node contains no child!"; continue; } + //check paths. use the same directory separators boost::filesystem::path nodePath(pathNode.text().get()); boost::filesystem::path gamePath(game->getPath()); - if (nodePath.generic_string() == gamePath.generic_string()) { + if (nodePath.generic_string() == gamePath.generic_string()) + { //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); @@ -316,10 +260,9 @@ void updateGamelist(SystemData* system) } //now write the file if (!doc.save_file(xmlpath.c_str())) { - LOG(LogError) << "Error saving XML file \"" << xmlpath << "\"!"; + LOG(LogError) << "Error saving gamelist.xml file \"" << xmlpath << "\"!"; } - } - else { + }else{ LOG(LogError) << "Found no root folder for system \"" << system->getName() << "\"!"; } } diff --git a/src/components/GuiGameList.cpp b/src/components/GuiGameList.cpp index 0e1dfaaf8..7f05eb609 100644 --- a/src/components/GuiGameList.cpp +++ b/src/components/GuiGameList.cpp @@ -353,11 +353,12 @@ void GuiGameList::updateDetailData() //if we've selected a game if(mList.getSelectedObject() && !mList.getSelectedObject()->isFolder()) { + GameData* game = (GameData*)mList.getSelectedObject(); //set image to either "not found" image or metadata image - if(((GameData*)mList.getSelectedObject())->getImagePath().empty()) + if(game->metadata()->get("image").empty()) mScreenshot.setImage(mTheme->getString("imageNotFoundPath")); else - mScreenshot.setImage(((GameData*)mList.getSelectedObject())->getImagePath()); + mScreenshot.setImage(game->metadata()->get("image")); Eigen::Vector3f imgOffset = Eigen::Vector3f(Renderer::getScreenWidth() * 0.10f, 0, 0); mScreenshot.setPosition(getImagePos() - imgOffset); @@ -372,7 +373,7 @@ void GuiGameList::updateDetailData() mDescription.setPosition(0, 0); mDescription.setSize(Eigen::Vector2f(Renderer::getScreenWidth() * (mTheme->getFloat("listOffsetX") - 0.03f), 0)); - mDescription.setText(((GameData*)mList.getSelectedObject())->getDescription()); + mDescription.setText(game->metadata()->get("desc")); }else{ mScreenshot.setImage(""); mDescription.setText(""); diff --git a/src/components/TextComponent.cpp b/src/components/TextComponent.cpp index 8219caab6..a36203c84 100644 --- a/src/components/TextComponent.cpp +++ b/src/components/TextComponent.cpp @@ -93,3 +93,13 @@ void TextComponent::calculateExtent() } } } + +void TextComponent::setValue(const std::string& value) +{ + setText(value); +} + +std::string TextComponent::getValue() const +{ + return mText; +} diff --git a/src/components/TextComponent.h b/src/components/TextComponent.h index 8df7749b1..0cba0cacb 100644 --- a/src/components/TextComponent.h +++ b/src/components/TextComponent.h @@ -18,6 +18,9 @@ public: void render(const Eigen::Affine3f& parentTrans) override; + std::string getValue() const override; + void setValue(const std::string& value) override; + private: std::shared_ptr getFont() const;