Moved LDObject lifetime management from Model to ResourceVector. This is a large refactor that removes some hacks from the Model class.

Sun, 05 Mar 2017 16:47:52 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Sun, 05 Mar 2017 16:47:52 +0200
changeset 1186
eae8b3bce545
parent 1185
c2e0db52ea07
child 1187
46dc716238fd

Moved LDObject lifetime management from Model to ResourceVector. This is a large refactor that removes some hacks from the Model class.

src/documentloader.cpp file | annotate | diff | comparison | revisions
src/documentloader.h file | annotate | diff | comparison | revisions
src/glcompiler.cpp file | annotate | diff | comparison | revisions
src/glrenderer.cpp file | annotate | diff | comparison | revisions
src/lddocument.cpp file | annotate | diff | comparison | revisions
src/lddocument.h file | annotate | diff | comparison | revisions
src/ldobjectiterator.h 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.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.h file | annotate | diff | comparison | revisions
src/linetypes/quadrilateral.h 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/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/resourcevector.h file | annotate | diff | comparison | revisions
--- a/src/documentloader.cpp	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/documentloader.cpp	Sun Mar 05 16:47:52 2017 +0200
@@ -55,7 +55,7 @@
 	return m_isOnForeground;
 }
 
-const QVector<LDObject*>& DocumentLoader::objects() const
+const ResourceVector<LDObject>& DocumentLoader::objects() const
 {
 	return _model->objects();
 }
--- a/src/documentloader.h	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/documentloader.h	Sun Mar 05 16:47:52 2017 +0200
@@ -37,7 +37,7 @@
 	bool hasAborted();
 	bool isDone() const;
 	bool isOnForeground() const;
-	const QVector<LDObject*>& objects() const;
+	const ResourceVector<LDObject>& objects() const;
 	int progress() const;
 	void read (QIODevice* fp);
 	Q_SLOT void start();
--- a/src/glcompiler.cpp	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/glcompiler.cpp	Sun Mar 05 16:47:52 2017 +0200
@@ -71,9 +71,9 @@
 	HierarchyElement (renderer),
 	m_renderer (renderer)
 {
-	connect(renderer->model(), SIGNAL(objectAdded(LDObject*)), this, SLOT(compileObject(LDObject*)));
+	connect(renderer->model(), SIGNAL(objectAdded(LDObject*, int)), this, SLOT(compileObject(LDObject*)));
 	connect(renderer->model(), SIGNAL(objectModified(LDObject*)), this, SLOT(compileObject(LDObject*)));
-	connect(renderer->model(), SIGNAL(aboutToRemoveObject(LDObject*)), this, SLOT(forgetObject(LDObject*)), Qt::DirectConnection);
+	connect(renderer->model(), SIGNAL(aboutToRemoveObject(LDObject*, int)), this, SLOT(forgetObject(LDObject*)), Qt::DirectConnection);
 	connect(renderer, SIGNAL(objectHighlightingChanged(LDObject*)), this, SLOT(compileObject(LDObject*)));
 	connect(m_window, SIGNAL(gridChanged()), this, SLOT(recompile()));
 
--- a/src/glrenderer.cpp	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/glrenderer.cpp	Sun Mar 05 16:47:52 2017 +0200
@@ -71,7 +71,7 @@
 	m_toolTipTimer->setSingleShot (true);
 	setAcceptDrops (true);
 	connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (showCameraIconTooltip()));
-	connect(model, SIGNAL(aboutToRemoveObject(LDObject*)), this, SLOT(removeObject(LDObject*)));
+	connect(model, SIGNAL(aboutToRemoveObject(LDObject*, int)), this, SLOT(removeObject(LDObject*)));
 	resetAllAngles();
 	m_needZoomToFit = true;
 
--- a/src/lddocument.cpp	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/lddocument.cpp	Sun Mar 05 16:47:52 2017 +0200
@@ -33,7 +33,12 @@
     m_history (new EditHistory (this)),
 	m_savePosition(-1),
     m_tabIndex(-1),
