Added LDObject serialization and refactored the internal resource managing to use it. No more tearing objects from one model into another, and this provides a stable way to keep an object's state in memory such as the edit history.

Mon, 05 Mar 2018 22:40:34 +0200

author
Santeri Piippo
date
Mon, 05 Mar 2018 22:40:34 +0200
changeset 1269
ec691d9472b3
parent 1268
7cbdaefda950
child 1270
6af79c9eb274

Added LDObject serialization and refactored the internal resource managing to use it. No more tearing objects from one model into another, and this provides a stable way to keep an object's state in memory such as the edit history.

CMakeLists.txt file | annotate | diff | comparison | revisions
src/colors.h file | annotate | diff | comparison | revisions
src/editHistory.cpp file | annotate | diff | comparison | revisions
src/editHistory.h file | annotate | diff | comparison | revisions
src/lddocument.cpp file | annotate | diff | comparison | revisions
src/lddocument.h file | annotate | diff | comparison | revisions
src/linetypes/comment.cpp file | annotate | diff | comparison | revisions
src/linetypes/comment.h 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/edgeline.cpp file | annotate | diff | comparison | revisions
src/linetypes/edgeline.h file | annotate | diff | comparison | revisions
src/linetypes/empty.cpp file | annotate | diff | comparison | revisions
src/linetypes/empty.h file | annotate | diff | comparison | revisions
src/linetypes/modelobject.cpp file | annotate | diff | comparison | revisions
src/linetypes/modelobject.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/triangle.cpp file | annotate | diff | comparison | revisions
src/linetypes/triangle.h file | annotate | diff | comparison | revisions
src/model.cpp file | annotate | diff | comparison | revisions
src/model.h file | annotate | diff | comparison | revisions
src/serializer.cpp file | annotate | diff | comparison | revisions
src/serializer.h file | annotate | diff | comparison | revisions
src/toolsets/algorithmtoolset.cpp file | annotate | diff | comparison | revisions
src/toolsets/basictoolset.cpp file | annotate | diff | comparison | revisions
src/toolsets/extprogramtoolset.cpp file | annotate | diff | comparison | revisions
src/toolsets/filetoolset.cpp file | annotate | diff | comparison | revisions
src/types/matrix.h file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Sat Mar 03 17:59:56 2018 +0200
+++ b/CMakeLists.txt	Mon Mar 05 22:40:34 2018 +0200
@@ -53,6 +53,7 @@
 	src/partdownloader.cpp
 	src/partdownloadrequest.cpp
 	src/primitives.cpp
+	src/serializer.cpp
 	src/ringFinder.cpp
 	src/version.cpp
 	src/dialogs/colorselector.cpp
@@ -120,6 +121,7 @@
 	src/partdownloadrequest.h
 	src/primitives.h
 	src/ringFinder.h
+	src/serializer.h
 	src/transform.h
 	src/dialogs/colorselector.h
 	src/dialogs/configdialog.h
--- a/src/colors.h	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/colors.h	Mon Mar 05 22:40:34 2018 +0200
@@ -92,6 +92,7 @@
 	qint32 m_index;
 };
 
