object editor widgets start to form up

Tue, 17 Mar 2020 23:13:29 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Tue, 17 Mar 2020 23:13:29 +0200
changeset 81
62373840e33a
parent 80
764381756899
child 82
70c67c2c4e36

object editor widgets start to form up

CMakeLists.txt file | annotate | diff | comparison | revisions
locale/fi.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/linetypes/conditionaledge.cpp file | annotate | diff | comparison | revisions
src/linetypes/conditionaledge.h file | annotate | diff | comparison | revisions
src/linetypes/edge.cpp file | annotate | diff | comparison | revisions
src/linetypes/edge.h file | annotate | diff | comparison | revisions
src/linetypes/object.cpp file | annotate | diff | comparison | revisions
src/linetypes/object.h file | annotate | diff | comparison | revisions
src/linetypes/quadrilateral.cpp file | annotate | diff | comparison | revisions
src/linetypes/quadrilateral.h file | annotate | diff | comparison | revisions
src/linetypes/subfilereference.cpp file | annotate | diff | comparison | revisions
src/linetypes/triangle.cpp file | annotate | diff | comparison | revisions
src/linetypes/triangle.h file | annotate | diff | comparison | revisions
src/main.h file | annotate | diff | comparison | revisions
src/model.h file | annotate | diff | comparison | revisions
src/ui/multiplyfactordialog.cpp file | annotate | diff | comparison | revisions
src/ui/multiplyfactordialog.h file | annotate | diff | comparison | revisions
src/ui/multiplyfactordialog.ui file | annotate | diff | comparison | revisions
src/ui/objecteditor.cpp file | annotate | diff | comparison | revisions
src/ui/objecteditor.h file | annotate | diff | comparison | revisions
src/ui/polygonobjecteditor.cpp file | annotate | diff | comparison | revisions
src/ui/polygonobjecteditor.h file | annotate | diff | comparison | revisions
src/widgets/vec3editor.cpp file | annotate | diff | comparison | revisions
src/widgets/vec3editor.h file | annotate | diff | comparison | revisions
src/widgets/vec3editor.ui file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Wed Mar 11 19:05:34 2020 +0200
+++ b/CMakeLists.txt	Tue Mar 17 23:13:29 2020 +0200
@@ -54,7 +54,12 @@
 	src/settingseditor/settingseditor.cpp
 	src/types/boundingbox.cpp
 	src/ui/canvas.cpp
+	src/ui/multiplyfactordialog.cpp
+	src/ui/objecteditor.cpp
+	src/ui/polygonobjecteditor.cpp
 	src/widgets/colorbutton.cpp
