Tue, 28 Sep 2021 23:07:23 +0300
Use QSaveFile to save the file more safely
/* * LDForge: LDraw parts authoring CAD * Copyright (C) 2013 - 2020 Teemu Piippo * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #pragma once #include <QAbstractListModel> #include <memory> #include "main.h" #include "header.h" #include "linetypes/object.h" #include "linetypes/metacommand.h" #include "gl/common.h" enum class HeaderProperty { Name }; class Model : public QAbstractListModel { Q_OBJECT public: class EditContext; Model(const QString& path, QObject* parent = nullptr); Model(QObject* parent = nullptr); Model(const Model&) = delete; int size() const; ldraw::id_t at(int index) const; EditContext edit(); int rowCount(const QModelIndex&) const override; QVariant data(const QModelIndex& index, int role) const override; QVariant getHeaderProperty(const HeaderProperty property); const QString& getName() const; QVariant getObjectProperty(const int index, const ldraw::Property property) const; std::vector<gl::Polygon> getPolygons(class DocumentManager* documents) const; QModelIndex lookup(ldraw::id_t id) const; ldraw::id_t resolve(const QModelIndex& index) const; template<typename R> ldraw::Id<R> checkType(ldraw::id_t id) const; template<typename R> bool isA(ldraw::id_t id) const; template<typename R> const R* get(ldraw::Id<R> id) const; template<typename R> struct Get2Result { QModelIndex index; const R* object; }; template<typename R> 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 makeUnofficial(); Q_SIGNALS: void objectAdded(ldraw::id_t id, int position); void objectModified(ldraw::id_t id, int position); private: using ModelObjectPointer = std::unique_ptr<ldraw::Object>; template<typename T, typename... Args> ldraw::Id<T> append(Args&&... args); void append(ModelObjectPointer&& object); template<typename T, typename... Args> ldraw::Id<T> insert(std::size_t position, Args&&... args); void remove(int position); ldraw::Object* objectAt(const QModelIndex& index); const ldraw::Object* objectAt(const QModelIndex& index) const; template<typename T> T* objectAt(ldraw::Id<T> id); template<typename T> const T* objectAt(ldraw::Id<T> id) const; void getObjectPolygons( const int index, std::vector<gl::Polygon>& polygons_out, ldraw::GetPolygonsContext* context) const; 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; mutable std::vector<gl::Polygon> cachedPolygons; mutable bool needRecache = true; /** * @brief Amount of model edit contexts active */ int editCounter = 0; }; /** * @brief Checks whether the id is exactly of the specified type * @tparam R Type of LDraw line type object to test for * @param id Id of object to test * @returns whether the type of the object specified by @c id is the same type as R. Returns false if it is a subclass. */ template<typename R> bool Model::isA(ldraw::id_t id) const { const ldraw::Object* object = this->objectAt(this->lookup(id)); const std::type_info& a = typeid(*object); const std::type_info& b = typeid(R); return a == b; } /** * @brief Calls the specified function to all matching objects in the model * @tparam R Type of LDraw line type object to filter by * @param fn Function to call. */ template<typename R, typename Fn> void Model::apply(Fn f) const { for (const ModelObjectPointer& object : this->body) { const R* subobject = dynamic_cast<const R*>(object.get()); if (subobject != nullptr) { f(subobject); } } } /** * \brief Checks type of object behind id * Checks whether the specified id refers to an object of the specified type. * \returns id casted to subclass if appropriate, null id otherwise */ template<typename R> ldraw::Id<R> Model::checkType(ldraw::id_t id) const { if (dynamic_cast<const R*>(this->objectAt(this->lookup(id))) != nullptr) { return ldraw::Id<R>{id.value}; } else { return ldraw::NULL_ID; } } template<typename T, typename... Args> ldraw::Id<T> Model::append(Args&&... args) { const int position = static_cast<int>(this->body.size()); Q_EMIT beginInsertRows({}, position, position); this->body.push_back(std::make_unique<T>(args...)); ldraw::Object* pointer = this->body.back().get(); this->objectsById[pointer->id] = pointer; Q_EMIT objectAdded(pointer->id, static_cast<int>(this->body.size() - 1)); Q_EMIT endInsertRows(); this->needRecache = true; return ldraw::Id<T>{pointer->id.value}; } template<typename T, typename... Args> ldraw::Id<T> Model::insert(const std::size_t position, Args&&... args) { Q_EMIT beginInsertRows({}, position, position); this->body.insert(std::begin(this->body) + position, std::make_unique<T>(args...)); ldraw::Object* pointer = this->body[position].get(); this->objectsById[pointer->id] = pointer; Q_EMIT objectAdded(pointer->id, static_cast<int>(position)); Q_EMIT endInsertRows(); this->needRecache = true; return ldraw::Id<T>{pointer->id.value}; } template<typename R> const R* Model::get(ldraw::Id<R> id) const { return this->get2(id).object; } template<typename R> Model::Get2Result<R> Model::get2(const ldraw::Id<R> id) const { Get2Result<R> result; result.index = this->lookup(id); if (result.index.isValid()) { result.object = static_cast<const R*>(this->objectAt(result.index)); } else { result.object = nullptr; } return result; } /** * @brief Gets an object pointer by id. Used by the editing context to actually modify objects. * @param id * @return object pointer */ template<typename T> T* Model::objectAt(ldraw::Id<T> id) { return static_cast<T*>(this->objectAt(this->lookup(id))); } template<typename T> const T* Model::objectAt(ldraw::Id<T> id) const { return static_cast<const T*>(this->objectAt(this->lookup(id))); }