+Q_DECLARE_METATYPE(LDColor)
 uint qHash(LDColor color);
 
 /*
--- a/src/editHistory.cpp	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/editHistory.cpp	Mon Mar 05 22:40:34 2018 +0200
@@ -28,6 +28,11 @@
 	m_isIgnoring (false),
 	m_position (-1) {}
 
+EditHistory::~EditHistory()
+{
+	clear();
+}
+
 void EditHistory::undo()
 {
 	if (m_changesets.isEmpty() or position() == -1)
@@ -58,7 +63,7 @@
 	const Changeset& set = changesetAt (position() + 1);
 
 	// Redo things in original order
-	for (const AbstractHistoryEntry* change : set)
+	for (AbstractHistoryEntry* change : set)
 		change->redo();
 
 	++m_position;
@@ -96,18 +101,6 @@
 	emit stepAdded();
 }
 
-void EditHistory::add (AbstractHistoryEntry* entry)
-{
-	if (isIgnoring())
-	{
-		delete entry;
-		return;
-	}
-
-	entry->setParent (this);
-	m_currentChangeset << entry;
-}
-
 int EditHistory::size() const
 {
 	return countof(m_changesets);
@@ -138,11 +131,9 @@
 	return m_document;
 }
 
-//
-// ---------------------------------------------------------------------------------------------------------------------
-//
+AbstractHistoryEntry::AbstractHistoryEntry(EditHistory* parent) :
+	m_parent {parent} {}
 
-AbstractHistoryEntry::AbstractHistoryEntry() {}
 AbstractHistoryEntry::~AbstractHistoryEntry() {}
 
 EditHistory* AbstractHistoryEntry::parent() const
@@ -150,83 +141,65 @@
 	return m_parent;
 }
 
-void AbstractHistoryEntry::setParent (EditHistory* parent)
-{
-	m_parent = parent;
-}
+AddHistoryEntry::AddHistoryEntry(const QModelIndex& index, EditHistory* parent) :
+	AbstractHistoryEntry {parent},
+	m_row {index.row()},
+	m_code {Serializer::store(parent->document()->lookup(index))} {}
 
-//
-// ---------------------------------------------------------------------------------------------------------------------
-//
-
-AddHistoryEntry::AddHistoryEntry (int idx, LDObject* obj) :
-	m_index (idx),
-	m_code (obj->asText()) {}
-
-void AddHistoryEntry::undo() const
+void AddHistoryEntry::undo()
 {
-	LDObject* object = parent()->document()->getObject(m_index);
+	LDObject* object = parent()->document()->getObject(m_row);
 	parent()->document()->remove(object);
 }
 
-void AddHistoryEntry::redo() const
+void AddHistoryEntry::redo()
 {
-	parent()->document()->insertFromString(m_index, m_code);
+	parent()->document()->insertFromArchive(m_row, m_code);
 }
 
-//
-// ---------------------------------------------------------------------------------------------------------------------
-//
-
-DelHistoryEntry::DelHistoryEntry (int idx, LDObject* obj) :
-	AddHistoryEntry (idx, obj) {}
-
-void DelHistoryEntry::undo() const
+void DelHistoryEntry::undo()
 {
 	AddHistoryEntry::redo();
 }
 
-void DelHistoryEntry::redo() const
+void DelHistoryEntry::redo()
 {
 	AddHistoryEntry::undo();
 }
 
-//
-// ---------------------------------------------------------------------------------------------------------------------
-//
+EditHistoryEntry::EditHistoryEntry(
+	const QModelIndex& index,
+	const Serializer::Archive& oldState,
+	const Serializer::Archive& newState,
+	EditHistory* parent
+) :
+	AbstractHistoryEntry {parent},
+	row {index.row()},
+	oldState {oldState},
+	newState {newState} {}
 
-EditHistoryEntry::EditHistoryEntry (int idx, QString oldCode, QString newCode) :
-	m_index (idx),
-	m_oldCode (oldCode),
-	m_newCode (newCode) {}
-
-void EditHistoryEntry::undo() const
+void EditHistoryEntry::undo()
 {
-	LDObject* object = parent()->document()->getObject (m_index);
-	parent()->document()->replaceWithFromString(object, m_oldCode);
+	parent()->document()->setObjectAt(row, oldState);
 }
 
-void EditHistoryEntry::redo() const
+void EditHistoryEntry::redo()
 {
-	LDObject* object = parent()->document()->getObject (m_index);
-	parent()->document()->replaceWithFromString(object, m_newCode);
+	parent()->document()->setObjectAt(row, newState);
 }
 
-//
-// ---------------------------------------------------------------------------------------------------------------------
-//
-
-SwapHistoryEntry::SwapHistoryEntry (int a, int b) :
+SwapHistoryEntry::SwapHistoryEntry (int a, int b, EditHistory* parent) :
+	AbstractHistoryEntry {parent},
 	m_a (a),
 	m_b (b) {}
 
 
-void SwapHistoryEntry::undo() const
+void SwapHistoryEntry::undo()
 {
 	parent()->document()->swapObjects(LDObject::fromID (m_a), LDObject::fromID (m_b));
 }
 
-void SwapHistoryEntry::redo() const
+void SwapHistoryEntry::redo()
 {
 	undo();
 }
--- a/src/editHistory.h	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/editHistory.h	Mon Mar 05 22:40:34 2018 +0200
@@ -18,6 +18,7 @@
 
 #pragma once
 #include "main.h"
+#include "serializer.h"
 #include "linetypes/modelobject.h"
 
 class AbstractHistoryEntry;
@@ -30,8 +31,15 @@
 	using Changeset = QList<AbstractHistoryEntry*>;
 
 	EditHistory (LDDocument* document);
+	~EditHistory();
 
-	void add (AbstractHistoryEntry* entry);
+	template<typename T, typename... Args>
+	void add(Args&&... args)
+	{
+		if (not isIgnoring())
+			m_currentChangeset << new T {args..., this};
+	}
+
 	void addStep();
 	const Changeset& changesetAt (int pos) const;
 	void clear();
@@ -59,57 +67,61 @@
 class AbstractHistoryEntry
 {
 public:
-	AbstractHistoryEntry();
+	AbstractHistoryEntry(EditHistory* parent);
 	virtual ~AbstractHistoryEntry();
 
 	EditHistory* parent() const;
-	virtual void redo() const = 0;
-	void setParent (EditHistory* parent);
-	virtual void undo() const = 0;
+	virtual void redo() = 0;
+	virtual void undo() = 0;
 
 private:
-	EditHistory* m_parent;
+	EditHistory* const m_parent;
 };
 
 class AddHistoryEntry : public AbstractHistoryEntry
 {
 public:
-	AddHistoryEntry (int idx, LDObject* obj);
-	void undo() const override;
-	void redo() const override;
+	AddHistoryEntry (const QModelIndex& index, EditHistory* parent);
+	void undo() override;
+	void redo() override;
 	
 private:
-	int m_index;
-	QString m_code;
+	int m_row;
+	Serializer::Archive m_code;
 };
 
 class DelHistoryEntry : public AddHistoryEntry
 {
 public:
-	DelHistoryEntry (int idx, LDObject* obj);
-	void undo() const override;
-	void redo() const override;
+	using AddHistoryEntry::AddHistoryEntry;
+	void undo() override;
+	void redo() override;
 };
 
 class EditHistoryEntry : public AbstractHistoryEntry
 {
 public:
-	EditHistoryEntry (int idx, QString oldCode, QString newCode);
-	void undo() const override;
-	void redo() const override;
+	EditHistoryEntry(
+		const QModelIndex& index,
+		const Serializer::Archive& oldCode,
+		const Serializer::Archive& newCode,
+		EditHistory* parent
+	);
+	void undo() override;
+	void redo() override;
 	
 private:
-	int m_index;
-	QString m_oldCode;
-	QString m_newCode;
+	int row;
+	Serializer::Archive oldState;
+	Serializer::Archive newState;
 };
 
 class SwapHistoryEntry : public AbstractHistoryEntry
 {
 public:
-	SwapHistoryEntry (int a, int b);
-	void undo() const override;
-	void redo() const override;
+	SwapHistoryEntry (int a, int b, EditHistory* parent);
+	void undo() override;
+	void redo() override;
 
 private:
 	int m_a;
--- a/src/lddocument.cpp	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/lddocument.cpp	Mon Mar 05 22:40:34 2018 +0200
@@ -33,7 +33,15 @@
     m_history (new EditHistory (this)),
 	m_savePosition(-1),
     m_tabIndex(-1),
-	m_manager (parent) {}
+	m_manager (parent)
+{
+	connect(
+		this,
+		SIGNAL(objectAdded(QModelIndex)),
+		this,
+		SLOT(handleNewObject(QModelIndex))
+	);
+}
 
 LDDocument::~LDDocument()
 {
@@ -131,11 +139,6 @@
 	history()->clear();
 }
 
-void LDDocument::addToHistory (AbstractHistoryEntry* entry)
-{
-	history()->add (entry);
-}
-
 void LDDocument::close()
 {
 	if (not isFrozen())
@@ -257,41 +260,53 @@
 	return true;
 }
 
-// =============================================================================
-//
-void LDDocument::insertObject (int pos, LDObject* obj)
+void LDDocument::handleNewObject(const QModelIndex& index)
 {
-	Model::insertObject(pos, obj);
-	history()->add(new AddHistoryEntry {pos, obj});
-	connect(obj, SIGNAL(codeChanged(QString,QString)), this, SLOT(objectChanged(QString,QString)));
+	LDObject* object = lookup(index);
+	history()->add<AddHistoryEntry>(index);
+	connect(
+		object,
+		SIGNAL(codeChanged(LDObjectState, LDObjectState)),
+		this,
+		SLOT(objectChanged(LDObjectState, LDObjectState))
+	);
+	connect(
+		this,
+		SIGNAL(aboutToRemoveObject(QModelIndex)),
+		this,
+		SLOT(handleImminentObjectRemoval(QModelIndex)),
+		Qt::DirectConnection
+	);
 
 #ifdef DEBUG
 	if (not isFrozen())
-		print("Inserted object #%1 (%2) at %3\n", obj->id(), obj->typeName(), pos);
+		print("Inserted object #%1 (%2) at %3\n", object->id(), object->typeName(), index.row());
 #endif
 }
 
-void LDDocument::objectChanged(QString before, QString after)
+void LDDocument::objectChanged(const LDObjectState& before, const LDObjectState& after)
 {
-	LDObject* object = static_cast<LDObject*>(sender());
-	QModelIndex index = this->indexOf(object);
-	addToHistory(new EditHistoryEntry {index.row(), before, after});
-	redoVertices();
-	emit objectModified(object);
-	emit dataChanged(index, index);
+	LDObject* object = qobject_cast<LDObject*>(sender());
+
+	if (object)
+	{
+		QModelIndex index = this->indexOf(object);
+		history()->add<EditHistoryEntry>(index, before, after);
+		redoVertices();
+		emit objectModified(object);
+		emit dataChanged(index, index);
+	}
 }
 
-LDObject* LDDocument::withdrawAt(int position)
+void LDDocument::handleImminentObjectRemoval(const QModelIndex& index)
 {
-	LDObject* object = getObject(position);
+	LDObject* object = lookup(index);
 
 	if (not isFrozen() and not m_isBeingDestroyed)
 	{
-		history()->add(new DelHistoryEntry {position, object});
+		history()->add<DelHistoryEntry>(index);
 		m_objectVertices.remove(object);
 	}
-
-	return Model::withdrawAt(position);
 }
 
 // =============================================================================
@@ -408,7 +423,7 @@
 {
 	if (Model::swapObjects(one, other))
 	{
-		addToHistory(new SwapHistoryEntry {one->id(), other->id()});
+		history()->add<SwapHistoryEntry>(one->id(), other->id());
 		return true;
 	}
 	else
--- a/src/lddocument.h	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/lddocument.h	Mon Mar 05 22:40:34 2018 +0200
@@ -47,7 +47,6 @@
 	~LDDocument();
 
 	void addHistoryStep();
-	void addToHistory (AbstractHistoryEntry* entry);
 	void clearHistory();
 	void close();
 	QString defaultName() const;
@@ -59,7 +58,6 @@
 	void inlineContents(Model& model, bool deep, bool renderinline);
 	QList<LDPolygon> inlinePolygons();
 	const QSet<Vertex>& inlineVertices();
-	void insertObject (int pos, LDObject* obj);
 	bool isFrozen() const;
 	bool isSafeToClose();
 	QString name() const;
@@ -83,9 +81,6 @@
 
 	static QString shortenName (QString a); // Turns a full path into a relative path
 
-public slots:
-	void objectChanged(QString before, QString after);
-
 protected:
 	LDObject* withdrawAt(int position);
 
@@ -105,6 +100,17 @@
 	QMap<LDObject*, QSet<Vertex>> m_objectVertices;
 	QSet<Vertex> m_vertices;
 	DocumentManager* m_manager;
+
+	template<typename T, typename... Args>
+	void addToHistory(Args&&... args)
+	{
+		m_history->add<T>(args...);
+	}
+
+private slots:
+	void objectChanged(const LDObjectState &before, const LDObjectState &after);
+	void handleNewObject(const QModelIndex& index);
+	void handleImminentObjectRemoval(const QModelIndex& index);
 };
 
 // Parses a string line containing an LDraw object and returns the object parsed.
--- a/src/linetypes/comment.cpp	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/linetypes/comment.cpp	Mon Mar 05 22:40:34 2018 +0200
@@ -17,12 +17,9 @@
  */
 
 #include "comment.h"