-	m_manager (parent) {}
+    m_manager (parent)
+{
+	connect(this, SIGNAL(objectAdded(LDObject*, int)), this, SLOT(handleNewObject(LDObject*, int)));
+	connect(this, SIGNAL(objectsSwapped(LDObject*, LDObject*)), this, SLOT(handleObjectSwap(LDObject*, LDObject*)));
+	connect(this, SIGNAL(aboutToRemoveObject(LDObject*, int)), this, SLOT(handleObjectRemoval(LDObject*, int)));
+}
 
 LDDocument::~LDDocument()
 {
@@ -293,15 +298,14 @@
 
 // =============================================================================
 //
-void LDDocument::insertObject (int pos, LDObject* obj)
+void LDDocument::handleNewObject(LDObject* object, int position)
 {
-	Model::insertObject(pos, obj);
-	history()->add(new AddHistoryEntry {pos, obj});
-	connect(obj, SIGNAL(codeChanged(QString,QString)), this, SLOT(objectChanged(QString,QString)));
+	history()->add(new AddHistoryEntry {position, object});
+	connect(object, SIGNAL(codeChanged(QString,QString)), this, SLOT(objectChanged(QString,QString)));
 
 #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(), position);
 #endif
 }
 
@@ -313,10 +317,8 @@
 	emit objectModified(object);
 }
 
-LDObject* LDDocument::withdrawAt(int position)
+void LDDocument::handleObjectRemoval(LDObject* object, int position)
 {
-	LDObject* object = getObject(position);
-
 	if (not isFrozen() and not m_isBeingDestroyed)
 	{
 		history()->add(new DelHistoryEntry {position, object});
@@ -324,7 +326,6 @@
 	}
 
 	m_selection.remove(object);
-	return Model::withdrawAt(position);
 }
 
 // =============================================================================
@@ -487,17 +488,9 @@
 
 // =============================================================================
 //
-bool LDDocument::swapObjects (LDObject* one, LDObject* other)
+void LDDocument::handleObjectSwap(LDObject* one, LDObject* other)
 {
-	if (Model::swapObjects(one, other))
-	{
-		addToHistory(new SwapHistoryEntry {one->id(), other->id()});
-		return true;
-	}
-	else
-	{
-		return false;
-	}
+	addToHistory(new SwapHistoryEntry {one->id(), other->id()});
 }
 
 // =============================================================================
--- a/src/lddocument.h	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/lddocument.h	Sun Mar 05 16:47:52 2017 +0200
@@ -62,7 +62,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;
@@ -81,7 +80,6 @@
 	void setName (QString value);
 	void setSavePosition (long value);
 	void setTabIndex (int value);
-	bool swapObjects (LDObject* one, LDObject* other);
 	int tabIndex() const;
 	void undo();
 	void vertexChanged (const Vertex& a, const Vertex& b);
@@ -91,9 +89,6 @@
 public slots:
 	void objectChanged(QString before, QString after);
 
-protected:
-	LDObject* withdrawAt(int position);
-
 private:
 	QString m_name;
 	QString m_fullPath;
@@ -111,6 +106,10 @@
 	QSet<Vertex> m_vertices;
 	QSet<LDObject*> m_selection;
 	DocumentManager* m_manager;
+
+	Q_SLOT void handleNewObject(LDObject* object, int position);
+	Q_SLOT void handleObjectSwap(LDObject* one, LDObject* other);
+	Q_SLOT void handleObjectRemoval(LDObject* object, int position);
 };
 
 // Parses a string line containing an LDraw object and returns the object parsed.
--- a/src/ldobjectiterator.h	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/ldobjectiterator.h	Sun Mar 05 16:47:52 2017 +0200
@@ -31,7 +31,7 @@
 		seekTillValid();
 	}
 
-	LDObjectIterator (const QVector<LDObject*>& objs) :
+	LDObjectIterator (const ResourceVector<LDObject>& objs) :
 		m_list (objs),
 		m_i (-1)
 	{
@@ -111,7 +111,7 @@
 	}
 
 private:
-	const QVector<LDObject*>& m_list;
+	const ResourceVector<LDObject>& m_list;
 	int m_i;
 };
 
--- a/src/linetypes/comment.h	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/linetypes/comment.h	Sun Mar 05 16:47:52 2017 +0200
@@ -37,9 +37,9 @@
 	void setText(QString value);
 
 protected:
