reworking default tip

Tue, 02 Nov 2021 15:43:57 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Tue, 02 Nov 2021 15:43:57 +0200
changeset 148
e1ced2523cad
parent 147
37f936073cac

reworking

CMakeLists.txt file | annotate | diff | comparison | revisions
src/basics.h file | annotate | diff | comparison | revisions
src/document.cpp file | annotate | diff | comparison | revisions
src/document.h file | annotate | diff | comparison | revisions
src/documentmanager.cpp file | annotate | diff | comparison | revisions
src/documentmanager.h file | annotate | diff | comparison | revisions
src/linetypes/object.h file | annotate | diff | comparison | revisions
src/linetypes/subfilereference.cpp file | annotate | diff | comparison | revisions
src/linetypes/subfilereference.h file | annotate | diff | comparison | revisions
src/main.cpp file | annotate | diff | comparison | revisions
src/main.h file | annotate | diff | comparison | revisions
src/mainwindow.cpp file | annotate | diff | comparison | revisions
src/mainwindow.h file | annotate | diff | comparison | revisions
src/model.cpp file | annotate | diff | comparison | revisions
src/model.h file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Sun Oct 24 11:33:32 2021 +0300
+++ b/CMakeLists.txt	Tue Nov 02 15:43:57 2021 +0200
@@ -157,7 +157,7 @@
 )
 
 set(LDFORGE_RESOURCES ldforge.qrc)
-set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD 20)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 set(CMAKE_CXX_EXTENSIONS OFF)
 include_directories("${PROJECT_BINARY_DIR}")
--- a/src/basics.h	Sun Oct 24 11:33:32 2021 +0300
+++ b/src/basics.h	Tue Nov 02 15:43:57 2021 +0200
@@ -181,6 +181,46 @@
 	return arg ? arg : coalesce(rest...);
 }
 
+/**
+ * @brief Finds an element in a map and possibly returns a reference to it if find
+ * @param map
+ * @param key
+ * @returns the value or nullptr if not found
+ */
+template<typename T, typename R, typename K>
+const R* findInMap(const std::map<T, R>& map, K&& key)
+{
+	auto pair = map.find(key);
+	if (pair != map.end())
+	{
+		return &pair->second;
+	}
+	else
+	{
+		return nullptr;
+	}
+}
+
+/**
+ * @brief Finds an element in a map and possibly returns a reference to it if find
+ * @param map
+ * @param key
+ * @returns the value or no value if not found
+ */
+template<typename T, typename R, typename K>
+R* findInMap(std::map<T, R>& map, K&& key)
+{
+	auto pair = map.find(key);
+	if (pair != map.end())
+	{
+		return &pair->second;
+	}
+	else
+	{
+		return nullptr;
+	}
+}
+
 template<typename T = double>
 constexpr std::enable_if_t<std::is_floating_point_v<T>, T> PI = static_cast<T>(M_PIl);
 
--- a/src/document.cpp	Sun Oct 24 11:33:32 2021 +0300
+++ b/src/document.cpp	Tue Nov 02 15:43:57 2021 +0200
@@ -228,33 +228,3 @@
 {
 	this->renderer->adjustGridToView();
 }
-
-/**
- * @brief Attempts to save the document
- * @param errors Where to report any errors that might occurr
- * @return whether or not it succeeded
- */
-bool Document::save(QTextStream& errors)
-{
-	this->model->makeUnofficial();
-	return this->model->save(errors);
-}
-
-/**
- * @brief Gets the current path
- * @return string
- */
-const QString &Document::modelPath() const
-{
-	return this->model->path();
-}
-
-/**
- * @brief Sets the path of the model
- * @param newPath
- */
-void Document::setModelPath(const QString &newPath)
-{
-	this->model->setPath(newPath);
-}
-
--- a/src/document.h	Sun Oct 24 11:33:32 2021 +0300
+++ b/src/document.h	Tue Nov 02 15:43:57 2021 +0200
@@ -48,9 +48,6 @@
 	void applyToVertices(VertexMap::ApplyFunction fn) const;
 	void handleKeyPress(QKeyEvent* event);
 	void adjustGridToView();
-	bool save(QTextStream &errors);
-	const QString& modelPath() const;
-	void setModelPath(const QString& newPath);
 Q_SIGNALS:
 	void newStatusText(const QString& newStatusText);
 	void splitterChanged();
