Sun, 09 Apr 2023 16:23:05 +0300
Add undo, redo, cut, copy and paste actions to MainWindow which pass onto the editor widget
/* * 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 <QPixmap> #include "src/model.h" constexpr unsigned int gcd(unsigned int a, unsigned int b) { while (a != b) { if (b > a) { b -= a; } else if (a > b) { a -= b; } } return a; } static_assert(gcd(16, 15) == 1); static_assert(gcd(16, 4) == 4); static_assert(gcd(272, 192) == 16); static constexpr const char* circularPrimitiveTypeString(const CircularPrimitive& circ) { return circularPrimitiveStems[circ.type]; } static QString circularPrimitiveFilePath(const CircularPrimitive& circ) { QString result; if (circ.fraction.divisions != 16) { result += QString::number(circ.fraction.divisions) + QStringLiteral("\\"); } const unsigned int factor = gcd(circ.fraction.segments, circ.fraction.divisions); unsigned int num = circ.fraction.segments / factor; unsigned int denom = circ.fraction.divisions / factor; if (denom < 4) { num *= 4 / denom; denom = 4; } result += QStringLiteral("%1-%2").arg(num).arg(denom); result += QString::fromLatin1(circularPrimitiveTypeString(circ)); result += QStringLiteral(".dat"); return result; } static const char* iconPathForElement(const ModelElement& element) { return std::visit(overloaded{ [](const Colored<SubfileReference>&) { return ":/icons/linetype-subfile.png"; }, [](const Colored<LineSegment>&) { return ":/icons/linetype-edgeline.png"; }, [](const Colored<Triangle>&) { return ":/icons/linetype-triangle.png"; }, [](const Colored<Quadrilateral>&) { return ":/icons/linetype-quadrilateral.png"; }, [](const Colored<ConditionalEdge>&) { return ":/icons/linetype-conditionaledge.png"; }, [](const Colored<CircularPrimitive>&) { return ":/icons/linetype-circularprimitive.png"; }, [](const Comment&) { return ":/icons/chatbubble-ellipses-outline.png"; }, [](const Empty&) { return ""; }, [](const ParseError&) { return ":/icons/linetype-errorline.png"; }, }, element); } static QPixmap iconForElement(const ModelElement& element) { // We avoid processing the same image over and over again by storing it // in a static constant. However, we need one per each possible type // of ModelElement, so we put the pixmap constant inside a templated lambda, // which gets instiated once for each type of ModelElement. return std::visit([](auto&& element){ static const QPixmap pixmap = QPixmap::fromImage( QImage{iconPathForElement(element)} .scaledToHeight(24, Qt::SmoothTransformation) ); return pixmap; }, element); } QString modelElementToString(const ModelElement &element) { return std::visit(overloaded{ [](const Colored<SubfileReference>& ref) { QString result; result += QStringLiteral("1 %1 %2 %3") .arg(ref.color.index) .arg(transformToString(ref.transformation)) .arg(ref.name); return result; }, [](const Colored<LineSegment>& seg) { return QStringLiteral("2 %1 %2 %3") .arg(seg.color.index) .arg(vertexToString(seg.p1)) .arg(vertexToString(seg.p2)); }, [](const Colored<Triangle>& triangle) { return QStringLiteral("3 %1 %2 %3 %4") .arg(triangle.color.index) .arg(vertexToString(triangle.p1)) .arg(vertexToString(triangle.p2)) .arg(vertexToString(triangle.p3)); }, [](const Colored<Quadrilateral>& quad) { return QStringLiteral("4 %1 %2 %3 %4 %5") .arg(quad.color.index) .arg(vertexToString(quad.p1)) .arg(vertexToString(quad.p2)) .arg(vertexToString(quad.p3)) .arg(vertexToString(quad.p4)); }, [](const Colored<ConditionalEdge>& cedge) { return QStringLiteral("5 %1 %2 %3 %4 %5") .arg(cedge.color.index) .arg(vertexToString(cedge.p1)) .arg(vertexToString(cedge.p2)) .arg(vertexToString(cedge.c1)) .arg(vertexToString(cedge.c2)); }, [](const Colored<CircularPrimitive>& circ) { return QStringLiteral("1 %1 %2 %3") .arg(circ.color.index) .arg(transformToString(circ.transformation)) .arg(circularPrimitiveFilePath(circ)); }, [](const Comment& comment) { return "0 " + comment.text; }, [](const Empty&) { return QStringLiteral(""); }, [](const ParseError& parseError) { return parseError.code; }, }, element); } template<typename K, typename V> void removeFromMap(std::map<K, V>& map, const K& key) { const auto it = map.find(key); if (it != map.end()) { map.erase(it); } } /** * @brief Sets the path to the model * @param path New path to use */ void updateHeaderNameField(QTextDocument& model, const QString &name) { #if 0 // Update the "Name: 1234.dat" comment if (model.size() >= 2) { if (const Comment* nameObject = std::get_if<Comment>(&model[1])) { if (nameObject->text.startsWith("Name: ")) { model[1] = Comment{"Name: " + name}; } } } #endif }