+	friend class ResourceVector<LDObject>;
 	LDComment(QString text, Model* model);
 	LDComment(Model* model);
-	friend class Model;
 
 private:
 	QString m_text;
--- a/src/linetypes/conditionaledge.cpp	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/linetypes/conditionaledge.cpp	Sun Mar 05 16:47:52 2017 +0200
@@ -54,9 +54,13 @@
 {
 	LDEdgeLine* replacement = model()->emplaceReplacement<LDEdgeLine>(this);
 
-	for (int i = 0; i < replacement->numVertices(); ++i)
-		replacement->setVertex (i, vertex (i));
+	if (replacement)
+	{
+		for (int i = 0; i < replacement->numVertices(); ++i)
+			replacement->setVertex (i, vertex (i));
 
-	replacement->setColor (color());
+		replacement->setColor (color());
+	}
+
 	return replacement;
 }
--- a/src/linetypes/conditionaledge.h	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/linetypes/conditionaledge.h	Sun Mar 05 16:47:52 2017 +0200
@@ -40,7 +40,7 @@
 	QString typeName() const override { return "condline"; }
 
 protected:
-	friend class Model;
+	friend class ResourceVector<LDObject>;
 	LDConditionalEdge (Model* model);
 	LDConditionalEdge (const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3, Model* model = nullptr);
 };
--- a/src/linetypes/edgeline.h	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/linetypes/edgeline.h	Sun Mar 05 16:47:52 2017 +0200
@@ -39,7 +39,7 @@
 	QString typeName() const override { return "line"; }
 
 protected:
-	friend class Model;
+	friend class ResourceVector<LDObject>;
 	LDEdgeLine (Model* model);
 	LDEdgeLine (Vertex v1, Vertex v2, Model* model = nullptr);
 };
--- a/src/linetypes/empty.cpp	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/linetypes/empty.cpp	Sun Mar 05 16:47:52 2017 +0200
@@ -41,3 +41,8 @@
 {
 	return "";
 }
+
+bool LDEmpty::isScemantic() const
+{
+	return false;
+}
--- a/src/linetypes/empty.h	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/linetypes/empty.h	Sun Mar 05 16:47:52 2017 +0200
@@ -28,11 +28,12 @@
 	static const LDObjectType SubclassType = LDObjectType::Empty;
 
 	QString asText() const override;
+	bool isScemantic() const override;
 	QString objectListText() const override;
 	LDObjectType type() const override;
 	QString typeName() const override;
 
 protected:
-	friend class Model;
+	friend class ResourceVector<LDObject>;
 	LDEmpty(Model* model);
 };
--- a/src/linetypes/modelobject.h	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/linetypes/modelobject.h	Sun Mar 05 16:47:52 2017 +0200
@@ -22,6 +22,7 @@
 #include "../basics.h"
 #include "../glShared.h"
 #include "../colors.h"
+#include "../types/resourcevector.h"
 
 class Model;
 class LDDocument;
@@ -95,7 +96,9 @@
 	void codeChanged(QString before, QString after);
 
 protected:
+	friend class ResourceVector<LDObject>;
 	friend class Model;
+
 	LDObject (Model* model = nullptr);
 	virtual ~LDObject();
 	void setDocument(Model* model);
@@ -159,7 +162,7 @@
 	QString typeName() const override { return "error"; }
 
 protected:
-	friend class Model;
+	friend class ResourceVector<LDObject>;
 	LDError (Model* model);
 	LDError (QString contents, QString reason, Model* model = nullptr);
 
@@ -202,7 +205,7 @@
 	virtual QString asText() const override;
 	virtual void invert() override;
 protected:
-	friend class Model;
+    friend class ResourceVector<LDObject>;
 	LDBfc (Model* model);
 
 public:
@@ -249,7 +252,7 @@
 	QString typeName() const override { return "subfilereference"; }
 
 protected:
-	friend class Model;
+	friend class ResourceVector<LDObject>;
 	LDSubfileReference (Model* model);
 	LDSubfileReference(LDDocument* reference, const Matrix& transformationMatrix, const Vertex& position, Model* model = nullptr);
 
