Tue, 28 Sep 2021 00:10:29 +0300
Saving works now
--- a/src/document.cpp Mon Sep 27 21:04:45 2021 +0300 +++ b/src/document.cpp Tue Sep 28 00:10:29 2021 +0300 @@ -239,3 +239,33 @@ { 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 Mon Sep 27 21:04:45 2021 +0300 +++ b/src/document.h Tue Sep 28 00:10:29 2021 +0300 @@ -48,6 +48,9 @@ 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/linetypes/comment.cpp Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/comment.cpp Tue Sep 28 00:10:29 2021 +0300 @@ -13,3 +13,8 @@ return Type::Comment; } +QString ldraw::Comment::toLDrawCode() const +{ + return ("0 // " + this->storedText).trimmed(); +} +
--- a/src/linetypes/comment.h Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/comment.h Tue Sep 28 00:10:29 2021 +0300 @@ -12,4 +12,5 @@ using MetaCommand::MetaCommand; QFont textRepresentationFont() const override; Type typeIdentifier() const override; + QString toLDrawCode() const override; };
--- a/src/linetypes/conditionaledge.cpp Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/conditionaledge.cpp Tue Sep 28 00:10:29 2021 +0300 @@ -13,3 +13,14 @@ { return Type::ConditionalEdge; } + +QString ldraw::ConditionalEdge::toLDrawCode() const +{ + return utility::format( + "5 %1 %2 %3 %4 %5", + this->colorIndex.index, + utility::vertexToString(this->points[0]), + utility::vertexToString(this->points[1]), + utility::vertexToString(this->points[2]), + utility::vertexToString(this->points[3])); +}
--- a/src/linetypes/conditionaledge.h Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/conditionaledge.h Tue Sep 28 00:10:29 2021 +0300 @@ -12,4 +12,5 @@ using PolygonObject<4>::PolygonObject; QString textRepresentation() const override; Type typeIdentifier() const override; + QString toLDrawCode() const override; };
--- a/src/linetypes/edge.cpp Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/edge.cpp Tue Sep 28 00:10:29 2021 +0300 @@ -20,3 +20,12 @@ { return Type::EdgeLine; } + +QString ldraw::Edge::toLDrawCode() const +{ + return utility::format( + "2 %1 %2 %3", + this->colorIndex.index, + utility::vertexToString(this->points[0]), + utility::vertexToString(this->points[1])); +}
--- a/src/linetypes/edge.h Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/edge.h Tue Sep 28 00:10:29 2021 +0300 @@ -13,4 +13,5 @@ QString textRepresentation() const override; void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override; Type typeIdentifier() const override; + QString toLDrawCode() const override; };
--- a/src/linetypes/errorline.cpp Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/errorline.cpp Tue Sep 28 00:10:29 2021 +0300 @@ -56,3 +56,8 @@ { return ldraw::Object::deserialize(stream) >> this->text; } + +QString ldraw::ErrorLine::toLDrawCode() const +{ + return this->text; +}
--- a/src/linetypes/errorline.h Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/errorline.h Tue Sep 28 00:10:29 2021 +0300 @@ -18,6 +18,7 @@ Type typeIdentifier() const override; QDataStream& serialize(QDataStream& stream) const override; QDataStream& deserialize(QDataStream& stream) override; + QString toLDrawCode() const override; QString text; QString message; protected:
--- a/src/linetypes/metacommand.cpp Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/metacommand.cpp Tue Sep 28 00:10:29 2021 +0300 @@ -51,3 +51,8 @@ return ldraw::Object::deserialize(stream) >> this->storedText; } +QString ldraw::MetaCommand::toLDrawCode() const +{ + return "0 " + this->storedText; +} +
--- a/src/linetypes/metacommand.h Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/metacommand.h Tue Sep 28 00:10:29 2021 +0300 @@ -17,6 +17,7 @@ Type typeIdentifier() const override; QDataStream& serialize(QDataStream& stream) const override; QDataStream& deserialize(QDataStream& stream) override; + QString toLDrawCode() const override; protected: void setProperty(SetPropertyResult* result, const PropertyKeyValue& pair) override; };
--- a/src/linetypes/object.cpp Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/object.cpp Tue Sep 28 00:10:29 2021 +0300 @@ -151,3 +151,8 @@ { return Type::Empty; } + +QString ldraw::Empty::toLDrawCode() const +{ + return ""; +}
--- a/src/linetypes/object.h Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/object.h Tue Sep 28 00:10:29 2021 +0300 @@ -62,6 +62,8 @@ virtual bool hasColor() const; virtual QVariant getProperty(Property id) const; template<ldraw::Property property> + PropertyType<property> getProperty() const; + template<ldraw::Property property> SetPropertyResult setProperty(const PropertyType<property>& value); SetPropertyResult setProperty(const PropertyKeyValue& pair); virtual QString textRepresentation() const = 0; @@ -75,6 +77,7 @@ virtual QDataStream& serialize(QDataStream& stream) const; virtual QDataStream& deserialize(QDataStream& stream); virtual Type typeIdentifier() const = 0; + virtual QString toLDrawCode() const = 0; protected: template<Property property, typename Function> @@ -100,6 +103,12 @@ } } +template<ldraw::Property property> +ldraw::PropertyType<property> ldraw::Object::getProperty() const +{ + return this->getProperty(property).value<ldraw::PropertyType<property>>(); +} + class ldraw::ColoredObject : public Object { public: @@ -120,4 +129,5 @@ { QString textRepresentation() const override; Type typeIdentifier() const override; + QString toLDrawCode() const override; };
--- a/src/linetypes/quadrilateral.cpp Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/quadrilateral.cpp Tue Sep 28 00:10:29 2021 +0300 @@ -34,3 +34,14 @@ { return Type::Quadrilateral; } + +QString ldraw::Quadrilateral::toLDrawCode() const +{ + return utility::format( + "4 %1 %2 %3 %4 %5", + this->colorIndex.index, + utility::vertexToString(this->points[0]), + utility::vertexToString(this->points[1]), + utility::vertexToString(this->points[2]), + utility::vertexToString(this->points[3])); +}
--- a/src/linetypes/quadrilateral.h Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/quadrilateral.h Tue Sep 28 00:10:29 2021 +0300 @@ -14,4 +14,5 @@ void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override; void invert() override; Type typeIdentifier() const override; + QString toLDrawCode() const override; };
--- a/src/linetypes/subfilereference.cpp Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/subfilereference.cpp Tue Sep 28 00:10:29 2021 +0300 @@ -36,13 +36,7 @@ QString ldraw::SubfileReference::textRepresentation() const { - QString out; - if (this->isInverted) - { - out += "0 BFC INVERTNEXT\r\n"; - } - out += referenceName + " " + utility::vertexToStringParens(this->position()); - return out; + return this->referenceName + " " + utility::vertexToStringParens(this->position()); } void ldraw::SubfileReference::getPolygons @@ -108,3 +102,29 @@ { return ColoredObject::deserialize(stream) >> this->transformation >> this->referenceName >> this->isInverted; } + +QString ldraw::SubfileReference::toLDrawCode() const +{ + QString result; + if (this->isInverted) + { + result += "0 BFC INVERTNEXT\r\n"; + } + result += utility::format( + "1 %1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14", + this->colorIndex.index, + this->transformation[3][0], + this->transformation[3][1], + this->transformation[3][2], + this->transformation[0][0], + this->transformation[1][0], + this->transformation[2][0], + this->transformation[0][1], + this->transformation[1][1], + this->transformation[2][1], + this->transformation[0][2], + this->transformation[1][2], + this->transformation[2][2], + this->referenceName); + return result; +}
--- a/src/linetypes/subfilereference.h Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/subfilereference.h Tue Sep 28 00:10:29 2021 +0300 @@ -25,6 +25,7 @@ Type typeIdentifier() const override; QDataStream& serialize(QDataStream& stream) const override; QDataStream& deserialize(QDataStream& stream) override; + QString toLDrawCode() const override; glm::mat4 transformation; QString referenceName; bool isInverted = false;
--- a/src/linetypes/triangle.cpp Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/triangle.cpp Tue Sep 28 00:10:29 2021 +0300 @@ -32,3 +32,13 @@ { return Type::Triangle; } + +QString ldraw::Triangle::toLDrawCode() const +{ + return utility::format( + "3 %1 %2 %3 %4", + this->colorIndex.index, + utility::vertexToString(this->points[0]), + utility::vertexToString(this->points[1]), + utility::vertexToString(this->points[2])); +}
--- a/src/linetypes/triangle.h Mon Sep 27 21:04:45 2021 +0300 +++ b/src/linetypes/triangle.h Tue Sep 28 00:10:29 2021 +0300 @@ -14,5 +14,6 @@ void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override; void invert() override; Type typeIdentifier() const override; + QString toLDrawCode() const override; };
--- a/src/main.h Mon Sep 27 21:04:45 2021 +0300 +++ b/src/main.h Tue Sep 28 00:10:29 2021 +0300 @@ -74,6 +74,8 @@ using edgeid_t = Id<class EdgeLine>; using conditionaledgeid_t = Id<class ConditionalEdge>; using subfileid_t = Id<class SubfileReference>; + using commentid_t = Id<class Comment>; + using metacommandid_t = Id<class MetaCommand>; constexpr struct NullId {
--- a/src/mainwindow.cpp Mon Sep 27 21:04:45 2021 +0300 +++ b/src/mainwindow.cpp Tue Sep 28 00:10:29 2021 +0300 @@ -72,6 +72,10 @@ this->currentDocument()->adjustGridToView(); } }); + connect(this->ui->actionSave, &QAction::triggered, + this, &MainWindow::actionSave); + connect(this->ui->actionSaveAs, &QAction::triggered, + this, &MainWindow::actionSaveAs); for (auto data : ::renderStyleButtons) { QAction* action = data.memberInstance(this->ui.get()); @@ -252,6 +256,56 @@ this->updateRenderPreferences(); } +/** + * @brief Handles the "Save" (Ctrl+S) action + */ +void MainWindow::actionSave() +{ + if (this->currentDocument() != nullptr) + { + if (this->currentDocument()->modelPath().isEmpty()) + { + this->actionSaveAs(); + } + else + { + QString error; + QTextStream errorStream{&error}; + const bool succeeded = this->currentDocument()->save(errorStream); + if (not succeeded) + { + QMessageBox::critical(this, tr("Save error"), error); + } + else + { + this->addRecentlyOpenedFile(this->currentDocument()->modelPath()); + } + } + } +} + +/** + * @brief Handles the "Save as…" (Ctrl+Shift+S) action + */ +void MainWindow::actionSaveAs() +{ + if (this->currentDocument() != nullptr) + { + const QString dir = QFileInfo{this->currentDocument()->modelPath()}.absoluteDir().path(); + const QString newPath = QFileDialog::getSaveFileName( + this, + tr("Save as…"), + dir, + tr("LDraw files (*.ldr *dat);;All files (*)") + ); + if (not newPath.isEmpty()) + { + this->currentDocument()->setModelPath(newPath); + this->actionSave(); + } + } +} + void MainWindow::changeEvent(QEvent* event) { if (event != nullptr)
--- a/src/mainwindow.h Mon Sep 27 21:04:45 2021 +0300 +++ b/src/mainwindow.h Tue Sep 28 00:10:29 2021 +0300 @@ -44,6 +44,8 @@ void updateRecentlyOpenedDocumentsMenu(); void openRecentFile(); void setRenderStyle(gl::RenderStyle renderStyle); + void actionSave(); + void actionSaveAs(); protected: void changeEvent(QEvent* event) override; void closeEvent(QCloseEvent* event) override;
--- a/src/mainwindow.ui Mon Sep 27 21:04:45 2021 +0300 +++ b/src/mainwindow.ui Tue Sep 28 00:10:29 2021 +0300 @@ -40,6 +40,8 @@ </widget> <addaction name="actionNew"/> <addaction name="actionOpen"/> + <addaction name="actionSave"/> + <addaction name="actionSaveAs"/> <addaction name="menuRecentFiles"/> <addaction name="separator"/> <addaction name="actionSettingsEditor"/> @@ -127,6 +129,22 @@ <string>Ctrl+G</string> </property> </action> + <action name="actionSave"> + <property name="text"> + <string>Save</string> + </property> + <property name="shortcut"> + <string>Ctrl+S</string> + </property> + </action> + <action name="actionSaveAs"> + <property name="text"> + <string>Save as…</string> + </property> + <property name="shortcut"> + <string>Ctrl+Shift+S</string> + </property> + </action> </widget> <resources/> <connections/>
--- a/src/model.cpp Mon Sep 27 21:04:45 2021 +0300 +++ b/src/model.cpp Tue Sep 28 00:10:29 2021 +0300 @@ -18,6 +18,7 @@ #include <QBrush> #include <QFile> +#include <QFileInfo> #include <QFont> #include "model.h" #include "modeleditcontext.h" @@ -36,7 +37,7 @@ */ Model::Model(const QString& path, QObject *parent) : QAbstractListModel{parent}, - path{path} + storedPath{path} { connect(this, &Model::dataChanged, [&](){ this->needRecache = true; }); } @@ -187,6 +188,39 @@ } /** + * @brief Gets the path to the model + * @return path + */ +const QString& Model::path() const +{ + return this->storedPath; +} + +/** + * @brief Sets the path to the model + * @param path New path to use + */ +void Model::setPath(const QString &path) +{ + this->storedPath = path; + this->header.name = QFileInfo{path}.fileName(); + // Update the "Name: 1234.dat" comment + if (this->body.size() >= 2) + { + const ldraw::id_t id = this->body[1]->id; + if (this->isA<ldraw::MetaCommand>(id)) + { + const QString& textBody = this->body[1]->getProperty<ldraw::Property::Text>(); + if (textBody.startsWith("Name: ")) + { + auto editor = this->edit(); + editor.setObjectProperty<ldraw::Property::Text>(id, "Name: " + this->header.name); + } + } + } +} + +/** * @brief Gets the GL polygons of the object at the specified position in the model * @param index Index of object in the model * @param polygons_out Vector to add polygons into @@ -270,16 +304,60 @@ /** * @brief Write out the model as text */ -void Model::save() const +bool Model::save(QTextStream &errors) const { - QFile file{this->path}; + QFile file{this->storedPath}; if (file.open(QIODevice::WriteOnly)) { QTextStream out{&file}; for (const ModelObjectPointer& object : this->body) { - out << object.get()->textRepresentation() << "\r\n"; + out << object.get()->toLDrawCode() << "\r\n"; } file.close(); + if (out.status() != QTextStream::Ok) + { + errors << tr("Write error while writing to %1").arg(this->storedPath); + return false; + } + else + { + return true; + } + } + else + { + errors << tr("Could not open %1 for writing: %2") + .arg(this->storedPath) + .arg(file.errorString()); + return false; + } +} + +/** + * @brief Modifies the !LDRAW_ORG line so that it becomes unofficial + */ +void Model::makeUnofficial() +{ + if (this->body.size() >= 4) + { + const ldraw::id_t id = this->body[3]->id; + if (this->isA<ldraw::MetaCommand>(id)) + { + const QString& body = this->body[3]->getProperty<ldraw::Property::Text>(); + if (body.startsWith("!LDRAW_ORG ") and not body.startsWith("!LDRAW_ORG Unofficial_")) + { + QStringList tokens = body.split(" "); + tokens[1] = "Unofficial_" + tokens[1]; + // Remove the UPDATE tag + if (tokens.size() >= 4 && tokens[2] == "UPDATE") + { + tokens.removeAt(3); + tokens.removeAt(2); + } + EditContext editor = this->edit(); + editor.setObjectProperty<ldraw::Property::Text>(id, tokens.join(" ")); + } + } } } \ No newline at end of file
--- a/src/model.h Mon Sep 27 21:04:45 2021 +0300 +++ b/src/model.h Tue Sep 28 00:10:29 2021 +0300 @@ -22,6 +22,7 @@ #include "main.h" #include "header.h" #include "linetypes/object.h" +#include "linetypes/metacommand.h" #include "gl/common.h" enum class HeaderProperty @@ -51,6 +52,8 @@ 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 @@ -62,7 +65,10 @@ Get2Result<R> get2(ldraw::Id<R> id) const; template<typename R, typename Fn> void apply(Fn f) const; - void save() 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); @@ -87,7 +93,7 @@ void editFinished(); void objectModified(ldraw::id_t id); bool modified = false; - QString path; + QString storedPath; LDHeader header; std::vector<ModelObjectPointer> body; std::map<ldraw::id_t, ldraw::Object*> objectsById; @@ -100,6 +106,21 @@ }; /** + * @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.
--- a/src/parser.cpp Mon Sep 27 21:04:45 2021 +0300 +++ b/src/parser.cpp Tue Sep 28 00:10:29 2021 +0300 @@ -253,6 +253,7 @@ } invertNext = false; } + /* // Test quadrilateral splitting by splitting all the quadrilaterals QVector<ldraw::quadrilateralid_t> quadrilateral_ids; for (int i = 0; i < editor.model().size(); i += 1) @@ -268,6 +269,7 @@ { ldraw::splitQuadrilateral(editor, id); } + */ } static ldraw::Color colorFromString(const QString& colorString) @@ -344,11 +346,12 @@ Q_UNUSED(tokens) if (line.startsWith("0 //")) { - return std::make_unique<ldraw::Comment>(line.mid(std::strlen("0 //")).simplified()); + // lol wut + return std::make_unique<ldraw::Comment>(line.mid(std::strlen("0 //")).trimmed()); } else { - return std::make_unique<ldraw::MetaCommand>(line.mid(1).simplified()); + return std::make_unique<ldraw::MetaCommand>(line.mid(1).trimmed()); } } @@ -394,7 +397,7 @@ std::unique_ptr<ldraw::Object> Parser::parseFromString(QString line) { - line = line.simplified(); + line = line.trimmed(); try { const QStringList tokens = line.split(QRegExp{R"(\s+)"});
--- a/src/utility.h Mon Sep 27 21:04:45 2021 +0300 +++ b/src/utility.h Tue Sep 28 00:10:29 2021 +0300 @@ -71,6 +71,16 @@ return std::find(std::begin(container), std::end(container), value) != std::end(container); } + /** + * @brief Converts the specified vertex to a simple string + * @param vertex vertex to convert + * @return "x y z"-formatted string + */ + inline QString vertexToString(const glm::vec3& vertex) + { + return utility::format("%1 %2 %3", vertex.x, vertex.y, vertex.z); + } + inline QString vertexToStringParens(const glm::vec3& vertex) { return utility::format("(%1, %2, %3)", vertex.x, vertex.y, vertex.z);