Sun, 03 Nov 2019 12:17:41 +0200
major update with many things
--- a/CMakeLists.txt Sat Oct 05 23:47:03 2019 +0300 +++ b/CMakeLists.txt Sun Nov 03 12:17:41 2019 +0200 @@ -15,10 +15,12 @@ include_directories(${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR}) set (LDFORGE_SOURCES + src/document.cpp src/documentmanager.cpp src/libraries.cpp src/main.cpp src/mainwindow.cpp + src/matrix.cpp src/model.cpp src/modeleditcontext.cpp src/parser.cpp @@ -28,6 +30,7 @@ src/objecttypes/conditionaledge.cpp src/objecttypes/edge.cpp src/objecttypes/errorline.cpp + src/objecttypes/metacommand.cpp src/objecttypes/modelobject.cpp src/objecttypes/polygon.cpp src/objecttypes/subfilereference.cpp @@ -37,19 +40,23 @@ set (LDFORGE_HEADERS src/basics.h src/colors.h + src/document.h src/documentmanager.h src/libraries.h src/main.h src/mainwindow.h + src/matrix.h src/model.h src/modeleditcontext.h src/parser.h + src/utility.h src/version.h src/vertex.h src/objecttypes/comment.h src/objecttypes/conditionaledge.h src/objecttypes/edge.h src/objecttypes/errorline.h + src/objecttypes/metacommand.h src/objecttypes/modelobject.h src/objecttypes/polygon.h src/objecttypes/subfilereference.h @@ -57,6 +64,7 @@ src/settingseditor/settingseditor.h ) set (LDFORGE_FORMS + src/document.ui src/mainwindow.ui src/settingseditor/librarieseditor.ui src/settingseditor/settingseditor.ui @@ -65,7 +73,6 @@ set(LDFORGE_LOCALES locale/fi.ts locale/sv.ts - locale/ru.ts ) set(LDFORGE_RESOURCES ldforge.qrc)
--- a/flags/ru.svg Sat Oct 05 23:47:03 2019 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 6" width="900" height="600"><rect fill="#fff" width="9" height="3"/><rect fill="#d52b1e" y="3" width="9" height="3"/><rect fill="#0039a6" y="2" width="9" height="2"/></svg> \ No newline at end of file
--- a/ldforge.qrc Sat Oct 05 23:47:03 2019 +0300 +++ b/ldforge.qrc Sun Nov 03 12:17:41 2019 +0200 @@ -2,7 +2,6 @@ <qresource prefix="/"> <file>flags/en.png</file> <file>flags/fi.png</file> - <file>flags/ru.png</file> <file>flags/sv.png</file> </qresource> </RCC>
--- a/locale/fi.ts Sat Oct 05 23:47:03 2019 +0300 +++ b/locale/fi.ts Sun Nov 03 12:17:41 2019 +0200 @@ -2,6 +2,14 @@ <!DOCTYPE TS> <TS version="2.1"> <context> + <name>Document</name> + <message> + <location filename="../src/document.ui" line="14"/> + <source>Form</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> <name>LibrariesEditor</name> <message> <location filename="../src/settingseditor/librarieseditor.ui" line="14"/> @@ -19,55 +27,75 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../src/settingseditor/librarieseditor.cpp" line="32"/> + <location filename="../src/settingseditor/librarieseditor.cpp" line="39"/> <source>Browse LDraw library</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/settingseditor/librarieseditor.cpp" line="45"/> + <location filename="../src/settingseditor/librarieseditor.cpp" line="52"/> <source>Library does not exist</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/settingseditor/librarieseditor.cpp" line="47"/> + <location filename="../src/settingseditor/librarieseditor.cpp" line="54"/> <source>The directory %1 does not exist.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/settingseditor/librarieseditor.cpp" line="55"/> + <location filename="../src/settingseditor/librarieseditor.cpp" line="62"/> <source>Unreadable library</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/settingseditor/librarieseditor.cpp" line="57"/> + <location filename="../src/settingseditor/librarieseditor.cpp" line="64"/> <source>The directory %1 cannot be read.</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../src/settingseditor/librarieseditor.cpp" line="78"/> + <source>Remove library</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/settingseditor/librarieseditor.cpp" line="84"/> + <source>Set role</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/settingseditor/librarieseditor.cpp" line="96"/> + <source>Move up</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/settingseditor/librarieseditor.cpp" line="102"/> + <source>Move down</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>LibraryManager</name> <message> - <location filename="../src/libraries.cpp" line="105"/> + <location filename="../src/libraries.cpp" line="202"/> <source>Official library</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/libraries.cpp" line="107"/> + <location filename="../src/libraries.cpp" line="204"/> <source>Unofficial library</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/libraries.cpp" line="109"/> + <location filename="../src/libraries.cpp" line="206"/> <source>Working library</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/libraries.cpp" line="150"/> + <location filename="../src/libraries.cpp" line="266"/> <source>Path</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/libraries.cpp" line="152"/> + <location filename="../src/libraries.cpp" line="268"/> <source>Role</source> <translation type="unfinished"></translation> </message> @@ -115,22 +143,22 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../src/mainwindow.cpp" line="41"/> + <location filename="../src/mainwindow.cpp" line="42"/> <source>Open model</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/mainwindow.cpp" line="43"/> + <location filename="../src/mainwindow.cpp" line="44"/> <source>LDraw models (*.ldr *.dat)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/mainwindow.cpp" line="55"/> + <location filename="../src/mainwindow.cpp" line="58"/> <source>Could not open %1: %2</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/mainwindow.cpp" line="58"/> + <location filename="../src/mainwindow.cpp" line="61"/> <source>Problem opening file</source> <translation type="unfinished"></translation> </message>
--- a/locale/ru.ts Sat Oct 05 23:47:03 2019 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,159 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!DOCTYPE TS> -<TS version="2.1" language="ru"> -<context> - <name>Dialog</name> - <message> - <source>System language</source> - <translation type="obsolete">Язык системы</translation> - </message> -</context> -<context> - <name>LibrariesEditor</name> - <message> - <source>Form</source> - <translation type="unfinished"></translation> - </message> - <message> - <source>…</source> - <translation type="unfinished"></translation> - </message> - <message> - <source>Add</source> - <translation type="unfinished"></translation> - </message> - <message> - <source>Browse LDraw library</source> - <translation type="unfinished"></translation> - </message> - <message> - <source>Library does not exist</source> - <translation type="unfinished"></translation> - </message> - <message> - <source>The directory %1 does not exist.</source> - <translation type="unfinished"></translation> - </message> - <message> - <source>Unreadable library</source> - <translation type="unfinished"></translation> - </message> - <message> - <source>The directory %1 cannot be read.</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>LibraryManager</name> - <message> - <source>Official library</source> - <translation type="unfinished"></translation> - </message> - <message> - <source>Unofficial library</source> - <translation type="unfinished"></translation> - </message> - <message> - <source>Working library</source> - <translation type="unfinished"></translation> - </message> - <message> - <source>Path</source> - <translation type="unfinished"></translation> - </message> - <message> - <source>Role</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>MainWindow</name> - <message> - <source>File</source> - <translation>Файл</translation> - </message> - <message> - <source>Quit</source> - <translation>Выход</translation> - </message> - <message> - <source>Open…</source> - <translation>Открыть...</translation> - </message> - <message> - <source>Ctrl+O</source> - <translation>Ctrl+O</translation> - </message> - <message> - <source>New</source> - <translation>Новый</translation> - </message> - <message> - <source>Ctrl+N</source> - <translation>Ctrl+N</translation> - </message> - <message> - <source>Language</source> - <translation type="vanished">Язык</translation> - </message> - <message> - <source>Open model</source> - <translation>Открыть модель</translation> - </message> - <message> - <source>LDraw models (*.ldr *.dat)</source> - <translation>ЛДрав-модели (*.ldr *.dat)</translation> - </message> - <message> - <source>Problem opening file</source> - <translation></translation> - </message> - <message> - <source>Could not open %1: %2</source> - <translation>Не удалось открыть %1: %2</translation> - </message> - <message> - <source>LDForge</source> - <translation type="unfinished"></translation> - </message> - <message> - <source>System language</source> - <translation type="vanished">Язык системы</translation> - </message> - <message> - <source>Preferences…</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>SettingsEditor</name> - <message> - <source>Dialog</source> - <translation type="unfinished"></translation> - </message> - <message> - <source>General</source> - <translation type="unfinished"></translation> - </message> - <message> - <source>Language:</source> - <translation>Язык:</translation> - </message> - <message> - <source>System language</source> - <translation>Язык системы</translation> - </message> - <message> - <source>English</source> - <translation type="unfinished">Английский</translation> - </message> - <message> - <source>LDraw libraries</source> - <translation type="vanished">Библиотеки ЛДрав-деталей</translation> - </message> - <message> - <source>LDraw parts libraries</source> - <translation>Библиотеки ЛДрав-деталей</translation> - </message> -</context> -</TS>
--- a/locale/sv.ts Sat Oct 05 23:47:03 2019 +0300 +++ b/locale/sv.ts Sun Nov 03 12:17:41 2019 +0200 @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1" language="sv"> +<TS version="2.1" language="sv_FI"> <context> <name>Dialog</name> <message> @@ -9,6 +9,13 @@ </message> </context> <context> + <name>Document</name> + <message> + <source>Form</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> <name>Libraries</name> <message> <source>Official library</source> @@ -73,6 +80,22 @@ <source>The directory %1 cannot be read.</source> <translation>Katalogen %1 kan läsas inte</translation> </message> + <message> + <source>Remove library</source> + <translation>Ta bort biblioteket</translation> + </message> + <message> + <source>Set role</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Move up</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Move down</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>LibrariesModel</name>
--- a/src/basics.h Sat Oct 05 23:47:03 2019 +0300 +++ b/src/basics.h Sun Nov 03 12:17:41 2019 +0200 @@ -17,8 +17,10 @@ */ #pragma once +#include <algorithm> #include <cstdio> #include <cstdlib> +#include <cstring> #include <cmath> #include <QMatrix4x4> #include <QObject> @@ -42,7 +44,7 @@ enum Winding { NoWinding, - CounterClockwise, + Anticlockwise, Clockwise, }; @@ -64,51 +66,4 @@ return one; } -template<typename T, std::size_t N> -constexpr std::size_t countof(T(&)[N]) -{ - return N; -} - static constexpr long double pi = M_PIl; - -// http://stackoverflow.com/a/18204188/3629665 -template<typename T> -inline auto rotl10(T x) - -> std::enable_if_t<std::is_arithmetic_v<T>, T> -{ - return (x << 10) | ((x >> 22) & 0x000000ff); -} - -template<typename T> -inline auto rotl20(T x) - -> std::enable_if_t<std::is_arithmetic_v<T>, T> -{ - return (x << 20) | ((x >> 12) & 0x000000ff); -} - -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> -QString format(const QString& format_string, T&& arg) -{ - return format_string.arg(arg); -} - -inline QString quoted(QString string) -{ - if (string.contains("'")) - { - string.replace("\"", "\\\""); - string = "\"" + string + "\""; - } - else - { - string = "'" + string + "'"; - } - return string; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/document.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -0,0 +1,17 @@ +#include "document.h" +#include "ui_document.h" +#include "model.h" + +Document::Document(Model* model, QWidget* parent) : + QWidget{parent}, + model{model}, + ui{*new Ui::Document} +{ + this->ui.setupUi(this); + this->ui.listView->setModel(model); +} + +Document::~Document() +{ + delete &this->ui; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/document.h Sun Nov 03 12:17:41 2019 +0200 @@ -0,0 +1,22 @@ +#pragma once +#include <memory> +#include <QWidget> + +namespace Ui +{ + class Document; +} + +class Model; + +class Document : public QWidget +{ + Q_OBJECT +public: + explicit Document(Model* model, QWidget *parent = nullptr); + ~Document(); + +private: + Model* model; + Ui::Document& ui; +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/document.ui Sun Nov 03 12:17:41 2019 +0200 @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Document</class> + <widget class="QWidget" name="Document"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>795</width> + <height>414</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QOpenGLWidget" name="viewport"/> + <widget class="QListView" name="listView"/> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui>
--- a/src/documentmanager.cpp Sat Oct 05 23:47:03 2019 +0300 +++ b/src/documentmanager.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -1,12 +1,96 @@ +#include <QFile> +#include <QDir> +#include <QFileInfo> #include "documentmanager.h" +#include "modeleditcontext.h" +#include "objecttypes/comment.h" +#include "parser.h" +/** + * @brief Constructs a new document manager + * @param parent Parent object + */ DocumentManager::DocumentManager(QObject* parent) : QObject{parent} { } -Model* DocumentManager::newModel() +/** + * @brief Creates a new model. + * @returns the name to the new model + */ +QString DocumentManager::newModel() +{ + const QString name = makeNewModelName(); + this->openModels.emplace(name, new Model); + return name; +} + +/** + * @brief Looks for a model by name + * @param name Name of the model + * @returns model or null + * ' + */ +Model* DocumentManager::findModelByName(const QString& name) +{ + const auto iterator = this->openModels.find(name); + if (iterator == std::end(this->openModels)) + { + return nullptr; + } + else + { + return iterator->second.get(); + } +} + +QString pathToName(const QFileInfo& path) { - openModels.emplace_back(); - return openModels.back().get(); + static const char* paths[] = { + "s", + "48" + "8" + }; + const QString baseName = path.fileName(); + const QString dirName = QFileInfo{path.dir().path()}.fileName(); + QString result; + if (utility::contains(paths, dirName)) + { + result = dirName + "\\" + baseName; + } + else + { + result = baseName; + } + return result; } + +QString DocumentManager::openModel(const QString& path, QTextStream& errorStream) +{ + QFile file{path}; + const QString name = pathToName(path); + file.open(QFile::ReadOnly | QFile::Text); + std::unique_ptr<Model> newModel = std::make_unique<Model>(); + QTextStream textStream{&file}; + Model::EditContext editor = newModel->edit(); + Parser parser{file}; + parser.parseBody(editor); + QString result; + if (file.error() == QFile::NoError) + { + openModels[name] = std::move(newModel); + result = name; + } + else + { + errorStream << file.errorString(); + } + return result; +} + +QString DocumentManager::makeNewModelName() +{ + untitledNameCounter += 1; + return "untitled-" + QString::number(untitledNameCounter); +}
--- a/src/documentmanager.h Sat Oct 05 23:47:03 2019 +0300 +++ b/src/documentmanager.h Sun Nov 03 12:17:41 2019 +0200 @@ -5,13 +5,18 @@ class DocumentManager : public QObject { Q_OBJECT + using ModelPointer = std::unique_ptr<Model>; public: DocumentManager(QObject* parent = nullptr); DocumentManager(const DocumentManager&) = delete; DocumentManager(DocumentManager&&) = default; DocumentManager& operator=(const DocumentManager&) = delete; DocumentManager& operator=(DocumentManager&&) = default; - Model* newModel(); + QString newModel(); + Model* findModelByName(const QString& name); + QString openModel(const QString& path, QTextStream& errorStream); + QString makeNewModelName(); private: - std::vector<std::unique_ptr<Model>> openModels; + int untitledNameCounter = 0; + std::map<QString, ModelPointer> openModels; };
--- a/src/header.h Sat Oct 05 23:47:03 2019 +0300 +++ b/src/header.h Sun Nov 03 12:17:41 2019 +0200 @@ -24,8 +24,8 @@ enum Qualifier { Alias = 1 << 0, - Physical_Color = 1 << 1, - Flexible_Section = 1 << 2, + PhysicalColour = 1 << 1, + FlexibleSection = 1 << 2, }; QFlags<Qualifier> qualfiers; QString description;
--- a/src/libraries.cpp Sat Oct 05 23:47:03 2019 +0300 +++ b/src/libraries.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -1,22 +1,39 @@ #include <QSettings> #include "libraries.h" +/** + * @brief Constructs a new library manager + * @param parent Parent object + */ LibraryManager::LibraryManager(QObject* parent): QAbstractTableModel{parent} { } +/** + * @brief Constructs a library manager from settings + * @param settings Settings to construct from + * @param parent Parent object + */ LibraryManager::LibraryManager(QSettings* settings, QObject* parent) : QAbstractTableModel{parent} { this->restoreFromSettings(settings); } +/** + * @brief Yields a begin-terator for the libraries + * @return iterator + */ QVector<Library>::const_iterator LibraryManager::begin() const { return this->libraries.begin(); } +/** + * @brief Yields an end-iterator for the libraries + * @return iterator + */ QVector<Library>::const_iterator LibraryManager::end() const { return this->libraries.end(); @@ -46,6 +63,10 @@ return path; } +/** + * @brief Adds a new library to the end of the libraries list. + * @param library Library to add + */ void LibraryManager::addLibrary(const Library& library) { emit layoutAboutToBeChanged(); @@ -53,11 +74,38 @@ emit layoutChanged(); } +/** + * @brief Removes a library by index. Does nothing if the index is not valid. + * @param libraryIndex Index of the library + */ +void LibraryManager::removeLibrary(const int libraryIndex) +{ + Q_ASSERT(isValidIndex(libraryIndex)); + if (isValidIndex(libraryIndex)) + { + emit layoutAboutToBeChanged(); + libraries.remove(libraryIndex); + emit layoutChanged(); + } +} + +/** + * @brief Gets a library by index. + * @warning Index is assumed to be valid. + * @param libraryIndex Index of the library + * @return const reference + */ const Library& LibraryManager::library(int libraryIndex) const { + Q_ASSERT(isValidIndex(libraryIndex)); return this->libraries[libraryIndex]; } +/** + * @brief Changes the path of the specified library + * @param libraryIndex Index of the library + * @param path New path + */ void LibraryManager::setLibraryPath(int libraryIndex, const QDir& path) { if (this->isValidIndex(libraryIndex)) @@ -67,6 +115,11 @@ } } +/** + * @brief Changes the role of the specified library + * @param libraryIndex Index of the library + * @param role New role + */ void LibraryManager::setLibraryRole(int libraryIndex, const Library::Role role) { if (this->isValidIndex(libraryIndex)) @@ -76,27 +129,71 @@ } } +/** + * @brief Restores the libraries from the specified settings object. All unsaved + * changes are lost. + * @param settings Settings object to restore from. + */ void LibraryManager::restoreFromSettings(QSettings* settings) { this->libraries = settings->value("libraries").value<Libraries>(); } +/** + * @brief Saves the libraries to the specified settings object. + * @param settings Settings object to modify. + */ void LibraryManager::storeToSettings(QSettings* settings) { QVariant librariesValue = QVariant::fromValue(this->libraries); settings->setValue("libraries", librariesValue); } +/** + * @returns the amount of libraries + */ int LibraryManager::count() const { return this->libraries.size(); } -bool LibraryManager::isValidIndex(int libraryIndex) +void LibraryManager::moveLibrary(const int libraryFromIndex, const int libraryToIndex) +{ + if (isValidIndex(libraryFromIndex) and + (isValidIndex(libraryToIndex) or libraryToIndex == count()) and + libraryFromIndex != libraryToIndex) + { + emit layoutAboutToBeChanged(); + const Library library = this->library(libraryFromIndex); + if (libraryToIndex > libraryFromIndex) + { + this->libraries.insert(libraryToIndex, library); + this->libraries.removeAt(libraryFromIndex); + } + else if (libraryToIndex < libraryFromIndex) + { + this->libraries.removeAt(libraryFromIndex); + this->libraries.insert(libraryToIndex, library); + } + emit layoutChanged(); + } +} + +/** + * @brief Checks whether the specified index points to a valid library. + * @param libraryIndex Index to check + * @returns whether or not it is valid + */ +bool LibraryManager::isValidIndex(const int libraryIndex) const { return libraryIndex >= 0 && libraryIndex < this->libraries.size(); } +/** + * @brief Gets a human-readable string for the specified role + * @param role Role to get a string for + * @returns string + */ QString Library::libraryRoleName(const Role role) { switch (role) @@ -112,12 +209,24 @@ } } +/** + * @brief Overload necessary to implement QAbstractTableModel + * @param index + * @return Item flags + */ Qt::ItemFlags LibraryManager::flags(const QModelIndex& index) const { Q_UNUSED(index); return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } +/** + * @brief Returns data needed to represent the libraries in a table. This function + * decides how data is represented in a table view. + * @param index Index to get data for + * @param role Role of the data (role as in Qt model-view role, not LDraw library role) + * @returns variant + */ QVariant LibraryManager::data(const QModelIndex& index, int role) const { if (role != Qt::DisplayRole) @@ -140,6 +249,13 @@ } } +/** + * @brief Returns header texts for a table view. We only implement column headers here. + * @param section Index of the column (can also be a row but we only have column headers) + * @param orientation Orientation (we only support horizontal orientation) + * @param role Role of the data (role as in Qt model-view role, not LDraw library role) + * @returns variant + */ QVariant LibraryManager::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal and role == Qt::DisplayRole) @@ -159,25 +275,44 @@ } } -int LibraryManager::rowCount(const QModelIndex& parent) const +/** + * @brief Overload necessary to implement QAbstractTableModel. + * @returns how many rows there are in the table. Since there is one row per library, this returns + * the amount of libraries. + */ +int LibraryManager::rowCount(const QModelIndex&) const { - Q_UNUSED(parent) return this->count(); } -int LibraryManager::columnCount(const QModelIndex& parent) const +/** + * @brief LibraryManager::columnCount + * @returns how many columns there are in the table. + */ +int LibraryManager::columnCount(const QModelIndex&) const { - Q_UNUSED(parent) return 2; } +/** + * @brief Signals view objects that the specified library has changed. This is necessary + * to update the table widget know when libraries are changed. + * @param libraryIndex Index of the library. + */ void LibraryManager::signalLibraryChange(int libraryIndex) { + Q_ASSERT(isValidIndex(libraryIndex)); const QModelIndex topLeft = this->index(libraryIndex, 0); - const QModelIndex bottomRight = this->index(libraryIndex, columnCount() - 1); + const QModelIndex bottomRight = this->index(libraryIndex, columnCount({}) - 1); emit dataChanged(topLeft, bottomRight); } +/** + * @brief Overload for operator<< to allow serializing a library into the configuration file. + * @param stream Stream to write the library into + * @param library Library to write into the stream + * @returns the stream + */ QDataStream& operator<<(QDataStream& stream, const Library& library) { const QString path = library.path.absolutePath(); @@ -185,6 +320,12 @@ return stream << path << role; } +/** + * @brief Overload for operator>> to allow serializing a library into the configuration file. + * @param stream Stream to read the library from + * @param library Library to obtain from the stream + * @returns the stream + */ QDataStream& operator>>(QDataStream& stream, Library& library) { QString path;
--- a/src/libraries.h Sat Oct 05 23:47:03 2019 +0300 +++ b/src/libraries.h Sun Nov 03 12:17:41 2019 +0200 @@ -15,9 +15,12 @@ } role; QDir path; static QString libraryRoleName(const Role role); + static bool isValidRole(const Role role); + constexpr static const Role allRoles[] = {OfficialLibrary, UnofficialLibrary, WorkingLibrary}; }; Q_DECLARE_METATYPE(Library) +Q_DECLARE_METATYPE(Library::Role) QDataStream &operator<<(QDataStream&, const Library&); QDataStream &operator>>(QDataStream&, Library&); @@ -34,12 +37,14 @@ QVector<Library>::const_iterator end() const; QFileInfo findFile(QString fileName) const; void addLibrary(const Library& library); + void removeLibrary(const int libraryIndex); const Library& library(int libraryIndex) const; void setLibraryPath(int libraryIndex, const QDir& path); void setLibraryRole(int libraryIndex, const Library::Role role); void restoreFromSettings(QSettings* settings); void storeToSettings(QSettings* settings); int count() const; + void moveLibrary(const int libraryFromIndex, const int libraryToIndex); // Definitions for QAbstractTableModel Qt::ItemFlags flags(const QModelIndex& index) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; @@ -47,8 +52,9 @@ int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; + int rowCount(const QModelIndex&) const override; + int columnCount(const QModelIndex&) const override; + bool isValidIndex(const int libraryIndex) const; private: enum Column { @@ -56,6 +62,5 @@ PathColumn }; void signalLibraryChange(int library); - bool isValidIndex(int libraryIndex); Libraries libraries; };
--- a/src/main.h Sat Oct 05 23:47:03 2019 +0300 +++ b/src/main.h Sun Nov 03 12:17:41 2019 +0200 @@ -4,6 +4,7 @@ #include <QSet> #include <memory> #include "basics.h" +#include "utility.h" namespace settingGroups {
--- a/src/mainwindow.cpp Sat Oct 05 23:47:03 2019 +0300 +++ b/src/mainwindow.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -7,6 +7,7 @@ #include "ui_mainwindow.h" #include "settingseditor/settingseditor.h" #include "version.h" +#include "document.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent}, @@ -43,19 +44,21 @@ tr("LDraw models (*.ldr *.dat)")); if (not path.isEmpty()) { - QFile file{path}; - const bool open_result = file.open(QIODevice::ReadOnly); - if (open_result) + QString errorString; + QTextStream errorStream{&errorString}; + QString modelName = this->documents.openModel(path, errorStream); + if (not modelName.isEmpty()) { - QMessageBox::critical(this, "Not implemented", "This functionality is not done yet"); + Document* document = new Document{this->documents.findModelByName(modelName)}; + this->ui->tabs->addTab(document, modelName); } else { - const QString errorString = format( + const QString errorMessage = utility::format( tr("Could not open %1: %2"), path, - file.errorString()); - QMessageBox::critical(this, tr("Problem opening file"), errorString); + errorString); + QMessageBox::critical(this, tr("Problem opening file"), errorMessage); } } }
--- a/src/mainwindow.h Sat Oct 05 23:47:03 2019 +0300 +++ b/src/mainwindow.h Sun Nov 03 12:17:41 2019 +0200 @@ -36,5 +36,4 @@ void restoreSettings(); void changeLanguage(QString localeCode); static QString pathToTranslation(const QString& localeCode); - };
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/matrix.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -0,0 +1,1 @@ +#include "matrix.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/matrix.h Sun Nov 03 12:17:41 2019 +0200 @@ -0,0 +1,21 @@ +#pragma once +#include <QMetaType> + +template<int Rows, int Columns, typename T = double> +struct Matrix +{ + T values[Rows][Columns]; + T& operator()(int row, int column) + { + return this->values[row][column]; + } + T operator()(int row, int column) const + { + return this->values[row][column]; + } +}; + +using Matrix3x3 = Matrix<3, 3>; +Q_DECLARE_METATYPE(Matrix3x3); +using Matrix4x4 = Matrix<4, 4>; +Q_DECLARE_METATYPE(Matrix4x4);
--- a/src/model.cpp Sat Oct 05 23:47:03 2019 +0300 +++ b/src/model.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -1,3 +1,5 @@ +#include <QBrush> +#include <QFont> #include "model.h" #include "modeleditcontext.h" @@ -15,3 +17,43 @@ { return {*this}; } + +int Model::rowCount(const QModelIndex&) const +{ + return size(); +} + +QVariant Model::data(const QModelIndex& index, int role) const +{ + const int row = index.row(); + modelobjects::BaseObject* object = this->body[row].get(); + switch(role) + { + case Qt::DisplayRole: + return object->textRepresentation(); + case Qt::ForegroundRole: + return object->textRepresentationForeground(); + case Qt::BackgroundRole: + return object->textRepresentationBackground(); + case Qt::FontRole: + return object->textRepresentationFont(); + default: + return {}; + } +} + +QVariant Model::getHeaderProperty(const HeaderProperty property) +{ + switch (property) + { + case HeaderProperty::Name: + return header.name; + default: + return {}; + } +} + +void Model::append(ModelObjectPointer&& object) +{ + this->body.push_back(std::move(object)); +}
--- a/src/model.h Sat Oct 05 23:47:03 2019 +0300 +++ b/src/model.h Sun Nov 03 12:17:41 2019 +0200 @@ -1,10 +1,16 @@ #pragma once +#include <QAbstractListModel> #include <memory> #include "main.h" #include "header.h" #include "objecttypes/modelobject.h" -class Model : public QObject +enum class HeaderProperty +{ + Name +}; + +class Model : public QAbstractListModel { Q_OBJECT public: @@ -13,16 +19,21 @@ Model(const Model&) = delete; int size() 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; signals: void objectAdded(modelobjects::Id id, int position); private: + using ModelObjectPointer = std::unique_ptr<modelobjects::BaseObject>; template<typename T, typename... Args> T* append(Args&&... args); + void append(ModelObjectPointer&& object); template<typename T, typename... Args> T* insert(int position, Args&&... args); bool modified = false; QString path; - using ModelObjectPointer = std::unique_ptr<modelobjects::BaseObject>; LDHeader header; std::vector<ModelObjectPointer> body; std::map<modelobjects::Id, modelobjects::BaseObject*> objectsById; @@ -31,19 +42,23 @@ template<typename T, typename... Args> T* Model::append(Args&&... args) { + emit layoutAboutToBeChanged(); this->body.push_back(std::make_unique<T>(args...)); T* pointer = static_cast<T*>(this->body.back().get()); this->objectsById[pointer->id] = pointer; emit objectAdded(pointer->id, this->body.size() - 1); + emit layoutChanged(); return pointer; } template<typename T, typename... Args> T* Model::insert(int position, Args&&... args) { + emit layoutAboutToBeChanged(); this->body.insert(position, std::make_unique<T>(args...)); T* pointer = static_cast<T*>(this->body[position]); this->objectsById[pointer->id] = pointer; emit objectAdded(pointer->id, position); + emit layoutChanged(); return pointer; }
--- a/src/modeleditcontext.cpp Sat Oct 05 23:47:03 2019 +0300 +++ b/src/modeleditcontext.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -5,6 +5,11 @@ { } +void Model::EditContext::append(std::unique_ptr<modelobjects::BaseObject>&& object) +{ + this->model.append(std::move(object)); +} + void Model::EditContext::setObjectProperty( modelobjects::BaseObject* object, modelobjects::Property property,
--- a/src/modeleditcontext.h Sat Oct 05 23:47:03 2019 +0300 +++ b/src/modeleditcontext.h Sun Nov 03 12:17:41 2019 +0200 @@ -7,6 +7,7 @@ public: template<typename T, typename... Args> T* append(Args&&... args); + void append(std::unique_ptr<modelobjects::BaseObject>&& object); template<typename T, typename... Args> T* insert(int position, Args&&... args); void setObjectProperty(
--- a/src/objecttypes/comment.cpp Sat Oct 05 23:47:03 2019 +0300 +++ b/src/objecttypes/comment.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -1,39 +1,6 @@ #include <QFont> #include "comment.h" -modelobjects::Comment::Comment(QStringView text) : - BaseObject{}, - storedText{text.toString()} {} - -QVariant modelobjects::Comment::getProperty(Property property) const -{ - switch (property) - { - case Property::Text: - return storedText; - default: - return BaseObject::getProperty(property); - } -} - -auto modelobjects::Comment::setProperty(Property property, const QVariant& value) - -> SetPropertyResult -{ - switch (property) - { - case Property::Text: - storedText = value.toString(); - return SetPropertyResult::Success; - default: - return BaseObject::setProperty(property, value); - } -} - -QString modelobjects::Comment::textRepresentation() const -{ - return this->storedText; -} - QFont modelobjects::Comment::textRepresentationFont() const { QFont font;
--- a/src/objecttypes/comment.h Sat Oct 05 23:47:03 2019 +0300 +++ b/src/objecttypes/comment.h Sun Nov 03 12:17:41 2019 +0200 @@ -1,22 +1,14 @@ #pragma once #include "modelobject.h" +#include "metacommand.h" namespace modelobjects { class Comment; } -class modelobjects::Comment : public BaseObject +class modelobjects::Comment : public MetaCommand { -public: - Comment() = default; - Comment(QStringView text); - QVariant getProperty(Property property) const override; - SetPropertyResult setProperty( - Property property, - const QVariant& value) override; - QString textRepresentation() const override; + using MetaCommand::MetaCommand; QFont textRepresentationFont() const override; -private: - QString storedText = ""; };
--- a/src/objecttypes/conditionaledge.cpp Sat Oct 05 23:47:03 2019 +0300 +++ b/src/objecttypes/conditionaledge.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -12,6 +12,13 @@ { } +modelobjects::ConditionalEdge::ConditionalEdge(const QVector<Vertex>& vertices, const Color color) : + Edge{vertices[0], vertices[1], color}, + controlPoint_1{vertices[2]}, + controlPoint_2{vertices[3]} +{ +} + QVariant modelobjects::ConditionalEdge::getProperty(Property property) const { switch (property) @@ -43,7 +50,7 @@ QString modelobjects::ConditionalEdge::textRepresentation() const { - return Edge::textRepresentation() + format("%1 %2", + return Edge::textRepresentation() + utility::format("%1 %2", vertexToStringParens(controlPoint_1), vertexToStringParens(controlPoint_2)); }
--- a/src/objecttypes/conditionaledge.h Sat Oct 05 23:47:03 2019 +0300 +++ b/src/objecttypes/conditionaledge.h Sun Nov 03 12:17:41 2019 +0200 @@ -16,6 +16,7 @@ const Vertex& controlPoint_1, const Vertex& controlPoint_2, const Color color_index = colors::edge); + ConditionalEdge(const QVector<Vertex>& vertices, const Color color); QVariant getProperty(Property property) const override; SetPropertyResult setProperty( Property property,
--- a/src/objecttypes/edge.cpp Sat Oct 05 23:47:03 2019 +0300 +++ b/src/objecttypes/edge.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -8,6 +8,13 @@ point_1{point_1}, point_2{point_2} {} +modelobjects::Edge::Edge(const QVector<Vertex>& vertices, const Color color) : + ColoredBaseObject{color}, + point_1{vertices[0]}, + point_2{vertices[1]} +{ +} + QVariant modelobjects::Edge::getProperty(Property property) const { switch (property) @@ -39,5 +46,5 @@ QString modelobjects::Edge::textRepresentation() const { - return format("%1 %2", vertexToStringParens(point_1), vertexToStringParens(point_2)); + return utility::format("%1 %2", vertexToStringParens(point_1), vertexToStringParens(point_2)); }
--- a/src/objecttypes/edge.h Sat Oct 05 23:47:03 2019 +0300 +++ b/src/objecttypes/edge.h Sun Nov 03 12:17:41 2019 +0200 @@ -13,6 +13,7 @@ Edge() = default; Edge(const Vertex& point_1, const Vertex& point_2, const Color color_index = colors::edge); + Edge(const QVector<Vertex>& vertices, const Color color); QVariant getProperty(Property property) const override; SetPropertyResult setProperty( Property property,
--- a/src/objecttypes/errorline.cpp Sat Oct 05 23:47:03 2019 +0300 +++ b/src/objecttypes/errorline.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -1,8 +1,9 @@ #include <QBrush> #include "errorline.h" -modelobjects::ErrorLine::ErrorLine(QStringView text) : - text{text.toString()} +modelobjects::ErrorLine::ErrorLine(QStringView text, QStringView message) : + text{text.toString()}, + message{message.toString()} { } @@ -11,7 +12,9 @@ switch (property) { case Property::Text: - return text; + return this->text; + case Property::ErrorMessage: + return this->message; default: return BaseObject::getProperty(property); } @@ -25,7 +28,10 @@ switch (property) { case Property::Text: - text = value.toString(); + this->text = value.toString(); + return SetPropertyResult::Success; + case Property::ErrorMessage: + this->message = value.toString(); return SetPropertyResult::Success; default: return BaseObject::setProperty(property, value);
--- a/src/objecttypes/errorline.h Sat Oct 05 23:47:03 2019 +0300 +++ b/src/objecttypes/errorline.h Sun Nov 03 12:17:41 2019 +0200 @@ -9,7 +9,7 @@ class modelobjects::ErrorLine : public BaseObject { public: - ErrorLine(QStringView text = u""); + ErrorLine(QStringView text = u"", QStringView message = u""); QVariant getProperty(Property property) const override; SetPropertyResult setProperty( Property property, @@ -19,4 +19,5 @@ QBrush textRepresentationBackground() const override; private: QString text; + QString message; };
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/objecttypes/metacommand.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -0,0 +1,34 @@ +#include "metacommand.h" + +modelobjects::MetaCommand::MetaCommand(QStringView text) : + BaseObject{}, + storedText{text.toString()} {} + +QVariant modelobjects::MetaCommand::getProperty(Property property) const +{ + switch (property) + { + case Property::Text: + return storedText; + default: + return BaseObject::getProperty(property); + } +} + +auto modelobjects::MetaCommand::setProperty(Property property, const QVariant& value) + -> SetPropertyResult +{ + switch (property) + { + case Property::Text: + storedText = value.toString(); + return SetPropertyResult::Success; + default: + return BaseObject::setProperty(property, value); + } +} + +QString modelobjects::MetaCommand::textRepresentation() const +{ + return this->storedText; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/objecttypes/metacommand.h Sun Nov 03 12:17:41 2019 +0200 @@ -0,0 +1,20 @@ +#pragma once +#include "modelobject.h" + +namespace modelobjects +{ + class MetaCommand; +} +class modelobjects::MetaCommand : public BaseObject +{ +public: + MetaCommand() = default; + MetaCommand(QStringView text); + QVariant getProperty(Property property) const override; + SetPropertyResult setProperty( + Property property, + const QVariant& value) override; + QString textRepresentation() const override; +private: + QString storedText = ""; +};
--- a/src/objecttypes/modelobject.cpp Sat Oct 05 23:47:03 2019 +0300 +++ b/src/objecttypes/modelobject.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -105,3 +105,8 @@ return BaseObject::setProperty(id, value); } } + +QString modelobjects::Empty::textRepresentation() const +{ + return ""; +}
--- a/src/objecttypes/modelobject.h Sat Oct 05 23:47:03 2019 +0300 +++ b/src/objecttypes/modelobject.h Sun Nov 03 12:17:41 2019 +0200 @@ -11,6 +11,7 @@ enum class Property; class BaseObject; class ColoredBaseObject; + class Empty; } enum class modelobjects::Property @@ -27,6 +28,7 @@ Transformation, ReferenceName, IsInverted, + ErrorMessage }; class modelobjects::BaseObject @@ -63,6 +65,11 @@ Color color_index = colors::main; }; +class modelobjects::Empty : public BaseObject +{ + QString textRepresentation() const override; +}; + namespace modelobjects { using Id = std::remove_const_t<decltype(BaseObject::id)>;
--- a/src/objecttypes/polygon.cpp Sat Oct 05 23:47:03 2019 +0300 +++ b/src/objecttypes/polygon.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -10,6 +10,12 @@ { } +modelobjects::Triangle::Triangle(const QVector<Vertex>& vertices, const Color color) : + ColoredBaseObject{color}, + points{vertices[0], vertices[1], vertices[2]} +{ +} + QVariant modelobjects::Triangle::getProperty(const Property id) const { switch (id) @@ -46,7 +52,7 @@ QString modelobjects::Triangle::textRepresentation() const { - return format("%1 %2 %3", + return utility::format("%1 %2 %3", vertexToStringParens(points[0]), vertexToStringParens(points[1]), vertexToStringParens(points[2])); @@ -63,6 +69,12 @@ { } +modelobjects::Quadrilateral::Quadrilateral(const QVector<Vertex>& vertices, const Color color) : + ColoredBaseObject{color}, + points{vertices[0], vertices[1], vertices[2], vertices[3]} +{ +} + QVariant modelobjects::Quadrilateral::getProperty(const Property id) const { switch (id) @@ -106,7 +118,7 @@ QString modelobjects::Quadrilateral::textRepresentation() const { - return format("%1 %2 %3 %4", + return utility::format("%1 %2 %3 %4", vertexToStringParens(points[0]), vertexToStringParens(points[1]), vertexToStringParens(points[2]),
--- a/src/objecttypes/polygon.h Sat Oct 05 23:47:03 2019 +0300 +++ b/src/objecttypes/polygon.h Sun Nov 03 12:17:41 2019 +0200 @@ -16,6 +16,7 @@ const Vertex &point_2, const Vertex &point_3, Color color_index = colors::main); + Triangle(const QVector<Vertex>& vertices, const Color color); QVariant getProperty(Property id) const override; SetPropertyResult setProperty(Property id, const QVariant& value) override; QString textRepresentation() const override; @@ -33,6 +34,7 @@ const Vertex &point_3, const Vertex &point_4, Color color_index = colors::main); + Quadrilateral(const QVector<Vertex>& vertices, const Color color); QVariant getProperty(Property id) const override; SetPropertyResult setProperty(Property id, const QVariant& value) override; QString textRepresentation() const override;
--- a/src/objecttypes/subfilereference.cpp Sat Oct 05 23:47:03 2019 +0300 +++ b/src/objecttypes/subfilereference.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -1,5 +1,17 @@ #include "subfilereference.h" +modelobjects::SubfileReference::SubfileReference( + const Vertex& position, + const Matrix3x3& transformation, + const QString& referenceName, + const Color color) : + ColoredBaseObject{color}, + position{position}, + transformation{transformation}, + referenceName{referenceName} +{ +} + QVariant modelobjects::SubfileReference::getProperty(Property property) const { switch (property) @@ -15,6 +27,27 @@ } } +auto modelobjects::SubfileReference::setProperty( + Property property, + const QVariant& value) + -> SetPropertyResult +{ + switch (property) + { + case Property::Position: + this->position = value.value<Vertex>(); + return SetPropertyResult::Success; + case Property::Transformation: + this->transformation = value.value<Matrix3x3>(); + return SetPropertyResult::Success; + case Property::ReferenceName: + this->referenceName = value.toString(); + return SetPropertyResult::Success; + default: + return ColoredBaseObject::setProperty(property, value); + } +} + QString modelobjects::SubfileReference::textRepresentation() const { return referenceName + " " + vertexToStringParens(this->position);
--- a/src/objecttypes/subfilereference.h Sat Oct 05 23:47:03 2019 +0300 +++ b/src/objecttypes/subfilereference.h Sun Nov 03 12:17:41 2019 +0200 @@ -1,19 +1,26 @@ #pragma once #include "modelobject.h" +#include "matrix.h" namespace modelobjects { class SubfileReference; } -class modelobjects::SubfileReference : ColoredBaseObject +class modelobjects::SubfileReference : public ColoredBaseObject { public: SubfileReference() = default; + SubfileReference( + const Vertex& position, + const Matrix3x3& transformation, + const QString &referenceName, + const Color color = colors::main); QVariant getProperty(Property property) const override; + SetPropertyResult setProperty(Property property, const QVariant& value) override; QString textRepresentation() const override; private: Vertex position; - QMatrix3x3 transformation; + Matrix3x3 transformation; QString referenceName; };
--- a/src/parser.cpp Sat Oct 05 23:47:03 2019 +0300 +++ b/src/parser.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -22,10 +22,16 @@ #include "objecttypes/conditionaledge.h" #include "objecttypes/edge.h" #include "objecttypes/errorline.h" +#include "objecttypes/metacommand.h" #include "objecttypes/modelobject.h" #include "objecttypes/polygon.h" #include "objecttypes/subfilereference.h" +struct BodyParseError +{ + QString message; +}; + /* * Constructs an LDraw parser */ @@ -41,7 +47,7 @@ return QString::fromUtf8(this->device.readLine()).trimmed(); } -static const QMap<QString, decltype(LDHeader::type)> typeStrings { +static const QMap<QString, LDHeader::FileType> typeStrings { {"Part", LDHeader::Part}, {"Subpart", LDHeader::Subpart}, {"Shortcut", LDHeader::Shortcut}, @@ -89,9 +95,9 @@ if (tokens.contains("Alias")) header.qualfiers |= LDHeader::Alias; if (tokens.contains("Physical_Color")) - header.qualfiers |= LDHeader::Physical_Color; + header.qualfiers |= LDHeader::PhysicalColour; if (tokens.contains("Flexible_Section")) - header.qualfiers |= LDHeader::Flexible_Section; + header.qualfiers |= LDHeader::FlexibleSection; return ParseSuccess; } else @@ -101,7 +107,7 @@ } else if (line == "0 BFC CERTIFY CCW") { - winding = CounterClockwise; + winding = Anticlockwise; return ParseSuccess; } else if (line == "0 BFC CERTIFY CW") @@ -248,104 +254,162 @@ invertNext = true; continue; } - modelobjects::BaseObject* object = parseFromString(editor, line); + std::unique_ptr<modelobjects::BaseObject> object = parseFromString(line); if (invertNext) { - editor.setObjectProperty(object, modelobjects::Property::IsInverted, true); + editor.setObjectProperty(object.get(), modelobjects::Property::IsInverted, true); } + editor.append(std::move(object)); invertNext = false; } } -namespace +static Color colorFromString(const QString& colorString) { - namespace regexes + bool colorSucceeded; + const Color color = {colorString.toInt(&colorSucceeded)}; + if (colorSucceeded) + { + return color; + } + else { - static const QRegExp comment {R"(^\s*0\s*\/\/\s*(.+)$)"}; - static const QRegExp metacommand {R"(^\s*0\s*(.+)$)"}; - static const QRegExp edgeline - { - R"(^\s*2)" // starting 2-token - R"(\s+(\d+))" // colour - R"(((?:\s+[^\s]+){3}))" // 1st vertex - R"(((?:\s+[^\s]+){3}))" // 2nd vertex - R"(\s*$)" // end - }; - static const QRegExp triangle + throw BodyParseError{"colour was not an integer value"}; + } +} + +static Vertex vertexFromStrings( + const QStringList& tokens, + const int startingPosition) +{ + bool ok_x; + const float x = tokens[startingPosition].toFloat(&ok_x); + bool ok_y; + const float y = tokens[startingPosition + 1].toFloat(&ok_y); + bool ok_z; + const float z = tokens[startingPosition + 2].toFloat(&ok_z); + if (not ok_x or not ok_y or not ok_z) + { + throw BodyParseError{"vertex contained illegal co-ordinates"}; + } + return {x, y, z}; +} + +static Matrix3x3 matrixFromStrings(const QStringList& tokens, const int startingPosition) +{ + Matrix3x3 result; + for (int i = 0; i < 9; i += 1) + { + const int row = i / 3; + const int column = i % 3; + const int index = i + startingPosition; + if (index >= tokens.size()) { - R"(^\s*3)" // starting 3-token - R"(\s+(\d+))" // colour - R"(((?:\s+[^\s]+){3}))" // 1st vertex - R"(((?:\s+[^\s]+){3}))" // 2nd vertex - R"(((?:\s+[^\s]+){3}))" // 3rd vertex - R"(\s*$)" // end - }; - static const QRegExp quadrilateral + throw BodyParseError{"too few tokens available"}; + } + bool ok; + result(row, column) = tokens[index].toFloat(&ok); + if (not ok) { - R"(^\s*4)" // starting 4-token - R"(\s+(\d+))" // colour - R"(((?:\s+[^\s]+){3}))" // 1st vertex - R"(((?:\s+[^\s]+){3}))" // 2nd vertex - R"(((?:\s+[^\s]+){3}))" // 3rd vertex - R"(((?:\s+[^\s]+){3}))" // 4th vertex - R"(\s*$)" // end - }; - static const QRegExp conditionaledge - { - R"(^\s*5)" // starting 5-token - R"(\s+(\d+))" // colour - R"(((?:\s+[^\s]+){3}))" // 1st vertex - R"(((?:\s+[^\s]+){3}))" // 2nd vertex - R"(((?:\s+[^\s]+){3}))" // 1st control point - R"(((?:\s+[^\s]+){3}))" // 2nd control point - R"(\s*$)" // end - }; + throw BodyParseError{"non-numeric values for matrix"}; + } + } + return result; +} + +static std::unique_ptr<modelobjects::BaseObject> parseType0Line( + const QString& line, + const QStringList& tokens) +{ + Q_UNUSED(tokens) + if (line.startsWith("0 //")) + { + return std::make_unique<modelobjects::Comment>(line.mid(std::strlen("0 //")).simplified()); + } + else + { + return std::make_unique<modelobjects::MetaCommand>(line.mid(1).simplified()); } } -static Vertex vertexFromString(const QString& vertex_string) +static std::unique_ptr<modelobjects::SubfileReference> parseType1Line( + const QString& line, + const QStringList& tokens) { - static const QRegExp pattern {R"(^\s*([^\s]+)\s+([^\s]+)\s+([^\s]+)\s*$)"}; - const bool succeeded = pattern.exactMatch(vertex_string); - if (succeeded) + Q_UNUSED(line) + constexpr int colorPosition = 1; + constexpr int transformPosition = 2; // 2..10 + constexpr int positionPosition = 11; // 11..13 + constexpr int namePosition = 14; + if (tokens.size() != 15) { - const float x = pattern.cap(1).toFloat(nullptr); - const float y = pattern.cap(2).toFloat(nullptr); - const float z = pattern.cap(3).toFloat(nullptr); - return {x, y, z}; + throw BodyParseError{"wrong amount of tokens in a type-1 line"}; } - else - { - return {}; - } + const Color color = colorFromString(tokens[colorPosition]); + const Vertex position = vertexFromStrings(tokens, positionPosition); + const Matrix3x3 transform = matrixFromStrings(tokens, transformPosition); + const QString& name = tokens[namePosition]; + return std::make_unique<modelobjects::SubfileReference>(position, transform, name, color); } -static modelobjects::Edge* parseEdgeline( - Model::EditContext& editor, - const QString& line) +template<typename T, int NumVertices> +static std::unique_ptr<T> parsePolygon( + const QString& line, + const QStringList& tokens) { - const bool succeeded = regexes::edgeline.exactMatch(line); - if (succeeded) + Q_UNUSED(line) + constexpr int colorPosition = 1; + auto vertexPosition = [](int n) { return 2 + 3*n; }; + if (tokens.size() != 2 + 3 * NumVertices) + { + throw BodyParseError{"wrong amount of tokens"}; + } + const Color color = colorFromString(tokens[colorPosition]); + QVector<Vertex> vertices; + vertices.reserve(NumVertices); + for (int i = 0; i < NumVertices; i += 1) + { + vertices.append(vertexFromStrings(tokens, vertexPosition(i))); + } + return std::make_unique<T>(vertices, color); +} + +std::unique_ptr<modelobjects::BaseObject> Parser::parseFromString(QString line) +{ + line = line.simplified(); + try { - const Color colour = {regexes::edgeline.cap(1).toInt(nullptr)}; - const Vertex v_1 = vertexFromString(regexes::edgeline.cap(2)); - const Vertex v_2 = vertexFromString(regexes::edgeline.cap(3)); - return editor.append<modelobjects::Edge>(v_1, v_2, colour); + const QStringList tokens = line.split(QRegExp{R"(\s+)"}); + if (tokens.empty()) + { + return std::make_unique<modelobjects::Empty>(); + } + bool ok_code; + const int code = tokens[0].toInt(&ok_code); + if (not ok_code) + { + throw BodyParseError{"line type was not an integer"}; + } + switch (code) + { + case 0: + return parseType0Line(line, tokens); + case 1: + return parseType1Line(line, tokens); + case 2: + return parsePolygon<modelobjects::Edge, 2>(line, tokens); + case 3: + return parsePolygon<modelobjects::Triangle, 3>(line, tokens); + case 4: + return parsePolygon<modelobjects::Quadrilateral, 4>(line, tokens); + case 5: + return parsePolygon<modelobjects::ConditionalEdge, 4>(line, tokens); + default: + throw BodyParseError{utility::format("bad line type '%1'", code)}; + } } - else + catch(const BodyParseError& error) { - return nullptr; + return std::make_unique<modelobjects::ErrorLine>(line, error.message); } } - -modelobjects::BaseObject* Parser::parseFromString( - Model::EditContext& editor, - const QString& line) -{ - modelobjects::Edge* edge = parseEdgeline(editor, line); - if (edge) - { - return edge; - } - return editor.append<modelobjects::ErrorLine>(line); -}
--- a/src/parser.h Sat Oct 05 23:47:03 2019 +0300 +++ b/src/parser.h Sun Nov 03 12:17:41 2019 +0200 @@ -31,8 +31,7 @@ Parser(QIODevice& device, QObject* parent = nullptr); LDHeader parseHeader(Winding& winding); void parseBody(Model::EditContext& editor); - static modelobjects::BaseObject* parseFromString(Model::EditContext& editor, - const QString& line); + static std::unique_ptr<modelobjects::BaseObject> parseFromString(QString line); private: enum HeaderParseResult {ParseSuccess, ParseFailure, StopParsing}; QString readLine();
--- a/src/settingseditor/librarieseditor.cpp Sat Oct 05 23:47:03 2019 +0300 +++ b/src/settingseditor/librarieseditor.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -1,4 +1,5 @@ #include <QFileDialog> +#include <QMenu> #include <QMessageBox> #include "librarieseditor.h" #include "ui_librarieseditor.h" @@ -20,6 +21,12 @@ this, &LibrariesEditor::addNewLibrary); this->ui.librariesTable->setModel(&this->libraries); + this->ui.librariesTable->setContextMenuPolicy(Qt::CustomContextMenu); + connect( + this->ui.librariesTable, + &QWidget::customContextMenuRequested, + this, + &LibrariesEditor::showContextMenu); } LibrariesEditor::~LibrariesEditor() @@ -43,9 +50,9 @@ { QMessageBox::critical(this, tr("Library does not exist"), - format( + utility::format( tr("The directory %1 does not exist."), - quoted(dir.path()))); + utility::quoted(dir.path()))); } else { @@ -53,15 +60,91 @@ { QMessageBox::warning(this, tr("Unreadable library"), - format( + utility::format( tr("The directory %1 cannot be read."), - quoted(dir.path()))); + utility::quoted(dir.path()))); } this->libraries.addLibrary({Library::OfficialLibrary, dir}); this->ui.newLibraryPath->clear(); } } +void LibrariesEditor::showContextMenu(const QPoint position) +{ + const int libraryIndex = this->currentLibraryIndex(); + if (this->libraries.isValidIndex(libraryIndex)) + { + QMenu* contextMenu = new QMenu{this}; + QAction* removeAction = new QAction{tr("Remove library")}; + connect( + removeAction, + &QAction::triggered, + this, + &LibrariesEditor::removeCurrentLibrary); + QMenu* roleMenu = new QMenu{tr("Set role"), contextMenu}; + for (const Library::Role role : Library::allRoles) + { + QAction* setRoleAction = new QAction{Library::libraryRoleName(role)}; + setRoleAction->setData(role); + roleMenu->addAction(setRoleAction); + connect( + setRoleAction, + &QAction::triggered, + this, + &LibrariesEditor::setCurrentLibraryRole); + } + QAction* moveUpAction = new QAction{tr("Move up")}; + connect( + moveUpAction, + &QAction::triggered, + this, + &LibrariesEditor::moveCurrentLibraryUp); + QAction* moveDownAction = new QAction{tr("Move down")}; + connect( + moveDownAction, + &QAction::triggered, + this, + &LibrariesEditor::moveCurrentLibraryDown); + contextMenu->addMenu(roleMenu); + contextMenu->addSeparator(); + contextMenu->addAction(moveDownAction); + contextMenu->addAction(moveUpAction); + contextMenu->addAction(removeAction); + contextMenu->popup(this->ui.librariesTable->mapToGlobal(position)); + } +} + +void LibrariesEditor::setCurrentLibraryRole() +{ + const int libraryIndex = currentLibraryIndex(); + QObject* senderObject = sender(); + QAction* senderAction = qobject_cast<QAction*>(senderObject); + const Library::Role role = senderAction->data().value<Library::Role>(); + this->libraries.setLibraryRole(libraryIndex, role); +} + +void LibrariesEditor::removeCurrentLibrary() +{ + this->libraries.removeLibrary(currentLibraryIndex()); +} + +void LibrariesEditor::moveCurrentLibraryUp() +{ + const int libraryIndex = this->currentLibraryIndex(); + this->libraries.moveLibrary(libraryIndex, libraryIndex - 1); +} + +void LibrariesEditor::moveCurrentLibraryDown() +{ + const int libraryIndex = this->currentLibraryIndex(); + this->libraries.moveLibrary(libraryIndex + 1, libraryIndex); +} + +int LibrariesEditor::currentLibraryIndex() const +{ + return this->ui.librariesTable->selectionModel()->currentIndex().row(); +} + void LibrariesEditor::saveSettings(QSettings* settings) { this->libraries.storeToSettings(settings);
--- a/src/settingseditor/librarieseditor.h Sat Oct 05 23:47:03 2019 +0300 +++ b/src/settingseditor/librarieseditor.h Sun Nov 03 12:17:41 2019 +0200 @@ -13,6 +13,11 @@ private slots: void searchPathForNewLibrary(); void addNewLibrary(); + void showContextMenu(const QPoint position); + void setCurrentLibraryRole(); + void removeCurrentLibrary(); + void moveCurrentLibraryUp(); + void moveCurrentLibraryDown(); private: enum { @@ -21,4 +26,5 @@ }; LibraryManager libraries; class Ui_LibrariesEditor& ui; + int currentLibraryIndex() const; };
--- a/src/settingseditor/librarieseditor.ui Sat Oct 05 23:47:03 2019 +0300 +++ b/src/settingseditor/librarieseditor.ui Sun Nov 03 12:17:41 2019 +0200 @@ -37,7 +37,25 @@ </item> <item> <widget class="QTableView" name="librariesTable"> - <attribute name="verticalHeaderCascadingSectionResizes"> + <property name="dragEnabled"> + <bool>true</bool> + </property> + <property name="dragDropMode"> + <enum>QAbstractItemView::InternalMove</enum> + </property> + <property name="defaultDropAction"> + <enum>Qt::MoveAction</enum> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::SingleSelection</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <attribute name="horizontalHeaderStretchLastSection"> <bool>true</bool> </attribute> </widget>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/utility.h Sun Nov 03 12:17:41 2019 +0200 @@ -0,0 +1,55 @@ +#pragma once +#include "basics.h" + +namespace utility +{ + template<typename T, std::size_t N> + constexpr std::size_t countof(T(&)[N]) + { + return N; + } + + // http://stackoverflow.com/a/18204188/3629665 + template<typename T> + inline T rotl10(T x) + { + return (x << 10) | ((x >> 22) & 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; + } + + 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) + { + if (string.contains("'")) + { + string.replace("\"", "\\\""); + string = "\"" + string + "\""; + } + else + { + string = "'" + string + "'"; + } + return string; + } + + template<typename T, typename R> + bool contains(T&& container, R&& value) + { + return std::find(std::begin(container), std::end(container), value) != std::end(container); + } +}
--- a/src/vertex.cpp Sat Oct 05 23:47:03 2019 +0300 +++ b/src/vertex.cpp Sun Nov 03 12:17:41 2019 +0200 @@ -211,10 +211,10 @@ unsigned int qHash(const Vertex& key) { - return qHash(key.x) ^ rotl10(qHash(key.y)) ^ rotl20(qHash(key.z)); + return qHash(key.x) ^ utility::rotl10(qHash(key.y)) ^ utility::rotl20(qHash(key.z)); } QString vertexToStringParens(const Vertex& vertex) { - return format("(%1, %2, %3)", vertex.x, vertex.y, vertex.z); + return utility::format("(%1, %2, %3)", vertex.x, vertex.y, vertex.z); }