+	src/widgets/doublespinbox.cpp
+	src/widgets/vec3editor.cpp
 )
 set (LDFORGE_HEADERS
 	src/basics.h
@@ -96,13 +101,20 @@
 	src/settingseditor/settingseditor.h
 	src/types/boundingbox.h
 	src/ui/canvas.h
+	src/ui/multiplyfactordialog.h
+	src/ui/objecteditor.h
+	src/ui/polygonobjecteditor.h
 	src/widgets/colorbutton.h
+	src/widgets/doublespinbox.h
+	src/widgets/vec3editor.h
 )
 set (LDFORGE_FORMS
 	src/document.ui
 	src/mainwindow.ui
 	src/settingseditor/librarieseditor.ui
 	src/settingseditor/settingseditor.ui
+	src/ui/multiplyfactordialog.ui
+	src/widgets/vec3editor.ui
 )
 
 set(LDFORGE_LOCALES
--- a/locale/fi.ts	Wed Mar 11 19:05:34 2020 +0200
+++ b/locale/fi.ts	Tue Mar 17 23:13:29 2020 +0200
@@ -136,77 +136,105 @@
         <translation>Näkymä</translation>
     </message>
     <message>
-        <location filename="../src/mainwindow.ui" line="63"/>
+        <location filename="../src/mainwindow.ui" line="64"/>
         <source>Quit</source>
         <translation>Lopeta</translation>
     </message>
     <message>
-        <location filename="../src/mainwindow.ui" line="68"/>
+        <location filename="../src/mainwindow.ui" line="69"/>
         <source>Open…</source>
         <translation>Avaa...</translation>
     </message>
     <message>
-        <location filename="../src/mainwindow.ui" line="71"/>
+        <location filename="../src/mainwindow.ui" line="72"/>
         <source>Ctrl+O</source>
         <translation>Ctrl+O</translation>
     </message>
     <message>
-        <location filename="../src/mainwindow.ui" line="76"/>
+        <location filename="../src/mainwindow.ui" line="77"/>
         <source>New</source>
         <translation>Uusi</translation>
     </message>
     <message>
-        <location filename="../src/mainwindow.ui" line="79"/>
+        <location filename="../src/mainwindow.ui" line="80"/>
         <source>Ctrl+N</source>
         <translation>Ctrl+N</translation>
     </message>
     <message>
-        <location filename="../src/mainwindow.ui" line="84"/>
+        <location filename="../src/mainwindow.ui" line="85"/>
         <source>Preferences…</source>
         <translation>Asetukset...</translation>
     </message>
     <message>
-        <location filename="../src/mainwindow.ui" line="92"/>
+        <location filename="../src/mainwindow.ui" line="93"/>
         <source>Normal colours</source>
         <translation type="unfinished">Perusvärit</translation>
     </message>
     <message>
-        <location filename="../src/mainwindow.ui" line="100"/>
+        <location filename="../src/mainwindow.ui" line="101"/>
         <source>BFC color coding</source>
         <translation type="unfinished">BFC-värikoodaus</translation>
     </message>
     <message>
-        <location filename="../src/mainwindow.ui" line="108"/>
+        <location filename="../src/mainwindow.ui" line="109"/>
         <source>Random colours</source>
         <translation type="unfinished">Satunnaiset värit</translation>
     </message>
     <message>
-        <location filename="../src/mainwindow.cpp" line="97"/>
+        <location filename="../src/mainwindow.ui" line="117"/>
+        <source>Pick scene colours</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../src/mainwindow.cpp" line="98"/>
         <source>Open model</source>
         <translation>Avaa malli</translation>
     </message>
     <message>
-        <location filename="../src/mainwindow.cpp" line="99"/>
+        <location filename="../src/mainwindow.cpp" line="100"/>
         <source>LDraw models (*.ldr *.dat)</source>
         <translation>LDraw-mallit (*.ldr *.dat)</translation>
     </message>
     <message>
-        <location filename="../src/mainwindow.cpp" line="118"/>
+        <location filename="../src/mainwindow.cpp" line="119"/>
         <source>Problem loading references</source>
         <translation type="unfinished">Ongelma viitteiden lataamisessa</translation>
     </message>
     <message>
-        <location filename="../src/mainwindow.cpp" line="128"/>
+        <location filename="../src/mainwindow.cpp" line="129"/>
         <source>Problem opening file</source>
         <translation>Ongelma tiedoston avaamisessa</translation>
     </message>
     <message>
-        <location filename="../src/mainwindow.cpp" line="130"/>
+        <location filename="../src/mainwindow.cpp" line="131"/>
         <source>Could not open %1: %2</source>
         <translation>Ei voitu avata %1: %2</translation>
     </message>
 </context>
 <context>
+    <name>MultiplyFactorDialog</name>
+    <message>
+        <location filename="../src/ui/multiplyfactordialog.ui" line="14"/>
+        <source>Multiply with a scalar</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../src/ui/multiplyfactordialog.ui" line="24"/>
+        <source>Factor:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../src/ui/multiplyfactordialog.ui" line="34"/>
+        <source>Invert</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../src/ui/multiplyfactordialog.ui" line="43"/>
+        <source>Preview</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
     <name>QObject</name>
     <message>
         <location filename="../src/gl/partrenderer.cpp" line="244"/>
@@ -328,4 +356,32 @@
         <translation>Näppäinyhdistelmät</translation>
     </message>
 </context>
+<context>
+    <name>Vec3Editor</name>
+    <message>
+        <location filename="../src/widgets/vec3editor.ui" line="14"/>
+        <source>Form</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../src/widgets/vec3editor.ui" line="20"/>
+        <source>x = </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../src/widgets/vec3editor.ui" line="36"/>
+        <source>y = </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../src/widgets/vec3editor.ui" line="52"/>
+        <source>z = </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../src/widgets/vec3editor.ui" line="68"/>
+        <source>×</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
 </TS>
--- a/locale/sv.ts	Wed Mar 11 19:05:34 2020 +0200
+++ b/locale/sv.ts	Tue Mar 17 23:13:29 2020 +0200
@@ -224,6 +224,29 @@
         <source>Random colours</source>
         <translation>Slumpmässiga färger</translation>
     </message>
+    <message>
+        <source>Pick scene colours</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>MultiplyFactorDialog</name>
+    <message>
+        <source>Multiply with a scalar</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Factor:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Invert</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Preview</source>
+        <translation type="unfinished"></translation>
+    </message>
 </context>
 <context>
     <name>PartRenderer</name>
@@ -335,6 +358,29 @@
     </message>
 </context>
 <context>
+    <name>Vec3Editor</name>
+    <message>
+        <source>Form</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>x = </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>y = </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>z = </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>×</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
     <name>gl::Compiler</name>
     <message>
         <source>Vertex shader:</source>
--- a/src/basics.h	Wed Mar 11 19:05:34 2020 +0200
+++ b/src/basics.h	Tue Mar 17 23:13:29 2020 +0200
@@ -129,6 +129,17 @@
 	return static_cast<double>(x);
 }
 
