Tue, 07 Jun 2022 01:37:26 +0300
Continue giant refactor
--- a/CMakeLists.txt Mon Jun 06 22:01:22 2022 +0300 +++ b/CMakeLists.txt Tue Jun 07 01:37:26 2022 +0300 @@ -31,7 +31,6 @@ src/libraries.cpp src/invert.cpp src/main.cpp - src/mainwindow.cpp src/model.cpp src/parser.cpp src/polygoncache.cpp @@ -70,7 +69,6 @@ src/ldrawalgorithm.h src/libraries.h src/main.h - src/mainwindow.h src/model.h src/parser.h src/polygoncache.h
--- a/src/document.cpp Mon Jun 06 22:01:22 2022 +0300 +++ b/src/document.cpp Tue Jun 07 01:37:26 2022 +0300 @@ -154,7 +154,7 @@ drawState->previewPolygon.back() = drawState->previewPoint; if (drawState->previewPolygon.size() > 2) { - drawState->isconcave = not geom::isConvex(drawState->previewPolygon); + drawState->isconcave = not isConvex(drawState->previewPolygon); } } @@ -169,7 +169,7 @@ bool isCloseToExistingPoints(const std::vector<glm::vec3>& points, const glm::vec3 &pos) { - return any(points, std::bind(geom::isclose, std::placeholders::_1, pos)); + return any(points, std::bind(isclose, std::placeholders::_1, pos)); } void EditorTabWidget::canvasMouseClick(QMouseEvent *event)
--- a/src/documentmanager.cpp Mon Jun 06 22:01:22 2022 +0300 +++ b/src/documentmanager.cpp Tue Jun 07 01:37:26 2022 +0300 @@ -188,8 +188,8 @@ if (not bag.missing.empty()) { bag.missing.sort(Qt::CaseInsensitive); - errorStream << utility::format( - "The following files could not be opened: %1", + errorStream << format( + tr("The following files could not be opened: %1"), bag.missing.join(", ")); } }
--- a/src/geometry.cpp Mon Jun 06 22:01:22 2022 +0300 +++ b/src/geometry.cpp Tue Jun 07 01:37:26 2022 +0300 @@ -9,9 +9,9 @@ * @param plane * @return point of intersection. Does not return a value if the line is in parallel to the plane. */ -std::optional<glm::vec3> geom::linePlaneIntersection( - const geom::Line<3>& line, - const geom::Plane& plane, +std::optional<glm::vec3> linePlaneIntersection( + const Line<3>& line, + const Plane& plane, const float epsilon) { const float denominator = glm::dot(line.direction, plane.normal); @@ -31,9 +31,9 @@ * @param triangle * @return plane */ -geom::Plane geom::planeFromTriangle(const geom::Triangle& triangle) +Plane planeFromTriangle(const Triangle& triangle) { - return geom::Plane{normalVector(triangle), triangle.p1}; + return Plane{normalVector(triangle), triangle.p1}; } /** @@ -41,7 +41,7 @@ * @param triangle * @return normal vector */ -glm::vec3 geom::normalVector(const geom::Triangle& triangle) +glm::vec3 normalVector(const Triangle& triangle) { return glm::normalize( glm::cross( @@ -55,10 +55,10 @@ * @param matrix Matrix to compute * @return scaling vector and unscaled matrix */ -geom::ScalingExtract geom::extractScaling(const glm::mat4& matrix) +ScalingExtract extractScaling(const glm::mat4& matrix) { - geom::ScalingExtract result; - result.scaling = geom::scalingVector(matrix); + ScalingExtract result; + result.scaling = scalingVector(matrix); result.unscaled = glm::scale(matrix, 1.0f / result.scaling); return result; } @@ -68,7 +68,7 @@ * @param matrix * @return scaling vector */ -glm::vec3 geom::scalingVector(const glm::mat4 matrix) +glm::vec3 scalingVector(const glm::mat4 matrix) { auto component = [](const glm::mat4& matrix, const int i) -> float { @@ -77,7 +77,7 @@ return glm::vec3{component(matrix, 0), component(matrix, 1), component(matrix, 2)}; } -std::optional<glm::vec2> geom::lineLineIntersection(const Line<2>& line_1, const Line<2>& line_2) +std::optional<glm::vec2> lineLineIntersection(const Line<2>& line_1, const Line<2>& line_2) { const float denominator = (line_1.direction.x * line_2.direction.y) - (line_1.direction.y * line_2.direction.x); constexpr float epsilon = 1e-6f; @@ -103,7 +103,7 @@ } } -std::optional<glm::vec2> geom::rayLineSegmentIntersection(const Ray<2>& ray, const LineSegment2D& line) +std::optional<glm::vec2> rayLineSegmentIntersection(const Ray<2>& ray, const LineSegment2D& line) { std::optional<glm::vec2> result = lineLineIntersection( rayToLine(ray), @@ -127,7 +127,7 @@ return result; } -std::optional<geom::PointOnRectagle> geom::rayRectangleIntersection(const Ray<2>& ray, const QRectF& rectangle) +std::optional<PointOnRectagle> rayRectangleIntersection(const Ray<2>& ray, const QRectF& rectangle) { std::optional<glm::vec2> position; std::optional<PointOnRectagle> result; @@ -167,7 +167,7 @@ return result; } -geom::LineSegment2D geom::top(const QRectF& rectangle) +LineSegment2D top(const QRectF& rectangle) { return { glm::vec2{rectangle.left(), rectangle.top()}, @@ -175,7 +175,7 @@ }; } -geom::LineSegment2D geom::bottom(const QRectF& rectangle) +LineSegment2D bottom(const QRectF& rectangle) { return { glm::vec2{rectangle.left(), rectangle.bottom()}, @@ -183,7 +183,7 @@ }; } -geom::LineSegment2D geom::left(const QRectF& rectangle) +LineSegment2D left(const QRectF& rectangle) { return { glm::vec2{rectangle.left(), rectangle.top()}, @@ -191,7 +191,7 @@ }; } -geom::LineSegment2D geom::right(const QRectF& rectangle) +LineSegment2D right(const QRectF& rectangle) { return { glm::vec2{rectangle.right(), rectangle.top()}, @@ -199,7 +199,7 @@ }; } -bool geom::isConvex(const std::vector<glm::vec3>& polygon) +bool isConvex(const std::vector<glm::vec3>& polygon) { const int n = polygon.size(); auto polygonRing = iter::ring(polygon, n); @@ -223,7 +223,7 @@ * @param polygon * @return winding */ -Winding geom::winding(const QPolygonF &polygon) +Winding winding(const QPolygonF &polygon) { // based on https://stackoverflow.com/a/1165943 double sum = 0.0; @@ -242,7 +242,7 @@ * @param t scalar between 0 and 1, with t=0 being P0 and t=1 being P3 * @return point on curve */ -glm::vec3 geom::pointOnCurve(const BezierCurve &curve, float t) +glm::vec3 pointOnCurve(const BezierCurve &curve, float t) { // clamp t as rounding errors might make it slightly out of bounds t = std::clamp(t, 0.0f, 1.0f); @@ -262,7 +262,7 @@ * @param t scalar between 0 and 1, with t=0 being P0 and t=1 being P3 * @return point on curve */ -glm::vec3 geom::derivativeOnCurve(const BezierCurve &curve, float t) +glm::vec3 derivativeOnCurve(const BezierCurve &curve, float t) { // clamp t as rounding errors might make it slightly out of bounds t = std::clamp(t, 0.0f, 1.0f);
--- a/src/geometry.h Mon Jun 06 22:01:22 2022 +0300 +++ b/src/geometry.h Tue Jun 07 01:37:26 2022 +0300 @@ -2,176 +2,172 @@ #include <QPolygonF> #include "basics.h" -namespace geom +struct Plane +{ + glm::vec3 normal; + glm::vec3 anchor; +}; + +template<int N, typename T = float, glm::qualifier Q = glm::defaultp> +struct Line +{ + glm::vec<N, T, Q> direction; + glm::vec<N, T, Q> anchor; +}; + +template<int N, typename T = float, glm::qualifier Q = glm::defaultp> +struct Ray +{ + glm::vec<N, T, Q> direction; + glm::vec<N, T, Q> anchor; +}; + +inline const glm::vec3 origin = {0, 0, 0}; +inline const Plane XY = {{0, 0, 1}, origin}; +inline const Plane XZ = {{0, 1, 0}, origin}; +inline const Plane YZ = {{1, 0, 0}, origin}; + +struct LineSegment +{ + glm::vec3 p1, p2; +}; +struct Triangle +{ + glm::vec3 p1, p2, p3; +}; +struct Quadrilateral { - struct Plane - { - glm::vec3 normal; - glm::vec3 anchor; - }; + glm::vec3 p1, p2, p3, p4; +}; +struct ConditionalEdge +{ + glm::vec3 p1, p2; + glm::vec3 c1, c2; +}; +struct LineSegment2D +{ + glm::vec2 p1, p2; +}; - template<int N, typename T = float, glm::qualifier Q = glm::defaultp> - struct Line - { - glm::vec<N, T, Q> direction; - glm::vec<N, T, Q> anchor; - }; +// get polygon type from amount of points +template<int N> +struct PolygonType {}; +template<> +struct PolygonType<2> { using type = LineSegment; }; +template<> +struct PolygonType<3> { using type = Triangle; }; +template<> +struct PolygonType<4> { using type = Quadrilateral; }; +template<int N> +using Polygon = typename PolygonType<N>::type; + +/** + * @brief Computes a line from two points + * @param point_1 + * @param point_2 + * @return line + */ +template<int N, typename T, glm::qualifier Q> +Line<N, T, Q> lineFromPoints(const glm::vec<N, T, Q>& point_1, const glm::vec<N, T, Q>& point_2) +{ + return {point_2 - point_1, point_1}; +} - template<int N, typename T = float, glm::qualifier Q = glm::defaultp> - struct Ray - { - glm::vec<N, T, Q> direction; - glm::vec<N, T, Q> anchor; - }; +template<int N, typename T, glm::qualifier Q> +Ray<N, T, Q> rayFromPoints(const glm::vec<N, T, Q>& point_1, const glm::vec<N, T, Q>& point_2) +{ + return {point_2 - point_1, point_1}; +} + +template<int N, typename T, glm::qualifier Q> +Line<N, T, Q> rayToLine(const Ray<N, T, Q>& ray) +{ + return {ray.direction, ray.anchor}; +} - inline const glm::vec3 origin = {0, 0, 0}; - inline const Plane XY = {{0, 0, 1}, origin}; - inline const Plane XZ = {{0, 1, 0}, origin}; - inline const Plane YZ = {{1, 0, 0}, origin}; +enum class RectangleSide +{ + Top, + Left, + Bottom, + Right +}; + +struct PointOnRectagle +{ + glm::vec2 position; + RectangleSide side; +}; - struct LineSegment - { - glm::vec3 p1, p2; - }; - struct Triangle - { - glm::vec3 p1, p2, p3; - }; - struct Quadrilateral - { - glm::vec3 p1, p2, p3, p4; +std::optional<glm::vec2> lineLineIntersection(const Line<2>& line_1, const Line<2>& line_2); +std::optional<glm::vec2> rayLineSegmentIntersection(const Ray<2>& ray, const LineSegment2D& line); +std::optional<PointOnRectagle> rayRectangleIntersection(const Ray<2>& ray, const QRectF& rectangle); +Plane planeFromTriangle(const Triangle& triangle); +glm::vec3 normalVector(const Triangle& triangle); +std::optional<glm::vec3> linePlaneIntersection( + const Line<3>& line, + const Plane& plane, const float epsilon = 1e-6f); +glm::vec3 scalingVector(const glm::mat4 matrix); +LineSegment2D top(const QRectF& rectangle); +LineSegment2D bottom(const QRectF& rectangle); +LineSegment2D left(const QRectF& rectangle); +LineSegment2D right(const QRectF& rectangle); +bool isConvex(const std::vector<glm::vec3>& polygon); +Winding winding(const QPolygonF& polygon); +struct ScalingExtract +{ + glm::vec3 scaling; + glm::mat4 unscaled; +}; +ScalingExtract extractScaling(const glm::mat4& matrix); + +struct NPolygon +{ + std::vector<glm::vec3> points; +}; + +inline constexpr bool isclose(const glm::vec3& a, const glm::vec3& b) +{ + return qFuzzyCompare(a.x, b.x) + and qFuzzyCompare(a.y, b.y) + and qFuzzyCompare(a.z, b.z); +} + +struct CircleF +{ + QPointF center; + qreal radius; +}; + +/** + * @brief Inscribes a circle + * @param circle + * @return a QRectF that inscribes the specified circle + */ +inline constexpr QRectF inscribe(const CircleF& circle) +{ + return { + circle.center.x() - circle.radius, + circle.center.y() - circle.radius, + circle.radius * 2, + circle.radius * 2 }; - struct ConditionalEdge - { - glm::vec3 p1, p2; - glm::vec3 c1, c2; - }; - struct LineSegment2D - { - glm::vec2 p1, p2; - }; - - // get polygon type from amount of points - template<int N> - struct PolygonType {}; - template<> - struct PolygonType<2> { using type = LineSegment; }; - template<> - struct PolygonType<3> { using type = Triangle; }; - template<> - struct PolygonType<4> { using type = Quadrilateral; }; - template<int N> - using Polygon = typename PolygonType<N>::type; +} - /** - * @brief Computes a line from two points - * @param point_1 - * @param point_2 - * @return line - */ - template<int N, typename T, glm::qualifier Q> - Line<N, T, Q> lineFromPoints(const glm::vec<N, T, Q>& point_1, const glm::vec<N, T, Q>& point_2) - { - return {point_2 - point_1, point_1}; - } - - template<int N, typename T, glm::qualifier Q> - Ray<N, T, Q> rayFromPoints(const glm::vec<N, T, Q>& point_1, const glm::vec<N, T, Q>& point_2) - { - return {point_2 - point_1, point_1}; - } - - template<int N, typename T, glm::qualifier Q> - Line<N, T, Q> rayToLine(const Ray<N, T, Q>& ray) - { - return {ray.direction, ray.anchor}; - } - - enum class RectangleSide +struct BezierCurve +{ + glm::vec3 points[4]; + const glm::vec3& operator[](int x) const { - Top, - Left, - Bottom, - Right - }; - - struct PointOnRectagle - { - glm::vec2 position; - RectangleSide side; - }; - - std::optional<glm::vec2> lineLineIntersection(const Line<2>& line_1, const Line<2>& line_2); - std::optional<glm::vec2> rayLineSegmentIntersection(const Ray<2>& ray, const LineSegment2D& line); - std::optional<PointOnRectagle> rayRectangleIntersection(const Ray<2>& ray, const QRectF& rectangle); - Plane planeFromTriangle(const Triangle& triangle); - glm::vec3 normalVector(const Triangle& triangle); - std::optional<glm::vec3> linePlaneIntersection( - const Line<3>& line, - const Plane& plane, const float epsilon = 1e-6f); - glm::vec3 scalingVector(const glm::mat4 matrix); - LineSegment2D top(const QRectF& rectangle); - LineSegment2D bottom(const QRectF& rectangle); - LineSegment2D left(const QRectF& rectangle); - LineSegment2D right(const QRectF& rectangle); - bool isConvex(const std::vector<glm::vec3>& polygon); - Winding winding(const QPolygonF& polygon); - struct ScalingExtract - { - glm::vec3 scaling; - glm::mat4 unscaled; - }; - ScalingExtract extractScaling(const glm::mat4& matrix); - - struct NPolygon - { - std::vector<glm::vec3> points; - }; - - inline constexpr bool isclose(const glm::vec3& a, const glm::vec3& b) + Q_ASSERT(x >= 0 and x < 4); + return this->points[x]; + } + glm::vec3& operator[](int x) { - return qFuzzyCompare(a.x, b.x) - and qFuzzyCompare(a.y, b.y) - and qFuzzyCompare(a.z, b.z); + Q_ASSERT(x >= 0 and x < 4); + return this->points[x]; } - - struct CircleF - { - QPointF center; - qreal radius; - }; +}; - /** - * @brief Inscribes a circle - * @param circle - * @return a QRectF that inscribes the specified circle - */ - inline constexpr QRectF inscribe(const CircleF& circle) - { - return { - circle.center.x() - circle.radius, - circle.center.y() - circle.radius, - circle.radius * 2, - circle.radius * 2 - }; - } - - struct BezierCurve - { - glm::vec3 points[4]; - const glm::vec3& operator[](int x) const - { - Q_ASSERT(x >= 0 and x < 4); - return this->points[x]; - } - glm::vec3& operator[](int x) - { - Q_ASSERT(x >= 0 and x < 4); - return this->points[x]; - } - }; - - glm::vec3 pointOnCurve(const BezierCurve& curve, float t); - glm::vec3 derivativeOnCurve(const BezierCurve& curve, float t); -} -using namespace geom; +glm::vec3 pointOnCurve(const BezierCurve& curve, float t); +glm::vec3 derivativeOnCurve(const BezierCurve& curve, float t);
--- a/src/gl/partrenderer.cpp Mon Jun 06 22:01:22 2022 +0300 +++ b/src/gl/partrenderer.cpp Tue Jun 07 01:37:26 2022 +0300 @@ -304,11 +304,11 @@ * @param plane Plane to raycast against * @return world co-ordinates, or no value if the point is behind the camera. */ -std::optional<glm::vec3> PartRenderer::screenToModelCoordinates(const QPoint& point, const geom::Plane& plane) const +std::optional<glm::vec3> PartRenderer::screenToModelCoordinates(const QPoint& point, const Plane& plane) const { - const geom::Line line = this->cameraLine(point); + const Line line = this->cameraLine(point); std::optional<glm::vec3> result; - result = geom::linePlaneIntersection(line, plane, 0.01f); + result = linePlaneIntersection(line, plane, 0.01f); // If the point lies behind the camera, do not return a result. if (result.has_value() and glm::dot(line.direction, *result - line.anchor) < 0) { @@ -332,11 +332,11 @@ return toQPointF(glm::vec2{projected.x, this->height() - projected.y}); } -geom::Line<3> PartRenderer::cameraLine(const QPoint& point) const +Line<3> PartRenderer::cameraLine(const QPoint& point) const { const glm::vec3 p1 = this->unproject({point.x(), point.y(), 0}); const glm::vec3 p2 = this->unproject({point.x(), point.y(), 1}); - return geom::lineFromPoints(p1, p2); + return lineFromPoints(p1, p2); } /**
--- a/src/gl/partrenderer.h Mon Jun 06 22:01:22 2022 +0300 +++ b/src/gl/partrenderer.h Tue Jun 07 01:37:26 2022 +0300 @@ -31,9 +31,9 @@ BoundingBox boundingBox; gl::ModelShaders shaders; ModelId highlighted = {0}; - std::optional<glm::vec3> screenToModelCoordinates(const QPoint& point, const geom::Plane& plane) const; + std::optional<glm::vec3> screenToModelCoordinates(const QPoint& point, const Plane& plane) const; QPointF modelToScreenCoordinates(const glm::vec3& point) const; - geom::Line<3> cameraLine(const QPoint& point) const; + Line<3> cameraLine(const QPoint& point) const; glm::vec3 unproject(const glm::vec3& win) const; glm::mat4 projectionMatrix; glm::mat4 viewMatrix;
--- a/src/libraries.cpp Mon Jun 06 22:01:22 2022 +0300 +++ b/src/libraries.cpp Tue Jun 07 01:37:26 2022 +0300 @@ -220,7 +220,7 @@ * @param errors Where to stream any encountered errors * @return color table */ -ldraw::ColorTable LibraryManager::loadColorTable(QTextStream& errors) +ldraw::ColorTable LibraryManager::loadColorTable(QTextStream& errors) const { ldraw::ColorTable result; for (const Library& library : this->libraries)
--- a/src/libraries.h Mon Jun 06 22:01:22 2022 +0300 +++ b/src/libraries.h Tue Jun 07 01:37:26 2022 +0300 @@ -78,7 +78,7 @@ int rowCount(const QModelIndex&) const override; int columnCount(const QModelIndex&) const override; bool isValidIndex(const int libraryIndex) const; - ldraw::ColorTable loadColorTable(QTextStream& errors); + ldraw::ColorTable loadColorTable(QTextStream& errors) const; private: enum Column {
--- a/src/main.cpp Mon Jun 06 22:01:22 2022 +0300 +++ b/src/main.cpp Tue Jun 07 01:37:26 2022 +0300 @@ -1,24 +1,14 @@ -/* - * 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/>. - */ +#include <QApplication> +#include <QFileDialog> +#include <QMessageBox> +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "version.h" +#include "document.h" +#include "settingseditor/settingseditor.h" +#include "widgets/colorselectdialog.h" -#include <QApplication> -#include "mainwindow.h" -#include "version.h" +static const QDir LOCALE_DIR {":/locale"}; static void doQtRegistrations() { @@ -29,11 +19,408 @@ qRegisterMetaTypeStreamOperators<Libraries>("Libraries"); } +template<typename BaseType, typename MemberType, typename DataType> +struct MemberData +{ + std::size_t member; + DataType payload; + constexpr MemberType memberInstance(BaseType* instance) const + { + return *reinterpret_cast<MemberType*>(reinterpret_cast<char*>(instance) + this->member); + } +}; + +static constexpr MemberData<Ui_MainWindow, QAction*, gl::RenderStyle> renderStyleButtons[] = { + { offsetof(Ui_MainWindow, actionRenderStyleNormal), gl::RenderStyle::Normal }, + { offsetof(Ui_MainWindow, actionRenderStyleBfc), gl::RenderStyle::BfcRedGreen }, + { offsetof(Ui_MainWindow, actionRenderStyleRandom), gl::RenderStyle::RandomColors }, + { offsetof(Ui_MainWindow, actionRenderStylePickScene), gl::RenderStyle::PickScene }, +}; + +static std::optional<ModelId> openModelFromPath( + const QString& path, + const LibraryManager* libraries, + DocumentManager* documents, + QWidget* parent) +{ + QString errorString; + QTextStream errorStream{&errorString}; + const std::optional<ModelId> modelIdOpt = documents->openModel( + path, + errorStream, + DocumentManager::OpenType::ManuallyOpened); + if (modelIdOpt.has_value()) { + documents->loadDependenciesForModel(modelIdOpt.value(), path, *libraries, errorStream); + if (not errorString.isEmpty()) { + QMessageBox::warning( + parent, + QObject::tr("Problem loading references"), + errorString); + } + } + else { + QMessageBox::critical( + parent, + QObject::tr("Problem opening file"), + format(QObject::tr("Could not open %1: %2"), path, errorString)); + } + return modelIdOpt; +} + +static QString getOpenModelPath(QWidget* parent) +{ + return QFileDialog::getOpenFileName( + parent, + QObject::tr("Open model"), + "", + QObject::tr("LDraw models (*.ldr *.dat)")); +} + +static const QString localeCode(const QString& locale) +{ + if (locale == "system") { + return QLocale::system().name(); + } + else { + return locale; + } +} + +/** + * @brief Changes the application language to the specified language + */ +static void changeLanguage(const QString& locale, QTranslator* translator) +{ + if (not locale.isEmpty()) { + const QString localeCode = ::localeCode(locale); + QLocale::setDefault({localeCode}); + qApp->removeTranslator(translator); + const QString path = LOCALE_DIR.filePath(localeCode + ".qm"); + const bool loadSuccessful = translator->load(path); + if (loadSuccessful) + { + qApp->installTranslator(translator); + } + } +} + +/* +void MainWindow::handleDocumentSplitterChange() +{ + EditorTabWidget* currentDocument = this->currentDocument(); + if (currentDocument != nullptr) + { + this->documentSplitterState = currentDocument->saveSplitterState(); + for (int i = 0; i < this->ui->tabs->count(); i += 1) + { + EditorTabWidget* document = qobject_cast<EditorTabWidget*>(this->ui->tabs->widget(i)); + if (document != nullptr and document != currentDocument) + { + document->restoreSplitterState(this->documentSplitterState); + } + } + this->settings.setMainSplitterState(this->documentSplitterState); + } +} +*/ + +static EditorTabWidget* currentTabWidget(Ui_MainWindow* ui) +{ + return qobject_cast<EditorTabWidget*>(ui->tabs->currentWidget()); +}; + + +static void closeDocument(DocumentManager* documents, EditorTabWidget *document) +{ + std::optional<ModelId> modelId = documents->findIdForModel(document->model); + if (modelId.has_value()) { + documents->closeDocument(modelId.value()); + delete document; + } +} + +static void handleTabCloseButton(Ui_MainWindow* ui, DocumentManager* documents, int tabIndex) +{ + if (tabIndex >= 0 and tabIndex < ui->tabs->count()) { + EditorTabWidget* tab = qobject_cast<EditorTabWidget*>(ui->tabs->widget(tabIndex)); + if (tab != nullptr) { + closeDocument(documents, tab); + } + } +} + +static std::optional<ModelId> findCurrentModelId(Ui_MainWindow* ui, DocumentManager* documents) +{ + const EditorTabWidget* tab = currentTabWidget(ui); + if (tab != nullptr) { + return documents->findIdForModel(tab->model); + } + else { + return {}; + } +} + +/** + * @brief Updates the title of the main window so to contain the app's name + * and version as well as the open document name. + */ +static QString title() +{ + QString title = ::appName; + title += " "; + title += fullVersionString(); + return title; +} + +static ldraw::ColorTable loadColors(const LibraryManager* libraries) +{ + QTextStream errors; + return libraries->loadColorTable(errors); +} + +static QString tabName(const QFileInfo& fileInfo) +{ + QString result = fileInfo.baseName(); + if (result.isEmpty()) { + result = QObject::tr("<unnamed>"); + } + return result; +} + +void rebuildRecentFilesMenu(QMenu* menu, const QStringList& strings, QWidget* parent) +{ + menu->clear(); + for (const QString& path : strings) { + QAction* action = new QAction{path, parent}; + action->setData(path); + menu->addAction(action); + } +} + +static void updateRenderPreferences( + Ui_MainWindow* ui, + const gl::RenderPreferences* renderPreferences) +{ + for (int i = 0; i < ui->tabs->count(); i += 1) { + EditorTabWidget* tab = qobject_cast<EditorTabWidget*>(ui->tabs->widget(i)); + if (tab != nullptr) { + tab->canvas->setRenderPreferences(*renderPreferences); + } + } + for (auto data : ::renderStyleButtons) { + QAction* action = data.memberInstance(ui); + action->setChecked(renderPreferences->style == data.payload); + } + ui->actionDrawAxes->setChecked(renderPreferences->drawAxes); +}; + +static gl::RenderPreferences loadRenderPreferences(Configuration* settings) +{ + return gl::RenderPreferences{ + .style = static_cast<gl::RenderStyle>(settings->renderStyle()), + .mainColor = settings->mainColor(), + .backgroundColor = settings->backgroundColor(), + .selectedColor = settings->selectedColor(), + .lineThickness = settings->lineThickness(), + .lineAntiAliasing = settings->lineAntiAliasing(), + .drawAxes = settings->drawAxes(), + }; +} + int main(int argc, char *argv[]) { doQtRegistrations(); QApplication app{argc, argv}; - MainWindow mainwindow; - mainwindow.show(); - return app.exec(); + QMainWindow mainWindow; + Ui_MainWindow ui; + DocumentManager documents{&mainWindow}; + QString currentLanguage = "en"; + QTranslator translator{&mainWindow}; + Configuration settings; + LibraryManager libraries{&mainWindow}; + QByteArray documentSplitterState; + QStringList recentlyOpenedFiles; + ldraw::ColorTable colorTable; + gl::RenderPreferences renderPreferences; + ui.setupUi(&mainWindow); + const uiutilities::KeySequenceMap defaultKeyboardShortcuts = + uiutilities::makeKeySequenceMap(uiutilities::collectActions(&mainWindow)); + const auto saveSettings = [&]{ + settings.setMainWindowGeometry(mainWindow.saveGeometry()); + settings.setRecentFiles(recentlyOpenedFiles); + settings.setMainSplitterState(documentSplitterState); + settings.setRenderStyle(static_cast<int>(renderPreferences.style)); + settings.setDrawAxes(renderPreferences.drawAxes); + libraries.storeToSettings(&settings); + }; + const auto updateRecentlyOpenedDocumentsMenu = [&]{ + rebuildRecentFilesMenu(ui.menuRecentFiles, recentlyOpenedFiles, &mainWindow); + for (QAction* action : ui.menuRecentFiles->findChildren<QAction*>()) { + QString path = action->data().toString(); + QObject::connect( + action, + &QAction::triggered, + [path, &libraries, &documents, &mainWindow]() { + openModelFromPath(path, &libraries, &documents, &mainWindow); + } + ); + } + }; + const auto restoreSettings = [&]{ + recentlyOpenedFiles = settings.recentFiles(); + documentSplitterState = settings.mainSplitterState(); + renderPreferences = loadRenderPreferences(&settings); + changeLanguage(settings.locale(), &translator); + libraries.restoreFromSettings(&settings); + updateRecentlyOpenedDocumentsMenu(); + colorTable = loadColors(&libraries); + updateRenderPreferences(&ui, &renderPreferences); + ui.retranslateUi(&mainWindow); + }; + const auto addRecentlyOpenedFile = [&](const QString& path){ + constexpr int maxRecentlyOpenedFiles = 10; + recentlyOpenedFiles.removeAll(path); + recentlyOpenedFiles.insert(0, path); + while (recentlyOpenedFiles.size() > maxRecentlyOpenedFiles) + { + recentlyOpenedFiles.removeLast(); + } + saveSettings(); + updateRecentlyOpenedDocumentsMenu(); + }; + const auto openModelForEditing = [&](const ModelId modelId){ + EditorTabWidget* document = new EditorTabWidget{ + documents.getModelById(modelId), + &documents, + colorTable, + }; + document->canvas->setRenderPreferences(renderPreferences); + QObject::connect( + document, + &EditorTabWidget::newStatusText, + [&](const QString& newStatusText) { + mainWindow.statusBar()->showMessage(newStatusText); + }); + const QFileInfo fileInfo{*documents.modelPath(modelId)}; + ui.tabs->addTab(document, tabName(fileInfo)); + ui.tabs->setCurrentWidget(document); + document->restoreSplitterState(documentSplitterState); + }; + const auto newModel = [&openModelForEditing](DocumentManager* documents){ + openModelForEditing(documents->newModel()); + }; + QObject::connect(ui.actionNew, &QAction::triggered, [&newModel, &documents]{ + newModel(&documents); + }); + QObject::connect(ui.actionOpen, &QAction::triggered, [&]{ + const QString path = getOpenModelPath(&mainWindow); + if (not path.isEmpty()) + { + const std::optional<ModelId> id = openModelFromPath(path, &libraries, &documents, &mainWindow); + if (id.has_value()) { + openModelForEditing(id.value()); + addRecentlyOpenedFile(path); + } + } + }); + QObject::connect(ui.actionSettingsEditor, &QAction::triggered, [&]{ + SettingsEditor settingsEditor{&settings, defaultKeyboardShortcuts, &mainWindow}; + const int result = settingsEditor.exec(); + if (result == QDialog::Accepted) + { + restoreSettings(); + } + }); + QObject::connect(ui.actionQuit, &QAction::triggered, &mainWindow, &QMainWindow::close); + QObject::connect(ui.actionAdjustGridToView, &QAction::triggered, [&ui]{ + EditorTabWidget* tab = currentTabWidget(&ui); + if (tab != nullptr) + { + adjustGridToView(tab->canvas); + } + }); + QObject::connect(ui.actionClose, &QAction::triggered, [&ui, &documents]{ + EditorTabWidget* tab = currentTabWidget(&ui); + if (tab != nullptr) + { + closeDocument(&documents, tab); + } + }); + const auto save = [&](ModelId modelId){ + QString error; + QTextStream errorStream{&error}; + const bool succeeded = documents.saveModel(modelId, errorStream); + if (not succeeded) + { + QMessageBox::critical(&mainWindow, QObject::tr("Save error"), error); + } + else + { + const QString* pathPtr = documents.modelPath(modelId); + if (pathPtr != nullptr) { + addRecentlyOpenedFile(*pathPtr); + } + } + }; + const auto actionSaveAs = [&]{ + const std::optional<ModelId> modelId = findCurrentModelId(&ui, &documents); + if (modelId.has_value()) + { + const QString* pathPtr = documents.modelPath(*modelId); + QString defaultPath = (pathPtr != nullptr) ? *pathPtr : ""; + const QString newPath = QFileDialog::getSaveFileName( + &mainWindow, + QObject::tr("Save as…"), + QFileInfo{defaultPath}.absoluteDir().path(), + QObject::tr("LDraw files (*.ldr *dat);;All files (*)") + ); + if (not newPath.isEmpty()) { + QString error; + QTextStream errorStream{&error}; + documents.setModelPath(*modelId, newPath, libraries, errorStream); + ui.tabs->setTabText(ui.tabs->currentIndex(), QFileInfo{newPath}.fileName()); + save(*modelId); + } + } + }; + QObject::connect(ui.actionSaveAs, &QAction::triggered, actionSaveAs); + QObject::connect(ui.actionSave, &QAction::triggered, [&]{ + if (currentTabWidget(&ui) != nullptr) { + const std::optional<ModelId> modelId = findCurrentModelId(&ui, &documents); + if (modelId.has_value()) { + const QString* path = documents.modelPath(*modelId); + if (path == nullptr or path->isEmpty()) { + actionSaveAs(); + } + else { + save(*modelId); + } + } + } + }); + QObject::connect(ui.tabs, &QTabWidget::tabCloseRequested, [&](int index){ + handleTabCloseButton(&ui, &documents, index); + }); + QObject::connect(ui.actionDrawAxes, &QAction::triggered, [&](bool drawAxes){ + renderPreferences.drawAxes = drawAxes; + saveSettings(); + updateRenderPreferences(&ui, &renderPreferences); + }); + for (auto data : ::renderStyleButtons) { + QAction* action = data.memberInstance(&ui); + QObject::connect(action, &QAction::triggered, [&, data]{ + renderPreferences.style = data.payload; + saveSettings(); + updateRenderPreferences(&ui, &renderPreferences); + }); + } + mainWindow.setWindowTitle(title()); + mainWindow.restoreGeometry(settings.mainWindowGeometry()); + restoreSettings(); + updateRenderPreferences(&ui, &renderPreferences); + newModel(&documents); + mainWindow.show(); + const int result = app.exec(); + saveSettings(); + return result; }
--- a/src/mainwindow.cpp Mon Jun 06 22:01:22 2022 +0300 +++ b/src/mainwindow.cpp Tue Jun 07 01:37:26 2022 +0300 @@ -15,564 +15,3 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#include <QLabel> -#include <QVBoxLayout> -#include <QCloseEvent> -#include <QFileDialog> -#include <QMessageBox> -#include "mainwindow.h" -#include "ui_mainwindow.h" -#include "settingseditor/settingseditor.h" -#include "version.h" -#include "document.h" -#include "uiutilities.h" -#include "widgets/colorselectdialog.h" - -template<typename BaseType, typename MemberType, typename DataType> -struct MemberData -{ - std::size_t member; - DataType payload; - constexpr MemberType memberInstance(BaseType* instance) const - { - return *reinterpret_cast<MemberType*>(reinterpret_cast<char*>(instance) + this->member); - } -}; - -static constexpr MemberData<Ui_MainWindow, QAction*, gl::RenderStyle> renderStyleButtons[] = { - { offsetof(Ui_MainWindow, actionRenderStyleNormal), gl::RenderStyle::Normal }, - { offsetof(Ui_MainWindow, actionRenderStyleBfc), gl::RenderStyle::BfcRedGreen }, - { offsetof(Ui_MainWindow, actionRenderStyleRandom), gl::RenderStyle::RandomColors }, - { offsetof(Ui_MainWindow, actionRenderStylePickScene), gl::RenderStyle::PickScene }, -}; - -class A : public QSettings -{ - using QSettings::QSettings; -}; - -MainWindow::MainWindow(QWidget *parent) : - QMainWindow{parent}, - ui{std::make_unique<Ui_MainWindow>()}, - documents{this}, - settings{}, - libraries{this} -{ - this->ui->setupUi(this); - defaultKeyboardShortcuts = uiutilities::makeKeySequenceMap(uiutilities::collectActions(this)); - connect(ui->actionNew, &QAction::triggered, this, &MainWindow::newModel); - connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::openModel); - connect(ui->actionQuit, &QAction::triggered, this, &QMainWindow::close); - connect(ui->actionSettingsEditor, &QAction::triggered, this, &MainWindow::runSettingsEditor); - connect(ui->actionAdjustGridToView, &QAction::triggered, [&]() - { - if (this->currentDocument() != nullptr) - { - adjustGridToView(this->currentDocument()->canvas); - } - }); - connect(this->ui->actionSave, &QAction::triggered, - this, &MainWindow::actionSave); - connect(this->ui->actionSaveAs, &QAction::triggered, - this, &MainWindow::actionSaveAs); - connect(this->ui->actionClose, &QAction::triggered, this, &MainWindow::actionClose); - connect(this->ui->actionDelete, &QAction::triggered, this, &MainWindow::actionDelete); - connect(this->ui->actionInvert, &QAction::triggered, this, &MainWindow::actionInvert); - connect(this->ui->tabs, &QTabWidget::tabCloseRequested, this, &MainWindow::handleTabCloseButton); - for (auto data : ::renderStyleButtons) - { - QAction* action = data.memberInstance(this->ui.get()); - connect(action, &QAction::triggered, [this, data]() - { - this->setRenderStyle(data.payload); - }); - } - connect(this->ui->actionDrawAxes, &QAction::triggered, this, &MainWindow::setDrawAxes); - this->updateTitle(); - this->restoreStartupSettings(); - this->restoreSettings(); - this->updateRenderPreferences(); - this->newModel(); -} - -// MainWindow needs a destructor even if it is empty because otherwise the destructor of the -// std::unique_ptr is resolved in the header file, where it will complain about Ui_MainWindow -// being incomplete. -MainWindow::~MainWindow() -{ -} - -void MainWindow::newModel() -{ - this->openModelForEditing(documents.newModel()); -} - -void MainWindow::openModel() -{ - const QString path = QFileDialog::getOpenFileName( - this, - tr("Open model"), - "", - tr("LDraw models (*.ldr *.dat)")); - if (not path.isEmpty()) - { - this->openModelFromPath(path); - } -} - -void MainWindow::openModelFromPath(const QString& path) -{ - QString errorString; - QTextStream errorStream{&errorString}; - std::optional<ModelId> modelIdOpt = this->documents.openModel( - path, - errorStream, - DocumentManager::OpenType::ManuallyOpened); - if (modelIdOpt.has_value()) - { - const ModelId modelId = modelIdOpt.value(); - this->documents.loadDependenciesForModel(modelId, path, this->libraries, errorStream); - if (not errorString.isEmpty()) - { - QMessageBox::warning( - this, - tr("Problem loading references"), - errorString); - } - this->openModelForEditing(modelId); - this->addRecentlyOpenedFile(path); - } - else - { - QMessageBox::critical( - this, - tr("Problem opening file"), - utility::format( - tr("Could not open %1: %2"), - path, - errorString)); - } -} - -/** - * @brief Changes the application language to the specified language - * @param localeCode Code of the locale to translate to - */ -void MainWindow::changeLanguage(QString localeCode) -{ - if (not localeCode.isEmpty() and localeCode != this->currentLanguage) - { - this->currentLanguage = localeCode; - if (localeCode == "system") - { - localeCode = QLocale::system().name(); - } - QLocale::setDefault({localeCode}); - qApp->removeTranslator(&this->translator); - const bool loadSuccessful = this->translator.load(pathToTranslation(localeCode)); - if (loadSuccessful) - { - qApp->installTranslator(&this->translator); - } - } -} - -void MainWindow::addRecentlyOpenedFile(const QString& path) -{ - this->recentlyOpenedFiles.removeAll(path); - this->recentlyOpenedFiles.insert(0, path); - while (this->recentlyOpenedFiles.size() > maxRecentlyOpenedFiles) - { - this->recentlyOpenedFiles.removeLast(); - } - this->saveSettings(); - this->updateRecentlyOpenedDocumentsMenu(); -} - -void MainWindow::openModelForEditing(const ModelId modelId) -{ - EditorTabWidget* document = new EditorTabWidget{ - this->documents.getModelById(modelId), - &this->documents, - this->colorTable, - }; - document->canvas->setRenderPreferences(this->renderPreferences); - connect(document, &EditorTabWidget::newStatusText, [&](const QString& newStatusText) - { - this->statusBar()->showMessage(newStatusText); - }); - const QFileInfo fileInfo{*this->documents.modelPath(modelId)}; - QString tabName = fileInfo.baseName(); - if (tabName.isEmpty()) - { - tabName = tr("<unnamed>"); - } - this->ui->tabs->addTab(document, tabName); - this->ui->tabs->setCurrentWidget(document); - document->restoreSplitterState(this->documentSplitterState); -} - -void MainWindow::runSettingsEditor() -{ - SettingsEditor settingsEditor{&this->settings, this->defaultKeyboardShortcuts, this}; - const int result = settingsEditor.exec(); - if (result == QDialog::Accepted) - { - this->restoreSettings(); - } -} - -EditorTabWidget* MainWindow::currentDocument() -{ - return qobject_cast<EditorTabWidget*>(this->ui->tabs->currentWidget()); -} - -const EditorTabWidget* MainWindow::currentDocument() const -{ - return qobject_cast<const EditorTabWidget*>(this->ui->tabs->currentWidget()); -} - -void MainWindow::handleDocumentSplitterChange() -{ - EditorTabWidget* currentDocument = this->currentDocument(); - if (currentDocument != nullptr) - { - this->documentSplitterState = currentDocument->saveSplitterState(); - for (int i = 0; i < this->ui->tabs->count(); i += 1) - { - EditorTabWidget* document = qobject_cast<EditorTabWidget*>(this->ui->tabs->widget(i)); - if (document != nullptr and document != currentDocument) - { - document->restoreSplitterState(this->documentSplitterState); - } - } - this->settings.setMainSplitterState(this->documentSplitterState); - } -} - -void MainWindow::updateRecentlyOpenedDocumentsMenu() -{ - this->ui->menuRecentFiles->clear(); - for (const QString& path : this->recentlyOpenedFiles) - { - QAction* action = new QAction{path, this}; - action->setData(path); - this->ui->menuRecentFiles->addAction(action); - connect(action, &QAction::triggered, this, &MainWindow::openRecentFile); - } -} - -void MainWindow::openRecentFile() -{ - QAction* action = qobject_cast<QAction*>(this->sender()); - if (action != nullptr) - { - const QString path = action->data().toString(); - this->openModelFromPath(path); - } -} - -void MainWindow::setRenderStyle(gl::RenderStyle renderStyle) -{ - this->renderPreferences.style = renderStyle; - this->saveSettings(); - this->updateRenderPreferences(); -} - -void MainWindow::setDrawAxes(bool drawAxes) -{ - this->renderPreferences.drawAxes = drawAxes; - this->saveSettings(); - this->updateRenderPreferences(); -} - -/** - * @brief Handles the "Save" (Ctrl+S) action - */ -void MainWindow::actionSave() -{ - if (this->currentDocument() != nullptr) - { - const std::optional<ModelId> modelId = this->findCurrentModelId(); - if (modelId.has_value()) - { - const QString* path = this->documents.modelPath(*modelId); - if (path == nullptr or path->isEmpty()) - { - this->actionSaveAs(); - } - else - { - QString error; - QTextStream errorStream{&error}; - const bool succeeded = this->documents.saveModel(*modelId, errorStream); - if (not succeeded) - { - QMessageBox::critical(this, tr("Save error"), error); - } - else - { - this->addRecentlyOpenedFile(*path); - } - } - } - } -} - -/** - * @brief Handles the "Save as…" (Ctrl+Shift+S) action - */ -void MainWindow::actionSaveAs() -{ - if (this->currentDocument() != nullptr) - { - const std::optional<ModelId> modelId = this->findCurrentModelId(); - if (modelId.has_value()) - { - const QString* pathPtr = this->documents.modelPath(*modelId); - QString defaultPath = (pathPtr != nullptr) ? *pathPtr : ""; - const QString newPath = QFileDialog::getSaveFileName( - this, - tr("Save as…"), - QFileInfo{defaultPath}.absoluteDir().path(), - tr("LDraw files (*.ldr *dat);;All files (*)") - ); - if (not newPath.isEmpty()) - { - 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(); - } - } - } -} - -/** - * @brief Handles the "Close" (Ctrl+W) action - */ -void MainWindow::actionClose() -{ - if (this->currentDocument() != nullptr) - { - this->closeDocument(this->currentDocument()); - } -} - -/** - * @brief Handles the "Delete" (Del) action - */ -void MainWindow::actionDelete() -{ - /* - EditorTabWidget* document = this->currentDocument(); - if (document != nullptr) - { - std::unique_ptr<ModelEditor> modelEditor = document->editModel(); - QSet<ldraw::id_t> ids = document->selectedObjects(); // copy - for (const ldraw::id_t id : ids) - { - const QModelIndex index = modelEditor->model().find(id); - if (index.isValid()) - { - modelEditor->remove(index.row()); - } - } - } - */ -} - -/** - * @brief Handles the "Invert" action - */ -void MainWindow::actionInvert() -{ - /* - EditorTabWidget* document = this->currentDocument(); - if (document != nullptr) - { - // TODO: simplify - std::unique_ptr<ModelEditor> modelEditor = document->editModel(); - const std::optional<ModelId> modelId = this->documents.findIdForModel(&modelEditor->model()); - if (modelId.has_value()) - { - ldraw::GetPolygonsContext context = { - .modelId = modelId.value(), - .documents = &this->documents, - }; - for (const ldraw::id_t id : document->selectedObjects()) - { - modelEditor->modifyObject(id, [&context](ldraw::Object* object) - { - object->invert(&context); - }); - } - } - } - */ -} - -/** - * @brief Removes the document at the specified tab index - * @param index - */ -void MainWindow::handleTabCloseButton(int tabIndex) -{ - if (tabIndex >= 0 and tabIndex < this->ui->tabs->count()) - { - EditorTabWidget* document = qobject_cast<EditorTabWidget*>(this->ui->tabs->widget(tabIndex)); - if (document != nullptr) - { - this->closeDocument(document); - } - } -} - -/** - * @brief Closes the specified document - * @param document - */ -void MainWindow::closeDocument(EditorTabWidget *document) -{ - std::optional<ModelId> modelId = this->documents.findIdForModel(document->model); - if (modelId.has_value()) - { - this->documents.closeDocument(modelId.value()); - delete document; - } -} - -std::optional<ModelId> MainWindow::findCurrentModelId() const -{ - const EditorTabWidget* document = this->currentDocument(); - if (document != nullptr) - { - return this->documents.findIdForModel(document->model); - } - else - { - return {}; - } -} - -void MainWindow::changeEvent(QEvent* event) -{ - if (event != nullptr) - { - switch (event->type()) - { - case QEvent::LanguageChange: - this->ui->retranslateUi(this); - break; - default: - break; - } - } - QMainWindow::changeEvent(event); -} - -/** - * @brief Handles closing the main window - * @param event Event information - */ -void MainWindow::closeEvent(QCloseEvent* event) -{ - saveSettings(); - event->accept(); -} - -/** - * @brief Updates the title of the main window so to contain the app's name - * and version as well as the open document name. - */ -void MainWindow::updateTitle() -{ - QString title = ::appName; - title += " "; - title += fullVersionString(); - setWindowTitle(title); -} - -void MainWindow::updateRenderPreferences() -{ - for (int i = 0; i < this->ui->tabs->count(); i += 1) - { - EditorTabWidget* document = qobject_cast<EditorTabWidget*>(this->ui->tabs->widget(i)); - if (document != nullptr) - { - document->canvas->setRenderPreferences(this->renderPreferences); - } - } - for (auto data : ::renderStyleButtons) - { - QAction* action = data.memberInstance(this->ui.get()); - action->setChecked(this->renderPreferences.style == data.payload); - } - this->ui->actionDrawAxes->setChecked(this->renderPreferences.drawAxes); -} - -/** - * @brief Stores the settings of the main window, storing geometry, etc - */ -void MainWindow::saveSettings() -{ - this->settings.setMainWindowGeometry(this->saveGeometry()); - this->settings.setRecentFiles(this->recentlyOpenedFiles); - this->settings.setMainSplitterState(this->documentSplitterState); - this->settings.setRenderStyle(static_cast<int>(this->renderPreferences.style)); - this->settings.setDrawAxes(this->renderPreferences.drawAxes); - this->libraries.storeToSettings(&this->settings); -} - -void MainWindow::restoreStartupSettings() -{ - this->restoreGeometry(this->settings.mainWindowGeometry()); -} - -/** - * @brief Restores saved settings relating to the main window - */ -void MainWindow::restoreSettings() -{ - this->recentlyOpenedFiles = this->settings.recentFiles(); - this->documentSplitterState = this->settings.mainSplitterState(); - this->renderPreferences.style = static_cast<gl::RenderStyle>(this->settings.renderStyle()); - this->renderPreferences.mainColor = this->settings.mainColor(); - this->renderPreferences.backgroundColor = this->settings.backgroundColor(); - this->renderPreferences.lineThickness = this->settings.lineThickness(); - this->renderPreferences.lineAntiAliasing = this->settings.lineAntiAliasing(); - this->renderPreferences.selectedColor = this->settings.selectedColor(); - this->renderPreferences.drawAxes = this->settings.drawAxes(); - const QString systemLocale = QLocale::system().name(); - const QVariant defaultLocale = this->settings.locale(); - this->changeLanguage(defaultLocale.toString()); - this->libraries.restoreFromSettings(&this->settings); - this->updateRecentlyOpenedDocumentsMenu(); - this->loadColors(); - this->updateRenderPreferences(); -} - -QString MainWindow::pathToTranslation(const QString& localeCode) -{ - QDir dir {":/locale"}; - return dir.filePath(localeCode + ".qm"); -} - -void MainWindow::loadColors() -{ - QTextStream errors; - this->colorTable = this->libraries.loadColorTable(errors); -} - -void MainWindow::keyReleaseEvent(QKeyEvent* /*event*/) -{ - /* - Document* document = this->currentDocument(); - if (document != nullptr) - { - document->handleKeyPress(event); - } - */ -}
--- a/src/mainwindow.h Mon Jun 06 22:01:22 2022 +0300 +++ b/src/mainwindow.h Tue Jun 07 01:37:26 2022 +0300 @@ -26,60 +26,3 @@ #include "libraries.h" #include "uiutilities.h" #include "ui/canvas.h" - -class EditorTabWidget; - -class MainWindow : public QMainWindow -{ - Q_OBJECT -public: - MainWindow(QWidget *parent = nullptr); - ~MainWindow() override; -private Q_SLOTS: - void newModel(); - void openModel(); - void openModelFromPath(const QString& path); - void runSettingsEditor(); - void handleDocumentSplitterChange(); - void updateRecentlyOpenedDocumentsMenu(); - void openRecentFile(); - void setRenderStyle(gl::RenderStyle renderStyle); - Q_SLOT void setDrawAxes(bool drawAxes); - void actionSave(); - void actionSaveAs(); - void actionClose(); - void actionDelete(); - void actionInvert(); - void handleTabCloseButton(int tabIndex); -protected: - void changeEvent(QEvent* event) override; - void closeEvent(QCloseEvent* event) override; - void keyReleaseEvent(QKeyEvent *event) override; -private: - std::unique_ptr<class Ui_MainWindow> ui; - DocumentManager documents; - QString currentLanguage = "en"; - QTranslator translator; - Configuration settings; - LibraryManager libraries; - QByteArray documentSplitterState; - uiutilities::KeySequenceMap defaultKeyboardShortcuts; - static constexpr int maxRecentlyOpenedFiles = 10; - QStringList recentlyOpenedFiles; - ldraw::ColorTable colorTable; - gl::RenderPreferences renderPreferences; - void updateTitle(); - void updateRenderPreferences(); - void saveSettings(); - void restoreStartupSettings(); - void restoreSettings(); - void changeLanguage(QString localeCode); - void addRecentlyOpenedFile(const QString& path); - void openModelForEditing(const ModelId modelId); - static QString pathToTranslation(const QString& localeCode); - void loadColors(); - EditorTabWidget *currentDocument(); - const EditorTabWidget *currentDocument() const; - void closeDocument(EditorTabWidget* document); - std::optional<ModelId> findCurrentModelId() const; -};
--- a/src/parser.cpp Mon Jun 06 22:01:22 2022 +0300 +++ b/src/parser.cpp Tue Jun 07 01:37:26 2022 +0300 @@ -225,7 +225,7 @@ return Colored<ConditionalEdge>{cedge, pair.second}; } default: - throw BodyParseError{utility::format("bad line type '%1'", code)}; + throw BodyParseError{format("bad line type '%1'", code)}; } } catch(const BodyParseError& error)
--- a/src/settingseditor/librarieseditor.cpp Mon Jun 06 22:01:22 2022 +0300 +++ b/src/settingseditor/librarieseditor.cpp Tue Jun 07 01:37:26 2022 +0300 @@ -50,9 +50,9 @@ { QMessageBox::critical(this, tr("Library does not exist"), - utility::format( + format( tr("The directory %1 does not exist."), - utility::quoted(dir.path()))); + quoted(dir.path()))); } else { @@ -60,9 +60,9 @@ { QMessageBox::warning(this, tr("Unreadable library"), - utility::format( + format( tr("The directory %1 cannot be read."), - utility::quoted(dir.path()))); + quoted(dir.path()))); } this->libraries.addLibrary({Library::OfficialLibrary, dir}); this->ui.newLibraryPath->clear();
--- a/src/ui/canvas.cpp Mon Jun 06 22:01:22 2022 +0300 +++ b/src/ui/canvas.cpp Tue Jun 07 01:37:26 2022 +0300 @@ -234,21 +234,21 @@ //font.setStyle(QFont::StyleItalic); painter.setFont(font); QFontMetrics fontMetrics{font}; - const auto renderText = [&](const QString& text, const geom::PointOnRectagle& intersection) + const auto renderText = [&](const QString& text, const PointOnRectagle& intersection) { QPointF position = toQPointF(intersection.position); - const geom::RectangleSide side = intersection.side; + const RectangleSide side = intersection.side; switch (side) { - case geom::RectangleSide::Top: + case RectangleSide::Top: position += QPointF{0, static_cast<qreal>(fontMetrics.ascent())}; break; - case geom::RectangleSide::Left: + case RectangleSide::Left: break; - case geom::RectangleSide::Bottom: + case RectangleSide::Bottom: position += QPointF{0, static_cast<qreal>(-fontMetrics.descent())}; break; - case geom::RectangleSide::Right: + case RectangleSide::Right: position += QPointF{static_cast<qreal>(-fontMetrics.horizontalAdvance(text)), 0}; break; } @@ -272,8 +272,8 @@ for (const auto& axis : directions) { const QPointF x_p = this->modelToScreenCoordinates(axis.direction); - const auto intersection = geom::rayRectangleIntersection( - geom::rayFromPoints(toVec2(p1), toVec2(x_p)), + const auto intersection = rayRectangleIntersection( + rayFromPoints(toVec2(p1), toVec2(x_p)), box); if (intersection.has_value()) { @@ -304,7 +304,7 @@ Winding Canvas::worldPolygonWinding(const std::vector<glm::vec3> &points) const { - return geom::winding(QPolygonF{this->convertWorldPointsToScreenPoints(points)}); + return winding(QPolygonF{this->convertWorldPointsToScreenPoints(points)}); } /** @@ -356,7 +356,7 @@ void Canvas::drawWorldPoint(QPainter* painter, const glm::vec3& worldPoint) const { const QPointF center = this->modelToScreenCoordinates(worldPoint); - painter->drawEllipse(geom::inscribe(geom::CircleF{center, 5})); + painter->drawEllipse(inscribe(CircleF{center, 5})); } /** @@ -366,12 +366,12 @@ void Canvas::setGridMatrix(const glm::mat4& newMatrix) { this->gridMatrix = newMatrix; - const geom::Triangle triangle { + const Triangle triangle { this->gridMatrix * glm::vec4{0, 0, 0, 1}, this->gridMatrix * glm::vec4{1, 0, 0, 1}, this->gridMatrix * glm::vec4{0, 1, 0, 1}, }; - this->gridPlane = geom::planeFromTriangle(triangle); + this->gridPlane = planeFromTriangle(triangle); this->gridProgram->setGridMatrix(this->gridMatrix); this->update(); }
--- a/src/ui/canvas.h Mon Jun 06 22:01:22 2022 +0300 +++ b/src/ui/canvas.h Tue Jun 07 01:37:26 2022 +0300 @@ -67,7 +67,7 @@ std::optional<AxesProgram> axesProgram; std::optional<VertexProgram> vertexProgram; glm::mat4 gridMatrix; - geom::Plane gridPlane; + Plane gridPlane; int totalMouseMove = 0; bool isDark = true; QSet<ModelId> selection;
--- a/src/utility.h Mon Jun 06 22:01:22 2022 +0300 +++ b/src/utility.h Tue Jun 07 01:37:26 2022 +0300 @@ -19,84 +19,79 @@ #pragma once #include "basics.h" -namespace utility +// http://stackoverflow.com/a/18204188/3629665 +template<typename T> +inline T rotl10(T x) { - // http://stackoverflow.com/a/18204188/3629665 - template<typename T> - inline T rotl10(T x) - { - return (x << 10) | ((x >> 22) & 0x000000ff); - } + return (x << 10) | ((x >> 22) & 0x000000ff); +} + +template<typename T> +inline T rotl20(T x) +{ + return (x << 20) | ((x >> 12) & 0x000000ff); +} - template<typename T> - inline T rotl20(T x) - { - return (x << 20) | ((x >> 12) & 0x000000ff); - } +inline QString format(const QString& format_string) +{ + return format_string; +} - inline QString format(const QString& format_string) - { - return format_string; - } +template<typename T, typename... Rest> +QString format(const QString& format_string, T&& arg, Rest&&... rest) +{ + return format(format_string.arg(arg), std::forward<Rest>(rest)...); +} - template<typename T, typename... Rest> - QString format(const QString& format_string, T&& arg, Rest&&... rest) - { - return format(format_string.arg(arg), std::forward<Rest>(rest)...); - } - - inline QString quoted(QString string) +inline QString quoted(QString string) +{ + if (string.contains("'")) { - if (string.contains("'")) - { - string.replace("\"", "\\\""); - string = "\"" + string + "\""; - } - else - { - string = "'" + string + "'"; - } - return string; - } - - /** - * @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); + string.replace("\"", "\\\""); + string = "\"" + string + "\""; } - - inline QString vertexToStringParens(const glm::vec3& vertex) - { - return utility::format("(%1, %2, %3)", vertex.x, vertex.y, vertex.z); - } - - inline QString transformToString(const glm::mat4& matrix) + else { - return utility::format( - "%1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12", - matrix[3][0], - matrix[3][1], - matrix[3][2], - matrix[0][0], - matrix[1][0], - matrix[2][0], - matrix[0][1], - matrix[1][1], - matrix[2][1], - matrix[0][2], - matrix[1][2], - matrix[2][2]); + string = "'" + string + "'"; } + return string; } -using namespace utility; +/** + * @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 format("%1 %2 %3", vertex.x, vertex.y, vertex.z); +} + +inline QString vertexToStringParens(const glm::vec3& vertex) +{ + return format("(%1, %2, %3)", vertex.x, vertex.y, vertex.z); +} + +inline QString transformToString(const glm::mat4& matrix) +{ + return format( + "%1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12", + matrix[3][0], + matrix[3][1], + matrix[3][2], + matrix[0][0], + matrix[1][0], + matrix[2][0], + matrix[0][1], + matrix[1][1], + matrix[2][1], + matrix[0][2], + matrix[1][2], + matrix[2][2]); +} template<typename T, glm::qualifier Q> -inline unsigned int qHash(const glm::vec<3, T, Q>& key) +constexpr unsigned int qHash(const glm::vec<3, T, Q>& key) { - return qHash(key.x) ^ utility::rotl10(qHash(key.y)) ^ utility::rotl20(qHash(key.z)); + return qHash(key.x) ^ rotl10(qHash(key.y)) ^ rotl20(qHash(key.z)); }