+#include "../editHistory.h"
 
-LDComment::LDComment(Model* model) :
-    LDObject {model} {}
-
-LDComment::LDComment(QString text, Model* model) :
-    LDObject {model},
+LDComment::LDComment(QString text) :
     m_text {text} {}
 
 LDObjectType LDComment::type() const
@@ -64,3 +61,9 @@
 {
 	return format ("0 %1", text());
 }
+
+void LDComment::serialize(Serializer& serializer)
+{
+	LDObject::serialize(serializer);
+	serializer << m_text;
+}
--- a/src/linetypes/comment.h	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/linetypes/comment.h	Mon Mar 05 22:40:34 2018 +0200
@@ -25,7 +25,9 @@
 class LDComment : public LDObject
 {
 public:
-	static constexpr LDObjectType SubclassType = LDObjectType::Comment;
+	static const LDObjectType SubclassType = LDObjectType::Comment;
+
+	LDComment(QString text = "");
 
 	QString asText() const override;
 	bool isColored() const override;
@@ -35,11 +37,7 @@
 	LDObjectType type() const override;
 	QString typeName() const override;
 	void setText(QString value);
-
-protected:
-	LDComment(QString text, Model* model);
-	LDComment(Model* model);
-	friend class Model;
+	void serialize(class Serializer& serializer) override;
 
 private:
 	QString m_text;
--- a/src/linetypes/conditionaledge.cpp	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/linetypes/conditionaledge.cpp	Mon Mar 05 22:40:34 2018 +0200
@@ -19,11 +19,7 @@
 #include "../model.h"
 #include "conditionaledge.h"
 
-LDConditionalEdge::LDConditionalEdge(Model* model) :
-	LDEdgeLine {model} {}
-
-LDConditionalEdge::LDConditionalEdge (const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3, Model* model) :
-	LDEdgeLine {model}
+LDConditionalEdge::LDConditionalEdge (const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3)
 {
 	setVertex (0, v0);
 	setVertex (1, v1);
--- a/src/linetypes/conditionaledge.h	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/linetypes/conditionaledge.h	Mon Mar 05 22:40:34 2018 +0200
@@ -25,11 +25,14 @@
 class LDConditionalEdge : public LDEdgeLine
 {
 public:
-	static constexpr LDObjectType SubclassType = LDObjectType::ConditionalEdge;
+	static const LDObjectType SubclassType = LDObjectType::ConditionalEdge;
+
+	LDConditionalEdge() = default;
+	LDConditionalEdge(const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3);
 
 	virtual LDObjectType type() const override
 	{
-		return SubclassType;
+		return LDObjectType::ConditionalEdge;
 	}
 
 	virtual QString asText() const override;
@@ -37,9 +40,4 @@
 	int numPolygonVertices() const override { return 2; }
 	LDColor defaultColor() const override { return EdgeColor; }
 	QString typeName() const override { return "condline"; }
-
-protected:
-	friend class Model;
-	LDConditionalEdge (Model* model);
-	LDConditionalEdge (const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3, Model* model = nullptr);
 };
--- a/src/linetypes/edgeline.cpp	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/linetypes/edgeline.cpp	Mon Mar 05 22:40:34 2018 +0200
@@ -19,16 +19,9 @@
 #include "edgeline.h"
 
 /*
- * Default-constructs this edge line.
- */
-LDEdgeLine::LDEdgeLine(Model* model) :
-	LDObject {model} {}
-
-/*
  * Constructs this edge line from two vertices.
  */
-LDEdgeLine::LDEdgeLine (Vertex v1, Vertex v2, Model* model) :
-	LDObject {model}
+LDEdgeLine::LDEdgeLine (Vertex v1, Vertex v2)
 {
 	setVertex(0, v1);
 	setVertex(1, v2);
--- a/src/linetypes/edgeline.h	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/linetypes/edgeline.h	Mon Mar 05 22:40:34 2018 +0200
@@ -25,20 +25,18 @@
 class LDEdgeLine : public LDObject
 {
 public:
-	static constexpr LDObjectType SubclassType = LDObjectType::EdgeLine;
+	static const LDObjectType SubclassType = LDObjectType::EdgeLine;
+
+	LDEdgeLine() = default;
+	LDEdgeLine(Vertex v1, Vertex v2);
 
 	virtual LDObjectType type() const override
 	{
-		return SubclassType;
+		return LDObjectType::EdgeLine;
 	}
 
 	virtual QString asText() const override;
 	int numVertices() const override { return 2; }
 	LDColor defaultColor() const override { return EdgeColor; }
 	QString typeName() const override { return "line"; }
-
-protected:
-	friend class Model;
-	LDEdgeLine (Model* model);
-	LDEdgeLine (Vertex v1, Vertex v2, Model* model = nullptr);
 };
--- a/src/linetypes/empty.cpp	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/linetypes/empty.cpp	Mon Mar 05 22:40:34 2018 +0200
@@ -19,9 +19,6 @@
 
 #include "empty.h"
 
-LDEmpty::LDEmpty(Model* model) :
-    LDObject{model} {}
-
 QString LDEmpty::typeName() const
 {
 	return "empty";
--- a/src/linetypes/empty.h	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/linetypes/empty.h	Mon Mar 05 22:40:34 2018 +0200
@@ -31,8 +31,4 @@
 	QString objectListText() const override;
 	LDObjectType type() const override;
 	QString typeName() const override;
-
-protected:
-	friend class Model;
-	LDEmpty(Model* model);
 };
--- a/src/linetypes/modelobject.cpp	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/linetypes/modelobject.cpp	Mon Mar 05 22:40:34 2018 +0200
@@ -27,27 +27,23 @@
 #include "../colors.h"
 #include "../glcompiler.h"
 #include "edgeline.h"
+#include "triangle.h"
+#include "quadrilateral.h"
+#include "conditionaledge.h"
+#include "comment.h"
+#include "empty.h"
 
 // List of all LDObjects
 QMap<qint32, LDObject*> g_allObjects;
 
 enum { MAX_LDOBJECT_IDS = (1 << 24) };
 
-#define LDOBJ_DEFAULT_CTOR(T,BASE) \
-	T :: T (Model* model) : \
-	    BASE {model} {}
-
 // =============================================================================
 // LDObject constructors
 //
-LDObject::LDObject (Model* model) :
-    m_isHidden {false},
-    m_isSelected {false},
-    _model {model},
-    m_coords {{0, 0, 0}}
+LDObject::LDObject() :
+	m_isHidden {false}
 {
-	assert(_model != nullptr);
-
 	// Let's hope that nobody goes to create 17 million objects anytime soon...
 	static qint32 nextId = 1; // 0 shalt be null
 	if (nextId < MAX_LDOBJECT_IDS)
@@ -61,13 +57,6 @@
 	m_randomColor = QColor::fromHsv (rand() % 360, rand() % 256, rand() % 96 + 128);
 }
 
-LDSubfileReference::LDSubfileReference (Model* model) :
-    LDMatrixObject (model) {}
-
-LDOBJ_DEFAULT_CTOR (LDError, LDObject)
-LDOBJ_DEFAULT_CTOR (LDBfc, LDObject)
-LDOBJ_DEFAULT_CTOR (LDBezierCurve, LDObject)
-
 LDObject::~LDObject()
 {
 	// Remove this object from the list of LDObjects
@@ -132,13 +121,12 @@
 
 // =============================================================================
 //
-LDBezierCurve::LDBezierCurve(const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3, Model* model) :
-    LDObject {model}
+LDBezierCurve::LDBezierCurve(const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3)
 {
-	setVertex (0, v0);
-	setVertex (1, v1);
-	setVertex (2, v2);
-	setVertex (3, v3);
+	setVertex(0, v0);
+	setVertex(1, v1);
+	setVertex(2, v2);
+	setVertex(3, v3);
 }
 
 // =============================================================================
@@ -287,11 +275,6 @@
 	m_isHidden = value;
 }
 
-bool LDObject::isSelected() const
-{
-	return m_isSelected;
-}
-
 qint32 LDObject::id() const
 {
 	return m_id;
@@ -319,6 +302,47 @@
 	return g_allObjects.value(id);
 }
 
+LDObject* LDObject::newFromType(LDObjectType type)
+{
+	switch (type)
+	{
+	case LDObjectType::SubfileReference:
+		return new LDSubfileReference {};
+
+	case LDObjectType::Quadrilateral:
+		return new LDQuadrilateral {};
+
+	case LDObjectType::Triangle:
+		return new LDTriangle {};
+
+	case LDObjectType::EdgeLine:
+		return new LDEdgeLine {};
+
+	case LDObjectType::ConditionalEdge:
+		return new LDConditionalEdge {};
+
+	case LDObjectType::Bfc:
+		return new LDBfc {};
+
+	case LDObjectType::Comment:
+		return new LDComment {};
+
+	case LDObjectType::Error:
+		return new LDError {};
+
+	case LDObjectType::Empty:
+		return new LDEmpty {};
+
+	case LDObjectType::BezierCurve:
+		return new LDBezierCurve {};
+
+	case LDObjectType::_End:
+		break;
+	}
+
+	return nullptr;
+}
+
 // =============================================================================
 //
 void LDObject::setColor (LDColor color)
@@ -344,12 +368,7 @@
 	changeProperty(&m_coords[i], vert);
 }
 
-LDMatrixObject::LDMatrixObject (Model* model) :
-    LDObject (model),
-    m_position {0, 0, 0} {}
-
-LDMatrixObject::LDMatrixObject (const Matrix& transform, const Vertex& pos, Model* model) :
-    LDObject (model),
+LDMatrixObject::LDMatrixObject (const Matrix& transform, const Vertex& pos) :
 	m_position (pos),
 	m_transformationMatrix (transform) {}
 
@@ -391,8 +410,7 @@
 	changeProperty(&m_transformationMatrix, val);
 }
 
-LDError::LDError (QString contents, QString reason, Model* model) :
-    LDObject (model),
+LDError::LDError (QString contents, QString reason) :
 	m_contents (contents),
 	m_reason (reason) {}
 
@@ -406,8 +424,7 @@
 	return m_contents;
 }
 