@@ -280,7 +283,7 @@
 	QString typeName() const override { return "beziercurve"; }
 
 protected:
-	friend class Model;
+	friend class ResourceVector<LDObject>;
 	LDBezierCurve (Model* model);
 	LDBezierCurve (const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3, Model* model = nullptr);
 };
--- a/src/linetypes/quadrilateral.h	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/linetypes/quadrilateral.h	Sun Mar 05 16:47:52 2017 +0200
@@ -35,7 +35,7 @@
 	QString typeName() const override;
 
 protected:
-	friend class Model;
+	friend class ResourceVector<LDObject>;
 	LDQuadrilateral(Model* model);
 	LDQuadrilateral(const Vertex& v1, const Vertex& v2, const Vertex& v3, const Vertex& v4, Model* model = nullptr);
 };
--- a/src/linetypes/triangle.h	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/linetypes/triangle.h	Sun Mar 05 16:47:52 2017 +0200
@@ -39,7 +39,7 @@
 	QString typeName() const override { return "triangle"; }
 
 protected:
-	friend class Model;
+	friend class ResourceVector<LDObject>;
 	LDTriangle (Model* model);
 	LDTriangle (Vertex const& v1, Vertex const& v2, Vertex const& v3, Model* model = nullptr);
-};
\ No newline at end of file
+};
--- a/src/model.cpp	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/model.cpp	Sun Mar 05 16:47:52 2017 +0200
@@ -16,6 +16,7 @@
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <cassert>
 #include "model.h"
 #include "linetypes/modelobject.h"
 #include "documentmanager.h"
@@ -30,20 +31,6 @@
     QObject {manager},
     _manager {manager} {}
 
-Model::~Model()
-{
-	for (int i = 0; i < countof(_objects); ++i)
-		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)
-{
-	insertObject(size(), object);
-}
-
 /*
  * Returns the amount of objects in this model.
  */
@@ -55,28 +42,39 @@
 /*
  * Returns the vector of objects in this model.
  */
-const QVector<LDObject*>& Model::objects() const
+const ResourceVector<LDObject>& Model::objects() const
+{
+	return _objects;
+}
+
+/*
+ * Returns the vector of objects in this model (private mutable access)
+ */
+ResourceVector<LDObject>& Model::mutableObjects()
 {
 	return _objects;
 }
 
 /*
- * Takes an existing object and migrates it to this model, at the specified position.
+ * Takes an existing object and adds it to this model, at the specified position.
+ * The object is assumed to be ownerless.
  */
-void Model::insertObject(int position, LDObject* object)
+void Model::finalizeNewObject(int position, LDObject* object)
 {
-	if (object->model() and object->model() != this)
-		object->model()->withdraw(object);
-
-	// TODO: check that the object isn't in the vector once there's a cheap way to do so!
-	_objects.insert(position, object);
+	assert(object->model() == this);
 	_needsTriangleRecount = true;
 	object->setDocument(this);
-	emit objectAdded(object);
+
+	// 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());
+
+	emit objectAdded(object, position);
 }
 
 /*
- * Swaps one object with another, assuming they both are in this model.
+ * Swaps one object with another, if they both are in this model.
  */
 bool Model::swapObjects(LDObject* one, LDObject* other)
 {
@@ -85,8 +83,8 @@
 
 	if (a != b and a != -1 and b != -1)
 	{
-		_objects[b] = one;
-		_objects[a] = other;
+		_objects.swap(a, b);
+		emit objectsSwapped(one, other);
 		return true;
 	}
 	else
@@ -96,23 +94,6 @@
 }
 
 /*
- * 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)
-{
-	if (idx < 0 or idx >= countof(_objects))
-	{
-		return false;
-	}
-	else
-	{
-		removeAt(idx);
-		insertObject(idx, obj);
-		return true;
-	}
-}
-
-/*
  * Returns the object at the specified position, or null if not found.
  */
 LDObject* Model::getObject(int position) const
