major update with many things

Sun, 03 Nov 2019 12:17:41 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Sun, 03 Nov 2019 12:17:41 +0200
changeset 8
44679e468ba9
parent 7
68443f5be176
child 9
8b9780700b5e

major update with many things

CMakeLists.txt file | annotate | diff | comparison | revisions
flags/ru.png file | annotate | diff | comparison | revisions
flags/ru.svg file | annotate | diff | comparison | revisions
ldforge.qrc file | annotate | diff | comparison | revisions
locale/fi.ts file | annotate | diff | comparison | revisions
locale/ru.ts file | annotate | diff | comparison | revisions
locale/sv.ts file | annotate | diff | comparison | revisions
src/basics.h file | annotate | diff | comparison | revisions
src/document.cpp file | annotate | diff | comparison | revisions
src/document.h file | annotate | diff | comparison | revisions
src/document.ui file | annotate | diff | comparison | revisions
src/documentmanager.cpp file | annotate | diff | comparison | revisions
src/documentmanager.h file | annotate | diff | comparison | revisions
src/header.h file | annotate | diff | comparison | revisions
src/libraries.cpp file | annotate | diff | comparison | revisions
src/libraries.h file | annotate | diff | comparison | revisions
src/main.h file | annotate | diff | comparison | revisions
src/mainwindow.cpp file | annotate | diff | comparison | revisions
src/mainwindow.h file | annotate | diff | comparison | revisions
src/matrix.cpp file | annotate | diff | comparison | revisions
src/matrix.h file | annotate | diff | comparison | revisions
src/model.cpp file | annotate | diff | comparison | revisions
src/model.h file | annotate | diff | comparison | revisions
src/modeleditcontext.cpp file | annotate | diff | comparison | revisions
src/modeleditcontext.h file | annotate | diff | comparison | revisions
src/objecttypes/comment.cpp file | annotate | diff | comparison | revisions
src/objecttypes/comment.h file | annotate | diff | comparison | revisions
src/objecttypes/conditionaledge.cpp file | annotate | diff | comparison | revisions
src/objecttypes/conditionaledge.h file | annotate | diff | comparison | revisions
src/objecttypes/edge.cpp file | annotate | diff | comparison | revisions
src/objecttypes/edge.h file | annotate | diff | comparison | revisions
src/objecttypes/errorline.cpp file | annotate | diff | comparison | revisions
src/objecttypes/errorline.h file | annotate | diff | comparison | revisions
src/objecttypes/metacommand.cpp file | annotate | diff | comparison | revisions
src/objecttypes/metacommand.h file | annotate | diff | comparison | revisions
src/objecttypes/modelobject.cpp file | annotate | diff | comparison | revisions
src/objecttypes/modelobject.h file | annotate | diff | comparison | revisions
src/objecttypes/polygon.cpp file | annotate | diff | comparison | revisions
src/objecttypes/polygon.h file | annotate | diff | comparison | revisions
src/objecttypes/subfilereference.cpp file | annotate | diff | comparison | revisions
src/objecttypes/subfilereference.h file | annotate | diff | comparison | revisions
src/parser.cpp file | annotate | diff | comparison | revisions
src/parser.h file | annotate | diff | comparison | revisions
src/settingseditor/librarieseditor.cpp file | annotate | diff | comparison | revisions
src/settingseditor/librarieseditor.h file | annotate | diff | comparison | revisions
src/settingseditor/librarieseditor.ui file | annotate | diff | comparison | revisions
src/utility.h file | annotate | diff | comparison | revisions
src/vertex.cpp file | annotate | diff | comparison | revisions
--- 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)
Binary file flags/ru.png has changed
--- 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);
 }

mercurial