+/**
+* @brief casts floating point values to qreal, converting non-floating point values causes an error
+* @param[in] x floating point value to cast
+* @returns qreal
+*/
+template<typename T>
+auto toQreal(T x) -> std::enable_if_t<std::is_floating_point_v<T>, qreal>
+{
+	return static_cast<qreal>(x);
+}
+
 template<int N, typename T, glm::qualifier Q>
 inline QPoint toQPoint(const glm::vec<N, T, Q>& vec)
 {
--- a/src/document.cpp	Wed Mar 11 19:05:34 2020 +0200
+++ b/src/document.cpp	Tue Mar 17 23:13:29 2020 +0200
@@ -31,13 +31,15 @@
 	documents{documents},
 	colorTable{colorTable},
 	renderer{new Canvas{model, documents, colorTable, this}},
-	ui{*new Ui::Document}
+	ui{*new Ui::Document},
+	objectEditor{model, ldraw::NULL_ID, this}
 {
 	this->ui.setupUi(this);
 	this->ui.listView->setModel(model);
-	QVBoxLayout* layout = new QVBoxLayout;
-	layout->addWidget(this->renderer);
-	this->ui.viewportFrame->setLayout(layout);
+	this->ui.viewportFrame->setLayout(new QVBoxLayout{this->ui.listView});
+	this->ui.viewportFrame->layout()->addWidget(this->renderer);
+	this->ui.objectEditorFrame->setLayout(new QVBoxLayout{this->ui.objectEditorFrame});
+	this->ui.objectEditorFrame->layout()->addWidget(&this->objectEditor);
 	this->setMouseTracking(true);
 	connect(this->ui.splitter, &QSplitter::splitterMoved, this, &Document::splitterChanged);
 	connect(this->renderer, &Canvas::newStatusText, this, &Document::newStatusText);
@@ -53,7 +55,9 @@
 				selection.select(index, index);
 			}
 		}
+		QSignalBlocker blocker{this};
 		selectionModel->select(selection, QItemSelectionModel::ClearAndSelect);
+		this->selectionChanged(newSelection);
 	});
 	connect(this->ui.listView->selectionModel(), &QItemSelectionModel::selectionChanged,
 		[&](const QItemSelection& selected, const QItemSelection& deselected)
@@ -64,6 +68,7 @@
 			return fn::map<QSet<ldraw::id_t>>(selection.indexes(), resolveIndex);
 		};
 		this->renderer->handleSelectionChange(resolve(selected), resolve(deselected));
+		this->selectionChanged(resolve(this->ui.listView->selectionModel()->selection()));
 	});
 }
 
@@ -86,3 +91,15 @@
 {
 	this->renderer->setRenderPreferences(newPreferences);
 }
+
+void Document::selectionChanged(const QSet<ldraw::id_t>& newSelection)
+{
+	if (newSelection.size() == 1)
+	{
+		this->objectEditor.setObjectId(*newSelection.begin());
+	}
+	else
+	{
+		this->objectEditor.setObjectId(ldraw::NULL_ID);
+	}
+}
--- a/src/document.h	Wed Mar 11 19:05:34 2020 +0200
+++ b/src/document.h	Tue Mar 17 23:13:29 2020 +0200
@@ -20,6 +20,7 @@
 #include <memory>
 #include <QWidget>
 #include "ui/canvas.h"