--- a/src/documentmanager.cpp	Sun Oct 24 11:33:32 2021 +0300
+++ b/src/documentmanager.cpp	Tue Nov 02 15:43:57 2021 +0200
@@ -19,6 +19,7 @@
 #include <QFile>
 #include <QDir>
 #include <QFileInfo>
+#include <QSaveFile>
 #include "documentmanager.h"
 #include "modeleditcontext.h"
 #include "linetypes/comment.h"
@@ -36,31 +37,62 @@
 
 /**
  * @brief Creates a new model.
- * @returns the name to the new model
+ * @returns the ID of the new model
  */
-QString DocumentManager::newModel()
+ModelId DocumentManager::newModel()
 {
+	const ModelId modelId{++this->modelIdCounter};
 	const QString name = makeNewModelName();
-	this->openModels.emplace(name, new Model);
-	return name;
+	this->openModels[modelId] = ModelInfo{
+		.model = std::make_unique<Model>(),
+		.opentype = OpenType::ManuallyOpened,
+	};
+	return modelId;
 }
 
 /**
  * @brief Looks for a model by name
  * @param name Name of the model
  * @returns model or null
- * '
  */
-Model* DocumentManager::findModelByName(const QString& name)
+Model* DocumentManager::findDependencyByName(const ModelId modelId, const QString& name)
 {
-	const auto iterator = this->openModels.find(name);
-	if (iterator == std::end(this->openModels))
+	const auto modelsIterator = this->openModels.find(modelId);
+	if (modelsIterator != std::end(this->openModels))
+	{
+		const auto& dependencies = modelsIterator->second.dependencies;
+		const auto dependenciesIterator = dependencies.find(name);
+		if (dependenciesIterator != dependencies.end())
+		{
+			ModelInfo& modelInfo = this->openModels[dependenciesIterator->second];
+			return modelInfo.model.get();
+		}
+		else
+		{
+			return nullptr;
+		}
+	}
+	else
 	{
 		return nullptr;
 	}
+}
+
+/**
+ * @brief Gets a model pointer by id or nullptr if not found
+ * @param modelId id of model to find
+ * @returns model pointer or null
+ */
+Model *DocumentManager::getModelById(ModelId modelId)
+{
+	const auto iterator = this->openModels.find(modelId);
+	if (iterator != this->openModels.end())
+	{
+		return iterator->second.model.get();
+	}
 	else
 	{
-		return iterator->second.model.get();
+		return nullptr;
 	}
 }
 
@@ -90,23 +122,27 @@
  * @param path Path to the model to open
  * @param errorStream Where to write any errors
  * @param openType rationale behind opening this file
- * @returns file name or "" on error
+ * @returns model id, or no value on error
  */
