Major overhaul of object→document relationship: added the Model class which models the object buffer. Each object is to be included in a model (an invariant that currently does not hold). A document is a subclass of a model. The LDObject is also now agnostic about selection, and the selection is now a set. A lot of things are probably broken now but it's a major step forward.

Sun, 29 Jan 2017 15:05:14 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Sun, 29 Jan 2017 15:05:14 +0200
changeset 1073
a0a0d581309b
parent 1072
9ce9496427f2
child 1074
a62f810ca26f

Major overhaul of object→document relationship: added the Model class which models the object buffer. Each object is to be included in a model (an invariant that currently does not hold). A document is a subclass of a model. The LDObject is also now agnostic about selection, and the selection is now a set. A lot of things are probably broken now but it's a major step forward.
The LDObject::destroy method is also now gone. The model decides when objects are destroyed and calls the destructor directly. The end result removes a lot of cruft and adds structure to LDObject relations.

Notes:
- Inlining does not currently work (nothing simply gets inlined in)
- More work is required to ensure that each object actually goes into a model

CMakeLists.txt file | annotate | diff | comparison | revisions
src/basics.cpp file | annotate | diff | comparison | revisions
src/basics.h file | annotate | diff | comparison | revisions
src/documentloader.cpp file | annotate | diff | comparison | revisions
src/documentloader.h file | annotate | diff | comparison | revisions
src/documentmanager.cpp file | annotate | diff | comparison | revisions
src/documentmanager.h file | annotate | diff | comparison | revisions
src/editHistory.cpp file | annotate | diff | comparison | revisions
src/editmodes/circleMode.cpp file | annotate | diff | comparison | revisions
src/editmodes/magicWandMode.cpp file | annotate | diff | comparison | revisions
src/glCompiler.cpp file | annotate | diff | comparison | revisions
src/glRenderer.cpp file | annotate | diff | comparison | revisions
src/hierarchyelement.cpp file | annotate | diff | comparison | revisions
src/hierarchyelement.h file | annotate | diff | comparison | revisions
src/ldDocument.cpp file | annotate | diff | comparison | revisions
src/ldDocument.h file | annotate | diff | comparison | revisions
src/ldObject.cpp file | annotate | diff | comparison | revisions
src/ldObject.h file | annotate | diff | comparison | revisions
src/ldobjectiterator.h file | annotate | diff | comparison | revisions
src/mainwindow.cpp file | annotate | diff | comparison | revisions
src/mainwindow.h file | annotate | diff | comparison | revisions
src/mathfunctions.cpp file | annotate | diff | comparison | revisions
src/mathfunctions.h file | annotate | diff | comparison | revisions
src/model.cpp file | annotate | diff | comparison | revisions
src/model.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/toolsets/movetoolset.cpp file | annotate | diff | comparison | revisions
src/toolsets/viewtoolset.cpp file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Sat Jan 28 17:47:06 2017 +0200
+++ b/CMakeLists.txt	Sun Jan 29 15:05:14 2017 +0200
@@ -50,6 +50,7 @@
 	src/matrixinput.cpp
 	src/messageLog.cpp
 	src/miscallenous.cpp
+	src/model.cpp
 	src/partdownloader.cpp
 	src/partdownloadrequest.cpp
 	src/primitives.cpp
@@ -108,6 +109,7 @@
 	src/matrixinput.h
 	src/messageLog.h
 	src/miscallenous.h
+	src/model.h
 	src/partdownloader.h
 	src/partdownloadrequest.h
 	src/primitives.h
--- a/src/basics.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/basics.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -151,11 +151,13 @@
 		break;
 
 	case OBJ_SubfileReference:
-		for (LDObject* it : static_cast<LDSubfileReference*> (obj)->inlineContents (true, false))
-		{
-			calcObject (it);
-			it->destroy();
-		}
+	    {
+		    Model model;
+			static_cast<LDSubfileReference*>(obj)->inlineContents(model, true, false);
+
+			for (LDObject* it : model.objects())
+				calcObject(it);
+	    }
 		break;
 
 	default:
--- a/src/basics.h	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/basics.h	Sun Jan 29 15:05:14 2017 +0200
@@ -21,6 +21,7 @@
 #include <QObject>
 #include <QStringList>
 #include <QMetaType>
+#include <QSet>
 #include <QVector3D>
 #include <QVector>
 #include <functional>
@@ -245,6 +246,12 @@
 }
 
 template<typename T>
+int countof(const QSet<T>& set)
+{
+	return set.size();
+}
+
+template<typename T>
 int countof(const std::initializer_list<T>& vector)
 {
 	return vector.size();
--- a/src/documentloader.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/documentloader.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -23,8 +23,9 @@
 #include "mainwindow.h"
 #include "dialogs/openprogressdialog.h"
 
-DocumentLoader::DocumentLoader (bool onForeground, QObject *parent) :
+DocumentLoader::DocumentLoader (Model* model, bool onForeground, QObject *parent) :
 	QObject (parent),
+    _model(model),
 	m_warningCount (0),
 	m_isDone (false),
 	m_hasAborted (false),
@@ -55,9 +56,9 @@
 	return m_isOnForeground;
 }
 
-const LDObjectList& DocumentLoader::objects() const
+const QVector<LDObject*>& DocumentLoader::objects() const
 {
-	return m_objects;
+	return _model->objects();
 }
 
 void DocumentLoader::read (QIODevice* fp)
@@ -98,10 +99,6 @@
 	// User wishes to abort, so stop here now.
 	if (hasAborted())
 	{
-		for (LDObject* obj : m_objects)
-			obj->destroy();
-
-		m_objects.clear();
 		m_isDone = true;
 		return;
 	}
@@ -126,7 +123,7 @@
 			++m_warningCount;
 		}
 
-		m_objects << obj;
+		_model->addObject(obj);
 	}
 
 	m_progress = i;
--- a/src/documentloader.h	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/documentloader.h	Sun Jan 29 15:05:14 2017 +0200
@@ -18,6 +18,7 @@
 
 #pragma once
 #include "main.h"
+#include "model.h"
 
 //
 // DocumentLoader
@@ -30,13 +31,13 @@
 	Q_OBJECT
 
 public:
-	DocumentLoader (bool onForeground = false, QObject* parent = 0);
+	DocumentLoader (Model* model, bool onForeground = false, QObject* parent = 0);
 
 	Q_SLOT void abort();
 	bool hasAborted();
 	bool isDone() const;
 	bool isOnForeground() const;
-	const LDObjectList& objects() const;
+	const QVector<LDObject*>& objects() const;
 	int progress() const;
 	void read (QIODevice* fp);
 	Q_SLOT void start();
@@ -44,7 +45,7 @@
 
 private:
 	class OpenProgressDialog* m_progressDialog;
-	LDObjectList m_objects;
+	Model* _model;
 	QStringList m_lines;
 	int m_progress;
 	int m_warningCount;
--- a/src/documentmanager.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/documentmanager.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -271,15 +271,13 @@
 	return nullptr;
 }
 
-LDObjectList DocumentManager::loadFileContents (QFile* fp, int* numWarnings, bool* ok)
+void DocumentManager::loadFileContents(QIODevice* input, Model& model, int* numWarnings, bool* ok)
 {
-	LDObjectList objs;
-
 	if (numWarnings)
 		*numWarnings = 0;
 
-	DocumentLoader* loader = new DocumentLoader (m_loadingMainFile);
-	loader->read (fp);
+	DocumentLoader* loader = new DocumentLoader {&model, m_loadingMainFile};
+	loader->read(input);
 	loader->start();
 
 	// After start() returns, if the loader isn't done yet, it's delaying
@@ -292,10 +290,6 @@
 	// If we wanted the success value, supply that now
 	if (ok)
 		*ok = not loader->hasAborted();
-
-	objs = loader->objects();
-	delete loader;
-	return objs;
 }
 
 LDDocument* DocumentManager::openDocument (QString path, bool search, bool implicit, LDDocument* fileToOverride,
@@ -335,7 +329,9 @@
 
 	int numWarnings;
 	bool ok;
-	LDObjectList objs = loadFileContents (fp, &numWarnings, &ok);
+	Model model;
+	loadFileContents(fp, model, &numWarnings, &ok);
+	load->merge(model);
 	fp->close();
 	fp->deleteLater();
 
@@ -348,8 +344,6 @@
 		return nullptr;
 	}
 
-	load->addObjects (objs);
-
 	if (m_loadingMainFile)
 	{
 		m_window->changeDocument (load);
@@ -411,7 +405,7 @@
 		print (tr ("Logoed studs loaded.\n"));
 }
 
-bool DocumentManager::preInline (LDDocument* doc, LDObjectList& objs, bool deep, bool renderinline)
+bool DocumentManager::preInline (LDDocument* doc, Model& model, bool deep, bool renderinline)
 {
 	// Possibly substitute with logoed studs:
 	// stud.dat -> stud-logo.dat
@@ -423,12 +417,12 @@
 
 		if (doc->name() == "stud.dat" and m_logoedStud)
 		{
-			objs = m_logoedStud->inlineContents (deep, renderinline);
+			m_logoedStud->inlineContents(model, deep, renderinline);
 			return true;
 		}
 		else if (doc->name() == "stud2.dat" and m_logoedStud2)
 		{
-			objs = m_logoedStud2->inlineContents (deep, renderinline);
+			m_logoedStud2->inlineContents(model, deep, renderinline);
 			return true;
 		}
 	}
--- a/src/documentmanager.h	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/documentmanager.h	Sun Jan 29 15:05:14 2017 +0200
@@ -21,6 +21,8 @@
 #include "main.h"
 #include "hierarchyelement.h"
 