-LDBfc::LDBfc (const BfcStatement type, Model* model) :
-    LDObject {model},
+LDBfc::LDBfc (const BfcStatement type) :
     m_statement {type} {}
 
 BfcStatement LDBfc::statement() const
@@ -501,10 +518,9 @@
 LDSubfileReference::LDSubfileReference(
 	QString referenceName,
 	const Matrix& transformationMatrix,
-	const Vertex& position,
-	Model* model
+	const Vertex& position
 ) :
-	LDMatrixObject {transformationMatrix, position, model},
+	LDMatrixObject {transformationMatrix, position},
 	m_referenceName {referenceName} {}
 
 // =============================================================================
@@ -582,3 +598,42 @@
 {
 	changeProperty(&m_hasInvertNext, value);
 }
+
+void LDObject::serialize(Serializer& serializer)
+{
+	serializer << m_hasInvertNext;
+	serializer << m_isHidden;
+	serializer << m_id;
+	serializer << m_color;
+	serializer << m_randomColor;
+	serializer << m_coords[0];
+	serializer << m_coords[1];
+	serializer << m_coords[2];
+	serializer << m_coords[3];
+}
+
+void LDMatrixObject::serialize(Serializer& serializer)
+{
+	LDObject::serialize(serializer);
+	serializer << m_position;
+	serializer << m_transformationMatrix;
+}
+
+void LDBfc::serialize(Serializer& serializer)
+{
+	LDObject::serialize(serializer);
+	serializer << m_statement;
+}
+
+void LDError::serialize(Serializer& serializer)
+{
+	LDObject::serialize(serializer);
+	serializer << m_contents;
+	serializer << m_reason;
+}
+
+void LDSubfileReference::serialize(Serializer& serializer)
+{
+	LDMatrixObject::serialize(serializer);
+	serializer << m_referenceName;
+}
--- a/src/linetypes/modelobject.h	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/linetypes/modelobject.h	Mon Mar 05 22:40:34 2018 +0200
@@ -22,6 +22,7 @@
 #include "../basics.h"
 #include "../glShared.h"
 #include "../colors.h"