@@ -138,8 +119,8 @@
  */
 void Model::removeAt(int position)
 {
-	LDObject* object = withdrawAt(position);
-	delete object;
+	emit aboutToRemoveObject(_objects[position], position);
+	_objects.removeAt(position);
 }
 
 /*
@@ -150,11 +131,8 @@
 	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]);
+		merge(model, position);
+		remove(object);
 	}
 }
 
@@ -186,29 +164,42 @@
 
 /*
  * Merges the given model into this model, starting at the given position. The other model is emptied in the process.
+ * If select is true, the new objects are added to the selection.
  */
-void Model::merge(Model& other, int position)
+void Model::merge(Model& other, int position, Filter filter, Callback callback)
 {
 	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;
+	if (filter == nullptr)
+		filter = [](LDObject*){return true;};
 
-	// Inform the contents of their new owner
-	for (LDObject* object : objectsCopy)
+	_objects.merge(other.mutableObjects(), position, [&](LDObject* object, int objectPosition)
 	{
-		insertObject(position, object);
-		position += 1;
-	}
+		if (filter(object))
+		{
+			emit other.aboutToRemoveObject(object, objectPosition);
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}, [&](LDObject* object, int objectPosition)
+	{
+		_needsTriangleRecount = true;
+		object->setDocument(this);
+		emit objectAdded(object, objectPosition);
 
-	other.clear();
+		if (callback)
+			callback(object, position);
+	});
 }
 
 /*
  * Returns the begin-iterator into this model, so that models can be used in foreach-loops.
  */
-QVector<LDObject*>::iterator Model::begin()
+LDObject* const* Model::begin()
 {
 	return _objects.begin();
 }
@@ -216,7 +207,7 @@
 /*
  * Returns the end-iterator into this mode, so that models can be used in foreach-loops.
  */
-QVector<LDObject*>::iterator Model::end()
+LDObject* const* Model::end()
 {
 	return _objects.end();
 }
@@ -234,35 +225,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)
-{
-	if (object->model() == this)
-	{
-		int position = object->lineNumber();
-
-		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];
-	emit aboutToRemoveObject(object);
-	_objects.removeAt(position);
-	_needsTriangleRecount = true;
-	return object;
-}
-
-/*
  * Returns whether or not this model is empty.
  */
 bool Model::isEmpty() const
--- a/src/model.h	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/model.h	Sun Mar 05 16:47:52 2017 +0200
@@ -19,6 +19,7 @@
 #pragma once
 #include "main.h"
 #include "linetypes/modelobject.h"