+class Model;
+
 class DocumentManager : public QObject, public HierarchyElement
 {
 	Q_OBJECT
@@ -39,13 +41,13 @@
 	QString findDocumentPath (QString relpath, bool subdirs);
 	LDDocument* getDocumentByName (QString filename);
 	bool isSafeToCloseAll();
-	LDObjectList loadFileContents (QFile* fp, int* numWarnings, bool* ok);
+	void loadFileContents(QIODevice* fp, Model& model, int* numWarnings, bool* ok);
 	void loadLogoedStuds();
 	LDDocument* openDocument (QString path, bool search, bool implicit, LDDocument* fileToOverride = nullptr,
 							  bool* aborted = nullptr);
 	QFile* openLDrawFile (QString relpath, bool subdirs, QString* pathpointer);
 	void openMainModel (QString path);
-	bool preInline (LDDocument* doc, LDObjectList&, bool deep, bool renderinline);
+	bool preInline (LDDocument* doc, Model& model, bool deep, bool renderinline);
 
 private:
 	Documents m_documents;
--- a/src/editHistory.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/editHistory.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -165,7 +165,8 @@
 
 void AddHistoryEntry::undo() const
 {
-	parent()->document()->getObject (m_index)->destroy();
+	LDObject* object = parent()->document()->getObject(m_index);
+	parent()->document()->remove(object);
 }
 
 void AddHistoryEntry::redo() const
@@ -201,16 +202,16 @@
 
 void EditHistoryEntry::undo() const
 {
-	LDObject* obj = parent()->document()->getObject (m_index);
-	LDObject* newobj = ParseLine (m_oldCode);
-	obj->replace (newobj);
+	LDObject* object = parent()->document()->getObject (m_index);
+	LDObject* newObject = ParseLine (m_oldCode);
+	parent()->document()->replace(object, newObject);
 }
 
 void EditHistoryEntry::redo() const
 {
-	LDObject* obj = parent()->document()->getObject (m_index);
-	LDObject* newobj = ParseLine (m_newCode);
-	obj->replace (newobj);
+	LDObject* object = parent()->document()->getObject (m_index);
+	LDObject* newObject = ParseLine (m_newCode);
+	parent()->document()->replace(object, newObject);
 }
 
 //
--- a/src/editmodes/circleMode.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/editmodes/circleMode.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -191,7 +191,7 @@
 		int l = (relZ == X ? 1 : 0);
 		int m = (relZ == Y ? 1 : 0);
 		int n = (relZ == Z ? 1 : 0);
-		math()->rotateObjects (l, m, n, -m_angleOffset, objs);
+		math()->rotateObjects (l, m, n, -m_angleOffset, objs.toVector());
 	}
 
 	finishDraw (objs);
--- a/src/editmodes/magicWandMode.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/editmodes/magicWandMode.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -180,12 +180,12 @@
 		currentDocument()->clearSelection();
 	case Additive:
 		for (LDObject* obj : m_selection)
-			obj->select();
+			currentDocument()->addToSelection(obj);
 		break;
 
 	case Subtractive:
 		for (LDObject* obj : m_selection)
-			obj->deselect();
+			currentDocument()->removeFromSelection(obj);
 		break;
 
 	case InternalRecursion:
--- a/src/glCompiler.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/glCompiler.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -258,7 +258,7 @@
 			continue;
 		}
 
-		if (it.key()->document() == currentDocument() and not it.key()->isHidden())
+		if (it.key()->model() == currentDocument() and not it.key()->isHidden())
 			vbodata += it->data[vbonum];
 
 		++it;
@@ -285,7 +285,7 @@
 
 void GLCompiler::compileObject (LDObject* obj)
 {
-	if (obj == nullptr or obj->document() == nullptr or obj->document()->isCache())
+	if (obj == nullptr)
 		return;
 
 	ObjectVBOInfo info;
--- a/src/glRenderer.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/glRenderer.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -909,7 +909,7 @@
 void GLRenderer::pick(const QRect& range, bool additive)
 {
 	makeCurrent();
-	QSet<LDObject*> priorSelection = selectedObjects().toSet();
+	QSet<LDObject*> priorSelection = selectedObjects();
 	QSet<LDObject*> newSelection;
 
 	// If we're doing an additive selection, we start off with the existing selection.
@@ -975,14 +975,14 @@
 	// Select all objects that we now have selected that were not selected before.
 	for (LDObject* object : newSelection - priorSelection)
 	{
-		object->select();
+		m_document->addToSelection(object);
 		compileObject(object);
 	}
 
 	// Likewise, deselect whatever was selected that isn't anymore.
 	for (LDObject* object : priorSelection - newSelection)
 	{
-		object->deselect();
+		m_document->removeFromSelection(object);
 		compileObject(object);
 	}
 
@@ -1447,11 +1447,11 @@
 			LDObject* nextobj = ovlobj->next();
 
 			if (nextobj and nextobj->type() == OBJ_Empty)
-				nextobj->destroy();
+				document()->remove(nextobj);
 
 			// If the overlay object was there and the overlay itself is
 			// not, remove the object.
-			ovlobj->destroy();
+			document()->remove(ovlobj);
 		}
 		else if (meta.image and ovlobj == nullptr)
 		{
@@ -1489,7 +1489,7 @@
 				document()->insertObject (i, ovlobj);
 
 				if (found)
-					document()->insertObject (i + 1, LDSpawn<LDEmpty>());
+					document()->emplaceAt<LDEmpty>(i + 1);
 			}
 		}
 
@@ -1563,13 +1563,9 @@
 	{
 		PrimitiveTreeItem* item = static_cast<PrimitiveTreeItem*> (m_window->getPrimitivesTree()->currentItem());
 		QString primitiveName = item->primitive()->name;
-		LDSubfileReference* ref = LDSpawn<LDSubfileReference>();
-		ref->setColor (MainColor);
+		LDSubfileReference* ref = currentDocument()->emplaceAt<LDSubfileReference>(m_window->suggestInsertPoint());
 		ref->setFileInfo (m_documents->getDocumentByName (primitiveName));
-		ref->setPosition (Origin);
-		ref->setTransformationMatrix (Matrix::identity);
-		currentDocument()->insertObject (m_window->suggestInsertPoint(), ref);
-		ref->select();
+		currentDocument()->addToSelection(ref);
 		m_window->buildObjectList();
 		m_window->renderer()->refresh();
 		ev->acceptProposedAction();
--- a/src/hierarchyelement.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/hierarchyelement.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -59,7 +59,7 @@
 }
 
 
-const LDObjectList& HierarchyElement::selectedObjects()
+const QSet<LDObject*>& HierarchyElement::selectedObjects()
 {
 	return m_window->selectedObjects();
 }
--- a/src/hierarchyelement.h	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/hierarchyelement.h	Sun Jan 29 15:05:14 2017 +0200
@@ -38,7 +38,7 @@
 public:
 	HierarchyElement (QObject* parent);
 
-	const LDObjectList& selectedObjects();
+	const QSet<LDObject*>& selectedObjects();
 	LDDocument* currentDocument();
 	GuiUtilities* guiUtilities() const;
 	PrimitiveManager* primitives();
--- a/src/ldDocument.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/ldDocument.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -40,18 +40,13 @@
 	m_history (new EditHistory (this)),
 	m_flags(IsCache | VerticesOutdated | NeedsVertexMerge | NeedsRecache),
 	m_savePosition(-1),
-	m_tabIndex(-1),
-	m_triangleCount(0),
+    m_tabIndex(-1),
 	m_gldata (new LDGLData),
 	m_manager (parent) {}
 
 LDDocument::~LDDocument()
 {
 	m_flags |= IsBeingDestroyed;
-
-	for (int i = 0; i < countof(m_objects); ++i)
-		m_objects[i]->destroy();
-
 	delete m_history;
 	delete m_gldata;
 }
@@ -66,11 +61,6 @@
 	m_name = value;
 }
 
-const LDObjectList& LDDocument::objects() const
-{
-	return m_objects;
-}
-
 EditHistory* LDDocument::history() const
 {
 	return m_history;
@@ -121,21 +111,6 @@
 	m_defaultName = value;
 }
 