+#include "ui/objecteditor.h"
 
 namespace Ui
 {
@@ -45,9 +46,11 @@
 	void newStatusText(const QString& newStatusText);
 	void splitterChanged();
 private:
+	void selectionChanged(const QSet<ldraw::id_t>& newSelection);
 	Model* model;
 	DocumentManager* const documents;
 	const ldraw::ColorTable& colorTable;
 	Canvas* renderer;
 	Ui::Document& ui;
+	ObjectEditor objectEditor;
 };
--- a/src/document.ui	Wed Mar 11 19:05:34 2020 +0200
+++ b/src/document.ui	Tue Mar 17 23:13:29 2020 +0200
@@ -19,6 +19,14 @@
      <property name="orientation">
       <enum>Qt::Horizontal</enum>
      </property>
+     <widget class="QFrame" name="viewportFrame">
+      <property name="frameShape">
+       <enum>QFrame::StyledPanel</enum>
+      </property>
+      <property name="frameShadow">
+       <enum>QFrame::Raised</enum>
+      </property>
+     </widget>
      <widget class="QListView" name="listView">
       <property name="alternatingRowColors">
        <bool>true</bool>
@@ -30,7 +38,7 @@
        <enum>QAbstractItemView::SelectRows</enum>
       </property>
      </widget>
-     <widget class="QFrame" name="viewportFrame">
+     <widget class="QFrame" name="objectEditorFrame">
       <property name="frameShape">
        <enum>QFrame::StyledPanel</enum>
       </property>
--- a/src/linetypes/conditionaledge.cpp	Wed Mar 11 19:05:34 2020 +0200
+++ b/src/linetypes/conditionaledge.cpp	Tue Mar 17 23:13:29 2020 +0200
@@ -54,3 +54,21 @@
 		utility::vertexToStringParens(controlPoint_1),
 		utility::vertexToStringParens(controlPoint_2));
 }
+
+int ldraw::ConditionalEdge::numPoints() const
+{
+	return 4;
+}
+
+const glm::vec3& ldraw::ConditionalEdge::getPoint(int index) const
+{
+	switch(index)
+	{
+	case 3:
+		return this->controlPoint_1;
+	case 4:
+		return this->controlPoint_2;
+	default:
+		return ldraw::Edge::getPoint(index);
+	}
+}
--- a/src/linetypes/conditionaledge.h	Wed Mar 11 19:05:34 2020 +0200
+++ b/src/linetypes/conditionaledge.h	Tue Mar 17 23:13:29 2020 +0200
@@ -22,6 +22,8 @@
 		Property property,
 		const QVariant& value) override;
 	QString textRepresentation() const override;
+	int numPoints() const override;
+	const glm::vec3& getPoint(int index) const override;
 	glm::vec3 controlPoint_1 = {};
 	glm::vec3 controlPoint_2 = {};
 };
--- a/src/linetypes/edge.cpp	Wed Mar 11 19:05:34 2020 +0200
+++ b/src/linetypes/edge.cpp	Tue Mar 17 23:13:29 2020 +0200
@@ -56,3 +56,21 @@
 	Q_UNUSED(context)
 	polygons.push_back(gl::edgeLine(this->point_1, this->point_2, this->colorIndex, this->id));
 }
+
+int ldraw::Edge::numPoints() const
+{
+	return 2;
+}
+
+const glm::vec3& ldraw::Edge::getPoint(int index) const
+{
+	switch (index)
+	{
+	case 0:
+		return this->point_1;
+	case 1:
+		return this->point_2;
+	default:
+		return ldraw::ColoredObject::getPoint(index);
+	}
+}
--- a/src/linetypes/edge.h	Wed Mar 11 19:05:34 2020 +0200
+++ b/src/linetypes/edge.h	Tue Mar 17 23:13:29 2020 +0200
@@ -20,6 +20,8 @@
 		const QVariant& value) override;
 	QString textRepresentation() const override;
 	void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override;
+	int numPoints() const override;
+	const glm::vec3& getPoint(int index) const override;
 	glm::vec3 point_1 = {};
 	glm::vec3 point_2 = {};
 };
--- a/src/linetypes/object.cpp	Wed Mar 11 19:05:34 2020 +0200
+++ b/src/linetypes/object.cpp	Tue Mar 17 23:13:29 2020 +0200
@@ -58,6 +58,11 @@
 	Q_UNUSED(context)
 }
 