+#include "types/resourcevector.h"
 
 /*
  * This class represents a LDraw model, consisting of a vector of objects. It manages LDObject ownership.
@@ -28,30 +29,28 @@
 	Q_OBJECT
 
 public:
+	using Filter = std::function<bool(LDObject*)>;
+	using Callback = std::function<void(LDObject*, int)>;
+
 	Model(class DocumentManager* manager);
 	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);
+	bool swapObjects(LDObject* one, LDObject* other);
 	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 remove(LDObject* object);
 	void replace(LDObject *object, Model& model);
 	void clear();
-	void merge(Model& other, int position = -1);
+	void merge(Model& other, int position = -1, Filter filter = nullptr, Callback callback = nullptr);
 	int size() const;
-	const QVector<LDObject*>& objects() const;
+	const ResourceVector<LDObject>& objects() const;
 	LDObject* getObject(int position) const;
 	void recountTriangles();
 	int triangleCount() const;
-	QVector<LDObject*>::iterator begin();
-	QVector<LDObject*>::iterator end();
+	LDObject* const* begin();
+	LDObject* const* end();
 	bool isEmpty() const;
 	class DocumentManager* documentManager() const;
 	LDObject* insertFromString(int position, QString line);
@@ -59,19 +58,22 @@
 	LDObject* replaceWithFromString(LDObject* object, QString line);
 
 signals:
-	void objectAdded(LDObject* object);
-	void aboutToRemoveObject(LDObject* object);
+	void objectAdded(LDObject* object, int position);
+	void aboutToRemoveObject(LDObject* object, int position);
 	void objectModified(LDObject* object);
+	void objectsSwapped(LDObject* one, LDObject* other);
 
 protected:
 	template<typename T, typename... Args> T* constructObject(Args&& ...args);
-	void withdraw(LDObject* object);
-	virtual LDObject* withdrawAt(int position);
 
-	QVector<LDObject*> _objects;
+	ResourceVector<LDObject> _objects;
 	class DocumentManager* _manager;
 	mutable int _triangleCount = 0;
 	mutable bool _needsTriangleRecount;
+
+private:
+	void finalizeNewObject(int position, LDObject* object);
+	ResourceVector<LDObject>& mutableObjects();
 };
 
 int countof(Model& model);
@@ -92,8 +94,8 @@
 template<typename T, typename... Args>
 T* Model::emplace(Args&& ...args)
 {
-	T* object = constructObject<T>(args...);
-	addObject(object);
+	T* object = _objects.append<T>(args..., this);
+	finalizeNewObject(size() - 1, object);
 	return object;
 }
 
@@ -104,8 +106,8 @@
 template<typename T, typename... Args>
 T* Model::emplaceAt(int position, Args&& ...args)
 {
-	T* object = constructObject<T>(args...);
-	insertObject(position, object);
+	T* object = _objects.insert<T>(position, args..., this);
+	finalizeNewObject(position, object);
 	return object;
 }
 
@@ -119,39 +121,13 @@
 	if (object->model() == this)
 	{
 		int position = object->lineNumber();
-		T* replacement = constructObject<T>(args...);
-		setObjectAt(position, replacement);
+		removeAt(position);
+		T* replacement = _objects.insert<T>(position, args..., this);
+		finalizeNewObject(position, 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};
-
-	// 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;
-}
--- a/src/toolsets/algorithmtoolset.cpp	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/toolsets/algorithmtoolset.cpp	Sun Mar 05 16:47:52 2017 +0200
@@ -59,7 +59,6 @@
 		Vertex v1 = object->vertex(1);
 		Vertex v2 = object->vertex(2);
 		Vertex v3 = object->vertex(3);
-		LDColor color = object->color();
 
 		// Find the index of this quad
 		int index = object->lineNumber();
@@ -72,12 +71,14 @@
 		// │   │  --→  │ ╱    ╱ │
 		// │   │  --→  │╱    ╱  │
 		// 1───2       1    1───2
-		LDTriangle* triangle1 = currentDocument()->emplaceReplacementAt<LDTriangle>(index, v0, v1, v3);
-		LDTriangle* triangle2 = currentDocument()->emplaceAt<LDTriangle>(index + 1, v1, v2, v3);
+		Model replacement {m_documents};
+		LDTriangle* triangle1 = replacement.emplace<LDTriangle>(v0, v1, v3);
+		LDTriangle* triangle2 = replacement.emplace<LDTriangle>(v1, v2, v3);
 
 		// The triangles also inherit the quad's color
-		triangle1->setColor(color);
-		triangle2->setColor(color);
+		triangle1->setColor(object->color());
+		triangle2->setColor(object->color());
+		currentDocument()->replace(object, replacement);
 		count += 1;
 	}
 
@@ -278,15 +279,15 @@
 
 void AlgorithmToolset::demote()
 {
-	int num = 0;
+	int count = 0;
 
 	for (LDConditionalEdge* line : filterByType<LDConditionalEdge>(selectedObjects()))
 	{
 		line->becomeEdgeLine();
-		++num;
+		count += 1;
 	}
 
-	print (tr ("Converted %1 conditional lines"), num);
+	print (tr ("Converted %1 conditional lines"), count);
 }
 
 bool AlgorithmToolset::isColorUsed (LDColor color)
--- a/src/toolsets/basictoolset.cpp	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/toolsets/basictoolset.cpp	Sun Mar 05 16:47:52 2017 +0200
@@ -98,19 +98,16 @@
 	{
 		// Get the index of the subfile so we know where to insert the
 		// inlined contents.
-		int idx = reference->lineNumber();
+		int position = reference->lineNumber();
 
-		if (idx != -1)
+		if (position != -1)
 		{
 			Model inlined {m_documents};
 			reference->inlineContents(inlined, deep, false);
-
-			// Merge in the inlined objects
-			for (LDObject* inlinedObject : inlined.objects())
+			currentDocument()->merge(inlined, position, nullptr, [&](LDObject* object, int)
 			{
-				currentDocument()->insertObject (idx++, inlinedObject);
-				currentDocument()->addToSelection(inlinedObject);
-			}
+				currentDocument()->addToSelection(object);
+			});
 
 			// Delete the subfile now as it's been inlined.
 			currentDocument()->remove(reference);
@@ -299,4 +296,4 @@
 void BasicToolset::modeLinePath()
 {
 	m_window->renderer()->setEditMode (EditModeType::LinePath);
-}
\ No newline at end of file
+}
--- a/src/toolsets/extprogramtoolset.cpp	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/toolsets/extprogramtoolset.cpp	Sun Mar 05 16:47:52 2017 +0200
@@ -157,14 +157,14 @@
 			LDSubfileReference* ref = static_cast<LDSubfileReference*> (obj);
 			Model model {m_documents};
 			ref->inlineContents(model, true, false);
-			writeObjects(model.objects(), f);
+			writeObjects(model.objects().toQVector(), f);
 		}
 		else if (obj->type() == LDObjectType::BezierCurve)
 		{
 			LDBezierCurve* curve = static_cast<LDBezierCurve*> (obj);
 			Model model {m_documents};
 			curve->rasterize(model, grid()->bezierCurveSegments());
-			writeObjects(model.objects(), f);
+			writeObjects(model.objects().toQVector(), f);
 		}
 		else
 			f.write ((obj->asText() + "\r\n").toUtf8());
@@ -321,15 +321,9 @@
 	for (LDColor color : colorsToReplace)
 		m_window->deleteByColor (color);
 
-	// Insert the new objects
+	// Insert the new objects.
 	currentDocument()->clearSelection();
-
-	for (LDObject* object : model.objects())
-	{
-		if (object->isScemantic())
-			currentDocument()->addObject(object);
-	}
-
+	currentDocument()->merge(model, -1, [](LDObject* object) {return object->isScemantic();});
 	m_window->doFullRefresh();
 }
 
--- a/src/toolsets/filetoolset.cpp	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/toolsets/filetoolset.cpp	Sun Mar 05 16:47:52 2017 +0200
@@ -120,14 +120,10 @@
 			m_documents->loadFileContents(&file, model, nullptr, nullptr);
 
 			currentDocument()->clearSelection();
-
-			for (LDObject* object : model.objects())
+			currentDocument()->merge(model, position, nullptr, [&](LDObject* object, int)
 			{
-				currentDocument()->insertObject (position, object);
 				currentDocument()->addToSelection(object);
-				position++;
-			}
-
+			});
 			m_window->refresh();
 			m_window->scrollToSelection();
 		}
--- a/src/types/resourcevector.h	Sun Mar 05 13:33:37 2017 +0200
+++ b/src/types/resourcevector.h	Sun Mar 05 16:47:52 2017 +0200
@@ -16,6 +16,7 @@
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#pragma once
 #include <QVector>
 
 /*
@@ -25,18 +26,27 @@
 class ResourceVector
 {
 public:
+	using Filter = std::function<bool(T*, int)>;
+	using Callback = std::function<void(T*, int)>;
+
 	~ResourceVector();
 	void append(T) = delete;
 	template<typename TT = T, typename... Args>
-	T* append(Args&&... args);
-	T** const begin() const;
-	T** const end() const;
+	TT* append(Args&&... args);
+	void assimilate(int position, T* resource);
+	T* const* begin() const;
+	T* const* end() const;
+	int indexOf(T* resource) const;
 	template<typename TT = T, typename... Args>
-	T* insert(int position, Args&&... args);
+	TT* insert(int position, Args&&... args);
+	bool isEmpty() const;
 	void clear();
 	void removeAt(int position);
 	T* operator[](int position) const;
 	int size() const;
+	void swap(int position1, int position2);
+	void merge(ResourceVector<T>& other, int position = -1, Filter filter = nullptr, Callback callback = nullptr);
+	const QVector<T*>& toQVector() const;
 
 private:
 	QVector<T*> _data;
@@ -59,17 +69,18 @@
  */
 template<typename T>
 template<typename TT, typename... Args>