+#include "../serializer.h"
 
 class Model;
 class LDDocument;
@@ -45,6 +46,7 @@
 	_End
 };
 
+Q_DECLARE_METATYPE(LDObjectType)
 MAKE_ITERABLE_ENUM(LDObjectType)
 
 inline int qHash(LDObjectType type)
@@ -60,6 +62,9 @@
 	Q_OBJECT
 
 public:
+	LDObject();
+	virtual ~LDObject();
+
 	virtual QString asText() const = 0; // This object as LDraw code
 	LDColor color() const;
 	virtual LDColor defaultColor() const; // What color does the object default to?
@@ -71,7 +76,6 @@
 	virtual bool isColored() const;
 	bool isHidden() const;
 	virtual bool isScemantic() const; // Does this object have meaning in the part model?
-	bool isSelected() const;
 	void move (Vertex vect);
 	virtual int numVertices() const;
 	virtual int numPolygonVertices() const;
@@ -86,16 +90,15 @@
 	virtual LDObjectType type() const = 0;
 	virtual QString typeName() const = 0;
 	const Vertex& vertex (int i) const;
+	virtual void serialize(class Serializer& serializer);
 
 	static LDObject* fromID(qint32 id);
+	static LDObject* newFromType(LDObjectType type);
 
 signals:
-	void codeChanged(QString before, QString after);
+	void codeChanged(const LDObjectState& before, const LDObjectState& after);
 
 protected:
-	friend class Model;
-	LDObject (Model* model = nullptr);
-	virtual ~LDObject();
 	void setDocument(Model* model);
 
 	template<typename T>
@@ -104,7 +107,6 @@
 private:
 	bool m_hasInvertNext = false;
 	bool m_isHidden;
-	bool m_isSelected;
 	Model* _model;
 	qint32 m_id;
 	LDColor m_color;
@@ -119,20 +121,19 @@
  */
 class LDMatrixObject : public LDObject
 {
-	Vertex m_position;
+public:
+	LDMatrixObject() = default;
+	LDMatrixObject(const Matrix& transformationMatrix, const Vertex& pos);
 
-public:
 	const Vertex& position() const;
 	void setCoordinate (const Axis ax, double value);
 	void setPosition (const Vertex& a);
 	void setTransformationMatrix (const Matrix& value);
 	const Matrix& transformationMatrix() const;
-
-protected:
-	LDMatrixObject (Model* model = nullptr);
-	LDMatrixObject (const Matrix& transformationMatrix, const Vertex& pos, Model* model = nullptr);
+	void serialize(class Serializer& serializer) override;
 
 private:
+	Vertex m_position;
 	Matrix m_transformationMatrix;
 };
 
@@ -142,11 +143,12 @@
 class LDError : public LDObject
 {
 public:
-	static constexpr LDObjectType SubclassType = LDObjectType::Error;
+	LDError() = default;
+	LDError(QString contents, QString reason);
 
 	virtual LDObjectType type() const override
 	{
-		return SubclassType;
+		return LDObjectType::Error;
 	}
 
 	virtual QString asText() const override;
@@ -155,11 +157,7 @@
 	QString objectListText() const override;
 	bool isColored() const override { return false; }
 	QString typeName() const override { return "error"; }
-
-protected:
-	friend class Model;
-	LDError (Model* model);
-	LDError (QString contents, QString reason, Model* model = nullptr);
+	void serialize(class Serializer& serializer) override;
 
 private:
 	QString m_contents; // The LDraw code that was being parsed
@@ -184,24 +182,23 @@
 	_End
 };
 
+Q_DECLARE_METATYPE(BfcStatement)
 MAKE_ITERABLE_ENUM(BfcStatement)
 
 class LDBfc : public LDObject
 {
-	public:
-	static constexpr LDObjectType SubclassType = LDObjectType::Bfc;
+public:
+	static const LDObjectType SubclassType = LDObjectType::Bfc;
+
+	LDBfc(BfcStatement type = BfcStatement::CertifyCCW);
 
 	virtual LDObjectType type() const override
 	{
-		return SubclassType;
+		return LDObjectType::Bfc;
 	}
 
 	virtual QString asText() const override;
-protected:
-	friend class Model;
-	LDBfc (Model* model);
 
-public:
 	bool isScemantic() const override { return statement() == BfcStatement::InvertNext; }
     QString objectListText() const override;
 	BfcStatement statement() const;
@@ -209,12 +206,10 @@
 	QString statementToString() const;
 	bool isColored() const override { return false; }
 	QString typeName() const override { return "bfc"; }
+	void serialize(class Serializer& serializer) override;
 
 	static QString statementToString (BfcStatement statement);
 
-protected:
-	LDBfc (const BfcStatement type, Model* model = nullptr);
-
 private:
 	BfcStatement m_statement;
 };