+const glm::vec3& ldraw::Object::getPoint(int index) const
+{
+	throw BadPointIndex{};
+}
+
 ldraw::ColoredObject::ColoredObject(const Color color_index) :
 	colorIndex{color_index}
 {
--- a/src/linetypes/object.h	Wed Mar 11 19:05:34 2020 +0200
+++ b/src/linetypes/object.h	Tue Mar 17 23:13:29 2020 +0200
@@ -81,6 +81,9 @@
 		PropertyNotHandled,
 		InvalidValue
 	};
+	class BadPointIndex : public std::exception
+	{
+	};
 	Object();
 	Object(const Object&) = delete;
 	virtual ~Object();
@@ -95,6 +98,8 @@
 	virtual QFont textRepresentationFont() const;
 	virtual void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const;
 	virtual void invert() {}
+	virtual int numPoints() const { return 0; }
+	virtual const glm::vec3& getPoint(int index) const;
 };
 
 class ldraw::ColoredObject : public Object
--- a/src/linetypes/quadrilateral.cpp	Wed Mar 11 19:05:34 2020 +0200
+++ b/src/linetypes/quadrilateral.cpp	Tue Mar 17 23:13:29 2020 +0200
@@ -87,3 +87,20 @@
 	// -> 2 1 0 3
 	std::swap(this->points[0], this->points[2]);
 }
+
+int ldraw::Quadrilateral::numPoints() const
+{
+	return 4;
+}
+
+const glm::vec3& ldraw::Quadrilateral::getPoint(int index) const
+{
+	if (index >= 0 and index < 4)
+	{
+		return points[index];
+	}
+	else
+	{
+		return ldraw::ColoredObject::getPoint(index);
+	}
+}
--- a/src/linetypes/quadrilateral.h	Wed Mar 11 19:05:34 2020 +0200
+++ b/src/linetypes/quadrilateral.h	Tue Mar 17 23:13:29 2020 +0200
@@ -22,5 +22,7 @@
 	QString textRepresentation() const override;
 	void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override;
 	void invert() override;
+	int numPoints() const override;
+	const glm::vec3& getPoint(int index) const override;
 	glm::vec3 points[4] = {{}};
 };
--- a/src/linetypes/subfilereference.cpp	Wed Mar 11 19:05:34 2020 +0200
+++ b/src/linetypes/subfilereference.cpp	Tue Mar 17 23:13:29 2020 +0200
@@ -81,7 +81,7 @@
 
 glm::vec3 ldraw::SubfileReference::position() const
 {
-	return {this->transformation[3][0], this->transformation[3][1], this->transformation[3][2]};
+	return this->transformation[3];
 }
 
 void ldraw::SubfileReference::invert()
--- a/src/linetypes/triangle.cpp	Wed Mar 11 19:05:34 2020 +0200
+++ b/src/linetypes/triangle.cpp	Tue Mar 17 23:13:29 2020 +0200
@@ -83,3 +83,20 @@
 	// -> 1 0 2
 	std::swap(this->points[0], this->points[1]);
 }
+
+int ldraw::Triangle::numPoints() const
+{
+	return 3;
+}
+
+const glm::vec3& ldraw::Triangle::getPoint(int index) const
+{
+	if (index >= 0 and index < 3)
+	{
+		return this->points[index];
+	}
+	else
+	{
+		return ldraw::ColoredObject::getPoint(index);
+	}
+}
--- a/src/linetypes/triangle.h	Wed Mar 11 19:05:34 2020 +0200
+++ b/src/linetypes/triangle.h	Tue Mar 17 23:13:29 2020 +0200
@@ -22,6 +22,8 @@
 	QString textRepresentation() const override;
 	void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override;
 	void invert() override;
+	int numPoints() const override;
+	const glm::vec3& getPoint(int index) const override;
 	glm::vec3 points[3] = {{}};
 };
 
--- a/src/main.h	Wed Mar 11 19:05:34 2020 +0200
+++ b/src/main.h	Tue Mar 17 23:13:29 2020 +0200
@@ -74,13 +74,14 @@
 	using conditionaledgeid_t = Id<class ConditionalEdge>;
 	using subfileid_t = Id<class SubfileReference>;
 