-int LDDocument::triangleCount()
-{
-	if (checkFlag(NeedsTriangleRecount))
-	{
-		m_triangleCount = 0;
-
-		for (LDObject* obj : m_objects)
-			m_triangleCount += obj->triangleCount();
-
-		unsetFlag(NeedsTriangleRecount);
-	}
-
-	return m_triangleCount;
-}
-
 void LDDocument::openForEditing()
 {
 	if (isCache())
@@ -316,14 +291,6 @@
 
 // =============================================================================
 //
-void LDDocument::clear()
-{
-	for (LDObject* obj : objects())
-		forgetObject (obj);
-}
-
-// =============================================================================
-//
 static void CheckTokenCount (const QStringList& tokens, int num)
 {
 	if (countof(tokens) != num)
@@ -575,18 +542,13 @@
 	{
 		if (obj->type() == OBJ_SubfileReference)
 		{
-			LDSubfileReference* ref = static_cast<LDSubfileReference*> (obj);
-			LDDocument* fileInfo = m_documents->getDocumentByName (ref->fileInfo()->name());
+			LDSubfileReference* reference = static_cast<LDSubfileReference*> (obj);
+			LDDocument* fileInfo = m_documents->getDocumentByName (reference->fileInfo()->name());
 
 			if (fileInfo)
-			{
-				ref->setFileInfo (fileInfo);
-			}
+				reference->setFileInfo (fileInfo);
 			else
-			{
-				ref->replace (LDSpawn<LDError> (ref->asText(),
-					format ("Could not open %1", ref->fileInfo()->name())));
-			}
+				emplaceReplacement<LDError>(reference, reference->asText(), format("Could not open %1", reference->fileInfo()->name()));
 		}
 
 		// Reparse gibberish files. It could be that they are invalid because
@@ -603,25 +565,12 @@
 
 // =============================================================================
 //
-int LDDocument::addObject (LDObject* obj)
+void LDDocument::addObjects (const LDObjectList& objects)
 {
-	history()->add (new AddHistoryEntry (countof(objects()), obj));
-	m_objects << obj;
-	addKnownVertices (obj);
-	obj->setDocument (this);
-	setFlag(NeedsTriangleRecount);
-	m_window->renderer()->compileObject (obj);
-	return getObjectCount() - 1;
-}
-
-// =============================================================================
-//
-void LDDocument::addObjects (const LDObjectList& objs)
-{
-	for (LDObject* obj : objs)
+	for (LDObject* object : objects)
 	{
-		if (obj)
-			addObject (obj);
+		if (object)
+			addObject (object);
 	}
 }
 
@@ -630,11 +579,9 @@
 void LDDocument::insertObject (int pos, LDObject* obj)
 {
 	history()->add (new AddHistoryEntry (pos, obj));
-	m_objects.insert (pos, obj);
-	obj->setDocument (this);
-	setFlag(NeedsTriangleRecount);
+	Model::insertObject(pos, obj);
 	m_window->renderer()->compileObject (obj);
-	
+	connect(obj, SIGNAL(codeChanged(int,QString,QString)), this, SLOT(objectChanged(int,QString,QString)));
 
 #ifdef DEBUG
 	if (not isCache())
@@ -642,6 +589,14 @@
 #endif
 }
 
+void LDDocument::objectChanged(int position, QString before, QString after)
+{
+	LDObject* object = static_cast<LDObject*>(sender());
+	addToHistory(new EditHistoryEntry {position, before, after});
+	m_window->renderer()->compileObject(object);
+	m_window->currentDocument()->redoVertices();
+}
+
 // =============================================================================
 //
 void LDDocument::addKnownVertices (LDObject* obj)
@@ -657,61 +612,18 @@
 	needVertexMerge();
 }
 
-// =============================================================================
-//
-void LDDocument::forgetObject (LDObject* obj)
+LDObject* LDDocument::withdrawAt(int position)
 {
-	int idx = obj->lineNumber();
-
-	if (m_objects[idx] == obj)
-	{
-		obj->deselect();
-
-		if (not isCache() and not checkFlag(IsBeingDestroyed))
-		{
-			history()->add (new DelHistoryEntry (idx, obj));
-			m_objectVertices.remove (obj);
-		}
+	LDObject* object = getObject(position);
 
-		m_objects.removeAt (idx);
-		setFlag(NeedsTriangleRecount);
-		obj->setDocument (nullptr);
-	}
-}
-
-// =============================================================================
-//
-void LDDocument::setObjectAt (int idx, LDObject* obj)
-{
-	if (idx < 0 or idx >= countof(m_objects))
-		return;
-
-	// Mark this change to history
-	if (not m_history->isIgnoring())
+	if (not isCache() and not checkFlag(IsBeingDestroyed))
 	{
-		QString oldcode = getObject (idx)->asText();
-		QString newcode = obj->asText();
-		m_history->add (new EditHistoryEntry (idx, oldcode, newcode));
+		history()->add(new DelHistoryEntry {position, object});
+		m_objectVertices.remove(object);
 	}
 
-	m_objectVertices.remove (m_objects[idx]);
-	m_objects[idx]->deselect();
-	m_objects[idx]->setDocument (nullptr);
-	obj->setDocument (this);
-	addKnownVertices (obj);
-	m_window->renderer()->compileObject (obj);
-	m_objects[idx] = obj;
-	needVertexMerge();
-}
-
-// =============================================================================
-//
-LDObject* LDDocument::getObject (int pos) const
-{
-	if (pos < countof(m_objects))
-		return m_objects[pos];
-	else
-		return nullptr;
+	m_selection.remove(object);
+	return Model::withdrawAt(position);
 }
 
 // =============================================================================
@@ -748,8 +660,10 @@
 	if (checkFlag(NeedsRecache))
 	{
 		m_vertices.clear();
+		Model model;
+		inlineContents(model, true, true);
 
-		for (LDObject* obj : inlineContents (true, true))
+		for (LDObject* obj : model.objects())
 		{
 			if (obj->type() == OBJ_SubfileReference)
 			{
@@ -774,8 +688,10 @@
 	if (checkFlag(VerticesOutdated))
 	{
 		m_objectVertices.clear();
+		Model model;
+		inlineContents(model, true, false);
 
-		for (LDObject* obj : inlineContents (true, false))
+		for (LDObject* obj : model)
 			addKnownVertices (obj);
 
 		mergeVertices();
@@ -808,12 +724,10 @@
 
 // =============================================================================
 // -----------------------------------------------------------------------------
-LDObjectList LDDocument::inlineContents (bool deep, bool renderinline)
+void LDDocument::inlineContents(Model& model, bool deep, bool renderinline)
 {
-	LDObjectList objs;
-
-	if (m_manager->preInline (this, objs, deep, renderinline))
-		return objs; // Manager dealt with this inline
+	if (m_manager->preInline(this, model, deep, renderinline))
+		return; // Manager dealt with this inline
 
 	for (LDObject* obj : objects())
 	{
@@ -823,26 +737,27 @@
 
 		// Got another sub-file reference, inline it if we're deep-inlining. If not,
 		// just add it into the objects normally. Yay, recursion!
-		if (deep == true and obj->type() == OBJ_SubfileReference)
-		{
-			for (LDObject* otherobj : static_cast<LDSubfileReference*> (obj)->inlineContents (deep, renderinline))
-				objs << otherobj;
-		}
+		if (deep and obj->type() == OBJ_SubfileReference)
+			static_cast<LDSubfileReference*>(obj)->inlineContents(model, deep, renderinline);
 		else
-			objs << obj->createCopy();
+			model.addObject(obj->createCopy());
 	}
-
-	return objs;
 }
 
 // =============================================================================
 //
 void LDDocument::addToSelection (LDObject* obj) // [protected]
 {
-	if (obj->isSelected() and obj->document() == this)
+	if (not m_selection.contains(obj) and obj->model() == this)
 	{
-		m_sel << obj;
+		m_selection.insert(obj);
 		m_window->renderer()->compileObject (obj);
+
+		// If this object is inverted with INVERTNEXT, select the INVERTNEXT as well.
+		LDBfc* invertnext;
+
+		if (obj->previousIsInvertnext(invertnext))
+			addToSelection(invertnext);
 	}
 }
 
@@ -850,10 +765,16 @@
 //
 void LDDocument::removeFromSelection (LDObject* obj) // [protected]
 {
-	if (not obj->isSelected() and obj->document() == this)
+	if (m_selection.contains(obj))
 	{
-		m_sel.removeOne (obj);
+		m_selection.remove(obj);
 		m_window->renderer()->compileObject (obj);
+
+		// If this object is inverted with INVERTNEXT, deselect the INVERTNEXT as well.
+		LDBfc* invertnext;
+
+		if (obj->previousIsInvertnext(invertnext))
+			removeFromSelection(invertnext);
 	}
 }
 
@@ -861,34 +782,31 @@
 //
 void LDDocument::clearSelection()
 {
-	for (LDObject* obj : m_sel)
-	{
-		obj->deselect();
-		m_window->renderer()->compileObject (obj);
-	}
+	for (LDObject* object : m_selection)
+		m_window->renderer()->compileObject(object);
 
-	m_sel.clear();
+	m_selection.clear();
 }
 
 // =============================================================================
 //
-const LDObjectList& LDDocument::getSelection() const
+const QSet<LDObject*>& LDDocument::getSelection() const
 {
-	return m_sel;
+	return m_selection;
 }
 
 // =============================================================================
 //
-void LDDocument::swapObjects (LDObject* one, LDObject* other)
+bool LDDocument::swapObjects (LDObject* one, LDObject* other)
 {
-	int a = m_objects.indexOf (one);
-	int b = m_objects.indexOf (other);
-
-	if (a != b and a != -1 and b != -1)
+	if (Model::swapObjects(one, other))
 	{
-		m_objects[b] = one;
-		m_objects[a] = other;
-		addToHistory (new SwapHistoryEntry (one->id(), other->id()));
+		addToHistory(new SwapHistoryEntry {one->id(), other->id()});
+		return true;
+	}
+	else
+	{
+		return false;
 	}
 }
 
@@ -923,7 +841,3 @@
 	setFlag(NeedsVertexMerge);
 }
 
-void LDDocument::recountTriangles()
-{
-	setFlag(NeedsTriangleRecount);
-}
--- a/src/ldDocument.h	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/ldDocument.h	Sun Jan 29 15:05:14 2017 +0200
@@ -22,6 +22,7 @@
 #include "ldObject.h"
 #include "editHistory.h"
 #include "glShared.h"
+#include "model.h"
 
 class EditHistory;
 class OpenProgressDialog;
@@ -39,7 +40,7 @@
 // The default name is a placeholder, initially suggested name for a file. The
 // primitive generator uses this to give initial names to primitives.
 //
-class LDDocument : public QObject, public HierarchyElement
+class LDDocument : public QObject, public Model, public HierarchyElement
 {
 	Q_OBJECT
 
@@ -51,7 +52,6 @@
 		NeedsVertexMerge = (1 << 2),
 		IsBeingDestroyed = (1 << 3),
 		NeedsRecache = (1 << 4), // The next polygon inline of this document rebuilds stored polygon data.
-		NeedsTriangleRecount = (1 << 5),
 	};
 
 	Q_DECLARE_FLAGS(Flags, Flag)
@@ -60,11 +60,9 @@
 	~LDDocument();
 
 	void addHistoryStep();
-	int addObject (LDObject* obj);
 	void addObjects (const LDObjectList& objs);
 	void addToHistory (AbstractHistoryEntry* entry);
 	void addToSelection (LDObject* obj);
-	void clear();
 	void clearHistory();
 	void clearSelection();
 	void close();
@@ -72,14 +70,13 @@
 	void forgetObject (LDObject* obj);
 	QString fullPath();
 	QString getDisplayName();
-	LDObject* getObject (int pos) const;
 	int getObjectCount() const;
-	const LDObjectList& getSelection() const;
+	const QSet<LDObject*>& getSelection() const;
 	LDGLData* glData();
 	bool hasUnsavedChanges() const;
 	EditHistory* history() const;
 	void initializeCachedData();
-	LDObjectList inlineContents (bool deep, bool renderinline);
+	void inlineContents(Model& model, bool deep, bool renderinline);
 	QList<LDPolygon> inlinePolygons();
 	const QSet<Vertex>& inlineVertices();
 	void insertObject (int pos, LDObject* obj);
@@ -87,7 +84,7 @@
 	bool isSafeToClose();
 	QString name() const;
 	void needVertexMerge();
-	const LDObjectList& objects() const;
+	void objectRemoved(LDObject* object, int index);
 	void openForEditing();
 	const QList<LDPolygon>& polygonData() const;
 	void recountTriangles();
@@ -100,22 +97,25 @@
 	void setDefaultName (QString value);
 	void setFullPath (QString value);
 	void setName (QString value);
-	void setObjectAt (int idx, LDObject* obj);
 	void setSavePosition (long value);
 	void setTabIndex (int value);
-	void swapObjects (LDObject* one, LDObject* other);
+	bool swapObjects (LDObject* one, LDObject* other);
 	int tabIndex() const;
-	int triangleCount();
 	void undo();
 	void vertexChanged (const Vertex& a, const Vertex& b);
 
 	static QString shortenName (QString a); // Turns a full path into a relative path
 
+public slots:
+	void objectChanged(int position, QString before, QString after);
+
+protected:
+	LDObject* withdrawAt(int position);
+
 private:
 	QString m_name;
 	QString m_fullPath;
 	QString m_defaultName;
-	LDObjectList m_objects;
 	EditHistory* m_history;
 	Flags m_flags;
 	long m_savePosition;
@@ -124,7 +124,7 @@
 	QList<LDPolygon> m_polygonData;
 	QMap<LDObject*, QSet<Vertex>> m_objectVertices;
 	QSet<Vertex> m_vertices;
-	LDObjectList m_sel;
+	QSet<LDObject*> m_selection;
 	LDGLData* m_gldata;
 	DocumentManager* m_manager;
 
--- a/src/ldObject.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/ldObject.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -37,20 +37,19 @@
 enum { MAX_LDOBJECT_IDS = (1 << 24) };
 
 #define LDOBJ_DEFAULT_CTOR(T,BASE) \
-	T :: T (LDDocument* document) : \
-		BASE (document) {}
+	T :: T (Model* model) : \
+	    BASE {model} {}
 
 // =============================================================================
 // LDObject constructors
 //
-LDObject::LDObject (LDDocument* document) :
+LDObject::LDObject (Model* model) :
 	m_isHidden (false),
-	m_isSelected (false),
-	m_isDestroyed (false),
-	m_document (nullptr)
+    m_isSelected (false),
+    _model (nullptr)
 {
-	if (document)
-		document->addObject (this);
+	if (model)
+		model->addObject (this);
 
 	memset (m_coords, 0, sizeof m_coords);
 
@@ -67,8 +66,8 @@
 	m_randomColor = QColor::fromHsv (rand() % 360, rand() % 256, rand() % 96 + 128);
 }
 
-LDSubfileReference::LDSubfileReference (LDDocument* document) :
-	LDMatrixObject (document) {}
+LDSubfileReference::LDSubfileReference (Model* model) :
+    LDMatrixObject (model) {}
 
 LDOBJ_DEFAULT_CTOR (LDEmpty, LDObject)
 LDOBJ_DEFAULT_CTOR (LDError, LDObject)
@@ -83,8 +82,12 @@
 
 LDObject::~LDObject()
 {
-	if (not m_isDestroyed)
-		print ("Warning: Object #%1 (%2) was not destroyed before being deleted\n", id(), this);
+	// Delete the GL lists
+	if (g_win)
+		g_win->renderer()->forgetObject(this);
+
+	// Remove this object from the list of LDObjects
+	g_allObjects.erase(g_allObjects.find(id()));
 }
 
 // =============================================================================
@@ -217,10 +220,7 @@
 	if (idx != -1)
 	{
 		// Replace the instance of the old object with the new object
-		document()->setObjectAt (idx, other);
-
-		// Remove the old object
-		destroy();
+		model()->setObjectAt(idx, other);
 	}
 }
 
@@ -231,10 +231,9 @@
 	if (idx != -1 and not others.isEmpty())
 	{
 		for (int i = 1; i < countof(others); ++i)
-			document()->insertObject (idx + i, others[i]);
+			model()->insertObject (idx + i, others[i]);
 
-		document()->setObjectAt (idx, others[0]);
-		destroy();
+		model()->setObjectAt(idx, others[0]);
 	}
 }
 
@@ -244,8 +243,8 @@
 //
 void LDObject::swap (LDObject* other)
 {
-	if (document() == other->document())
-		document()->swapObjects (this, other);
+	if (model() == other->model())
+		model()->swapObjects (this, other);
 }
 
 int LDObject::triangleCount() const
@@ -323,34 +322,9 @@
 
 // =============================================================================
 //
-// Deletes this object
-//
-void LDObject::destroy()
+void LDObject::setDocument (Model* model)
 {
-	deselect();
-
-	// If this object was associated to a file, remove it off it now
-	if (document())
-		document()->forgetObject (this);
-
-	// Delete the GL lists
-	if (g_win)
-		g_win->renderer()->forgetObject (this);
-
-	// Remove this object from the list of LDObjects
-	g_allObjects.erase (g_allObjects.find (id()));
-	m_isDestroyed = true;
-	delete this;
-}
-
-// =============================================================================
-//
-void LDObject::setDocument (LDDocument* document)
-{
-	m_document = document;
-
-	if (document == nullptr)
-		m_isSelected = false;
+	_model = model;
 }
 
 // =============================================================================
@@ -392,15 +366,16 @@
 
 // =============================================================================
 // -----------------------------------------------------------------------------
-LDObjectList LDSubfileReference::inlineContents (bool deep, bool render)
+void LDSubfileReference::inlineContents(Model& model, bool deep, bool render)
 {
-	LDObjectList objs = fileInfo()->inlineContents (deep, render);
+	Model inlined;
+	fileInfo()->inlineContents(inlined, deep, render);
 
 	// Transform the objects
-	for (LDObject* obj : objs)
-		TransformObject (obj, transformationMatrix(), position(), color());
+	for (LDObject* object : inlined)
+		TransformObject(object, transformationMatrix(), position(), color());
 
-	return objs;
+	model.merge(inlined);
 }
 
 // =============================================================================
@@ -449,11 +424,11 @@
 //
 int LDObject::lineNumber() const
 {
-	if (document())
+	if (model())
 	{
-		for (int i = 0; i < document()->getObjectCount(); ++i)
+		for (int i = 0; i < model()->size(); ++i)
 		{
-			if (document()->getObject (i) == this)
+			if (model()->getObject(i) == this)
 				return i;
 		}
 	}
@@ -510,12 +485,7 @@
 //
 LDObject* LDObject::next() const
 {
-	int idx = lineNumber();
-
-	if (idx == -1 or idx == document()->getObjectCount() - 1)
-		return nullptr;
-
-	return document()->getObject (idx + 1);
+	return model()->getObject(lineNumber() + 1);
 }
 
 // =============================================================================
@@ -524,12 +494,7 @@
 //
 LDObject* LDObject::previous() const
 {
-	int idx = lineNumber();
-
-	if (idx <= 0)
-		return nullptr;
-
-	return document()->getObject (idx - 1);
+	return model()->getObject(lineNumber() - 1);
 }
 
 // =============================================================================
@@ -597,14 +562,9 @@
 	return m_randomColor;
 }
 
-LDDocument* LDObject::document() const
+Model* LDObject::model() const
 {
-	return m_document;
-}
-
-bool LDObject::isDestroyed() const
-{
-	return m_isDestroyed;
+	return _model;
 }
 
 // =============================================================================
@@ -668,14 +628,15 @@
 //
 void LDSubfileReference::invert()
 {
-	if (document() == nullptr)
+	if (model() == nullptr)
 		return;
 
 	// Check whether subfile is flat
 	int axisSet = (1 << X) | (1 << Y) | (1 << Z);
-	LDObjectList objs = fileInfo()->inlineContents (true, false);
+	Model model;
+	fileInfo()->inlineContents(model, true, false);
 
-	for (LDObject* obj : objs)
+	for (LDObject* obj : model.objects())
 	{
 		for (int i = 0; i < obj->numVertices(); ++i)
 		{
@@ -724,13 +685,13 @@
 		if (bfc and bfc->statement() == BfcStatement::InvertNext)
 		{
 			// This is prefixed with an invertnext, thus remove it.
-			bfc->destroy();
+			this->model()->remove(bfc);
 			return;
 		}
 	}
 
 	// Not inverted, thus prefix it with a new invertnext.
-	document()->insertObject (idx, new LDBfc (BfcStatement::InvertNext));
+	this->model()->insertObject (idx, new LDBfc (BfcStatement::InvertNext));
 }
 
 // =============================================================================
@@ -768,7 +729,7 @@
 
 // =============================================================================
 //
-LDLine* LDCondLine::toEdgeLine()
+LDLine* LDCondLine::becomeEdgeLine()
 {
 	LDLine* replacement = new LDLine;
 
@@ -776,20 +737,15 @@
 		replacement->setVertex (i, vertex (i));
 
 	replacement->setColor (color());
-	replace (replacement);
+	model()->replace(this, replacement);
 	return replacement;
 }
 
 // =============================================================================
 //
-LDObject* LDObject::fromID (int id)
+LDObject* LDObject::fromID(int32 id)
 {
-	auto it = g_allObjects.find (id);
-
-	if (it != g_allObjects.end())
-		return *it;
-
-	return nullptr;
+	return g_allObjects.value(id);
 }
 
 // =============================================================================
@@ -809,29 +765,22 @@
 // makes history stuff work out of the box.
 //
 template<typename T>
-static void changeProperty (LDObject* obj, T* ptr, const T& val)
+static void changeProperty(LDObject* object, T* property, const T& value)
 {
-	int idx;
-
-	if (*ptr == val)
+	if (*property == value)
 		return;
 
-	if (obj->document() and (idx = obj->lineNumber()) != -1)
-	{
-		QString before = obj->asText();
-		*ptr = val;
-		QString after = obj->asText();
+	int position = object->lineNumber();
 
-		if (before != after)
-		{
-			obj->document()->addToHistory (new EditHistoryEntry (idx, before, after));
-			g_win->renderer()->compileObject (obj);
-			g_win->currentDocument()->redoVertices();
-		}
+	if (position != -1)
+	{
+		QString before = object->asText();
+		*property = value;
+		emit object->codeChanged(position, before, object->asText());
 	}
 	else
 	{
-		*ptr = val;
+		*property = value;
 	}
 }
 
@@ -860,12 +809,12 @@
 	changeProperty (this, &m_coords[i], vert);
 }
 
-LDMatrixObject::LDMatrixObject (LDDocument* document) :
-	LDObject (document),
+LDMatrixObject::LDMatrixObject (Model* model) :
+    LDObject (model),
 	m_position (Origin) {}
 
-LDMatrixObject::LDMatrixObject (const Matrix& transform, const Vertex& pos, LDDocument* document) :
-	LDObject (document),
+LDMatrixObject::LDMatrixObject (const Matrix& transform, const Vertex& pos, Model* model) :
+    LDObject (model),
 	m_position (pos),
 	m_transformationMatrix (transform) {}
 
@@ -907,8 +856,8 @@
 	changeProperty (this, &m_transformationMatrix, val);
 }
 
-LDError::LDError (QString contents, QString reason, LDDocument* document) :
-	LDObject (document),
+LDError::LDError (QString contents, QString reason, Model* model) :
+    LDObject (model),
 	m_contents (contents),
 	m_reason (reason) {}
 
@@ -1062,19 +1011,16 @@
 		return Vertex();
 }
 
-LDObjectList LDBezierCurve::rasterize (int segments)
+void LDBezierCurve::rasterize(Model& model, int segments)
 {
 	QVector<LDPolygon> polygons = rasterizePolygons(segments);
-	LDObjectList result;
 
 	for (LDPolygon& poly : polygons)
 	{
 		LDLine* line = LDSpawn<LDLine> (poly.vertices[0], poly.vertices[1]);
 		line->setColor (poly.color);
-		result << line;
+		model.addObject(line);
 	}
-
-	return result;
 }
 
 QVector<LDPolygon> LDBezierCurve::rasterizePolygons(int segments)
@@ -1104,44 +1050,17 @@
 
 // =============================================================================
 //
-// Selects this object.
-//
-void LDObject::select()
-{
-	if (not isSelected() and document())
-	{
-		m_isSelected = true;
-		document()->addToSelection (this);
-	}
-}
-
-// =============================================================================
-//
-// Removes this object from selection
-//
-void LDObject::deselect()
-{
-	if (isSelected() and document())
-	{
-		m_isSelected = false;
-		document()->removeFromSelection (this);
-
-		// If this object is inverted with INVERTNEXT, deselect the INVERTNEXT as well.
-		LDBfc* invertnext;
-
-		if (previousIsInvertnext (invertnext))
-			invertnext->deselect();
-	}
-}
-
-// =============================================================================
-//
 LDObject* LDObject::createCopy() const
 {
 	LDObject* copy = ParseLine (asText());
 	return copy;
 }
 
+LDSubfileReference::LDSubfileReference(LDDocument* reference, const Matrix& transformationMatrix,
+                                       const Vertex& position, Model* model) :
+    LDMatrixObject {transformationMatrix, position, model},
+    m_fileInfo {reference} {}
+
 // =============================================================================
 //
 LDDocument* LDSubfileReference::fileInfo() const
@@ -1153,8 +1072,8 @@
 {
 	changeProperty (this, &m_fileInfo, newReferee);
 
-	if (document())
-		document()->recountTriangles();
+	if (model())
+		model()->recountTriangles();
 
 	// If it's an immediate subfile reference (i.e. this subfile is in an opened document), we need to pre-compile the
 	// GL polygons for the document if they don't exist already.
--- a/src/ldObject.h	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/ldObject.h	Sun Jan 29 15:05:14 2017 +0200
@@ -23,10 +23,12 @@
 #include "glShared.h"
 #include "colors.h"
 
+class Model;
+
 #define LDOBJ(T)												\
 public:															\
 	static constexpr LDObjectType SubclassType = OBJ_##T;		\
-	LD##T (LDDocument* document = nullptr);						\
+	LD##T (Model* model = nullptr);								\
 																\
 	virtual LDObjectType type() const override					\
 	{															\
@@ -88,25 +90,24 @@
 // which is a token of the object's type. The object can be casted into
 // sub-classes based on this enumerator.
 //
-class LDObject
+class LDObject : public QObject
 {
+    Q_OBJECT
+
 public:
-	LDObject (LDDocument* document = nullptr);
+    LDObject (Model* model = nullptr);
 
 	virtual QString asText() const = 0; // This object as LDraw code
 	LDColor color() const;
 	LDObject* createCopy() const;
 	virtual LDColor defaultColor() const = 0; // What color does the object default to?
-	void deselect(); 
-	void destroy();
-	LDDocument* document() const;
+	Model* model() const;
 	LDPolygon* getPolygon();
 	virtual void getVertices (QSet<Vertex>& verts) const;
 	virtual bool hasMatrix() const = 0; // Does this object have a matrix and position? (see LDMatrixObject)
 	qint32 id() const;
 	virtual void invert() = 0; // Inverts this object (winding is reversed)
 	virtual bool isColored() const = 0;
-	bool isDestroyed() const;
 	bool isHidden() const;
 	virtual bool isScemantic() const = 0; // Does this object have meaning in the part model?
 	bool isSelected() const;
@@ -119,9 +120,8 @@
 	QColor randomColor() const;
 	void replace (LDObject* other);
 	void replace (const LDObjectList& others);
-	void select();
 	void setColor (LDColor color);
-	void setDocument (LDDocument* document);
+	void setDocument (Model* model);
 	void setHidden (bool value);
 	void setVertex (int i, const Vertex& vert);
 	void swap (LDObject* other);
@@ -131,18 +131,21 @@
 	const Vertex& vertex (int i) const;
 
 	static QString describeObjects (const LDObjectList& objs);
-	static LDObject* fromID (int id);
+	static LDObject* fromID(int32 id);
 	static LDObject* getDefault (const LDObjectType type);
 	static QString typeName (LDObjectType type);
 
+signals:
+	void codeChanged(int position, QString before, QString after);
+
 protected:
+	friend class Model;
 	virtual ~LDObject();
 
 private:
 	bool m_isHidden;
 	bool m_isSelected;
-	bool m_isDestroyed;
-	LDDocument* m_document;
+	Model* _model;
 	qint32 m_id;
 	LDColor m_color;
 	QColor m_randomColor;
@@ -179,8 +182,8 @@
 	Vertex m_position;
 
 public:
-	LDMatrixObject (LDDocument* document = nullptr);
-	LDMatrixObject (const Matrix& transformationMatrix, const Vertex& pos, LDDocument* document = nullptr);
+	LDMatrixObject (Model* model = nullptr);
+	LDMatrixObject (const Matrix& transformationMatrix, const Vertex& pos, Model* model = nullptr);
 
 	const Vertex& position() const;
 	void setCoordinate (const Axis ax, double value);
@@ -208,7 +211,7 @@
 	LDOBJ_NO_MATRIX
 
 public:
-	LDError (QString contents, QString reason, LDDocument* document = nullptr);
+	LDError (QString contents, QString reason, Model* model = nullptr);
 	QString reason() const;
 	QString contents() const;
 	QString fileReferenced() const;
@@ -248,7 +251,7 @@
 	LDOBJ_NO_MATRIX
 
 public:
-	LDComment (QString text, LDDocument* document = nullptr);
+	LDComment (QString text, LDDocument* model = nullptr);
 	QString text() const;
 	void setText (QString value);
 
@@ -287,7 +290,7 @@
 	LDOBJ_NO_MATRIX
 
 public:
-	LDBfc (const BfcStatement type, LDDocument* document = nullptr);
+    LDBfc (const BfcStatement type, LDDocument* model = nullptr);
 
 	BfcStatement statement() const;
 	void setStatement (BfcStatement value);
@@ -315,10 +318,12 @@
 	LDOBJ_HAS_MATRIX
 
 public:
+	LDSubfileReference(LDDocument* reference, const Matrix& transformationMatrix, const Vertex& position, Model* model = nullptr);
+
 	// Inlines this subfile.
 	LDDocument* fileInfo() const;
 	virtual void getVertices (QSet<Vertex>& verts) const override;
-	LDObjectList inlineContents (bool deep, bool render);
+	void inlineContents(Model& model, bool deep, bool render);
 	QList<LDPolygon> inlinePolygons();
 	void setFileInfo (LDDocument* fileInfo);
 	int triangleCount() const override;
@@ -343,7 +348,7 @@
 	LDOBJ_NO_MATRIX
 
 public:
-	LDLine (Vertex v1, Vertex v2, LDDocument* document = nullptr);
+	LDLine (Vertex v1, Vertex v2, LDDocument* model = nullptr);
 };
 
 //
@@ -362,8 +367,8 @@
 	LDOBJ_NO_MATRIX
 
 public:
-	LDCondLine (const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3, LDDocument* document = nullptr);
-	LDLine* toEdgeLine();
+	LDCondLine (const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3, LDDocument* model = nullptr);
+	LDLine* becomeEdgeLine();
 };
 
 //
@@ -384,7 +389,7 @@
 	LDOBJ_NO_MATRIX
 
 public:
-	LDTriangle (Vertex const& v1, Vertex const& v2, Vertex const& v3, LDDocument* document = nullptr);
+	LDTriangle (Vertex const& v1, Vertex const& v2, Vertex const& v3, LDDocument* model = nullptr);
 	int triangleCount() const override;
 };
 
@@ -405,7 +410,7 @@
 	LDOBJ_NO_MATRIX
 
 public:
-	LDQuad (const Vertex& v1, const Vertex& v2, const Vertex& v3, const Vertex& v4, LDDocument* document = nullptr);
+	LDQuad (const Vertex& v1, const Vertex& v2, const Vertex& v3, const Vertex& v4, LDDocument* model = nullptr);
 
 	// Split this quad into two triangles
 	QList<LDTriangle*> splitToTriangles();
@@ -461,9 +466,9 @@
 
 public:
 	LDBezierCurve (const Vertex& v0, const Vertex& v1,
-		const Vertex& v2, const Vertex& v3, LDDocument* document = nullptr);
+	    const Vertex& v2, const Vertex& v3, LDDocument* model = nullptr);
 	Vertex pointAt (qreal t) const;
-	LDObjectList rasterize (int segments);
+	void rasterize(Model& model, int segments);
 	QVector<LDPolygon> rasterizePolygons (int segments);
 };
 