@@ -225,7 +220,10 @@
 class LDSubfileReference : public LDMatrixObject
 {
 public:
-	static constexpr LDObjectType SubclassType = LDObjectType::SubfileReference;
+	static const LDObjectType SubclassType = LDObjectType::SubfileReference;
+
+	LDSubfileReference() = default;
+	LDSubfileReference(QString referenceName, const Matrix& transformationMatrix, const Vertex& position);
 
 	virtual LDObjectType type() const override
 	{
@@ -242,11 +240,7 @@
 	int triangleCount(DocumentManager *context) const override;
 	bool hasMatrix() const override { return true; }
 	QString typeName() const override { return "subfilereference"; }
-
-protected:
-	friend class Model;
-	LDSubfileReference(Model* model);
-	LDSubfileReference(QString referenceName, const Matrix& transformationMatrix, const Vertex& position, Model* model = nullptr);
+	void serialize(class Serializer& serializer) override;
 
 private:
 	QString m_referenceName;
@@ -258,7 +252,10 @@
 class LDBezierCurve : public LDObject
 {
 public:
-	static constexpr LDObjectType SubclassType = LDObjectType::BezierCurve;
+	static const LDObjectType SubclassType = LDObjectType::BezierCurve;
+
+	LDBezierCurve() = default;
+	LDBezierCurve(const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3);
 
 	virtual LDObjectType type() const override
 	{
@@ -272,11 +269,6 @@
 	int numVertices() const override { return 4; }
 	LDColor defaultColor() const override { return EdgeColor; }
 	QString typeName() const override { return "beziercurve"; }
-
-protected:
-	friend class Model;
-	LDBezierCurve (Model* model);
-	LDBezierCurve (const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3, Model* model = nullptr);
 };
 
 enum
@@ -293,8 +285,8 @@
 {
 	if (*property != value)
 	{
-		QString before = asText();
+		Serializer::Archive before = Serializer::store(this);
 		*property = value;
-		emit codeChanged(before, asText());
+		emit codeChanged(before, Serializer::store(this));
 	}
 }
--- a/src/linetypes/quadrilateral.cpp	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/linetypes/quadrilateral.cpp	Mon Mar 05 22:40:34 2018 +0200
@@ -18,11 +18,7 @@
 
 #include "quadrilateral.h"
 
-LDQuadrilateral::LDQuadrilateral(Model *model) :
-	LDObject {model} {}
-
-LDQuadrilateral::LDQuadrilateral(const Vertex& v1, const Vertex& v2, const Vertex& v3, const Vertex& v4, Model* model) :
-	LDObject {model}
+LDQuadrilateral::LDQuadrilateral(const Vertex& v1, const Vertex& v2, const Vertex& v3, const Vertex& v4)
 {
 	setVertex(0, v1);
 	setVertex(1, v2);
@@ -57,5 +53,5 @@
 
 LDObjectType LDQuadrilateral::type() const
 {
-	return SubclassType;
+	return LDObjectType::Quadrilateral;
 }
--- a/src/linetypes/quadrilateral.h	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/linetypes/quadrilateral.h	Mon Mar 05 22:40:34 2018 +0200
@@ -27,14 +27,12 @@
 public:
 	static constexpr LDObjectType SubclassType = LDObjectType::Quadrilateral;
 
+	LDQuadrilateral() = default;
+	LDQuadrilateral(const Vertex& v1, const Vertex& v2, const Vertex& v3, const Vertex& v4);
+
 	QString asText() const override;
 	int numVertices() const override;
 	int triangleCount(DocumentManager*) const override;
 	LDObjectType type() const override;
 	QString typeName() const override;
-
-protected:
-	friend class Model;
-	LDQuadrilateral(Model* model);
-	LDQuadrilateral(const Vertex& v1, const Vertex& v2, const Vertex& v3, const Vertex& v4, Model* model = nullptr);
 };
--- a/src/linetypes/triangle.cpp	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/linetypes/triangle.cpp	Mon Mar 05 22:40:34 2018 +0200
@@ -18,11 +18,7 @@
 
 #include "triangle.h"
 
-LDTriangle::LDTriangle(Model *model) :
-	LDObject {model} {}
-
-LDTriangle::LDTriangle(const Vertex& v1, const Vertex& v2, const Vertex& v3, Model* model) :
-	LDObject {model}
+LDTriangle::LDTriangle(const Vertex& v1, const Vertex& v2, const Vertex& v3)
 {
 	setVertex(0, v1);
 	setVertex(1, v2);
--- a/src/linetypes/triangle.h	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/linetypes/triangle.h	Mon Mar 05 22:40:34 2018 +0200
@@ -27,18 +27,16 @@
 public:
 	static constexpr LDObjectType SubclassType = LDObjectType::Triangle;
 
+	LDTriangle() = default;
+	LDTriangle(Vertex const& v1, Vertex const& v2, Vertex const& v3);
+
 	virtual LDObjectType type() const override
 	{
-		return SubclassType;
+		return LDObjectType::Triangle;
 	}
 
 	virtual QString asText() const override;
 	int triangleCount(DocumentManager*) const override;
 	int numVertices() const override { return 3; }
 	QString typeName() const override { return "triangle"; }
-
-protected:
-	friend class Model;
-	LDTriangle (Model* model);
-	LDTriangle (Vertex const& v1, Vertex const& v2, Vertex const& v3, Model* model = nullptr);
 };
--- a/src/model.cpp	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/model.cpp	Mon Mar 05 22:40:34 2018 +0200
@@ -25,6 +25,7 @@
 #include "linetypes/empty.h"
 #include "linetypes/quadrilateral.h"
 #include "linetypes/triangle.h"
+#include "editHistory.h"
 
 Model::Model(DocumentManager* manager) :
 	QAbstractListModel {manager},
@@ -36,12 +37,14 @@
 		delete _objects[i];
 }
 
-/*
- * Takes an existing object and migrates it to the end of this model. The object is removed from its old model in the process.
- */
-void Model::addObject(LDObject *object)
+void Model::installObject(int row, LDObject* object)
 {
-	insertObject(size(), object);
+	connect(object, SIGNAL(codeChanged(LDObjectState, LDObjectState)), this, SLOT(recountTriangles()));
+	beginInsertRows({}, row, row);
+	_objects.insert(row, object);
+	recountTriangles();
+	emit objectAdded(index(row));
+	endInsertRows();
 }
 
 /*
@@ -61,22 +64,19 @@
 }
 
 /*
- * Takes an existing object and migrates it to this model, at the specified position.
+ * Takes an existing object and copies it to this model, at the specified position.
  */
-void Model::insertObject(int position, LDObject* object)
+void Model::insertCopy(int position, LDObject* object)
 {
-	connect(object, SIGNAL(codeChanged(QString,QString)), this, SLOT(recountTriangles()));
+	installObject(position, Serializer::clone(object));
+}
 
-	if (object->model() and object->model() != this)
-		object->model()->withdraw(object);
+void Model::insertFromArchive(int row, Serializer::Archive& archive)
+{
+	LDObject* object = Serializer::restore(archive);
 
-	// TODO: check that the object isn't in the vector once there's a cheap way to do so!
-	beginInsertRows({}, position, position);
-	_objects.insert(position, object);
-	_needsTriangleRecount = true;
-	object->setDocument(this);
-	emit objectAdded(object);
-	endInsertRows();
+	if (object)
+		installObject(row, object);
 }
 
 /*
@@ -84,13 +84,14 @@
  */
 bool Model::swapObjects(LDObject* one, LDObject* other)
 {
-	int a = _objects.indexOf(one);
-	int b = _objects.indexOf(other);
+	QModelIndex a = indexOf(one);
+	QModelIndex b = indexOf(other);
 
-	if (a != b and a != -1 and b != -1)
+	if (a.isValid() and b.isValid() and a != b)
 	{
-		_objects[b] = one;
-		_objects[a] = other;
+		_objects[b.row()] = one;
+		_objects[a.row()] = other;
+		emit objectsSwapped(a, b);
 		return true;
 	}
 	else
@@ -102,17 +103,19 @@
 /*
  * Assigns a new object to the specified position in the model. The object that already is in the position is deleted in the process.
  */
-bool Model::setObjectAt(int idx, LDObject* obj)
+bool Model::setObjectAt(int row, Serializer::Archive& archive)
 {
-	if (idx < 0 or idx >= countof(_objects))
+	LDObject* object = Serializer::restore(archive);
+
+	if (object and row >= 0 and row < countof(_objects))
 	{
-		return false;
+		removeAt(row);
+		insertCopy(row, object);
+		return true;
 	}
 	else
 	{
-		removeAt(idx);
-		insertObject(idx, obj);
-		return true;
+		return false;
 	}
 }
 
@@ -143,7 +146,12 @@
  */
 void Model::removeAt(int position)
 {
-	LDObject* object = withdrawAt(position);
+	beginRemoveRows({}, position, position);
+	LDObject* object = _objects[position];
+	emit aboutToRemoveObject(this->index(position));
+	_objects.removeAt(position);
+	_needsTriangleRecount = true;
+	endRemoveRows();
 	delete object;
 }
 
@@ -161,10 +169,10 @@
 
 	if (index.isValid())
 	{
-		for (int i = countof(model.objects()) - 1; i >= 1; i -= 1)
-			insertObject(index.row() + i, model.objects()[i]);
+		removeAt(index.row());
 
-		setObjectAt(index.row(), model.objects()[0]);
+		for (signed int i = countof(model.objects()) - 1; i >= 0; i -= 1)
+			insertCopy(index.row() + i, model.objects()[i]);
 	}
 }
 
@@ -208,7 +216,7 @@
 	// Inform the contents of their new owner
 	for (LDObject* object : objectsCopy)
 	{
-		insertObject(position, object);
+		insertCopy(position, object);
 		position += 1;
 	}
 
@@ -244,34 +252,6 @@
 }
 
 /*
- * Drops the object from the model. The object becomes a free object as a result (thus violating the invariant that every object
- * has a model!). The caller must immediately add the withdrawn object to another model.
- *
- * This private method is only used to implement public API.
- */
-void Model::withdraw(LDObject* object)
-{
-	QModelIndex index = this->indexOf(object);
-
-	if (index.isValid())
-		withdrawAt(index.row());
-}
-
-/*
- * Drops an object from the model at the provided position. The caller must immediately put the result value object into a new model.
- */
-LDObject* Model::withdrawAt(int position)
-{
-	beginRemoveRows({}, position, position);
-	LDObject* object = _objects[position];
-	emit aboutToRemoveObject(object);
-	_objects.removeAt(position);
-	_needsTriangleRecount = true;
-	endRemoveRows();
-	return object;
-}
-
-/*
  * Returns whether or not this model is empty.
  */
 bool Model::isEmpty() const
--- a/src/model.h	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/model.h	Mon Mar 05 22:40:34 2018 +0200
@@ -19,6 +19,7 @@
 #pragma once
 #include <QAbstractListModel>
 #include "main.h"
+#include "serializer.h"
 #include "linetypes/modelobject.h"
 
 class IndexGenerator
@@ -86,14 +87,12 @@
 	Model(const Model& other) = delete;
 	~Model();
 
-	void addObject(LDObject* object);
-	virtual void insertObject(int position, LDObject* object);
-	virtual bool swapObjects(LDObject* one, LDObject* other);
-	virtual bool setObjectAt(int idx, LDObject* obj);
+	void insertCopy(int position, LDObject* object);
+	void insertFromArchive(int row, Serializer::Archive& archive);
+	bool swapObjects(LDObject* one, LDObject* other);
+	bool setObjectAt(int idx, Serializer::Archive& archive);
 	template<typename T, typename... Args> T* emplace(Args&& ...args);
 	template<typename T, typename... Args> T* emplaceAt(int position, Args&& ...args);
-	template<typename T, typename... Args> T* emplaceReplacement(LDObject* object, Args&& ...args);
-	template<typename T, typename... Args> T* emplaceReplacementAt(int position, Args&& ...args);
 	void removeAt(int position);
 	void removeAt(const QModelIndex& index);
 	void remove(LDObject* object);
@@ -119,22 +118,23 @@
 
 	int rowCount(const QModelIndex& parent) const override;
 	QVariant data(const QModelIndex& index, int role) const override;
-	// bool removeRows(int row, int count, const QModelIndex& ) override;
 
 signals:
-	void objectAdded(LDObject* object);
-	void aboutToRemoveObject(LDObject* object);
+	void objectAdded(const QModelIndex& object);
+	void aboutToRemoveObject(const QModelIndex& index);
 	void objectModified(LDObject* object);
+	void objectsSwapped(const QModelIndex& index_1, const QModelIndex& index_2);
 
 protected:
 	template<typename T, typename... Args> T* constructObject(Args&& ...args);
-	void withdraw(LDObject* object);
-	virtual LDObject* withdrawAt(int position);
 
 	QVector<LDObject*> _objects;
 	class DocumentManager* _manager;
 	mutable int _triangleCount = 0;
 	mutable bool _needsTriangleRecount;
+
+private:
+	void installObject(int row, LDObject* object);
 };
 
 int countof(Model& model);
