summaryrefslogtreecommitdiff
path: root/core/src/Model
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/Model')
-rw-r--r--core/src/Model/GlobalStates.cpp17
-rw-r--r--core/src/Model/GlobalStates.hpp5
-rw-r--r--core/src/Model/Items.cpp88
-rw-r--r--core/src/Model/Items.hpp209
-rw-r--r--core/src/Model/Project.cpp66
-rw-r--r--core/src/Model/Project.hpp6
-rw-r--r--core/src/Model/fwd.hpp1
7 files changed, 258 insertions, 134 deletions
diff --git a/core/src/Model/GlobalStates.cpp b/core/src/Model/GlobalStates.cpp
index cd076f4..0c4e58e 100644
--- a/core/src/Model/GlobalStates.cpp
+++ b/core/src/Model/GlobalStates.cpp
@@ -17,8 +17,12 @@ static std::unique_ptr<GlobalStates> globalStateInstance;
static fs::path globalDataPath;
void GlobalStates::Init() {
+ Init(StandardDirectories::UserData() / "cplt");
+}
+
+void GlobalStates::Init(std::filesystem::path userDataDir) {
globalStateInstance = std::make_unique<GlobalStates>();
- globalDataPath = StandardDirectories::UserData() / "cplt";
+ globalDataPath = userDataDir;
fs::create_directories(globalDataPath);
// Reading recent projects
@@ -79,6 +83,17 @@ void GlobalStates::AddRecentProject(const Project& project) {
MarkDirty();
}
+void GlobalStates::MoveProjectToTop(const Project& project) {
+ for (auto it = mRecentProjects.begin(); it != mRecentProjects.end(); ++it) {
+ if (it->path == project.GetPath()) {
+ std::rotate(it, it + 1, mRecentProjects.end());
+ MarkDirty();
+ return;
+ }
+ }
+ AddRecentProject(project);
+}
+
void GlobalStates::RemoveRecentProject(int idx) {
assert(idx >= 0 && idx < mRecentProjects.size());
diff --git a/core/src/Model/GlobalStates.hpp b/core/src/Model/GlobalStates.hpp
index e6d823b..8375569 100644
--- a/core/src/Model/GlobalStates.hpp
+++ b/core/src/Model/GlobalStates.hpp
@@ -10,6 +10,7 @@
class GlobalStates {
public:
static void Init();
+ static void Init(std::filesystem::path userDataDir);
static void Shutdown();
static GlobalStates& GetInstance();
@@ -31,6 +32,10 @@ public:
const std::vector<RecentProject>& GetRecentProjects() const;
void ClearRecentProjects();
void AddRecentProject(const Project& project);
+ /// Move or add the project to end of the recent projects list.
+ /// If the project is not in the list of recently used projects, it will be appended, otherwise
+ /// it will be moved to the end.
+ void MoveProjectToTop(const Project& project);
void RemoveRecentProject(int idx);
// TODO async autosaving to prevent data loss on crash
diff --git a/core/src/Model/Items.cpp b/core/src/Model/Items.cpp
index db2d39f..7679eb9 100644
--- a/core/src/Model/Items.cpp
+++ b/core/src/Model/Items.cpp
@@ -1,37 +1,5 @@
#include "Items.hpp"
-#include <limits>
-#include <utility>
-
-ItemBase::ItemBase()
- : mId{ std::numeric_limits<size_t>::max() } {
-}
-
-ItemBase::ItemBase(size_t id)
- : mId{ id } {
-}
-
-bool ItemBase::IsInvalid() const {
- return mId == std::numeric_limits<size_t>::max();
-}
-
-size_t ItemBase::GetId() const {
- return mId;
-}
-
-ProductItem::ProductItem(size_t id, std::string name)
- : ItemBase(id)
- , mName{ std::move(name) } {
-}
-
-const std::string& ProductItem::GetName() const {
- return mName;
-}
-
-void ProductItem::SetName(std::string name) {
- mName = std::move(name);
-}
-
const std::string& ProductItem::GetDescription() const {
return mDescription;
}
@@ -40,17 +8,14 @@ void ProductItem::SetDescription(std::string description) {
mDescription = std::move(description);
}
-FactoryItem::FactoryItem(size_t id, std::string name)
- : ItemBase(id)
- , mName{ std::move(name) } {
-}
-
-const std::string& FactoryItem::GetName() const {
- return mName;
+Json::Value ProductItem::Serialize() const {
+ Json::Value elm;
+ elm["Description"] = mDescription;
+ return elm;
}
-void FactoryItem::SetName(std::string name) {
- mName = std::move(name);
+void ProductItem::Deserialize(const Json::Value& elm) {
+ mDescription = elm["Description"].asString();
}
const std::string& FactoryItem::GetDescription() const {
@@ -61,17 +26,24 @@ void FactoryItem::SetDescription(std::string description) {
mDescription = std::move(description);
}
-CustomerItem::CustomerItem(size_t id, std::string name)
- : ItemBase(id)
- , mName{ std::move(name) } {
+const std::string& FactoryItem::GetEmail() const {
+ return mEmail;
}
-const std::string& CustomerItem::GetName() const {
- return mName;
+void FactoryItem::SetEmail(std::string email) {
+ mEmail = std::move(email);
}
-void CustomerItem::SetName(std::string name) {
- mName = std::move(name);
+Json::Value FactoryItem::Serialize() const {
+ Json::Value elm;
+ elm["Description"] = mDescription;
+ elm["Email"] = mEmail;
+ return elm;
+}
+
+void FactoryItem::Deserialize(const Json::Value& elm) {
+ mDescription = elm["Description"].asString();
+ mEmail = elm["Email"].asString();
}
const std::string& CustomerItem::GetDescription() const {
@@ -81,3 +53,23 @@ const std::string& CustomerItem::GetDescription() const {
void CustomerItem::SetDescription(std::string description) {
mDescription = std::move(description);
}
+
+const std::string& CustomerItem::GetEmail() const {
+ return mEmail;
+}
+
+void CustomerItem::SetEmail(std::string email) {
+ mEmail = std::move(email);
+}
+
+Json::Value CustomerItem::Serialize() const {
+ Json::Value elm;
+ elm["Description"] = mDescription;
+ elm["Email"] = mEmail;
+ return elm;
+}
+
+void CustomerItem::Deserialize(const Json::Value& elm) {
+ mDescription = elm["Description"].asString();
+ mEmail = elm["Email"].asString();
+}
diff --git a/core/src/Model/Items.hpp b/core/src/Model/Items.hpp
index 0c7be41..e20a290 100644
--- a/core/src/Model/Items.hpp
+++ b/core/src/Model/Items.hpp
@@ -1,54 +1,21 @@
#pragma once
+#include "cplt_fwd.hpp"
+
+#include <json/reader.h>
+#include <json/value.h>
+#include <json/writer.h>
#include <tsl/array_map.h>
#include <cstddef>
+#include <limits>
#include <stdexcept>
#include <string>
#include <string_view>
+#include <utility>
#include <vector>
-/// Pointers and references returned by accessors are valid as long as no non-const functions have been called.
template <class T>
class ItemList {
-public:
- class Iterator {
- private:
- typename std::vector<T>::const_iterator mBackingIter;
-
- public:
- Iterator(typename std::vector<T>::const_iterator it)
- : mBackingIter{ it } {
- }
-
- Iterator& operator++() {
- ++mBackingIter;
- return *this;
- }
-
- Iterator& operator++(int) {
- auto tmp = *this;
- ++mBackingIter;
- return tmp;
- }
-
- Iterator& operator--() {
- --mBackingIter;
- return *this;
- }
-
- Iterator& operator--(int) {
- auto tmp = *this;
- --mBackingIter;
- return tmp;
- }
-
- const T& operator*() const {
- return *mBackingIter;
- }
-
- friend bool operator==(const Iterator&, const Iterator&) = default;
- };
-
private:
std::vector<T> mStorage;
tsl::array_map<char, size_t> mNameLookup;
@@ -61,9 +28,28 @@ public:
throw std::runtime_error("Duplicate key.");
}
+ for (size_t i = 0; i < mStorage.size(); ++i) {
+ if (mStorage[i].IsInvalid()) {
+ mStorage[i] = T(*this, i, std::move(name), std::forward<Args>(args)...);
+ mNameLookup.insert(name, i);
+ return mStorage[i];
+ }
+ }
+
size_t id = mStorage.size();
mNameLookup.insert(name, id);
- return mStorage.emplace_back(id, std::move(name), std::forward<Args>(args)...);
+ mStorage.emplace_back(*this, id, std::move(name), std::forward<Args>(args)...);
+ return mStorage[id];
+ }
+
+ void Remove(size_t index) {
+ auto& item = mStorage[index];
+ mNameLookup.erase(item.GetName());
+ mStorage[index] = T(*this);
+ }
+
+ T* Find(size_t id) {
+ return &mStorage[id];
}
const T* Find(size_t id) const {
@@ -79,68 +65,157 @@ public:
}
}
- Iterator begin() const {
- return Iterator(mStorage.begin());
+ Json::Value Serialize() const {
+ Json::Value items(Json::arrayValue);
+ for (auto& item : mStorage) {
+ if (!item.IsInvalid()) {
+ auto elm = item.Serialize();
+ elm["Id"] = item.GetId();
+ elm["Name"] = item.GetName();
+ items.append(elm);
+ }
+ }
+
+ Json::Value root;
+ root["MaxItemId"] = mStorage.size();
+ root["Items"] = std::move(items);
+
+ return root;
}
- Iterator end() const {
- return Iterator(mStorage.end());
+ ItemList() = default;
+
+ ItemList(const Json::Value& root) {
+ constexpr const char* kMessage = "Failed to load item list from JSON.";
+
+ auto& itemCount = root["MaxItemId"];
+ if (!itemCount.isIntegral()) throw std::runtime_error(kMessage);
+
+ mStorage.resize(itemCount.asInt64(), T(*this));
+
+ auto& items = root["Items"];
+ if (!items.isArray()) throw std::runtime_error(kMessage);
+
+ for (auto& elm : items) {
+ if (!elm.isObject()) throw std::runtime_error(kMessage);
+
+ auto& id = elm["Id"];
+ if (!id.isIntegral()) throw std::runtime_error(kMessage);
+ auto& name = elm["Name"];
+ if (!name.isString()) throw std::runtime_error(kMessage);
+
+ size_t iid = id.asInt64();
+ mStorage[iid] = T(*this, iid, name.asString());
+ mStorage[iid].Deserialize(elm);
+ }
+ }
+
+ typename decltype(mStorage)::iterator begin() {
+ return mStorage.begin();
+ }
+
+ typename decltype(mStorage)::iterator end() {
+ return mStorage.end();
+ }
+
+ typename decltype(mStorage)::const_iterator begin() const {
+ return mStorage.begin();
+ }
+
+ typename decltype(mStorage)::const_iterator end() const {
+ return mStorage.end();
+ }
+
+private:
+ template <class TSelf>
+ friend class ItemBase;
+
+ void UpdateItemName(const T& item, const std::string& newName) {
+ mNameLookup.erase(item.GetName());
+ mNameLookup.insert(newName, item.GetId());
}
};
+template <class TSelf>
class ItemBase {
private:
+ ItemList<TSelf>* mList;
size_t mId;
+ std::string mName;
public:
- ItemBase();
- ItemBase(size_t id);
+ ItemBase(ItemList<TSelf>& list, size_t id = std::numeric_limits<size_t>::max(), std::string name = "")
+ : mList{ &list }
+ , mId{ id }
+ , mName{ std::move(name) } {
+ }
- bool IsInvalid() const;
- size_t GetId() const;
+ bool IsInvalid() const {
+ return mId == std::numeric_limits<size_t>::max();
+ }
+
+ ItemList<TSelf>& GetList() const {
+ return *mList;
+ }
+
+ size_t GetId() const {
+ return mId;
+ }
+
+ const std::string& GetName() const {
+ return mName;
+ }
+
+ void SetName(std::string name) {
+ mList->UpdateItemName(static_cast<TSelf&>(*this), name);
+ mName = std::move(name);
+ }
};
-class ProductItem : public ItemBase {
+class ProductItem : public ItemBase<ProductItem> {
private:
- std::string mName;
std::string mDescription;
public:
- ProductItem() {}
- ProductItem(size_t id, std::string name);
+ using ItemBase::ItemBase;
- const std::string& GetName() const;
- void SetName(std::string mName);
const std::string& GetDescription() const;
void SetDescription(std::string description);
+
+ Json::Value Serialize() const;
+ void Deserialize(const Json::Value& elm);
};
-class FactoryItem : public ItemBase {
+class FactoryItem : public ItemBase<FactoryItem> {
private:
- std::string mName;
std::string mDescription;
+ std::string mEmail;
public:
- FactoryItem() {}
- FactoryItem(size_t id, std::string name);
+ using ItemBase::ItemBase;
- const std::string& GetName() const;
- void SetName(std::string name);
const std::string& GetDescription() const;
void SetDescription(std::string description);
+ const std::string& GetEmail() const;
+ void SetEmail(std::string email);
+
+ Json::Value Serialize() const;
+ void Deserialize(const Json::Value& elm);
};
-class CustomerItem : public ItemBase {
+class CustomerItem : public ItemBase<CustomerItem> {
private:
- std::string mName;
std::string mDescription;
+ std::string mEmail;
public:
- CustomerItem() {}
- CustomerItem(size_t id, std::string name);
+ using ItemBase::ItemBase;
- const std::string& GetName() const;
- void SetName(std::string name);
const std::string& GetDescription() const;
void SetDescription(std::string description);
+ const std::string& GetEmail() const;
+ void SetEmail(std::string email);
+
+ Json::Value Serialize() const;
+ void Deserialize(const Json::Value& elm);
};
diff --git a/core/src/Model/Project.cpp b/core/src/Model/Project.cpp
index f070940..cdb88c6 100644
--- a/core/src/Model/Project.cpp
+++ b/core/src/Model/Project.cpp
@@ -10,40 +10,62 @@
namespace fs = std::filesystem;
-Project Project::Load(const fs::path& path) {
+template <class T>
+void ReadItemList(ItemList<T>& list, const fs::path& filePath) {
+ std::ifstream ifs(filePath);
+ if (ifs) {
+ Json::Value root;
+ ifs >> root;
+
+ list = ItemList<T>(root);
+ }
+}
+
+Project Project::Load(const fs::path& projectFilePath) {
// TODO better diagnostic
const char* kInvalidFormatErr = "Failed to load project: invalid format.";
- std::ifstream ifs(path);
+ std::ifstream ifs(projectFilePath);
if (!ifs) {
std::string message;
message += "Failed to load project file at '";
- message += path.string();
+ message += projectFilePath.string();
message += "'.";
throw std::runtime_error(message);
}
Project proj;
- proj.mRootPath = path.parent_path();
+ proj.mRootPath = projectFilePath.parent_path();
proj.mRootPathString = proj.mRootPath.string();
- Json::Value root;
- ifs >> root;
+ {
+ Json::Value root;
+ ifs >> root;
- const auto& croot = root; // Use const reference so that accessors default to returning a null if not found, instead of silently creating new elements
- if (!croot.isObject()) {
- throw std::runtime_error(kInvalidFormatErr);
- }
+ const auto& croot = root; // Use const reference so that accessors default to returning a null if not found, instead of silently creating new elements
+ if (!croot.isObject()) {
+ throw std::runtime_error(kInvalidFormatErr);
+ }
- if (auto& name = croot["Name"]; name.isString()) {
- proj.mName = name.asString();
- } else {
- throw std::runtime_error(kInvalidFormatErr);
+ if (auto& name = croot["Name"]; name.isString()) {
+ proj.mName = name.asString();
+ } else {
+ throw std::runtime_error(kInvalidFormatErr);
+ }
}
+ auto itemsDir = proj.mRootPath / "items";
+ ReadItemList(proj.Products, itemsDir / "products.json");
+ ReadItemList(proj.Factories, itemsDir / "factories.json");
+ ReadItemList(proj.Customers, itemsDir / "customers.json");
+
return proj;
}
+Project Project::LoadDir(const std::filesystem::path& projectPath) {
+ return Load(projectPath / "cplt_project.json");
+}
+
Project Project::Create(std::string name, const fs::path& path) {
Project proj;
proj.mRootPath = path;
@@ -76,8 +98,20 @@ Json::Value Project::Serialize() {
return root;
}
+template <class T>
+static void WriteItemList(ItemList<T>& list, const fs::path& filePath) {
+ std::ofstream ofs(filePath);
+ ofs << list.Serialize();
+}
+
void Project::WriteToDisk() {
- auto root = Serialize();
std::ofstream ofs(mRootPath / "cplt_project.json");
- ofs << root;
+ ofs << this->Serialize();
+
+ auto itemsDir = mRootPath / "items";
+ fs::create_directories(itemsDir);
+
+ WriteItemList(Products, itemsDir / "products.json");
+ WriteItemList(Factories, itemsDir / "factories.json");
+ WriteItemList(Customers, itemsDir / "customers.json");
}
diff --git a/core/src/Model/Project.hpp b/core/src/Model/Project.hpp
index 23eafc1..280eaf3 100644
--- a/core/src/Model/Project.hpp
+++ b/core/src/Model/Project.hpp
@@ -19,12 +19,14 @@ private:
public:
/// Load the project from a cplt_project.json file.
- static Project Load(const std::filesystem::path& path);
+ static Project Load(const std::filesystem::path& projectFilePath);
+ /// Load the project from the directory containing the cplt_project.json file.
+ static Project LoadDir(const std::filesystem::path& projectPath);
/// Create a project with the given name in the given path. Note that the path should be a directory that will contain the project files once created.
/// This function assumes the given directory will exist and is empty.
static Project Create(std::string name, const std::filesystem::path& path);
- // Path to a *directory* that contains the project file.
+ /// Path to a *directory* that contains the project file.
const std::filesystem::path& GetPath() const;
const std::string& GetPathString() const;
diff --git a/core/src/Model/fwd.hpp b/core/src/Model/fwd.hpp
index bf9a8cf..146f74a 100644
--- a/core/src/Model/fwd.hpp
+++ b/core/src/Model/fwd.hpp
@@ -6,6 +6,7 @@ class GlobalStates;
// Items.hpp
template <class T>
class ItemList;
+template <class TSelf>
class ItemBase;
class ProductItem;
class FactoryItem;