--- a/src/ldobjectiterator.h	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/ldobjectiterator.h	Sun Jan 29 15:05:14 2017 +0200
@@ -24,14 +24,14 @@
 class LDObjectIterator
 {
 public:
-	LDObjectIterator (LDDocument* doc) :
-		m_list (doc->objects()),
+	LDObjectIterator (Model* model) :
+	    m_list (model->objects()),
 		m_i (-1)
 	{
 		seekTillValid();
 	}
 
-	LDObjectIterator (const LDObjectList& objs) :
+	LDObjectIterator (const QVector<LDObject*>& objs) :
 		m_list (objs),
 		m_i (-1)
 	{
@@ -111,7 +111,7 @@
 	}
 
 private:
-	const LDObjectList& m_list;
+	const QVector<LDObject*>& m_list;
 	int m_i;
 };
 
@@ -120,8 +120,12 @@
 {
 	QVector<T*> result;
 
-	for (LDObjectIterator<T> it (stuff); it.isValid(); ++it)
-		result << it;
+	for (LDObject* object : stuff)
+	{
+		T* casted = dynamic_cast<T*>(object);
+		if (casted != nullptr)
+			result << casted;
+	}
 
 	return result;
 }
--- a/src/mainwindow.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/mainwindow.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -358,14 +358,14 @@
 	if (selectedObjects().isEmpty())
 		return 0;
 