-QString DocumentManager::openModel(const QString& path, QTextStream& errorStream, const OpenType openType)
-{
+std::optional<ModelId> DocumentManager::openModel(
+	const QString& path,
+	QTextStream& errorStream,
+	const OpenType openType
+) {
 	QFile file{path};
 	const QString name = pathToName(path);
 	file.open(QFile::ReadOnly | QFile::Text);
-	std::unique_ptr<Model> newModel = std::make_unique<Model>(path);
+	std::unique_ptr<Model> newModel = std::make_unique<Model>(this);
 	QTextStream textStream{&file};
 	Model::EditContext editor = newModel->edit();
 	Parser parser{file};
 	parser.parseBody(editor);
-	QString result;
+	std::optional<ModelId> result;
 	if (file.error() == QFile::NoError)
 	{
-		openModels[name] = {std::move(newModel), openType};
-		result = name;
+		const ModelId modelId{++this->modelIdCounter};
+		this->openModels[modelId] = {std::move(newModel), path, openType};
+		result = modelId;
 	}
 	else
 	{
@@ -121,59 +157,192 @@
 	return "untitled-" + QString::number(untitledNameCounter);
 }
 
+void DocumentManager::loadDependenciesForAllModels(const LibraryManager& libraries, QTextStream& errorStream)
+{
+	for (const auto& modelInfoPair : this->openModels)
+	{
+		this->loadDependenciesForModel(modelInfoPair.first, modelInfoPair.second.path, libraries, errorStream);
+	}
+}
+
+struct DocumentManager::LoadDepedenciesBag
+{
+	const LibraryManager& libraries;
+	QStringList missing;
+	QSet<ModelId> processed;
+	QTextStream& errorStream;
+};
+
 void DocumentManager::loadDependenciesForModel(
-	const QString& modelName,
+	const ModelId modelId,
 	const QString& path,
 	const LibraryManager& libraries,
 	QTextStream& errorStream)
 {
-	QStringList missing;
-	QStringList processed;
-	loadDependenciesForModel(modelName, path, libraries, missing, processed, errorStream);
-	if (not missing.empty())
+	LoadDepedenciesBag bag {
+		.libraries = libraries,
+		.missing = {},
+		.processed = {},
+		.errorStream = errorStream,
+	};
+	this->loadDependenciesForModel(modelId, path, bag);
+	if (not bag.missing.empty())
 	{
-		missing.sort(Qt::CaseInsensitive);
+		bag.missing.sort(Qt::CaseInsensitive);
 		errorStream << utility::format(
 			"The following files could not be opened: %1",
-			missing.join(", "));
+			bag.missing.join(", "));
+	}
+}
+
+void DocumentManager::closeDocument(const ModelId modelId)
+{
+	ModelInfo* modelInfo = findInMap(this->openModels, modelId);
+	if (modelInfo != nullptr)
+	{
+		modelInfo->opentype = OpenType::AutomaticallyOpened;
+		this->prune();
+	}
+}
+
+const QString *DocumentManager::modelPath(ModelId modelId) const
+{
+	const auto iterator = this->openModels.find(modelId);
+	if (iterator != this->openModels.end())
+	{
+		return &iterator->second.path;
+	}
+	else
+	{
+		return nullptr;
+	}
+}
+
+/**
+ * @brief Changes the path of the specified model. Since the name of the file may change,
+ * changing the path can cause dependencies to be resolved differently. As such, dependencies
+ * need to be resolved for all files after this operation.
+ * @param modelId Model to change the path of
+ * @param newPath New path
+ * @param libraries Library manager for the purpose of dependency resolving
+ * @param errorStream Where to write any errors regarding dependency resolving
+ */
+void DocumentManager::setModelPath(
+	const ModelId modelId,
+	const QString &newPath,
+	const LibraryManager &libraries,
+	QTextStream &errorStream)
+{
+	auto modelInfoPair = this->openModels.find(modelId);
+	if (true
+		and modelInfoPair != this->openModels.end()
+		and modelInfoPair->second.opentype == OpenType::ManuallyOpened
+	) {
+		modelInfoPair->second.path = newPath;
+		this->loadDependenciesForAllModels(libraries, errorStream);
 	}
 }
 
-void DocumentManager::closeDocument(const QString &name)
+bool DocumentManager::saveModel(const ModelId modelId, QTextStream &errors)
 {
-	const auto& it = this->openModels.find(name);
-	if (it != this->openModels.end())
+	const QString* const path = this->modelPath(modelId);
+	if (path != nullptr)
 	{
-		this->openModels.erase(it);
-	}
-	QSet<QString> referenced;
-	for (const auto& it : this->openModels)
-	{
-		if (it.second.opentype == OpenType::ManuallyOpened)
+		QSaveFile file{*path};
+		file.setDirectWriteFallback(true);
+		if (file.open(QSaveFile::WriteOnly))
 		{
-			this->collectReferences(referenced, it.first, it.second.model.get());
+			// if path is not nullptr, getModelById will always return a value as well
+			this->getModelById(modelId)->save(&file);
+			const bool commitSucceeded = file.commit();
+			if (not commitSucceeded)
+			{
+				errors << tr("Could not save: %1").arg(file.errorString());
+				return false;
+			}
+			else
+			{
+				return true;
+			}
+		}
+		else
+		{
+			errors << tr("Could not open %1 for writing: %2")
+				.arg(file.fileName())
+				.arg(file.errorString());
+			return false;
 		}
 	}
-	
+	else
+	{
+		errors << tr("Bad model ID %1").arg(modelId.value);
+		return false;
+	}
 }
 
-void DocumentManager::collectReferences(QSet<QString>& referenced, const QString &name, const Model *model)
+/**
+ * @brief Searches the open models for the specified model and returns its id if found
+ * @param model model to look for
+ * @return id or no value if not found
+ */
+std::optional<ModelId> DocumentManager::findIdForModel(const Model *model) const
 {
-	if (not referenced.contains(name))
+	std::optional<ModelId> result;
+	for (auto it = this->openModels.begin(); it != this->openModels.end(); ++it)
 	{
-		referenced.insert(name);
-		model->apply<ldraw::SubfileReference>([&](const ldraw::SubfileReference* referenceObject)
+		if (it->second.model.get() == model)
 		{
-			const ldraw::id_t id = referenceObject->id;
-			const QString& referenceName = model->getObjectProperty<ldraw::Property::ReferenceName>(id);
-			auto it = this->openModels.find(referenceName);
-			if (it != this->openModels.end())
+			result = it->first;
+			break;
+		}
+	}
+	return result;
+}
+
+/**
+ * @brief Cleans up and erases models that are no longer required.
+ */
+void DocumentManager::prune()
+{
+	for (auto it = this->openModels.begin(); it != this->openModels.end(); ++it)
+	{
+		// Find models that are not edited by the user and are not needed by any other model
+		if (true
+			and it->second.opentype == OpenType::AutomaticallyOpened
+			and not this->isReferencedByAnything(it->first)
+		) {
+			// Remove the model
+			this->openModels.erase(it);
+			// We need to start over now. It is possible that other models that previously
+			// were referenced by the model we just erased have become prunable.
+			// Moreover, our iterator is invalid now and we cannot continue in this for loop.
+			this->prune();
+			break;
+		}
+	}
+}
+
+/**
+ * @brief Finds out whether the specified model id is referenced by any other model
+ * @param modelId
+ * @returns bool
+ */
+bool DocumentManager::isReferencedByAnything(const ModelId modelId) const
+{
+	for (auto& haystackModelPair : this->openModels)
+	{
+		if (haystackModelPair.first != modelId)
+		{
+			for (auto& dependencyPair : haystackModelPair.second.dependencies)
 			{
-				const Model* const model = it->second.model.get();
-				this->collectReferences(referenced, referenceName, model);
+				if (dependencyPair.second == modelId)
+				{
+					return true;
+				}
 			}
-		});
+		}
 	}
+	return false;
 }
 
 static QString findFile(QString referenceName, const QString& path, const LibraryManager& libraries)