@@ -146,7 +146,7 @@
  *
  * For instance, the LDLine contains a constructor as such:
  *
- *     LDLine(Vertex v1, Vertex v2, Model* model);
+ *     LDLine(Vertex v1, Vertex v2);
  *
  * This constructor can be invoked as such:
  *
@@ -155,9 +155,7 @@
 template<typename T, typename... Args>
 T* Model::emplace(Args&& ...args)
 {
-	T* object = constructObject<T>(args...);
-	addObject(object);
-	return object;
+	return emplaceAt<T>(size(), args...);
 }
 
 /*
@@ -168,51 +166,18 @@
 T* Model::emplaceAt(int position, Args&& ...args)
 {
 	T* object = constructObject<T>(args...);
-	insertObject(position, object);
+	installObject(position, object);
 	return object;
 }
 
 /*
- * Like emplace<>() but instead of inserting the constructed object, the new object replaces the object given in the first parameter.
- * If the old object cannot be replaced, the new object will not be constructed at all.
- */
-template<typename T, typename... Args>
-T* Model::emplaceReplacement(LDObject* object, Args&& ...args)
-{
-	QModelIndex position = this->indexOf(object);
-
-	if (position.isValid())
-	{
-		T* replacement = constructObject<T>(args...);
-		setObjectAt(position.row(), replacement);
-		return replacement;
-	}
-	else
-	{
-		return nullptr;
-	}
-}
-
-/*
- * Like emplaceAt<>() but instead of inserting the constructed object, it replaces the document at the given spot instead.
- * The replaced object is deleted in the process.
- */
-template<typename T, typename... Args>
-T* Model::emplaceReplacementAt(int position, Args&& ...args)
-{
-	T* replacement = constructObject<T>(args...);
-	setObjectAt(position, replacement);
-	return replacement;
-}
-
-/*
  * Constructs an LDObject such that it gets this model as its model pointer.
  */
 template<typename T, typename... Args>
 T* Model::constructObject(Args&& ...args)
 {
 	static_assert (std::is_base_of<LDObject, T>::value, "Can only use this function with LDObject-derivatives");
-	T* object = new T {args..., this};
+	T* object = new T {args...};
 
 	// Set default color. Relying on virtual functions, this cannot be done in the c-tor.
 	// TODO: store -1 as the default color
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/serializer.cpp	Mon Mar 05 22:40:34 2018 +0200
@@ -0,0 +1,59 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2017 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "serializer.h"
+#include "linetypes/modelobject.h"
+
+Serializer::Serializer(Archive& archive, Action action) :
+	archive {archive},
+	action {action} {}
+
+Serializer::Archive Serializer::store(LDObject* object)
+{
+	Archive result;
+	result.append(static_cast<int>(object->type()));
+	Serializer serializer {result, Store};
+	object->serialize(serializer);
+	return result;
+}
+
+LDObject* Serializer::restore(Archive& archive)
+{
+	if (archive.size() > 0)
+	{
+		Serializer serializer {archive, Restore};
+		int type;
+		serializer << type;
+		LDObject* object = LDObject::newFromType(static_cast<LDObjectType>(type));
+
+		if (object)
+			object->serialize(serializer);
+
+		return object;
+	}
+	else
+	{
+		return nullptr;
+	}
+}
+
+LDObject* Serializer::clone(LDObject* object)
+{
+	Archive archive = store(object);
+	return restore(archive);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/serializer.h	Mon Mar 05 22:40:34 2018 +0200
@@ -0,0 +1,74 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2018 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include "main.h"
+
+using LDObjectState = QVector<QVariant>;
+
+class Serializer
+{
+public:
+	using Archive = LDObjectState;
+
+	enum Action
+	{
+		Store,
+		Restore,
+	};
+
+	Serializer(Archive& archive, Action action);
+
+	template<typename T>
+	Serializer& operator<<(T& value);
+
+	static Archive store(LDObject* object);
+	static LDObject* restore(Archive& archive) __attribute__((warn_unused_result));
+	static LDObject* clone(LDObject* object) __attribute__((warn_unused_result));
+
+private:
+	Archive& archive;
+	int cursor = 0;
+	Action action;
+};
+
+template<typename T>
+Serializer& Serializer::operator<<(T& value)
+{
+	switch (action)
+	{
+	case Store:
+		this->archive.append(QVariant::fromValue(value));
+		break;
+
+	case Restore:
+		if (this->cursor < this->archive.size())
+		{
+			value = this->archive[this->cursor].value<T>();
+			this->cursor += 1;
+		}
+		else
+		{
+			fprintf(stderr, "warning: archive ran out while restoring\n");
+			value = T {};
+		}
+		break;
+	}
+
+	return *this;
+}
--- a/src/toolsets/algorithmtoolset.cpp	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/toolsets/algorithmtoolset.cpp	Mon Mar 05 22:40:34 2018 +0200
@@ -69,8 +69,10 @@
 		// │   │  --→  │ ╱    ╱ │
 		// │   │  --→  │╱    ╱  │
 		// 1───2       1    1───2
-		LDTriangle* triangle1 = currentDocument()->emplaceReplacementAt<LDTriangle>(index.row(), v0, v1, v3);
-		LDTriangle* triangle2 = currentDocument()->emplaceAt<LDTriangle>(index.row() + 1, v1, v2, v3);
+		int row = index.row();
+		currentDocument()->removeAt(index);
+		LDTriangle* triangle1 = currentDocument()->emplaceAt<LDTriangle>(row, v0, v1, v3);
+		LDTriangle* triangle2 = currentDocument()->emplaceAt<LDTriangle>(row + 1, v1, v2, v3);
 
 		// The triangles also inherit the quad's color
 		triangle1->setColor(color);
--- a/src/toolsets/basictoolset.cpp	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/toolsets/basictoolset.cpp	Mon Mar 05 22:40:34 2018 +0200
@@ -109,7 +109,7 @@
 			// Merge in the inlined objects
 			for (LDObject* inlinedObject : inlined.objects())
 			{
-				currentDocument()->insertObject(row, inlinedObject);
+				currentDocument()->insertCopy(row, inlinedObject);
 				mainWindow()->select(currentDocument()->index(row));
 				row += 1;
 			}
--- a/src/toolsets/extprogramtoolset.cpp	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/toolsets/extprogramtoolset.cpp	Mon Mar 05 22:40:34 2018 +0200
@@ -327,7 +327,7 @@
 	for (LDObject* object : model.objects())
 	{
 		if (object->isScemantic())
-			currentDocument()->addObject(object);
+			currentDocument()->insertCopy(currentDocument()->size(), object);
 	}
 
 	m_window->doFullRefresh();
--- a/src/toolsets/filetoolset.cpp	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/toolsets/filetoolset.cpp	Mon Mar 05 22:40:34 2018 +0200
@@ -123,7 +123,7 @@
 
 			for (LDObject* object : model.objects())
 			{
-				currentDocument()->insertObject (position, object);
+				currentDocument()->insertCopy (position, object);
 				mainWindow()->select(currentDocument()->index(position));
 				position += 1;
 			}
--- a/src/types/matrix.h	Sat Mar 03 17:59:56 2018 +0200
+++ b/src/types/matrix.h	Mon Mar 05 22:40:34 2018 +0200
@@ -59,6 +59,8 @@
 	double m_values[9];
 };
 
+Q_DECLARE_METATYPE(Matrix)
+
 /*
  * A structure that provides a view into a row in a matrix.
  * This is returned by operator[] so that the matrix can be accessed by A[i][j]

mercurial