-	LDObjectList selCopy = selectedObjects();
+	QSet<LDObject*> selectionCopy = selectedObjects();
 
 	// Delete the objects that were being selected
-	for (LDObject* obj : selCopy)
-		obj->destroy();
+	for (LDObject* object : selectionCopy)
+		m_currentDocument->remove(object);
 
 	refresh();
-	return countof(selCopy);
+	return countof(selectionCopy);
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -508,7 +508,8 @@
 	if (selectedObjects().isEmpty())
 		return;
 
-	LDObject* obj = selectedObjects().first();
+	// TODO: Need a way to properly figure out the first selected object!
+	LDObject* obj = *selectedObjects().begin();
 	ui.objectList->scrollToItem (m_objectsInList[obj]);
 }
 
@@ -519,7 +520,7 @@
 	if (m_isSelectionLocked == true or m_currentDocument == nullptr)
 		return;
 
-	QSet<LDObject*> priorSelection = selectedObjects().toSet();
+	QSet<LDObject*> priorSelection = selectedObjects();
 
 	// Get the objects from the object list selection
 	m_currentDocument->clearSelection();
@@ -531,7 +532,7 @@
 		{
 			if (item == m_objectsInList[obj])
 			{
-				obj->select();
+				m_currentDocument->addToSelection(obj);
 				break;
 			}
 		}