@@ -191,56 +360,61 @@
 }
 
 void DocumentManager::loadDependenciesForModel(
-	const QString& modelName,
+	const ModelId modelId,
 	const QString &path,
-	const LibraryManager& libraries,
-	QStringList& missing,
-	QStringList& processed,
-	QTextStream& errorStream)
+	LoadDepedenciesBag& bag)
 {
+	QSet<QString> failedToOpen;
 	struct LoadingError
 	{
 		QString message;
 	};
-	processed.append(modelName);
-	Model* model = this->findModelByName(modelName);
-	for (int i = 0; i < model->size(); i += 1)
+	bag.processed.insert(modelId);
+	if (not this->openModels.contains(modelId))
 	{
-		const QString referenceName = model->getObjectProperty(i, ldraw::Property::ReferenceName).toString();
+		bag.errorStream << tr("bad model ID %1").arg(modelId.value);
+		return;
+	}
+	ModelInfo& modelInfo = this->openModels[modelId];
+	modelInfo.dependencies.clear();
+	for (int i = 0; i < modelInfo.model->size(); i += 1)
+	{
+		const QString referenceName = modelInfo.model->getObjectProperty(i, ldraw::Property::ReferenceName).toString();
 		if (not referenceName.isEmpty()
-			and openModels.find(referenceName) == std::end(openModels)
-			and not missing.contains(referenceName))
+			and modelInfo.dependencies.count(referenceName) == 0
+			and not failedToOpen.contains(referenceName))
 		{
 			try
 			{
-				const QString referencedFilePath = findFile(referenceName, path, libraries);
+				const QString referencedFilePath = ::findFile(referenceName, path, bag.libraries);
 				if (referencedFilePath.isEmpty())
 				{
-					throw LoadingError{utility::format("'%1' was not found.", referenceName)};
+					throw LoadingError{tr("could not find '%1'").arg(referenceName)};
 				}
-				QString errorString;
-				QTextStream localErrorStream{&errorString};
-				QString resultName = this->openModel(
+				QString loadErrorString;
+				QTextStream localErrorStream{&loadErrorString};
+				const std::optional<ModelId> modelIdOpt = this->openModel(
 					referencedFilePath,
 					localErrorStream,
 					OpenType::AutomaticallyOpened);
-				if (resultName.isEmpty())
+				if (not modelIdOpt.has_value())
 				{
-					throw LoadingError{utility::format(
-						"could not load '%1': %2",
-						referencedFilePath,
-						errorString)};
+					const QString& errorMessage = tr("could not load '%1': %2")
+						.arg(referencedFilePath)
+						.arg(loadErrorString);
+					throw LoadingError{errorMessage};
 				}
-				if (not processed.contains(referenceName))
+				modelInfo.dependencies[referenceName] = modelIdOpt.value();
+				if (not bag.processed.contains(modelIdOpt.value()))
 				{
-					loadDependenciesForModel(referenceName, path, libraries, missing, processed, errorStream);
+					this->loadDependenciesForModel(modelIdOpt.value(), referencedFilePath, bag);
 				}
 			}
 			catch(const LoadingError& error)
 			{
-				errorStream << error.message << "\n";
-				missing.append(referenceName);
-				processed.append(referenceName);
+				bag.errorStream << error.message << "\n";
+				failedToOpen.insert(referenceName);
+				bag.missing.append(referenceName);
 			}
 		}
 	}
--- a/src/documentmanager.h	Sun Oct 24 11:33:32 2021 +0300
+++ b/src/documentmanager.h	Tue Nov 02 15:43:57 2021 +0200
@@ -26,34 +26,52 @@
 public:
 	enum OpenType
 	{
+		/**
+		  * Document was opened manually by the user
+		  */
 		ManuallyOpened,
+		/**
+		  * Document was opened automatically in order to resolve subfile references
+		  */
 		AutomaticallyOpened,
 	};
     DocumentManager(QObject* parent = nullptr);
-	QString newModel();
-	Model* findModelByName(const QString& name);
-	QString openModel(const QString& path, QTextStream& errorStream, const OpenType openType);
+	ModelId newModel();
+	Model* findDependencyByName(const ModelId modelId, const QString& name);
+	Model* getModelById(ModelId modelId);
+	std::optional<ModelId> openModel(const QString& path, QTextStream& errorStream, const OpenType openType);
 	QString makeNewModelName();
-	void loadDependenciesForModel(const QString& modelName,
+	void loadDependenciesForAllModels(const LibraryManager &libraries, QTextStream &errorStream);
+	void loadDependenciesForModel(const ModelId modelId,
 		const QString& path,
 		const LibraryManager& libraries,
 		QTextStream& errorStream);
-	void closeDocument(const QString& name);
+	void closeDocument(const ModelId modelId);
+	const QString* modelPath(ModelId modelId) const;
+	void setModelPath(
+			const ModelId modelId,
+			const QString& newPath,
+			const LibraryManager &libraries,
+			QTextStream &errorStream);
+	bool saveModel(const ModelId modelId, QTextStream& errors);
+	std::optional<ModelId> findIdForModel(const Model* model) const;
 private:
-	void collectReferences(QSet<QString> &referenced, const QString& name, const Model* model);
 	struct ModelInfo
 	{
 		std::unique_ptr<Model> model;
+		QString path;
 		OpenType opentype;
+		std::map<QString, ModelId> dependencies = {};
 	};
+	struct LoadDepedenciesBag;
+	int modelIdCounter = 0;
 	int untitledNameCounter = 0;
-	std::map<QString, ModelInfo> openModels;
-	void loadDependenciesForModel(const QString& modelName,
-		const QString& path,
-		const LibraryManager& libraries,
-		QStringList& missing,
-		QStringList& processed,
-		QTextStream& errorStream);
+	std::map<ModelId, ModelInfo> openModels;
+	void loadDependenciesForModel(const ModelId modelId, const QString& path, LoadDepedenciesBag& bag);
+	void collectReferences(QSet<QString> &referenced, const QString& name, const Model* model);
+	void updateDependencies(ModelInfo* model);
+	void prune();
+	bool isReferencedByAnything(const ModelId modelId) const;
 };
 
 QString pathToName(const QFileInfo& path);
--- a/src/linetypes/object.h	Sun Oct 24 11:33:32 2021 +0300
+++ b/src/linetypes/object.h	Tue Nov 02 15:43:57 2021 +0200
@@ -22,6 +22,7 @@
 
 struct ldraw::GetPolygonsContext
 {
+	::ModelId modelId;
 	::DocumentManager* documents;
 };
 
--- a/src/linetypes/subfilereference.cpp	Sun Oct 24 11:33:32 2021 +0300
+++ b/src/linetypes/subfilereference.cpp	Tue Nov 02 15:43:57 2021 +0200
@@ -45,11 +45,11 @@
 	GetPolygonsContext* context
 ) const
 {
-	Model* model = this->resolve(context->documents);
-	if (model != nullptr)
+	Model* referencedModel = this->resolve(context->modelId, context->documents);
+	if (referencedModel != nullptr)
 	{
 		const bool needInverting = glm::determinant(this->transformation) < 0;
-		const std::vector<gl::Polygon> modelPolygons = model->getPolygons(context->documents);
+		const std::vector<gl::Polygon> modelPolygons = referencedModel->getPolygons(context->documents);
 		polygons.reserve(polygons.size() + modelPolygons.size());
 		for (gl::Polygon polygon : modelPolygons)
 		{
@@ -83,9 +83,9 @@
 	this->isInverted = not this->isInverted;
 }
 
-Model* ldraw::SubfileReference::resolve(DocumentManager* documents) const
+Model* ldraw::SubfileReference::resolve(const ModelId callingModelId, DocumentManager* documents) const
 {
-	return documents->findModelByName(this->referenceName);
+	return documents->findDependencyByName(callingModelId, this->referenceName);
 }
 
 ldraw::Object::Type ldraw::SubfileReference::typeIdentifier() const
--- a/src/linetypes/subfilereference.h	Sun Oct 24 11:33:32 2021 +0300
+++ b/src/linetypes/subfilereference.h	Tue Nov 02 15:43:57 2021 +0200
@@ -21,7 +21,7 @@
 	void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override;
 	glm::vec3 position() const;
 	void invert() override;
-	Model* resolve(DocumentManager* documents) const;
+	Model* resolve(const ModelId callingModelId, DocumentManager* documents) const;
 	Type typeIdentifier() const override;
 	QDataStream& serialize(QDataStream& stream) const override;
 	QDataStream& deserialize(QDataStream& stream) override;
--- a/src/main.cpp	Sun Oct 24 11:33:32 2021 +0300
+++ b/src/main.cpp	Tue Nov 02 15:43:57 2021 +0200
@@ -29,6 +29,10 @@
 	QCoreApplication::setOrganizationDomain("hecknology.net");
 	::qRegisterMetaTypeStreamOperators<Library>("Library");
 	::qRegisterMetaTypeStreamOperators<Libraries>("Libraries");
+	
+	glm::mat4 mat = glm::scale(glm::mat4{1}, {3, 3, 3});
+	glm::vec4 x = {1,2,3,4};
+	
 	QApplication app{argc, argv};
 	/*
 	QMessageBox::information(nullptr, "", QMetaType::typeName( qMetaTypeId<ldraw::Color>() ));
--- a/src/main.h	Sun Oct 24 11:33:32 2021 +0300
+++ b/src/main.h	Tue Nov 02 15:43:57 2021 +0200
@@ -226,6 +226,44 @@
 	>{map};
 }
 
+template<typename T, typename IdentifierType>
+struct TypeValue
+{
+	T value;
+	bool operator==(TypeValue<T, IdentifierType> other) const
+	{
+		return value == other.value;
+	}
+	bool operator!=(TypeValue<T, IdentifierType> other) const
+	{
+		return value != other.value;
+	}
+	bool operator<(TypeValue<T, IdentifierType> other) const
+	{
+		return value < other.value;
+	}
+	bool operator>(TypeValue<T, IdentifierType> other) const
+	{
+		return value > other.value;
+	}
+	bool operator<=(TypeValue<T, IdentifierType> other) const
+	{
+		return value <= other.value;
+	}
+	bool operator>=(TypeValue<T, IdentifierType> other) const
+	{
+		return value >= other.value;
+	}
+};
+
+template<typename T, typename R>
+int qHash(TypeValue<T, R> value)
+{
+	return qHash(value.value);
+}
+
+using ModelId = TypeValue<int, struct TypeValueModelId>;
+
 /**
  * Iterates a @c glm::mat
  */
--- a/src/mainwindow.cpp	Sun Oct 24 11:33:32 2021 +0300
+++ b/src/mainwindow.cpp	Tue Nov 02 15:43:57 2021 +0200
@@ -120,10 +120,14 @@
 {
 	QString errorString;
 	QTextStream errorStream{&errorString};
-	QString modelName = this->documents.openModel(path, errorStream);
-	if (not modelName.isEmpty())
+	std::optional<ModelId> modelIdOpt = this->documents.openModel(
+		path,
+		errorStream,
+		DocumentManager::OpenType::ManuallyOpened);
+	if (modelIdOpt.has_value())
 	{
-		this->documents.loadDependenciesForModel(modelName, path, this->libraries, errorStream);
+		const ModelId modelId = modelIdOpt.value();
+		this->documents.loadDependenciesForModel(modelId, path, this->libraries, errorStream);
 		if (not errorString.isEmpty())
 		{
 			QMessageBox::warning(
@@ -131,7 +135,7 @@
 				tr("Problem loading references"),
 				errorString);
 		}
-		this->openModelForEditing(modelName);
+		this->openModelForEditing(modelId);
 		this->addRecentlyOpenedFile(path);
 	}
 	else
@@ -181,15 +185,16 @@
 	this->updateRecentlyOpenedDocumentsMenu();
 }
 
-void MainWindow::openModelForEditing(const QString& modelName)
+void MainWindow::openModelForEditing(const ModelId modelId)
 {
-	Document* document = new Document{this->documents.findModelByName(modelName), &this->documents, this->colorTable};
+	Document* document = new Document{this->documents.getModelById(modelId), &this->documents, this->colorTable};
 	document->setRenderPreferences(this->renderPreferences);
 	connect(document, &Document::newStatusText, [&](const QString& newStatusText)
 	{
 		this->statusBar()->showMessage(newStatusText);
 	});
-	this->ui->tabs->addTab(document, modelName);
+	const QFileInfo fileInfo{*this->documents.modelPath(modelId)};
+	this->ui->tabs->addTab(document, fileInfo.baseName());
 	this->ui->tabs->setCurrentWidget(document);
 	document->restoreSplitterState(this->documentSplitterState);
 }
@@ -209,6 +214,7 @@
 	return qobject_cast<Document*>(this->ui->tabs->currentWidget());
 }
 
+/*
 void MainWindow::closeDocument(Document *document)
 {
 	const int tabIndex = this->ui->tabs->indexOf(document);
@@ -228,6 +234,7 @@
 		}
 	}
 }
+*/
 
 void MainWindow::handleDocumentSplitterChange()
 {
@@ -283,7 +290,9 @@
 {
 	if (this->currentDocument() != nullptr)
 	{
-		if (this->currentDocument()->modelPath().isEmpty())
+		const ModelId modelId = {0};
+		const QString* path = this->documents.modelPath(modelId);
+		if (path == nullptr or path->isEmpty())
 		{
 			this->actionSaveAs();
 		}
@@ -291,14 +300,14 @@
 		{
 			QString error;
 			QTextStream errorStream{&error};
-			const bool succeeded = this->currentDocument()->save(errorStream);
+			const bool succeeded = this->documents.saveModel(modelId, errorStream);
 			if (not succeeded)
 			{
 				QMessageBox::critical(this, tr("Save error"), error);
 			}
 			else
 			{
-				this->addRecentlyOpenedFile(this->currentDocument()->modelPath());
+				this->addRecentlyOpenedFile(*path);
 			}
 		}
 	}
@@ -311,16 +320,20 @@
 {
 	if (this->currentDocument() != nullptr)
 	{
-		const QString dir = QFileInfo{this->currentDocument()->modelPath()}.absoluteDir().path();
+		const ModelId modelId = {0};
+		const QString* pathPtr = this->documents.modelPath(modelId);
+		QString defaultPath = (pathPtr != nullptr) ? *pathPtr : "";
 		const QString newPath = QFileDialog::getSaveFileName(
 			this,
 			tr("Save as…"),
-			dir, 
+			QFileInfo{defaultPath}.absoluteDir().path(), 
 			tr("LDraw files (*.ldr *dat);;All files (*)")
 		);
 		if (not newPath.isEmpty())
 		{
-			this->currentDocument()->setModelPath(newPath);
+			QString error;
+			QTextStream errorStream{&error};
+			this->documents.setModelPath(modelId, newPath, this->libraries, errorStream);
 			this->ui->tabs->setTabText(this->ui->tabs->currentIndex(), QFileInfo{newPath}.fileName());
 			this->actionSave();
 		}
--- a/src/mainwindow.h	Sun Oct 24 11:33:32 2021 +0300
+++ b/src/mainwindow.h	Tue Nov 02 15:43:57 2021 +0200
@@ -71,9 +71,8 @@
 	void restoreSettings();
 	void changeLanguage(QString localeCode);
 	void addRecentlyOpenedFile(const QString& path);
-	void openModelForEditing(const QString& modelName);
+	void openModelForEditing(const ModelId modelId);
 	static QString pathToTranslation(const QString& localeCode);
 	void loadColors();
 	Document *currentDocument();
-	void closeDocument(Document* document);
 };
--- a/src/model.cpp	Sun Oct 24 11:33:32 2021 +0300
+++ b/src/model.cpp	Tue Nov 02 15:43:57 2021 +0200
@@ -23,22 +23,14 @@
 #include <QSaveFile>
 #include "model.h"
 #include "modeleditcontext.h"
+#include "documentmanager.h"
 
 /**
  * @brief Constructs a model
  * @param parent QObject parent to pass forward
  */
-Model::Model(QObject* parent) : 
-	Model{"", parent} {}
-
-/**
- * @brief Constructs a model
- * @param path Path that was used to open the model
- * @param parent QObject parent to pass forward
- */
-Model::Model(const QString& path, QObject *parent) :
-	QAbstractListModel{parent},
-	storedPath{path}
+Model::Model(QObject *parent) :
+	QAbstractListModel{parent}
 {
 	connect(this, &Model::dataChanged, [&](){ this->needRecache = true; });
 }
@@ -150,10 +142,14 @@
 	if (this->needRecache)
 	{
 		this->cachedPolygons.clear();
-		ldraw::GetPolygonsContext context{documents};
-		for (int i = 0; i < this->size(); i += 1)
+		const std::optional<ModelId> modelId = documents->findIdForModel(this);
+		if (modelId.has_value())
 		{
-			this->getObjectPolygons(i, this->cachedPolygons, &context);
+			ldraw::GetPolygonsContext context{modelId.value(), documents};
+			for (int i = 0; i < this->size(); i += 1)
+			{
+				this->getObjectPolygons(i, this->cachedPolygons, &context);
+			}
 		}
 		this->needRecache = false;
 	}
@@ -188,15 +184,7 @@
 	return this->objectAt(index)->id;
 }
 
-/**
- * @brief Gets the path to the model
- * @return path
- */
-const QString& Model::path() const
-{
-	return this->storedPath;
-}
-
+#if 0
 /**
  * @brief Sets the path to the model
  * @param path New path to use
@@ -220,6 +208,7 @@
 		}
 	}
 }
+#endif
 
 /**
  * @brief Gets the GL polygons of the object at the specified position in the model
@@ -304,39 +293,13 @@
 
 /**
  * @brief Attempts the save the model
- * @param errors Where to write any errors
- * @returns whether it succeeded
  */
-bool Model::save(QTextStream &errors) const
+void Model::save(QIODevice* device) const
 {
-	QSaveFile file{this->path()};
-	file.setDirectWriteFallback(true);
-	if (file.open(QSaveFile::WriteOnly))
+	QTextStream out{device};
+	for (const ModelObjectPointer& object : this->body)
 	{
-		QTextStream out{&file};
-		for (const ModelObjectPointer& object : this->body)
-		{
-			out << object.get()->toLDrawCode() << "\r\n";
-		}
-		const bool commitSucceeded = file.commit();
-		if (not commitSucceeded)
-		{
-			errors << tr("Could not save to %1: %2")
-				.arg(this->path())
-				.arg(file.errorString());
-			return false;
-		}
-		else
-		{
-			return true;
-		}
-	}
-	else
-	{
-		errors << tr("Could not open %1 for writing: %2")
-			.arg(file.fileName())
-			.arg(file.errorString());
-		return false;
+		out << object.get()->toLDrawCode() << "\r\n";
 	}
 }
 
--- a/src/model.h	Sun Oct 24 11:33:32 2021 +0300
+++ b/src/model.h	Tue Nov 02 15:43:57 2021 +0200
@@ -35,7 +35,6 @@
 	Q_OBJECT
 public:
 	class EditContext;
-	Model(const QString& path, QObject* parent = nullptr);
 	Model(QObject* parent = nullptr);
 	Model(const Model&) = delete;
 	int size() const;
@@ -67,9 +66,7 @@
 	Get2Result<R> get2(ldraw::Id<R> id) const;
 	template<typename R, typename Fn>
 	void apply(Fn f) const;
-	const QString& path() const;
-	void setPath(const QString& path);
-	bool save(QTextStream& errors) const;
+	void save(QIODevice *device) const;
 	void makeUnofficial();
 Q_SIGNALS:
 	void objectAdded(ldraw::id_t id, int position);
@@ -95,7 +92,6 @@
 	void editFinished();
 	void objectModified(ldraw::id_t id);
 	bool modified = false;
-	QString storedPath;
 	LDHeader header;
 	std::vector<ModelObjectPointer> body;
 	std::map<ldraw::id_t, ldraw::Object*> objectsById;
@@ -237,7 +233,7 @@
 	ldraw::PropertyType<Property> result;
 	if (object != nullptr)
 	{
-		result = object->getProperty<Property>()
+		result = object->getProperty<Property>();
 	}
 	return result;
-}
\ No newline at end of file
+}

mercurial