-	constexpr struct
+	constexpr struct NullId
 	{
 		template<typename T>
 		constexpr operator Id<T>() const
 		{
 			return Id<T>{0};
 		}
+		static constexpr decltype(ldraw::id_t::value) value = 0;
 	} NULL_ID = {};
 
 	template<typename T>
--- a/src/model.h	Wed Mar 11 19:05:34 2020 +0200
+++ b/src/model.h	Tue Mar 17 23:13:29 2020 +0200
@@ -135,5 +135,12 @@
 	{
 		*index_out = index;
 	}
-	return static_cast<const R*>(this->objectAt(index));
+	if (index.isValid())
+	{
+		return static_cast<const R*>(this->objectAt(index));
+	}
+	else
+	{
+		return nullptr;
+	}
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ui/multiplyfactordialog.cpp	Tue Mar 17 23:13:29 2020 +0200
@@ -0,0 +1,39 @@
+#include "multiplyfactordialog.h"
+#include "ui_multiplyfactordialog.h"
+
+MultiplyFactorDialog::MultiplyFactorDialog(const glm::vec3& baseVector, QWidget* parent) :
+	QDialog{parent},
+	baseVector{baseVector},
+	preview{baseVector, parent, Vec3Editor::NoMultiplyButton}
+{
+	ui = std::make_unique<Ui::MultiplyFactorDialog>();
+	ui->setupUi(this);
+	this->preview.setEnabled(false);
+	this->ui->previewGroupBox->setLayout(new QVBoxLayout{parent});
+	this->ui->previewGroupBox->layout()->addWidget(&this->preview);
+	connect(this->ui->invert, &QCheckBox::clicked, this, &MultiplyFactorDialog::updatePreview);
+	connect(this->ui->factor, qOverload<double>(&DoubleSpinBox::valueChanged), this, &MultiplyFactorDialog::updatePreview);
+}
+
+MultiplyFactorDialog::~MultiplyFactorDialog()
+{
+}
+
+glm::vec3 MultiplyFactorDialog::value() const
+{
+	glm::vec3 result = baseVector;
+	if (this->ui->invert->isChecked())
+	{
+		result /= this->ui->factor->value();
+	}
+	else
+	{
+		result *= this->ui->factor->value();
+	}
+	return result;
+}
+
+void MultiplyFactorDialog::updatePreview()
+{
+	this->preview.setValue(this->value());
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ui/multiplyfactordialog.h	Tue Mar 17 23:13:29 2020 +0200
@@ -0,0 +1,23 @@
+#pragma once
+#include <QDialog>
+#include "../main.h"
+#include "../widgets/vec3editor.h"
+
+namespace Ui
+{
+	class MultiplyFactorDialog;
+}
+
+class MultiplyFactorDialog : public QDialog
+{
+	Q_OBJECT
+public:
+	explicit MultiplyFactorDialog(const glm::vec3& baseVector = glm::vec3{}, QWidget *parent = nullptr);
+	~MultiplyFactorDialog();
+	glm::vec3 value() const;
+private:
+	Q_SLOT void updatePreview();
+	std::unique_ptr<Ui::MultiplyFactorDialog> ui;
+	const glm::vec3 baseVector;
+	Vec3Editor preview;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ui/multiplyfactordialog.ui	Tue Mar 17 23:13:29 2020 +0200
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MultiplyFactorDialog</class>
+ <widget class="QDialog" name="MultiplyFactorDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>436</width>
+    <height>128</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Multiply with a scalar</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
+     <item>
+      <layout class="QFormLayout" name="formLayout">
+       <item row="0" column="0">
+        <widget class="QLabel" name="label">
+         <property name="text">
+          <string>Factor:</string>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="1">
+        <widget class="DoubleSpinBox" name="factor"/>
+       </item>
+       <item row="1" column="1">
+        <widget class="QCheckBox" name="invert">
+         <property name="text">
+          <string>Invert</string>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+     <item>
+      <widget class="QGroupBox" name="previewGroupBox">
+       <property name="title">
+        <string>Preview</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>DoubleSpinBox</class>
+   <extends>QDoubleSpinBox</extends>
+   <header>widgets/doublespinbox.h</header>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>MultiplyFactorDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>MultiplyFactorDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ui/objecteditor.cpp	Tue Mar 17 23:13:29 2020 +0200
@@ -0,0 +1,32 @@
+#include <QVBoxLayout>
+#include "objecteditor.h"
+
+ObjectEditor::ObjectEditor(Model* model, const ldraw::id_t id, QWidget *parent) :
+	QWidget{parent},
+	model{model}
+{
+	this->setObjectId(id);
+	this->setLayout(new QVBoxLayout{this});
+}
+
+void ObjectEditor::setObjectId(const ldraw::id_t id)
+{
+	this->objectId = id;
+	const ldraw::Object* object = this->model->get(id);
+	if (object != nullptr and object->numPoints() > 0)
+	{
+		if (not this->polygonEditor.has_value())
+		{
+			this->polygonEditor.emplace(this->model, id);
+			this->layout()->addWidget(&*this->polygonEditor);
+		}
+		else
+		{
+			this->polygonEditor->setObjectId(id);
+		}
+	}
+	else
+	{
+		this->polygonEditor.reset();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ui/objecteditor.h	Tue Mar 17 23:13:29 2020 +0200
@@ -0,0 +1,17 @@
+#pragma once
+#include <QWidget>
+#include "../main.h"
+#include "../model.h"
+#include "polygonobjecteditor.h"
+
+class ObjectEditor : public QWidget
+{
+	Q_OBJECT
+public:
+	explicit ObjectEditor(Model* model, ldraw::id_t id = ldraw::NULL_ID, QWidget* parent = nullptr);
+	void setObjectId(ldraw::id_t id);
+private:
+	Model* const model;
+	ldraw::id_t objectId = ldraw::NULL_ID;
+	std::optional<PolygonObjectEditor> polygonEditor;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ui/polygonobjecteditor.cpp	Tue Mar 17 23:13:29 2020 +0200
@@ -0,0 +1,48 @@
+#include <QVBoxLayout>
+#include "model.h"
+#include "widgets/vec3editor.h"
+#include "polygonobjecteditor.h"
+
+PolygonObjectEditor::PolygonObjectEditor(Model* model, ldraw::id_t id, QWidget* parent) :
+	QWidget{parent},
+	model{model},
+	storedObjectId{ldraw::NULL_ID.value}
+{
+	this->setLayout(new QVBoxLayout{this});
+	this->setObjectId(id);
+}
+
+// destructor needed for std::unique_ptr
+PolygonObjectEditor::~PolygonObjectEditor()
+{
+}
+
+ldraw::id_t PolygonObjectEditor::objectId() const
+{
+	return this->storedObjectId;
+}
+
+void PolygonObjectEditor::setObjectId(ldraw::id_t id)
+{
+	this->storedObjectId = id;
+	this->updateNumRows();
+}
+
+void PolygonObjectEditor::updateNumRows()
+{
+	const ldraw::Object* object = model->get(this->storedObjectId);
+	const int number = object->numPoints();
+	if (static_cast<int>(this->vec3Editors.size()) > number)
+	{
+		this->vec3Editors.resize(number);
+	}
+	else
+	{
+		while (static_cast<int>(this->vec3Editors.size()) < number)
+		{
+			const glm::vec3& value = object->getPoint(this->vec3Editors.size());
+			this->vec3Editors.emplace_back(std::make_unique<Vec3Editor>(value, this));
+			this->layout()->addWidget(this->vec3Editors.back().get());
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ui/polygonobjecteditor.h	Tue Mar 17 23:13:29 2020 +0200
@@ -0,0 +1,19 @@
+#pragma once
+#include <QWidget>
+#include "main.h"
+
+class Model;
+
+class PolygonObjectEditor : public QWidget
+{
+public:
+	PolygonObjectEditor(Model* model, ldraw::id_t id, QWidget* parent = nullptr);
+	~PolygonObjectEditor();
+	ldraw::id_t objectId() const;
+	void setObjectId(ldraw::id_t id);
+private:
+	void updateNumRows();
+	Model* model;
+	ldraw::id_t storedObjectId;
+	std::vector<std::unique_ptr<class Vec3Editor>> vec3Editors;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets/vec3editor.cpp	Tue Mar 17 23:13:29 2020 +0200
@@ -0,0 +1,60 @@
+#include <QDialog>
+#include <QCheckBox>
+#include <QSignalBlocker>
+#include "vec3editor.h"
+#include "ui_vec3editor.h"
+#include "../ui/multiplyfactordialog.h"
+
+Vec3Editor::Vec3Editor(const glm::vec3& value, QWidget *parent, QFlags<Flag> flags) :
+	QWidget{parent},
+	ui{new Ui::Vec3Editor}
+{
+	this->ui->setupUi(this);
+	this->setValue(value);
+	if (flags.testFlag(NoMultiplyButton))
+	{
+		this->ui->multiply->setVisible(false);
+	}
+	else
+	{
+		connect(this->ui->multiply, &QPushButton::clicked, this, &Vec3Editor::multiplyPressed);
+	}
+}
+
+Vec3Editor::~Vec3Editor()
+{
+}
+
+glm::vec3 Vec3Editor::value() const
+{
+	auto get = [](DoubleSpinBox* spinbox){ return toFloat(spinbox->value()); };
+	return {get(this->ui->x), get(this->ui->y), get(this->ui->z)};
+}
+
+void Vec3Editor::setValue(const glm::vec3& value)
+{
+	auto set = [](DoubleSpinBox* spinbox, float value)
+	{
+		QSignalBlocker blocker{spinbox};
+		spinbox->setValue(toQreal(value));
+	};
+	set(this->ui->x, value.x);
+	set(this->ui->y, value.y);
+	set(this->ui->z, value.z);
+	Q_EMIT this->valueChanged(value);
+}
+
+std::array<DoubleSpinBox*, 3> Vec3Editor::spinboxes()
+{
+	return {this->ui->x, this->ui->y, this->ui->z};
+}
+
+void Vec3Editor::multiplyPressed()
+{
+	MultiplyFactorDialog dialog{this->value(), this};
+	const int dialogResult = dialog.exec();
+	if (dialogResult == QDialog::Accepted)
+	{
+		this->setValue(dialog.value());
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets/vec3editor.h	Tue Mar 17 23:13:29 2020 +0200
@@ -0,0 +1,30 @@
+#pragma once
+#include <QWidget>
+#include "main.h"
+
+namespace Ui
+{
+	class Vec3Editor;
+}
+
+class Vec3Editor : public QWidget
+{
+	Q_OBJECT
+public:
+	enum Flag
+	{
+		NoMultiplyButton = 0x1
+	};
+	explicit Vec3Editor(const glm::vec3& value = {}, QWidget* parent = nullptr, QFlags<Flag> flags = 0);
+	~Vec3Editor();
+	glm::vec3 value() const;
+	void setValue(const glm::vec3& value);
+Q_SIGNALS:
+	void valueChanged(const glm::vec3& value);
+private:
+	std::array<class DoubleSpinBox*, 3> spinboxes();
+	Q_SLOT void multiplyPressed();
+	std::unique_ptr<Ui::Vec3Editor> ui;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QFlags<Vec3Editor::Flag>)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets/vec3editor.ui	Tue Mar 17 23:13:29 2020 +0200
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Vec3Editor</class>
+ <widget class="QWidget" name="Vec3Editor">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>613</width>
+    <height>46</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QHBoxLayout" name="horizontalLayout" stretch="1,1,1,0">
+   <item>
+    <widget class="DoubleSpinBox" name="x">
+     <property name="prefix">
+      <string>x = </string>
+     </property>
+     <property name="decimals">
+      <number>4</number>
+     </property>
+     <property name="minimum">
+      <double>-1000000.000000000000000</double>
+     </property>
+     <property name="maximum">
+      <double>1000000.000000000000000</double>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="DoubleSpinBox" name="y">
+     <property name="prefix">
+      <string>y = </string>
+     </property>
+     <property name="decimals">
+      <number>4</number>
+     </property>
+     <property name="minimum">
+      <double>-1000000.000000000000000</double>
+     </property>
+     <property name="maximum">
+      <double>1000000.000000000000000</double>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="DoubleSpinBox" name="z">
+     <property name="prefix">
+      <string>z = </string>
+     </property>
+     <property name="decimals">
+      <number>4</number>
+     </property>
+     <property name="minimum">
+      <double>-1000000.000000000000000</double>
+     </property>
+     <property name="maximum">
+      <double>1000000.000000000000000</double>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QPushButton" name="multiply">
+     <property name="text">
+      <string>×</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>DoubleSpinBox</class>
+   <extends>QDoubleSpinBox</extends>
+   <header>widgets/doublespinbox.h</header>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>

mercurial