@@ -542,7 +543,7 @@
 	updateSelection();
 
 	// Update the GL renderer
-	for (LDObject* obj : (priorSelection + selectedObjects().toSet()))
+	for (LDObject* obj : priorSelection + selectedObjects())
 		renderer()->compileObject (obj);
 
 	renderer()->update();
@@ -595,8 +596,9 @@
 int MainWindow::suggestInsertPoint()
 {
 	// If we have a selection, put the item after it.
+	// TODO: fix this properly!
 	if (not selectedObjects().isEmpty())
-		return selectedObjects().last()->lineNumber() + 1;
+		return (*(selectedObjects().end() - 1))->lineNumber() + 1;
 
 	// Otherwise place the object at the end.
 	return m_currentDocument->getObjectCount();
@@ -720,7 +722,7 @@
 void MainWindow::spawnContextMenu (const QPoint& position)
 {
 	const bool single = (countof(selectedObjects()) == 1);
-	LDObject* singleObj = single ? selectedObjects().first() : nullptr;
+	LDObject* singleObj = single ? *selectedObjects().begin() : nullptr;
 
 	bool hasSubfiles = false;
 
@@ -785,18 +787,18 @@
 //
 void MainWindow::deleteByColor (LDColor color)
 {
-	LDObjectList objs;
+	LDObjectList unwanted;
 
-	for (LDObject* obj : m_currentDocument->objects())
+	for (LDObject* object : m_currentDocument->objects())
 	{
-		if (not obj->isColored() or obj->color() != color)
+		if (not object->isColored() or object->color() != color)
 			continue;
 
-		objs << obj;
+		unwanted.append(object);
 	}
 
-	for (LDObject* obj : objs)
-		obj->destroy();
+	for (LDObject* obj : unwanted)
+		m_currentDocument->remove(obj);
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -1289,7 +1291,7 @@
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-const LDObjectList& MainWindow::selectedObjects()
+const QSet<LDObject*>& MainWindow::selectedObjects()
 {
 	return m_currentDocument->getSelection();
 }
--- a/src/mainwindow.h	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/mainwindow.h	Sun Jan 29 15:05:14 2017 +0200
@@ -101,7 +101,7 @@
 	bool save (LDDocument* doc, bool saveAs);
 	void saveShortcuts();
 	void scrollToSelection();
-	const LDObjectList& selectedObjects();
+	const QSet<LDObject*>& selectedObjects();
 	void setQuickColors (const QVector<ColorToolbarItem> &colors);
 	void spawnContextMenu (const QPoint& position);
 	int suggestInsertPoint();
--- a/src/mathfunctions.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/mathfunctions.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -33,7 +33,7 @@
 }
 
 
-void MathFunctions::rotateObjects(int l, int m, int n, double angle, const LDObjectList& objects) const
+void MathFunctions::rotateObjects(int l, int m, int n, double angle, const QVector<LDObject*>& objects) const
 {
 	Vertex rotationPoint = getRotationPoint (objects);
 	double cosAngle = cos(angle);
@@ -83,7 +83,7 @@
 }
 
 
-Vertex MathFunctions::getRotationPoint(const LDObjectList& objs) const
+Vertex MathFunctions::getRotationPoint(const QVector<LDObject*>& objs) const
 {
 	switch (RotationPoint (m_config->rotationPointType()))
 	{
--- a/src/mathfunctions.h	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/mathfunctions.h	Sun Jan 29 15:05:14 2017 +0200
@@ -33,8 +33,8 @@
 public:
 	MathFunctions(QObject* parent);
 
-	void rotateObjects(int l, int m, int n, double angle, const LDObjectList& objects) const;
-	Vertex getRotationPoint(const LDObjectList& objs) const;
+	void rotateObjects(int l, int m, int n, double angle, const QVector<LDObject*>& objects) const;
+	Vertex getRotationPoint(const QVector<LDObject*>& objs) const;
 
 private:
 	void rotateVertex(Vertex& vertex, const Vertex& rotationPoint, const Matrix& transformationMatrix) const;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/model.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -0,0 +1,194 @@
+#include "model.h"
+#include "ldObject.h"
+
+Model::Model() {}
+
+Model::~Model()
+{
+	for (int i = 0; i < countof(_objects); ++i)
+		delete _objects[i];
+}
+
+void Model::addObject(LDObject *object)
+{
+	insertObject(size(), object);
+}
+
+int Model::size() const
+{
+	return _objects.size();
+}
+
+const QVector<LDObject*>& Model::objects() const
+{
+	return _objects;
+}
+
+void Model::insertObject(int position, LDObject* object)
+{
+	if (object->model() and object->model() != this)
+		object->model()->withdraw(object);
+
+	if (not object->model())
+	{
+		_objects.insert(position, object);
+		_needsTriangleRecount = true;
+		object->setDocument(this);
+		print("Object %1 added to position %2", object->id(), position);
+	}
+}
+
+bool Model::swapObjects(LDObject* one, LDObject* other)
+{
+	int a = _objects.indexOf(one);
+	int b = _objects.indexOf(other);
+
+	if (a != b and a != -1 and b != -1)
+	{
+		_objects[b] = one;
+		_objects[a] = other;
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+bool Model::setObjectAt(int idx, LDObject* obj)
+{
+	if (idx < 0 or idx >= countof(_objects))
+	{
+		return false;
+	}
+	else
+	{
+		removeAt(idx);
+		insertObject(idx, obj);
+		return true;
+	}
+}
+
+LDObject* Model::getObject(int position) const
+{
+	if (position >= 0 and position < countof(_objects))
+		return _objects[position];
+	else
+		return nullptr;
+}
+
+void Model::remove(LDObject* object)
+{
+	int position = object->lineNumber();
+	printf("Going to remove %d from %p at %d (there are %d objects)\n", object->id(), this, position, countof(objects()));
+	if (_objects[position] == object)
+		removeAt(position);
+}
+
+void Model::removeAt(int position)
+{
+	LDObject* object = withdrawAt(position);
+	delete object;
+}
+
+void Model::replace(LDObject* object, LDObject* newObject)
+{
+	if (object->model() == this)
+		setObjectAt(object->lineNumber(), newObject);
+}
+
+void Model::replace(LDObject *object, Model &model)
+{
+	if (object->model() == this)
+	{
+		int position = object->lineNumber();
+
+		for (int i = countof(model.objects()) - 1; i >= 1; i -= 1)
+			insertObject(position + i, model.objects()[i]);
+
+		setObjectAt(position, model.objects()[0]);
+	}
+}
+
+void Model::recountTriangles()
+{
+	_needsTriangleRecount = true;
+}
+
+int Model::triangleCount() const
+{
+	if (_needsTriangleRecount)
+	{
+		_triangleCount = 0;
+
+		for (LDObject* object : objects())
+			_triangleCount += object->triangleCount();
+
+		_needsTriangleRecount = false;
+	}
+
+	return _triangleCount;
+}
+
+void Model::merge(Model& other, int position)
+{
+	if (position < 0)
+		position = countof(_objects);
+
+	// Copy the vector of objects to copy, so that we don't change the vector while iterating over it.
+	QVector<LDObject*> objectsCopy = other._objects;
+
+	// Inform the contents of their new owner
+	for (LDObject* object : objectsCopy)
+	{
+		insertObject(position, object);
+		position += 1;
+	}
+
+	other.clear();
+}
+
+QVector<LDObject*>::iterator Model::begin()
+{
+	return _objects.begin();
+}
+
+QVector<LDObject*>::iterator Model::end()
+{
+	return _objects.end();
+}
+
+void Model::clear()
+{
+	for (int i = _objects.size() - 1; i >= 0; i -= 1)
+		removeAt(i);
+
+	_needsTriangleRecount = true;
+}
+
+/*
+ * 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.
+ */
+void Model::withdraw(LDObject* object)
+{
+	if (object->model() == this)
+	{
+		int position = object->lineNumber();
+		print("Withdrawing %1 from %2 at %3\n", object->id(), this, position);
+
+		if (_objects[position] == object)
+			withdrawAt(position);
+	}
+}
+
+/*
+ * 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)
+{
+	LDObject* object = _objects[position];
+	_objects.removeAt(position);
+	_needsTriangleRecount = true;
+	return object;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/model.h	Sun Jan 29 15:05:14 2017 +0200
@@ -0,0 +1,81 @@
+#pragma once
+#include "main.h"
+#include "ldObject.h"
+
+class Model
+{
+public:
+	Model();
+	Model(const Model& other) = delete;
+	~Model();
+
+	virtual 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 removeAt(int position);
+	void remove(LDObject* object);
+	void replace(LDObject *object, Model& model);
+	void clear();
+	void merge(Model& other, int position = -1);
+	void replace(LDObject* object, LDObject* newObject);
+	int size() const;
+	const QVector<LDObject*>& objects() const;
+	LDObject* getObject(int position) const;
+	void recountTriangles();
+	int triangleCount() const;
+	QVector<LDObject*>::iterator begin();
+	QVector<LDObject*>::iterator end();
+
+	template<typename T, typename... Args>
+	T* emplace(Args&& ...args)
+	{
+		T* object = constructObject<T>(args...);
+		addObject(object);
+		return object;
+	}
+
+	template<typename T, typename... Args>
+	T* emplaceAt(int position, Args&& ...args)
+	{
+		T* object = constructObject<T>(args...);
+		insertObject(position, object);
+		return object;
+	}
+
+	template<typename T, typename... Args>
+	T* emplaceReplacement(LDObject* object, Args&& ...args)
+	{
+		if (object->model() == this)
+		{
+			int position = object->lineNumber();
+			T* replacement = constructObject<T>(args...);
+			setObjectAt(position, replacement);
+			return replacement;
+		}
+		else
+			return nullptr;
+	}
+
+protected:
+	template<typename T, typename... Args>
+	T* 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};
+
+		// Set default color. Relying on virtual functions, this cannot be done in the c-tor.
+		// TODO: store -1 as the default color
+		if (object->isColored())
+			object->setColor(object->defaultColor());
+
+		return object;
+	}
+
+	void withdraw(LDObject* object);
+	virtual LDObject* withdrawAt(int position);
+
+	QVector<LDObject*> _objects;
+	mutable int _triangleCount = 0;
+	mutable bool _needsTriangleRecount;
+};
--- a/src/toolsets/algorithmtoolset.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/toolsets/algorithmtoolset.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -51,8 +51,9 @@
 void AlgorithmToolset::splitQuads()
 {
 	int num = 0;
+	QVector<LDObject*> selected = selectedObjects().toList().toVector();
 
-	for (LDObjectIterator<LDQuad> it (selectedObjects()); it.isValid(); ++it)
+	for (LDObjectIterator<LDQuad> it (selected); it.isValid(); ++it)
 	{
 		// Find the index of this quad
 		int index = it->lineNumber();
@@ -77,7 +78,7 @@
 	if (countof(selectedObjects()) != 1)
 		return;
 
-	LDObject* obj = selectedObjects()[0];
+	LDObject* obj = *(selectedObjects().begin());
 	QDialog* dlg = new QDialog;
 	Ui::EditRawUI ui;
 
@@ -102,10 +103,9 @@
 
 void AlgorithmToolset::makeBorders()
 {
-	LDObjectList objs = selectedObjects();
 	int num = 0;
 
-	for (LDObject* obj : objs)
+	for (LDObject* obj : selectedObjects())
 	{
 		const LDObjectType type = obj->type();
 
@@ -277,9 +277,9 @@
 {
 	int num = 0;
 
-	for (LDObjectIterator<LDCondLine> it (selectedObjects()); it.isValid(); ++it)
+	for (LDCondLine* line : filterByType<LDCondLine>(selectedObjects()))
 	{
-		it->toEdgeLine();
+		line->becomeEdgeLine();
 		++num;
 	}
 
@@ -422,7 +422,7 @@
 		for (LDObject* seg : newsegs)
 			currentDocument()->insertObject (ln++, seg);
 
-		obj->destroy();
+		currentDocument()->remove(obj);
 	}
 
 	m_window->buildObjectList();
@@ -458,7 +458,8 @@
 	QString			fullsubname;
 
 	// Where to insert the subfile reference?
-	int				refidx (selectedObjects()[0]->lineNumber());
+	// TODO: the selection really should be sorted by position...
+	int				refidx = (*selectedObjects().begin())->lineNumber();
 
 	// Determine title of subfile
 	if (titleobj)
@@ -568,8 +569,8 @@
 	{
 		// Save was successful. Delete the original selection now from the
 		// main document.
-		for (LDObject* obj : selectedObjects())
-			obj->destroy();
+		for (LDObject* object : selectedObjects())
+			currentDocument()->remove(object);
 
 		// Add a reference to the new subfile to where the selection was
 		LDSubfileReference* ref = LDSpawn<LDSubfileReference>();
--- a/src/toolsets/basictoolset.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/toolsets/basictoolset.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -37,12 +37,12 @@
 
 int BasicToolset::copyToClipboard()
 {
-	LDObjectList objs = selectedObjects();
+	const QSet<LDObject*>& objects = selectedObjects();
 	int count = 0;
 	qApp->clipboard()->clear();
 	QString data;
 
-	for (LDObject* obj : objs)
+	for (LDObject* obj : objects)
 	{
 		if (not data.isEmpty())
 			data += "\n";
@@ -79,7 +79,7 @@
 	{
 		LDObject* pasted = ParseLine (line);
 		currentDocument()->insertObject (idx++, pasted);
-		pasted->select();
+		currentDocument()->addToSelection(pasted);
 		++num;
 	}
 
@@ -96,33 +96,36 @@
 
 void BasicToolset::doInline (bool deep)
 {
-	for (LDSubfileReference* ref : filterByType<LDSubfileReference> (selectedObjects()))
+	for (LDSubfileReference* reference : filterByType<LDSubfileReference> (selectedObjects()))
 	{
 		// Get the index of the subfile so we know where to insert the
 		// inlined contents.
-		int idx = ref->lineNumber();
+		int idx = reference->lineNumber();
 
 		if (idx != -1)
 		{
-			LDObjectList objs = ref->inlineContents (deep, false);
-	
+			Model inlined;
+			reference->inlineContents(inlined, deep, false);
+
 			// Merge in the inlined objects
-			for (LDObject* inlineobj : objs)
+			print("Inlined %1 objects.\n", countof(inlined.objects()));
+			for (LDObject* inlinedObject : inlined.objects())
 			{
-				QString line = inlineobj->asText();
-				inlineobj->destroy();
-				LDObject* newobj = ParseLine (line);
-				currentDocument()->insertObject (idx++, newobj);
-				newobj->select();
+				currentDocument()->insertObject (idx++, inlinedObject);
+				currentDocument()->addToSelection(inlinedObject);
 			}
-	
+
 			// Delete the subfile now as it's been inlined.
-			ref->destroy();
+			currentDocument()->remove(reference);
 		}
 	}
 
 	for (LDBezierCurve* curve : filterByType<LDBezierCurve> (selectedObjects()))
-		curve->replace (curve->rasterize(grid()->bezierCurveSegments()));
+	{
+		Model curveModel;
+		curve->rasterize(curveModel, grid()->bezierCurveSegments());
+		currentDocument()->replace(curve, curveModel);
+	}
 }
 
 void BasicToolset::inlineShallow()
@@ -187,7 +190,7 @@
 		LDObject* obj = ParseLine (line);
 
 		currentDocument()->insertObject (idx, obj);
-		obj->select();
+		currentDocument()->addToSelection(obj);
 		idx++;
 	}
 
@@ -200,7 +203,7 @@
 	if (selectedObjects().isEmpty())
 		return;
 
-	LDObjectList objs = selectedObjects();
+	QSet<LDObject*> objs = selectedObjects();
 
 	// If all selected objects have the same color, said color is our default
 	// value to the color selection dialog.
@@ -263,7 +266,7 @@
 {
 	if (countof(selectedObjects()) == 1)
 	{
-		LDObject* obj = selectedObjects().first();
+		LDObject* obj = *selectedObjects().begin();
 		AddObjectDialog::staticDialog (obj->type(), obj);
 	}
 }
--- a/src/toolsets/extprogramtoolset.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/toolsets/extprogramtoolset.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -170,20 +170,16 @@
 		if (obj->type() == OBJ_SubfileReference)
 		{
 			LDSubfileReference* ref = static_cast<LDSubfileReference*> (obj);
-			LDObjectList objs = ref->inlineContents (true, false);
-			writeObjects (objs, f);
-
-			for (LDObject* obj : objs)
-				obj->destroy();
+			Model model;
+			ref->inlineContents(model, true, false);
+			writeObjects(model.objects().toList(), f);
 		}
 		else if (obj->type() == OBJ_BezierCurve)
 		{
 			LDBezierCurve* curve = static_cast<LDBezierCurve*> (obj);
-			LDObjectList objs = curve->rasterize(grid()->bezierCurveSegments());
-			writeObjects (objs, f);
-
-			for (LDObject* obj : objs)
-				obj->destroy();
+			Model model;
+			curve->rasterize(model, grid()->bezierCurveSegments());
+			writeObjects(model.objects().toList(), f);
 		}
 		else
 			f.write ((obj->asText() + "\r\n").toUtf8());
@@ -215,7 +211,7 @@
 //
 void ExtProgramToolset::writeSelection (QString fname)
 {
-	writeObjects (selectedObjects(), fname);
+	writeObjects (selectedObjects().toList(), fname);
 }
 
 // =============================================================================
@@ -327,7 +323,8 @@
 
 	// TODO: I don't like how I need to go to the document manager to load objects from a file...
 	// We're not loading this as a document so it shouldn't be necessary.
-	LDObjectList objs = m_documents->loadFileContents (&f, nullptr, nullptr);
+	Model model;
+	m_documents->loadFileContents(&f, model, nullptr, nullptr);
 
 	// If we replace the objects, delete the selection now.
 	if (replace)
@@ -339,16 +336,10 @@
 	// Insert the new objects
 	currentDocument()->clearSelection();
 
-	for (LDObject* obj : objs)
+	for (LDObject* object : model.objects())
 	{
-		if (not obj->isScemantic())
-		{
-			obj->destroy();
-			continue;
-		}
-
-		currentDocument()->addObject (obj);
-		obj->select();
+		if (object->isScemantic())
+			currentDocument()->addObject(object);
 	}
 
 	m_window->doFullRefresh();
--- a/src/toolsets/filetoolset.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/toolsets/filetoolset.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -118,14 +118,15 @@
 		if (file.open(QIODevice::ReadOnly))
 		{
 			// TODO: shouldn't need to go to the document manager to parse a file
-			LDObjectList objects = m_documents->loadFileContents(&file, nullptr, nullptr);
+			Model model;
+			m_documents->loadFileContents(&file, model, nullptr, nullptr);
 
 			currentDocument()->clearSelection();
 
-			for (LDObject* object : objects)
+			for (LDObject* object : model.objects())
 			{
 				currentDocument()->insertObject (position, object);
-				object->select();
+				currentDocument()->addToSelection(object);
 				m_window->renderer()->compileObject (object);
 				position++;
 			}
--- a/src/toolsets/movetoolset.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/toolsets/movetoolset.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -30,7 +30,9 @@
 
 void MoveToolset::moveSelection (bool up)
 {
-	LDObjectList objs = selectedObjects();
+	// TODO: order these!
+	LDObjectList objs = selectedObjects().toList();
+
 	if (objs.isEmpty())
 		return;
 
@@ -39,7 +41,7 @@
 	int end = up ? countof(objs) : -1;
 	int increment = up ? 1 : -1;
 	QSet<LDObject*> objsToCompile;
-	LDDocument* file = objs[0]->document();
+	Model* model = (*objs.begin())->model();
 
 	for (int i = start; i != end; i += increment)
 	{
@@ -48,7 +50,7 @@
 		int idx = obj->lineNumber();
 		int target = idx + (up ? -1 : 1);
 
-		if ((up and idx == 0) or (not up and idx == countof(file->objects()) - 1))
+		if ((up and idx == 0) or (not up and idx == countof(model->objects()) - 1))
 		{
 			// One of the objects hit the extrema. If this happens, this should be the first
 			// object to be iterated on. Thus, nothing has changed yet and it's safe to just
@@ -57,8 +59,8 @@
 		}
 
 		objsToCompile << obj;
-		objsToCompile << file->getObject(target);
-		obj->swap(file->getObject(target));
+		objsToCompile << model->getObject(target);
+		obj->swap(model->getObject(target));
 	}
 
 	// The objects need to be recompiled, otherwise their pick lists are left with
@@ -143,32 +145,32 @@
 
 void MoveToolset::rotateXPos()
 {
-	math()->rotateObjects (1, 0, 0, getRotateActionAngle(), selectedObjects());
+	math()->rotateObjects (1, 0, 0, getRotateActionAngle(), selectedObjects().toList().toVector());
 }
 
 void MoveToolset::rotateYPos()
 {
-	math()->rotateObjects (0, 1, 0, getRotateActionAngle(), selectedObjects());
+	math()->rotateObjects (0, 1, 0, getRotateActionAngle(), selectedObjects().toList().toVector());
 }
 
 void MoveToolset::rotateZPos()
 {
-	math()->rotateObjects (0, 0, 1, getRotateActionAngle(), selectedObjects());
+	math()->rotateObjects (0, 0, 1, getRotateActionAngle(), selectedObjects().toList().toVector());
 }
 
 void MoveToolset::rotateXNeg()
 {
-	math()->rotateObjects (-1, 0, 0, getRotateActionAngle(), selectedObjects());
+	math()->rotateObjects (-1, 0, 0, getRotateActionAngle(), selectedObjects().toList().toVector());
 }
 
 void MoveToolset::rotateYNeg()
 {
-	math()->rotateObjects (0, -1, 0, getRotateActionAngle(), selectedObjects());
+	math()->rotateObjects (0, -1, 0, getRotateActionAngle(), selectedObjects().toList().toVector());
 }
 
 void MoveToolset::rotateZNeg()
 {
-	math()->rotateObjects (0, 0, -1, getRotateActionAngle(), selectedObjects());
+	math()->rotateObjects (0, 0, -1, getRotateActionAngle(), selectedObjects().toList().toVector());
 }
 
 void MoveToolset::configureRotationPoint()
--- a/src/toolsets/viewtoolset.cpp	Sat Jan 28 17:47:06 2017 +0200
+++ b/src/toolsets/viewtoolset.cpp	Sun Jan 29 15:05:14 2017 +0200
@@ -35,7 +35,7 @@
 void ViewToolset::selectAll()
 {
 	for (LDObject* obj : currentDocument()->objects())
-		obj->select();
+		currentDocument()->addToSelection(obj);
 }
 
 void ViewToolset::selectByColor()
@@ -56,7 +56,7 @@
 	for (LDObject* obj : currentDocument()->objects())
 	{
 		if (colors.contains (obj->color()))
-			obj->select();
+			currentDocument()->addToSelection(obj);
 	}
 }
 
@@ -92,7 +92,7 @@
 			continue;
 		}
 
-		obj->select();
+		currentDocument()->addToSelection(obj);
 	}
 }
 
@@ -254,7 +254,7 @@
 void ViewToolset::jumpTo()
 {
 	bool ok;
-	int defaultValue = (countof(selectedObjects()) == 1) ? selectedObjects()[0]->lineNumber() : 0;
+	int defaultValue = (countof(selectedObjects()) == 1) ? (*selectedObjects().begin())->lineNumber() : 0;
 	int index = QInputDialog::getInt (nullptr, "Go to line", "Go to line:", defaultValue, 1, currentDocument()->getObjectCount(), 1, &ok);
 
 	if (ok)
@@ -264,7 +264,7 @@
 		if (object)
 		{
 			currentDocument()->clearSelection();
-			object->select();
+			currentDocument()->addToSelection(object);
 			m_window->updateSelection();
 		}
 	}

mercurial