-T* ResourceVector<T>::append(Args&&... args)
+TT* ResourceVector<T>::append(Args&&... args)
 {
-	_data.append(new TT {args...});
-	return _data[size() - 1];
+	TT* resource = new TT {args...};
+	_data.append(resource);
+	return resource;
 }
 
 /*
  * Returns an iterator to the beginning of this vector.
  */
 template<typename T>
-T** const ResourceVector<T>::begin() const
+T* const* ResourceVector<T>::begin() const
 {
 	return _data.cbegin();
 }
@@ -78,7 +89,7 @@
  * Returns an iterator to the end of this vector.
  */
 template<typename T>
-T** const ResourceVector<T>::end() const
+T* const* ResourceVector<T>::end() const
 {
 	return _data.cend();
 }
@@ -88,9 +99,11 @@
  */
 template<typename T>
 template<typename TT, typename... Args>
-T* ResourceVector<T>::insert(int position, Args&&... args)
+TT* ResourceVector<T>::insert(int position, Args&&... args)
 {
-	_data.insert(position, new TT {args...});
+	TT* resource = new TT {args...};
+	_data.insert(position, resource);
+	return resource;
 }
 
 /*
@@ -111,7 +124,7 @@
 template<typename T>
 void ResourceVector<T>::removeAt(int position)
 {
-	if (position < _data.size())
+	if (position >= 0 and position < _data.size())
 	{
 		delete _data[position];
 		_data.removeAt(position);
@@ -128,7 +141,7 @@
 template<typename T>
 T* ResourceVector<T>::operator[](int position) const
 {
-	if (position < _data.size())
+	if (position >= 0 and position < _data.size())
 		return _data[position];
 	else
 		throw std::domain_error {"index out of range"};
@@ -144,6 +157,96 @@
 }
 
 /*
+ * Inserts an already existing resource into the vector. The resource is assumed to not be managed by anything else!
+ * Please think twice before using this method.
+ */
+template<typename T>
+void ResourceVector<T>::assimilate(int position, T* resource)
+{
+	_data.insert(position, resource);
+}
+
+/*
+ * Returns the index of the provided resource in the vector, or -1 if not found.
+ * This operation is of linear complexity O(n).
+ */
+template<typename T>
+int ResourceVector<T>::indexOf(T* resource) const
+{
+	return _data.indexOf(resource);
+}
+
+/*
+ * Swaps the locations of two resource in this vector.
+ */
+template<typename T>
+void ResourceVector<T>::swap(int position1, int position2)
+{
+	if (position1 >= 0 and position2 >= 0 and position1 < size() and position2 < size())
+	{
+		qSwap(_data[position1], _data[position2]);
+	}
+	else
+	{
+		throw std::domain_error {"index out of range"};
+	}
+}
+
+/*
+ * Returns whether or not the vector is empty.
+ */
+template<typename T>
+bool ResourceVector<T>::isEmpty() const
+{
+	return size() == 0;
+}
+
+/*
+ * Migrates resources from the provided resource vector to this one.
+ * - `position` indicates where to insert the new resources. Default is to the end.
+ * - `filter` specifies a filter function for the migration. Objects not passing the filter will not be migrated.
+ * - `callback` specifies a function that is called for each successfully migrated object.
+ */
+template<typename T>
+void ResourceVector<T>::merge(ResourceVector<T>& other, int position, Filter filter, Callback callback)
+{
+	if (position < 0)
+		position = size();
+
+	if (filter == nullptr)
+		filter = [](T*, int){return true;};
+
+	if (callback == nullptr)
+		callback = [](T*, int){};
+
+	for (int i = 0; i < other.size();)
+	{
+		T* resource = other[i];
+
+		if (filter(resource, i))
+		{
+			_data.insert(position, resource);
+			other._data.removeAt(i);
+			callback(resource, position);
+			position += 1;
+		}
+		else
+		{
+			i += 1;
+		}
+	}
+}
+
+/*
+ * Returns the underlying vector.
+ */
+template<typename T>
+const QVector<T*>& ResourceVector<T>::toQVector() const
+{
+	return _data;
+}
+
+/*
  * countof() overload for resource vectors. Returns the amount of resources in the given vector.
  */
 template<typename T>

mercurial