# HG changeset patch # User Teemu Piippo # Date 1635860637 -7200 # Node ID e1ced2523cadcafdebbc0b117f53f0f1deaa16b1 # Parent 37f936073cac97b11c1ae8eecebec88eefed78ef reworking diff -r 37f936073cac -r e1ced2523cad CMakeLists.txt --- 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}") diff -r 37f936073cac -r e1ced2523cad src/basics.h --- 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 +const R* findInMap(const std::map& 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 +R* findInMap(std::map& map, K&& key) +{ + auto pair = map.find(key); + if (pair != map.end()) + { + return &pair->second; + } + else + { + return nullptr; + } +} + template constexpr std::enable_if_t, T> PI = static_cast(M_PIl); diff -r 37f936073cac -r e1ced2523cad src/document.cpp --- 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); -} - diff -r 37f936073cac -r e1ced2523cad src/document.h --- 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(); diff -r 37f936073cac -r e1ced2523cad src/documentmanager.cpp --- 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 #include #include +#include #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(), + .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 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 newModel = std::make_unique(path); + std::unique_ptr newModel = std::make_unique(this); QTextStream textStream{&file}; Model::EditContext editor = newModel->edit(); Parser parser{file}; parser.parseBody(editor); - QString result; + std::optional 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 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 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& 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 DocumentManager::findIdForModel(const Model *model) const { - if (not referenced.contains(name)) + std::optional result; + for (auto it = this->openModels.begin(); it != this->openModels.end(); ++it) { - referenced.insert(name); - model->apply([&](const ldraw::SubfileReference* referenceObject) + if (it->second.model.get() == model) { - const ldraw::id_t id = referenceObject->id; - const QString& referenceName = model->getObjectProperty(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 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 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); } } } diff -r 37f936073cac -r e1ced2523cad src/documentmanager.h --- 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 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 findIdForModel(const Model* model) const; private: - void collectReferences(QSet &referenced, const QString& name, const Model* model); struct ModelInfo { std::unique_ptr model; + QString path; OpenType opentype; + std::map dependencies = {}; }; + struct LoadDepedenciesBag; + int modelIdCounter = 0; int untitledNameCounter = 0; - std::map openModels; - void loadDependenciesForModel(const QString& modelName, - const QString& path, - const LibraryManager& libraries, - QStringList& missing, - QStringList& processed, - QTextStream& errorStream); + std::map openModels; + void loadDependenciesForModel(const ModelId modelId, const QString& path, LoadDepedenciesBag& bag); + void collectReferences(QSet &referenced, const QString& name, const Model* model); + void updateDependencies(ModelInfo* model); + void prune(); + bool isReferencedByAnything(const ModelId modelId) const; }; QString pathToName(const QFileInfo& path); diff -r 37f936073cac -r e1ced2523cad src/linetypes/object.h --- 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; }; diff -r 37f936073cac -r e1ced2523cad src/linetypes/subfilereference.cpp --- 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 modelPolygons = model->getPolygons(context->documents); + const std::vector 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 diff -r 37f936073cac -r e1ced2523cad src/linetypes/subfilereference.h --- 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& 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; diff -r 37f936073cac -r e1ced2523cad src/main.cpp --- 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"); ::qRegisterMetaTypeStreamOperators("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() )); diff -r 37f936073cac -r e1ced2523cad src/main.h --- 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 +struct TypeValue +{ + T value; + bool operator==(TypeValue other) const + { + return value == other.value; + } + bool operator!=(TypeValue other) const + { + return value != other.value; + } + bool operator<(TypeValue other) const + { + return value < other.value; + } + bool operator>(TypeValue other) const + { + return value > other.value; + } + bool operator<=(TypeValue other) const + { + return value <= other.value; + } + bool operator>=(TypeValue other) const + { + return value >= other.value; + } +}; + +template +int qHash(TypeValue value) +{ + return qHash(value.value); +} + +using ModelId = TypeValue; + /** * Iterates a @c glm::mat */ diff -r 37f936073cac -r e1ced2523cad src/mainwindow.cpp --- 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 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(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(); } diff -r 37f936073cac -r e1ced2523cad src/mainwindow.h --- 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); }; diff -r 37f936073cac -r e1ced2523cad src/model.cpp --- 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 #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 = 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"; } } diff -r 37f936073cac -r e1ced2523cad src/model.h --- 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 get2(ldraw::Id id) const; template 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 body; std::map objectsById; @@ -237,7 +233,7 @@ ldraw::PropertyType result; if (object != nullptr) { - result = object->getProperty() + result = object->getProperty(); } return result; -} \ No newline at end of file +}