diff --git a/.gitignore b/.gitignore
index 3ab8784078c0af970f261f7dcb46450e62e8cebe..25057c7a145d9e84bf4067d34f0c2a44a1e047b5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,9 @@
 /bin-Release/
 /build-Debug/
 /build-Release/
+/CodeBackup_d.lst
+/CodeBackup_r+.lst
+/CodeBackup_r-.lst
+/CreateCodeBackup.cmd
+/Grinder.smproj
+/Grinder.tdl
diff --git a/Grinder/Grinder.pro b/Grinder/Grinder.pro
index 4841788786e77f0640974dc569ee559e8afeda8a..d00773d77a56f0876ebe0b6b552c8cac4d2eb4e5 100644
--- a/Grinder/Grinder.pro
+++ b/Grinder/Grinder.pro
@@ -100,24 +100,24 @@ SOURCES += \
     engine/processors/ConvertToGrayscaleProcessor.cpp \
     ui/graph/GraphLayout.cpp \
 	core/GrinderExceptions.cpp \
-	project/serialization/SettingsContainer.cpp \
-	project/serialization/SerializationExceptions.cpp \
-	project/serialization/SettingsCodec.cpp \
-	project/serialization/JsonSettingsCodec.cpp \
-	project/serialization/ProjectSerializer.cpp \
-    project/serialization/SerializationContext.cpp \
-	project/serialization/DeserializationContext.cpp \
+	common/serialization/SettingsContainer.cpp \
+	common/serialization/SerializationExceptions.cpp \
+	common/serialization/SettingsCodec.cpp \
+	common/serialization/JsonSettingsCodec.cpp \
+	project/ProjectSerializer.cpp \
+	common/serialization/SerializationContext.cpp \
+	common/serialization/DeserializationContext.cpp \
     util/SerializationUtils.cpp \
     common/MRUStringList.cpp \
 	ui/mainwnd/RecentProjectsMenu.cpp \
     ui/dlg/OptionsDialog.cpp \
-    ui/property/PropertyTreeWidget.cpp \
-    ui/property/PropertyTreeItem.cpp \
-    ui/property/BlockPropertyTreeItem.cpp \
-    ui/property/PropertyTreeItemDelegate.cpp \
-    ui/property/ValuePropertyTreeItem.cpp \
-    ui/property/editors/BoolPropertyEditor.cpp \
-    ui/property/editors/TextPropertyEditor.cpp \
+	ui/properties/PropertyTreeWidget.cpp \
+	ui/properties/PropertyTreeItem.cpp \
+	ui/properties/BlockPropertyTreeItem.cpp \
+	ui/properties/PropertyTreeItemDelegate.cpp \
+	ui/properties/ValuePropertyTreeItem.cpp \
+	ui/properties/editors/BoolPropertyEditor.cpp \
+	ui/properties/editors/TextPropertyEditor.cpp \
     pipeline/blocks/OutputBlock.cpp \
     pipeline/blocks/InputBlock.cpp \
     image/ImageBuild.cpp \
@@ -139,19 +139,19 @@ SOURCES += \
     image/LayerVector.cpp \
     ui/image/LayersListWidget.cpp \
     ui/image/LayersListItem.cpp \
-    common/properties/BoolProperty.cpp \
-    common/properties/IntProperty.cpp \
-    common/properties/RealProperty.cpp \
-    common/properties/StringProperty.cpp \
-    common/properties/UIntProperty.cpp \
-    common/PropertyBase.cpp \
-    common/PropertyExceptions.cpp \
-    common/PropertyID.cpp \
-    common/PropertyVector.cpp \
+	common/properties/types/BoolProperty.cpp \
+	common/properties/types/IntProperty.cpp \
+	common/properties/types/RealProperty.cpp \
+	common/properties/types/StringProperty.cpp \
+	common/properties/types/UIntProperty.cpp \
+	common/properties/PropertyBase.cpp \
+	common/properties/PropertyExceptions.cpp \
+	common/properties/PropertyID.cpp \
+	common/properties/PropertyVector.cpp \
     image/DraftItemType.cpp \
     image/DraftItem.cpp \
-    common/properties/PointProperty.cpp \
-    common/properties/ColorProperty.cpp \
+	common/properties/types/PointProperty.cpp \
+	common/properties/types/ColorProperty.cpp \
     image/DraftItemVector.cpp \
     image/DraftItemCatalog.cpp \
     image/draftitems/BoxDraftItem.cpp \
@@ -159,7 +159,7 @@ SOURCES += \
     image/draftitems/BoxDraftItemRenderer.cpp \
     image/draftitems/LineDraftItemRenderer.cpp \
     image/DraftItemRendererBase.cpp \
-    common/properties/SizeProperty.cpp \
+	common/properties/types/SizeProperty.cpp \
     ui/visscene/VisualNode.cpp \
     ui/image/ImageEditorNode.cpp \
     ui/image/DraftItemNode.cpp \
@@ -173,14 +173,14 @@ SOURCES += \
     ui/image/draftitems/BoxDraftItemNode.cpp \
     ui/image/draftitems/LineDraftItemNode.cpp \
     ui/image/ImageEditorPropertyWidget.cpp \
-    ui/property/editors/PointPropertyEditor.cpp \
-    ui/property/editors/SizePropertyEditor.cpp \
+	ui/properties/editors/PointPropertyEditor.cpp \
+	ui/properties/editors/SizePropertyEditor.cpp \
     ui/widget/AutoFocusLineEdit.cpp \
     ui/widget/ColorWidget.cpp \
     ui/image/tools/ColorPickerTool.cpp \
     ui/widget/GrinderDockWidget.cpp \
-    common/properties/AngleProperty.cpp \
-    ui/property/editors/AnglePropertyEditor.cpp \
+	common/properties/types/AngleProperty.cpp \
+	ui/properties/editors/AnglePropertyEditor.cpp \
 	ui/image/ImageEditorEnvironment.cpp \
     util/MathUtils.cpp \
     ui/image/ImageEditorWidget.cpp \
@@ -189,10 +189,12 @@ SOURCES += \
     ui/image/InPlaceEditorDragHandle.cpp \
     ui/image/InPlaceEditor.cpp \
     ui/image/editors/LinearInPlaceEditor.cpp \
-    common/PropertyObject.cpp \
+	common/properties/PropertyObject.cpp \
     ui/image/editors/RectangularInPlaceEditor.cpp \
     ui/visscene/VisualSceneInputHandler.cpp \
-    ui/image/ColorPresetsWidget.cpp
+    ui/image/ColorPresetsWidget.cpp \
+    common/serialization/SerializationBase.cpp \
+    core/ClipboardManager.cpp
 
 HEADERS += \        
 	ui/mainwnd/GrinderWindow.h \
@@ -279,14 +281,14 @@ HEADERS += \
     engine/processors/ConvertToGrayscaleProcessor.h \
     ui/graph/GraphLayout.h \
 	core/GrinderExceptions.h \
-	project/serialization/SettingsContainer.h \
-	project/serialization/SerializationExceptions.h \
-	project/serialization/SettingsContainer.impl.h \
-	project/serialization/SettingsCodec.h \
-	project/serialization/JsonSettingsCodec.h \
-	project/serialization/ProjectSerializer.h \
-    project/serialization/SerializationContext.h \
-	project/serialization/DeserializationContext.h \
+	common/serialization/SettingsContainer.h \
+	common/serialization/SerializationExceptions.h \
+	common/serialization/SettingsContainer.impl.h \
+	common/serialization/SettingsCodec.h \
+	common/serialization/JsonSettingsCodec.h \
+	project/ProjectSerializer.h \
+	common/serialization/SerializationContext.h \
+	common/serialization/DeserializationContext.h \
     util/SerializationUtils.h \
     util/SerializationUtils.impl.h \
     common/MRUStringList.h \
@@ -294,21 +296,21 @@ HEADERS += \
 	ui/mainwnd/RecentProjectsMenu.h \
     ui/dlg/OptionsDialog.h \
     util/StringUtils.impl.h \
-    ui/property/PropertyTreeWidget.h \
-    ui/property/PropertyTreeItem.h \
-    ui/property/BlockPropertyTreeItem.h \
-    ui/property/PropertyTreeItemDelegate.h \
-    ui/property/ValuePropertyTreeItem.h \
-	ui/property/PropertyEditor.h \
-	ui/property/PropertyEditor.impl.h \
-    ui/property/editors/BoolPropertyEditor.h \
-    ui/property/editors/TextPropertyEditor.h \
+	ui/properties/PropertyTreeWidget.h \
+	ui/properties/PropertyTreeItem.h \
+	ui/properties/BlockPropertyTreeItem.h \
+	ui/properties/PropertyTreeItemDelegate.h \
+	ui/properties/ValuePropertyTreeItem.h \
+	ui/properties/PropertyEditor.h \
+	ui/properties/PropertyEditor.impl.h \
+	ui/properties/editors/BoolPropertyEditor.h \
+	ui/properties/editors/TextPropertyEditor.h \
     res/Resources.h \
     pipeline/blocks/OutputBlock.h \
     pipeline/blocks/InputBlock.h \
     image/ImageBuild.h \
-    project/serialization/SerializationContext.impl.h \
-    project/serialization/DeserializationContext.impl.h \
+	common/serialization/SerializationContext.impl.h \
+	common/serialization/DeserializationContext.impl.h \
     image/ImageBuildVector.h \
     image/ImageBuildPool.h \
     image/ImageExceptions.h \
@@ -332,27 +334,27 @@ HEADERS += \
     image/LayerVector.h \
     ui/image/LayersListWidget.h \
     ui/image/LayersListItem.h \
-    common/properties/BoolProperty.h \
-    common/properties/IntProperty.h \
-    common/properties/RangeConstraint.h \
-    common/properties/RangeConstraint.impl.h \
-    common/properties/RealProperty.h \
+	common/properties/types/BoolProperty.h \
+	common/properties/types/IntProperty.h \
+	common/properties/types/RangeConstraint.h \
+	common/properties/types/RangeConstraint.impl.h \
+	common/properties/types/RealProperty.h \
     common/properties/StandardProperties.h \
-    common/properties/StringProperty.h \
-    common/properties/UIntProperty.h \
-    common/Property.h \
-    common/Property.impl.h \
-    common/PropertyBase.h \
-    common/PropertyConstraint.h \
-    common/PropertyConstraint.impl.h \
-    common/PropertyExceptions.h \
-    common/PropertyID.h \
-    common/PropertyVector.h \
-    common/PropertyVector.impl.h \
+	common/properties/types/StringProperty.h \
+	common/properties/types/UIntProperty.h \
+	common/properties/Property.h \
+	common/properties/Property.impl.h \
+	common/properties/PropertyBase.h \
+	common/properties/PropertyConstraint.h \
+	common/properties/PropertyConstraint.impl.h \
+	common/properties/PropertyExceptions.h \
+	common/properties/PropertyID.h \
+	common/properties/PropertyVector.h \
+	common/properties/PropertyVector.impl.h \
     image/DraftItemType.h \
     image/DraftItem.h \
-    common/properties/PointProperty.h \
-    common/properties/ColorProperty.h \
+	common/properties/types/PointProperty.h \
+	common/properties/types/ColorProperty.h \
     image/DraftItemVector.h \
     image/DraftItemCatalog.h \
     image/draftitems/BoxDraftItem.h \
@@ -362,7 +364,7 @@ HEADERS += \
     image/draftitems/LineDraftItemRenderer.h \
     image/DraftItemRenderer.impl.h \
     image/DraftItemRendererBase.h \
-    common/properties/SizeProperty.h \
+	common/properties/types/SizeProperty.h \
     ui/visscene/VisualNode.h \
     ui/image/ImageEditorNode.h \
     ui/visscene/VisualNodeFactory.h \
@@ -380,16 +382,16 @@ HEADERS += \
     ui/image/draftitems/BoxDraftItemNode.h \
     ui/image/draftitems/LineDraftItemNode.h \
     ui/image/ImageEditorPropertyWidget.h \
-    ui/property/editors/DualTextPropertyEditor.h \
-    ui/property/editors/PointPropertyEditor.h \
-    ui/property/editors/DualTextPropertyEditor.impl.h \
-    ui/property/editors/SizePropertyEditor.h \
+	ui/properties/editors/DualTextPropertyEditor.h \
+	ui/properties/editors/PointPropertyEditor.h \
+	ui/properties/editors/DualTextPropertyEditor.impl.h \
+	ui/properties/editors/SizePropertyEditor.h \
     ui/widget/AutoFocusLineEdit.h \
     ui/widget/ColorWidget.h \
     ui/image/tools/ColorPickerTool.h \
     ui/widget/GrinderDockWidget.h \
-    common/properties/AngleProperty.h \
-    ui/property/editors/AnglePropertyEditor.h \
+	common/properties/types/AngleProperty.h \
+	ui/properties/editors/AnglePropertyEditor.h \
 	ui/image/ImageEditorEnvironment.h \
     util/MathUtils.h \
     ui/image/ImageEditorWidget.h \
@@ -398,12 +400,15 @@ HEADERS += \
     ui/image/InPlaceEditorDragHandle.h \
     ui/image/InPlaceEditor.h \
     ui/image/editors/LinearInPlaceEditor.h \
-    common/PropertyObject.h \
-    common/PropertyObject.impl.h \
+	common/properties/PropertyObject.h \
+	common/properties/PropertyObject.impl.h \
     ui/image/editors/RectangularInPlaceEditor.h \
     ui/visscene/VisualSceneInputHandler.h \
     ui/visscene/VisualSceneInputHandler.impl.h \
-    ui/image/ColorPresetsWidget.h
+    ui/image/ColorPresetsWidget.h \
+    common/serialization/SerializationBase.h \
+    core/ClipboardManager.h \
+    core/ClipboardManager.impl.h
 
 FORMS += \        
 	ui/mainwnd/GrinderWindow.ui \
diff --git a/Grinder/Version.h b/Grinder/Version.h
index 1ebf1c94cd8dd20b8e8c5f0228691d4b3bab65cf..e7ead7212016287f8a21a69478975bcf8bc767e6 100644
--- a/Grinder/Version.h
+++ b/Grinder/Version.h
@@ -10,14 +10,14 @@
 
 #define GRNDR_INFO_TITLE		"Grinder"
 #define GRNDR_INFO_COPYRIGHT	"Copyright (c) WWU Muenster"
-#define GRNDR_INFO_DATE			"03.04.2018"
+#define GRNDR_INFO_DATE			"06.04.2018"
 #define GRNDR_INFO_COMPANY		"WWU Muenster"
 #define GRNDR_INFO_WEBSITE		"http://www.uni-muenster.de"
 
 #define GRNDR_VERSION_MAJOR		0
 #define GRNDR_VERSION_MINOR		2
 #define GRNDR_VERSION_REVISION	0
-#define GRNDR_VERSION_BUILD		125
+#define GRNDR_VERSION_BUILD		129
 
 namespace grndr
 {
diff --git a/Grinder/common/ObjectVector.h b/Grinder/common/ObjectVector.h
index 339f31bb8ece302fd4fcab0f863cd94c758bfe06..0ca1f04655236adb33a01e1484bd62b0b9096630 100644
--- a/Grinder/common/ObjectVector.h
+++ b/Grinder/common/ObjectVector.h
@@ -9,8 +9,8 @@
 #include <vector>
 #include <memory>
 
-#include "project/serialization/SerializationContext.h"
-#include "project/serialization/DeserializationContext.h"
+#include "common/serialization/SerializationContext.h"
+#include "common/serialization/DeserializationContext.h"
 
 namespace grndr
 {
diff --git a/Grinder/common/properties/StandardProperties.h b/Grinder/common/properties/StandardProperties.h
index 4dd7d4078fc7b9e0149810fa86895506898fac38..207721a29d2fc1916990ff099b12d6e6ea1c283f 100644
--- a/Grinder/common/properties/StandardProperties.h
+++ b/Grinder/common/properties/StandardProperties.h
@@ -6,14 +6,16 @@
 #ifndef STANDARDPROPERTIES_H
 #define STANDARDPROPERTIES_H
 
-#include "BoolProperty.h"
-#include "IntProperty.h"
-#include "RealProperty.h"
-#include "AngleProperty.h"
-#include "StringProperty.h"
-#include "UIntProperty.h"
-#include "PointProperty.h"
-#include "SizeProperty.h"
-#include "ColorProperty.h"
+#include "types/BoolProperty.h"
+#include "types/IntProperty.h"
+#include "types/RealProperty.h"
+#include "types/AngleProperty.h"
+#include "types/StringProperty.h"
+#include "types/UIntProperty.h"
+#include "types/PointProperty.h"
+#include "types/SizeProperty.h"
+#include "types/ColorProperty.h"
+
+#include "types/RangeConstraint.h"
 
 #endif
diff --git a/Grinder/controller/ImageEditorController.cpp b/Grinder/controller/ImageEditorController.cpp
index ce5c25d19818ac6614431b3b70f951a01ac27ae5..ae08544291ce13c6d1a5197285f3f4e86aca22c7 100644
--- a/Grinder/controller/ImageEditorController.cpp
+++ b/Grinder/controller/ImageEditorController.cpp
@@ -92,6 +92,51 @@ void ImageEditorController::switchLayer(Layer* layer)
 	emit layerSwitched(_activeLayer);
 }
 
+void ImageEditorController::copyImageBuild() const
+{
+	callControllerFunction("Copying image build", [this]() {
+		grinder()->clipboardManager().serialize<ImageBuild>(ImageBuildVector::Serialization_Element, {_activeImageBuild});
+		return true;
+	});
+}
+
+void ImageEditorController::pasteImageBuild()
+{
+	callControllerFunction("Pasting an image build", [this]() {
+		clearImageBuild(_activeImageBuild);
+
+		grinder()->clipboardManager().deserialize<ImageBuild>(ImageBuildVector::Serialization_Element, [this](const SettingsContainer& settings) {
+			Q_UNUSED(settings);
+			return _activeImageBuild;
+		});
+
+		return true;
+	});
+}
+
+void ImageEditorController::duplicateImageBuild()
+{
+	// TODO: Flexibler machen, kein erzwungenes Switching; verstecktes Execute mit naechstem Bild
+	auto clipboardContent = grinder()->clipboardManager().saveClipboard();	// Save the current clipboard contents to restore them afterwards
+	copyImageBuild();
+
+	// Switch to the next image reference
+	auto currentImageRef = grinder()->projectController().activeImageReference();
+	grinder()->projectController().switchToNextImageReference();
+
+	if (grinder()->projectController().activeImageReference() != currentImageRef)	// Did the image reference really change?
+	{
+		// Execute the active pipeline with the new image reference
+		if (const auto activeLabel = grinder()->projectController().activeLabel())
+			grinder()->engineController().executeLabel(activeLabel, Engine::ExecutionMode::View);
+
+		// The active image build has now been switched, so paste the previously copied image build
+		pasteImageBuild();
+	}
+
+	grinder()->clipboardManager().restoreClipboard(clipboardContent);
+}
+
 std::shared_ptr<Layer> ImageEditorController::createLayer(QString name) const
 {
 	return callControllerFunction("Creating a new layer", [this](QString name) {
@@ -99,6 +144,40 @@ std::shared_ptr<Layer> ImageEditorController::createLayer(QString name) const
 	}, name);
 }
 
+void ImageEditorController::copyLayer(const Layer* layer) const
+{
+	callControllerFunction("Copying a layer", [this](const Layer* layer) {
+		grinder()->clipboardManager().serialize<Layer>(LayerVector::Serialization_Element, {layer});
+		return true;
+	}, layer);
+}
+
+void ImageEditorController::pasteLayer()
+{
+	callControllerFunction("Pasting a layer", [this]() {
+		auto layers = grinder()->clipboardManager().deserialize<Layer>(LayerVector::Serialization_Element, [this](const SettingsContainer& settings) {
+			QString name = settings[Layer::Serialization_Value_Name].toString();
+
+			if (_activeImageBuild->layers().selectByName(name))
+				name += " - Copy";
+
+			return createLayer(name).get();
+		});
+
+		// Switch to the last pasted layer
+		if (!layers.empty())
+			switchLayer(layers.back());
+
+		return true;
+	});
+}
+
+void ImageEditorController::cutLayer(const Layer* layer)
+{
+	copyLayer(layer);
+	removeLayer(layer);
+}
+
 void ImageEditorController::removeLayer(const Layer* layer)
 {
 	if (layer)
@@ -117,19 +196,21 @@ void ImageEditorController::removeLayer(const Layer* layer)
 void ImageEditorController::removeAllLayers()
 {
 	if (_activeImageBuild)
-	{
-		auto layers = _activeImageBuild->layers();
-
-		for (const auto& layer : layers)
-			removeLayer(layer.get());
-	}
+		_activeImageBuild->removeAllLayers();
 }
 
-std::shared_ptr<DraftItem> ImageEditorController::createDraftItem(DraftItemType type, Layer* layer) const
+std::shared_ptr<DraftItem> ImageEditorController::createDraftItem(DraftItemType type, Layer* layer, bool autoCreateLayer)
 {
 	if (!layer)
 		layer = _activeLayer;
 
+	// If there are no layers, create a default one and activate it
+	if (!layer && autoCreateLayer)
+	{
+		layer = createLayer("Default layer").get();
+		switchLayer(layer);
+	}
+
 	if (layer)
 	{
 		return callControllerFunction("Creating a draft item", [this](DraftItemType type, Layer* layer) {
@@ -192,6 +273,56 @@ void ImageEditorController::setNodesPrimaryColor(QColor color, const std::vector
 	}
 }
 
+void ImageEditorController::copySelectedNodes() const
+{
+	callControllerFunction("Copying draft items", [this]() {
+		// Copy all selected draft items
+		auto selItems = _activeScene->getNodes<DraftItemNode>(true);
+		std::vector<const DraftItem*> draftItems;
+
+		for (const auto& node : selItems)
+		{
+			if (const auto& item = node->draftItem().lock())
+				draftItems.push_back(item.get());
+		}
+
+		grinder()->clipboardManager().serialize(DraftItemVector::Serialization_Element, draftItems);
+		return true;
+	});
+}
+
+void ImageEditorController::pasteSelectedNodes()
+{
+	callControllerFunction("Pasting draft items", [this]() {
+		// Create draft items for each object in the clipboard
+		auto draftItems = grinder()->clipboardManager().deserialize<DraftItem>(DraftItemVector::Serialization_Element, [this](const SettingsContainer& settings) {
+			DraftItemType type = settings[DraftItem::Serialization_Value_Type].toString();
+
+			return createDraftItem(type).get();
+		});
+
+		// Select the pasted items
+		if (!draftItems.empty())
+		{
+			_activeScene->clearSelection();
+
+			for (const auto& draftItem : draftItems)
+			{
+				if (auto draftItemNode = _activeScene->findDraftItemNode(draftItem))
+					draftItemNode->setSelected(true);
+			}
+		}
+
+		return true;
+	});
+}
+
+void ImageEditorController::cutSelectedNodes() const
+{
+	copySelectedNodes();
+	removeSelectedNodes();
+}
+
 void ImageEditorController::removeSelectedNodes() const
 {
 	if (_activeScene)
@@ -214,6 +345,24 @@ void ImageEditorController::controllerFunctionCalled() const
 		_activeScene->update();
 }
 
+void ImageEditorController::clearImageBuild(ImageBuild* imageBuild)
+{
+	callControllerFunction("Clearing an image build", [this](ImageBuild* imageBuild) {
+		if (_activeImageBuild == imageBuild)
+		{
+			// Refocus the scene view before clearing everything
+			if (_activeScene)
+				_activeScene->view()->setFocus();
+
+			// Also unset any active layer
+			switchLayer(nullptr);
+		}
+
+		imageBuild->removeAllLayers();
+		return true;
+	}, imageBuild);
+}
+
 void ImageEditorController::validateLayerName(QString name, const Layer* layer) const
 {
 	auto imageBuild = layer ? layer->imageBuild() : _activeImageBuild;
diff --git a/Grinder/controller/ImageEditorController.h b/Grinder/controller/ImageEditorController.h
index 73ae110dacf7db95c84f5f557b7a6a455e8aea73..d249368450f56dada5808fa5757a68ca19bd64d8 100644
--- a/Grinder/controller/ImageEditorController.h
+++ b/Grinder/controller/ImageEditorController.h
@@ -42,11 +42,18 @@ namespace grndr
 		const Layer* activeLayer() const { return _activeLayer; }		
 
 	public:
+		void copyImageBuild() const;
+		void pasteImageBuild();
+		void duplicateImageBuild();
+
 		std::shared_ptr<Layer> createLayer(QString name = "") const;
+		void copyLayer(const Layer* layer) const;
+		void pasteLayer();
+		void cutLayer(const Layer* layer);
 		void removeLayer(const Layer* layer);
 		void removeAllLayers();
 
-		std::shared_ptr<DraftItem> createDraftItem(DraftItemType type, Layer* layer = nullptr) const;
+		std::shared_ptr<DraftItem> createDraftItem(DraftItemType type, Layer* layer = nullptr, bool autoCreateLayer = true);
 		void removeDraftItem(const DraftItem* item) const;
 
 		void setLayerVisibility(Layer* layer, bool visible) const;		
@@ -56,6 +63,9 @@ namespace grndr
 
 		void setNodesPrimaryColor(QColor color, const std::vector<DraftItemNode*>& nodes) const;
 
+		void copySelectedNodes() const;
+		void pasteSelectedNodes();
+		void cutSelectedNodes() const;
 		void removeSelectedNodes() const;
 
 	signals:
@@ -69,6 +79,8 @@ namespace grndr
 		virtual void controllerFunctionCalled() const override;
 
 	private:
+		void clearImageBuild(ImageBuild* imageBuild);
+
 		void validateLayerName(QString name, const Layer* layer = nullptr) const;
 
 		void connectLayerSignals(ImageBuild* imageBuild, bool connectSignals = true) const;
diff --git a/Grinder/controller/ProjectController.cpp b/Grinder/controller/ProjectController.cpp
index f8efed3f663b65658a36819c0249cd608796b33d..ca9f9c1e84e95d0930fe5187ece0f0797c5019f6 100644
--- a/Grinder/controller/ProjectController.cpp
+++ b/Grinder/controller/ProjectController.cpp
@@ -9,8 +9,8 @@
 #include "core/GrinderApplication.h"
 #include "project/Project.h"
 #include "project/ProjectExceptions.h"
-#include "project/serialization/JsonSettingsCodec.h"
-#include "project/serialization/ProjectSerializer.h"
+#include "project/ProjectSerializer.h"
+#include "common/serialization/JsonSettingsCodec.h"
 #include "ui/mainwnd/LabelsListWidget.h"
 #include "ui/mainwnd/ImageReferencesListWidget.h"
 #include "util/ImageUtils.h"
@@ -174,6 +174,18 @@ void ProjectController::switchImageReference(ImageReference* imageRef)
 	emit imageReferenceSwitched(_activeImageReference);
 }
 
+void ProjectController::switchToNextImageReference()
+{
+	if (auto nextImageRef = getNeighboringImageReference(false))
+		switchImageReference(nextImageRef);
+}
+
+void ProjectController::switchToPreviousImageReference()
+{
+	if (auto nextImageRef = getNeighboringImageReference(true))
+		switchImageReference(nextImageRef);
+}
+
 std::shared_ptr<Label> ProjectController::createLabel(QString name) const
 {
 	return callControllerFunction("Creating a new label", [this](QString name) {
@@ -289,6 +301,34 @@ void ProjectController::updateCurrentProjectData()
 	_currentProjectSettings = serializer.serializeProject();
 }
 
+ImageReference* ProjectController::getNeighboringImageReference(bool getPrevious) const
+{
+	// Copy and sort all image references
+	auto imageRefs = _project->imageReferences();
+	std::sort(imageRefs.begin(), imageRefs.end(), [](const auto& imageRef1, const auto& imageRef2) { return *imageRef1 < *imageRef2; });
+
+	// Get the next/previous image reference, wrapping around if necessary
+	auto index = imageRefs.indexOf(_activeImageReference);
+
+	if (index != -1)
+	{
+		if (getPrevious)
+		{
+			if (--index < 0)
+				index = static_cast<int>(imageRefs.size()) - 1;
+		}
+		else
+		{
+			if (++index >= static_cast<int>(imageRefs.size()))
+				index = 0;
+		}
+
+		return imageRefs[index].get();
+	}
+	else
+		return nullptr;
+}
+
 void ProjectController::labelCreated(const std::shared_ptr<Label>& label) const
 {
 	if (_labelsList)
diff --git a/Grinder/controller/ProjectController.h b/Grinder/controller/ProjectController.h
index e3cad83cd2f01ed3c3e04150b648ddf8f1bc110e..e8ed3e5054e27a6b109138a22605262e2aad15df 100644
--- a/Grinder/controller/ProjectController.h
+++ b/Grinder/controller/ProjectController.h
@@ -9,7 +9,7 @@
 #include <memory>
 
 #include "GenericController.h"
-#include "project/serialization/SettingsContainer.h"
+#include "common/serialization/SettingsContainer.h"
 
 namespace grndr
 {
@@ -44,6 +44,8 @@ namespace grndr
 		const Label* activeLabel() const { return _activeLabel; }
 
 		void switchImageReference(ImageReference* imageRef);
+		void switchToNextImageReference();
+		void switchToPreviousImageReference();
 		ImageReference* activeImageReference() { return _activeImageReference; }
 		const ImageReference* activeImageReference() const { return _activeImageReference; }
 
@@ -79,6 +81,9 @@ namespace grndr
 		void setCurrentProjectFile(QString fileName);
 		void updateCurrentProjectData();
 
+	private:
+		ImageReference* getNeighboringImageReference(bool getPrevious) const;
+
 	private slots:
 		void labelCreated(const std::shared_ptr<Label>& label) const;
 		void labelRemoved(const std::shared_ptr<Label>& label) const;
diff --git a/Grinder/core/GrinderApplication.cpp b/Grinder/core/GrinderApplication.cpp
index 9099ea5a4422239f344576406ed7b67c5e66725f..0239d8254dcdc7dd26069efc585a95149e8c01a8 100644
--- a/Grinder/core/GrinderApplication.cpp
+++ b/Grinder/core/GrinderApplication.cpp
@@ -25,7 +25,7 @@ QFont GrinderApplication::boldFont(QWidget* widget)
 }
 
 GrinderApplication::GrinderApplication(int& argc, char** argv, int flags) : QApplication(argc, argv, flags),
-	_settings{GRNDR_INFO_COMPANY, GRNDR_INFO_TITLE}, _projectController{&_project}, _engineController{&_engine}, _imageEditorManager{&_pipelineManager}
+	_settings{GRNDR_INFO_COMPANY, GRNDR_INFO_TITLE}, _projectController{&_project}, _engineController{&_engine}, _imageEditorManager{&_project, &_pipelineManager}
 {
 	s_appInstance = this;
 
diff --git a/Grinder/core/GrinderApplication.h b/Grinder/core/GrinderApplication.h
index 0ecb9519cfadfb0e966baf0bfa21f3e683be240d..e18922826be2cc5d6bc4e06983586b056187f48e 100644
--- a/Grinder/core/GrinderApplication.h
+++ b/Grinder/core/GrinderApplication.h
@@ -9,6 +9,7 @@
 #include <QApplication>
 
 #include "GrinderSettings.h"
+#include "ClipboardManager.h"
 #include "pipeline/PipelineManager.h"
 #include "project/Project.h"
 #include "engine/Engine.h"
@@ -41,6 +42,8 @@ namespace grndr
 	public:		
 		GrinderSettings& settings() { return _settings; }
 		const GrinderSettings& settings() const { return _settings; }
+		ClipboardManager& clipboardManager() { return _clipboardManager; }
+		const ClipboardManager& clipboardManager() const { return _clipboardManager; }
 
 		PipelineManager& pipelineManager() { return _pipelineManager; }
 		const PipelineManager& pipelineManager() const { return _pipelineManager; }
@@ -68,6 +71,7 @@ namespace grndr
 
 	private:
 		GrinderSettings _settings;
+		ClipboardManager _clipboardManager;
 
 		PipelineManager _pipelineManager;
 		PipelineController _pipelineController;
diff --git a/Grinder/image/DraftItem.cpp b/Grinder/image/DraftItem.cpp
index 73e8eea6c5f5be7142cca41b9dd9e4dbbd720cd5..47dd8190f17b0e858de367e57f4cba2028efc08f 100644
--- a/Grinder/image/DraftItem.cpp
+++ b/Grinder/image/DraftItem.cpp
@@ -6,7 +6,6 @@
 #include "Grinder.h"
 #include "DraftItem.h"
 #include "Layer.h"
-#include "common/properties/RangeConstraint.h"
 
 const char* DraftItem::Serialization_Value_Type = "Type";
 
diff --git a/Grinder/image/DraftItem.h b/Grinder/image/DraftItem.h
index 5ac87df4060fe3f3171375602e60f6c588f44dd6..a16ab4ff76586c90490f44cf2249d445cf1e02c8 100644
--- a/Grinder/image/DraftItem.h
+++ b/Grinder/image/DraftItem.h
@@ -6,7 +6,7 @@
 #ifndef DRAFTITEM_H
 #define DRAFTITEM_H
 
-#include "common/PropertyObject.h"
+#include "common/properties/PropertyObject.h"
 #include "DraftItemType.h"
 #include "DraftItemRendererBase.h"
 
diff --git a/Grinder/image/ImageBuild.cpp b/Grinder/image/ImageBuild.cpp
index a035a70c6fcac9a77c69f01f83eb930af6f1a0c4..a2061eb361c7094d33b675a6330256dc8fb53cf2 100644
--- a/Grinder/image/ImageBuild.cpp
+++ b/Grinder/image/ImageBuild.cpp
@@ -54,6 +54,14 @@ void ImageBuild::removeLayer(const Layer* layer)
 	}
 }
 
+void ImageBuild::removeAllLayers()
+{
+	auto layers = _layers;
+
+	for (const auto& layer : layers)
+		removeLayer(layer.get());
+}
+
 void ImageBuild::moveLayer(const Layer* layer, bool up)
 {
 	int index = _layers.indexOf(layer);
@@ -80,7 +88,8 @@ void ImageBuild::moveLayer(const Layer* layer, bool up)
 void ImageBuild::serialize(SerializationContext& ctx) const
 {
 	// Serialize values
-	ctx.settings()[Serialization_Value_ImageReference] = ctx.getImageReferenceIndex(_imageReference);
+	if (ctx.getMode() == SerializationContext::Mode::ProjectSerialization)
+		ctx.settings()[Serialization_Value_ImageReference] = ctx.getImageReferenceIndex(_imageReference);
 
 	// Serialize all layers
 	ctx.beginGroup(LayerVector::Serialization_Group, true);
@@ -91,12 +100,15 @@ void ImageBuild::serialize(SerializationContext& ctx) const
 void ImageBuild::deserialize(DeserializationContext& ctx)
 {
 	// Deserialize values
-	int imageRefIndex = ctx.settings()[Serialization_Value_ImageReference].toInt();
-
-	if (imageRefIndex != -1)
+	if (ctx.getMode() == SerializationContext::Mode::ProjectSerialization)
 	{
-		if (auto imageRef = ctx.getImageReference(imageRefIndex))
-			_imageReference = imageRef;
+		int imageRefIndex = ctx.settings()[Serialization_Value_ImageReference].toInt();
+
+		if (imageRefIndex != -1)
+		{
+			if (auto imageRef = ctx.getImageReference(imageRefIndex))
+				_imageReference = imageRef;
+		}
 	}
 
 	// Deserialize all layers
diff --git a/Grinder/image/ImageBuild.h b/Grinder/image/ImageBuild.h
index 4d6ac052255a850cadd6f51524c4eaf082738a58..16dcf034fa39424d657164dfa7082aeb3698a0ef 100644
--- a/Grinder/image/ImageBuild.h
+++ b/Grinder/image/ImageBuild.h
@@ -27,6 +27,7 @@ namespace grndr
 	public:
 		std::shared_ptr<Layer> createLayer(QString name = "");
 		void removeLayer(const Layer* layer);
+		void removeAllLayers();
 
 		void moveLayer(const Layer* layer, bool up);
 
diff --git a/Grinder/image/ImageBuildItem.h b/Grinder/image/ImageBuildItem.h
index 91b224b608e485ead3b66f8b376136d1773d1d35..b5a3bda8821b379d35847064595163e18d61bc75 100644
--- a/Grinder/image/ImageBuildItem.h
+++ b/Grinder/image/ImageBuildItem.h
@@ -8,8 +8,8 @@
 
 #include <QObject>
 
-#include "project/serialization/SerializationContext.h"
-#include "project/serialization/DeserializationContext.h"
+#include "common/serialization/SerializationContext.h"
+#include "common/serialization/DeserializationContext.h"
 
 namespace grndr
 {
diff --git a/Grinder/image/draftitems/BoxDraftItem.cpp b/Grinder/image/draftitems/BoxDraftItem.cpp
index 9b09637196ccfa66577de5e7c1674fe5982e6331..dde2b2a78992c329a9bd851b6bb8090cff28895a 100644
--- a/Grinder/image/draftitems/BoxDraftItem.cpp
+++ b/Grinder/image/draftitems/BoxDraftItem.cpp
@@ -6,7 +6,6 @@
 #include "Grinder.h"
 #include "BoxDraftItem.h"
 #include "BoxDraftItemRenderer.h"
-#include "common/properties/RangeConstraint.h"
 
 const DraftItemType BoxDraftItem::type_value = DraftItemType::Box;
 
diff --git a/Grinder/image/draftitems/LineDraftItem.cpp b/Grinder/image/draftitems/LineDraftItem.cpp
index d1689263df7fb44d056c97bfa10a51eb8df91eed..5d906b2dd8f95c16473ae2082f69d2431c61d61b 100644
--- a/Grinder/image/draftitems/LineDraftItem.cpp
+++ b/Grinder/image/draftitems/LineDraftItem.cpp
@@ -6,7 +6,6 @@
 #include "Grinder.h"
 #include "LineDraftItem.h"
 #include "LineDraftItemRenderer.h"
-#include "common/properties/RangeConstraint.h"
 
 const DraftItemType LineDraftItem::type_value = DraftItemType::Line;
 
diff --git a/Grinder/pipeline/PipelineItem.h b/Grinder/pipeline/PipelineItem.h
index 61e1bff1c5a922926c06d1900b4d85a4870d9235..b65fb1fc8ddffb7d481808d0c33f64de4f436f70 100644
--- a/Grinder/pipeline/PipelineItem.h
+++ b/Grinder/pipeline/PipelineItem.h
@@ -6,7 +6,7 @@
 #ifndef PIPELINEITEM_H
 #define PIPELINEITEM_H
 
-#include "common/PropertyObject.h"
+#include "common/properties/PropertyObject.h"
 
 namespace grndr
 {
diff --git a/Grinder/pipeline/blocks/BinaryThresholdBlock.cpp b/Grinder/pipeline/blocks/BinaryThresholdBlock.cpp
index 073a3c35357cb7203127f0112dd38e1f8c70f166..878b70bea2f3092805018ef066150d24667364cb 100644
--- a/Grinder/pipeline/blocks/BinaryThresholdBlock.cpp
+++ b/Grinder/pipeline/blocks/BinaryThresholdBlock.cpp
@@ -5,7 +5,6 @@
 
 #include "Grinder.h"
 #include "BinaryThresholdBlock.h"
-#include "common/properties/RangeConstraint.h"
 #include "engine/processors/BinaryThresholdProcessor.h"
 
 const BlockType BinaryThresholdBlock::type_value = BlockType::BinaryThreshold;
diff --git a/Grinder/project/ImageReference.h b/Grinder/project/ImageReference.h
index 6c0f575ad313e9f7ee4ffab56cd178411eb98654..57d29e584223194dce077811e8a3d885de45f130 100644
--- a/Grinder/project/ImageReference.h
+++ b/Grinder/project/ImageReference.h
@@ -11,8 +11,8 @@
 #include <opencv2/core.hpp>
 
 #include "project/ProjectItem.h"
-#include "project/serialization/SerializationContext.h"
-#include "project/serialization/DeserializationContext.h"
+#include "common/serialization/SerializationContext.h"
+#include "common/serialization/DeserializationContext.h"
 
 namespace grndr
 {
diff --git a/Grinder/project/Project.h b/Grinder/project/Project.h
index b8b7fb5c04dfec56e57f97e2cc9763e1fce4165c..0d655b192ec9ea4602bbdaf7c9e1844b8e4805e8 100644
--- a/Grinder/project/Project.h
+++ b/Grinder/project/Project.h
@@ -10,8 +10,8 @@
 
 #include "LabelVector.h"
 #include "ImageReferenceVector.h"
-#include "serialization/SerializationContext.h"
-#include "serialization/DeserializationContext.h"
+#include "common/serialization/SerializationContext.h"
+#include "common/serialization/DeserializationContext.h"
 
 namespace grndr
 {
diff --git a/Grinder/res/Grinder.qrc b/Grinder/res/Grinder.qrc
index 65b460948d7de489c5e8f7f54742b75a62ca81a3..3717e2047e7fcd750dfae9581166bee64f01f7cb 100644
--- a/Grinder/res/Grinder.qrc
+++ b/Grinder/res/Grinder.qrc
@@ -35,6 +35,10 @@
         <file>icons/drag.png</file>
         <file>icons/arrowheads-of-thin-outline-to-the-left.png</file>
         <file>icons/right-thin-arrowheads.png</file>
+        <file>icons/clipboard-paste-option.png</file>
+        <file>icons/cut.png</file>
+        <file>icons/copy-documents-option.png</file>
+        <file>icons/documents-exchange-2.png</file>
     </qresource>
     <qresource prefix="/">
         <file>css/global.css</file>
diff --git a/Grinder/res/Resources.h b/Grinder/res/Resources.h
index 6c083f38bde30d4d00355a9f241f7ac8732edb74..aa6db3d6c3cc28e0284490e201ff7542735e6f5d 100644
--- a/Grinder/res/Resources.h
+++ b/Grinder/res/Resources.h
@@ -29,6 +29,9 @@
 #define FILE_ICON_DELETE ":/icons/icons/delete.png"
 #define FILE_ICON_DELETE_SELECTED ":/icons/icons/delete-sel-items.png"
 #define FILE_ICON_SELECTALL ":/icons/icons/select-all.png"
+#define FILE_ICON_COPY ":/icons/icons/copy-documents-option.png"
+#define FILE_ICON_PASTE ":/icons/icons/clipboard-paste-option.png"
+#define FILE_ICON_CUT ":/icons/icons/cut.png"
 
 #define FILE_ICON_MOVEUP ":/icons/icons/arrow-up.png"
 #define FILE_ICON_MOVEDOWN ":/icons/icons/arrow-down.png"
@@ -50,6 +53,7 @@
 #define FILE_ICON_EDITOR_COLORPICKER ":/icons/icons/painting/eyedropper.png"
 #define FILE_ICON_EDITOR_SHOWDIRECTIONS ":/icons/icons/show-arrows.png"
 #define FILE_ICON_EDITOR_SHOWTAGS ":/icons/icons/show-tags.png"
+#define FILE_ICON_EDITOR_COPYFROMPREVIOUS ":/icons/icons/documents-exchange-2.png"
 
 /* Cursors */
 
diff --git a/Grinder/ui/graph/GraphBlockNode.cpp b/Grinder/ui/graph/GraphBlockNode.cpp
index 207defbab22c653fcfba7e397c5dd1fb4d59cbb0..a3a19d6ca7df00e8aab343b5e33c01e2a90f70c9 100644
--- a/Grinder/ui/graph/GraphBlockNode.cpp
+++ b/Grinder/ui/graph/GraphBlockNode.cpp
@@ -39,8 +39,8 @@ GraphBlockNode::GraphBlockNode(grndr::GraphScene* scene, const std::shared_ptr<B
 	_nameEditWidget->setWidget(_nameEdit);	
 
 	// Create node actions
-	_renameAction = createNodeAction("&Rename block", FILE_ICON_EDIT, SLOT(beginRenameBlock()), "Rename the current block", "F2");
-	_deleteAction->setText("&Delete block");
+	_renameAction = createNodeAction("&Rename block", FILE_ICON_EDIT, SLOT(beginRenameBlock()), "Rename the selected block", "F2");
+	_deleteAction->setText("&Delete block(s)");
 
 	// Set the tooltip to the block's description
 	QString description = BlockCatalog::getDescription(block->getType());
diff --git a/Grinder/ui/graph/GraphConnectionNode.cpp b/Grinder/ui/graph/GraphConnectionNode.cpp
index 859e4ed1f1653a2b8b0db7c913bda2013c882313..f4e903ff25c79db844c701318f4f8dda32afa877 100644
--- a/Grinder/ui/graph/GraphConnectionNode.cpp
+++ b/Grinder/ui/graph/GraphConnectionNode.cpp
@@ -23,7 +23,7 @@ GraphConnectionNode::GraphConnectionNode(GraphScene* scene, const std::shared_pt
 	updateGeometry();
 
 	// Create node actions
-	_deleteAction->setText("&Remove connection");
+	_deleteAction->setText("&Remove connection(s)");
 
 	// Set the tooltip showing information about this connection
 	updateToolTip();
diff --git a/Grinder/ui/graph/GraphLayout.h b/Grinder/ui/graph/GraphLayout.h
index 8a612ca174a7aefea5bf767566767d044686beba..5a940f9361aa75ebfd6a2e74e6877aaf126da204 100644
--- a/Grinder/ui/graph/GraphLayout.h
+++ b/Grinder/ui/graph/GraphLayout.h
@@ -11,8 +11,8 @@
 #include <memory>
 
 #include "pipeline/BlockHierarchy.h"
-#include "project/serialization/SerializationContext.h"
-#include "project/serialization/DeserializationContext.h"
+#include "common/serialization/SerializationContext.h"
+#include "common/serialization/DeserializationContext.h"
 
 namespace grndr
 {
diff --git a/Grinder/ui/graph/GraphNode.cpp b/Grinder/ui/graph/GraphNode.cpp
index 6ea31edac0cfd7bc6cc656c6c1da6488e2788bac..ce6eae34af94c5b16d34e89651ea2fd6a4a2318f 100644
--- a/Grinder/ui/graph/GraphNode.cpp
+++ b/Grinder/ui/graph/GraphNode.cpp
@@ -18,16 +18,7 @@ GraphNode::GraphNode(GraphScene* scene, QGraphicsItem* parent) : VisualNode(scen
 	_scene{scene}, _style{scene->view()->sceneStyle()}
 {
 	// Create node actions
-	_deleteAction = createNodeAction("&Delete", FILE_ICON_DELETE, SLOT(deleteNode()), "Remove the selected item", "Del");
-}
-
-std::vector<QAction*> GraphNode::getNodesActions(QMenu& menu) const
-{
-	// If multiple items are selected, only show a "delete all" action
-	auto action = UIUtils::createAction(&menu, "&Delete selected items", FILE_ICON_DELETE_SELECTED, nullptr, "Delete the selected items", "Del");
-	action->connect(action, &QAction::triggered, _scene->view(), &GraphView::removeSelectedItems);
-
-	return {action};
+	_deleteAction = createNodeAction("&Delete item(s)", FILE_ICON_DELETE, SLOT(deleteNode()), "Remove the selected items", "Del");
 }
 
 void GraphNode::deleteNode()
diff --git a/Grinder/ui/graph/GraphNode.h b/Grinder/ui/graph/GraphNode.h
index fa1646fcdce9c0e19d2cd63a4ba8363b0ce00008..15834ee305557e9e502fde01de01950f85a8f892 100644
--- a/Grinder/ui/graph/GraphNode.h
+++ b/Grinder/ui/graph/GraphNode.h
@@ -22,7 +22,7 @@ namespace grndr
 
 	protected:
 		virtual std::vector<QAction*> getNodeActions(QMenu& menu) const override { Q_UNUSED(menu); return {nullptr, _deleteAction}; }
-		virtual std::vector<QAction*> getNodesActions(QMenu& menu) const override;
+		virtual std::vector<QAction*> getNodesActions(QMenu& menu) const override { Q_UNUSED(menu); return {_deleteAction}; }
 
 	protected slots:
 		void deleteNode();
diff --git a/Grinder/ui/image/DraftItemNode.cpp b/Grinder/ui/image/DraftItemNode.cpp
index 9ef754fb7b4693b49b2bbf21ff1bb274cca8e403..71a823014def470491a5e02e3aa8173e07ad29ba 100644
--- a/Grinder/ui/image/DraftItemNode.cpp
+++ b/Grinder/ui/image/DraftItemNode.cpp
@@ -33,7 +33,7 @@ DraftItemNode::DraftItemNode(ImageEditorScene* scene, const std::shared_ptr<Draf
 	updateVisibility();
 
 	// Create node actions
-	_deleteAction = createNodeAction("&Delete", FILE_ICON_DELETE, SLOT(deleteNode()), "Remove the selected item", "Del");
+	_deleteAction = createNodeAction("&Delete item(s)", FILE_ICON_DELETE, SLOT(deleteNode()), "Remove the selected items", "Del");
 }
 
 void DraftItemNode::initDraftItemNode()
@@ -111,8 +111,15 @@ void DraftItemNode::updateNode()
 
 void DraftItemNode::updateZOrder()
 {
-	if (auto draftItem = _draftItem.lock())	// Make sure that the underlying draft item still exists
-		setZValue(draftItem->getZOrder());	// Use the z-order of the draft item
+	if (isSelected())	// If selected, show on top of all other items (makes in-place editing easier)
+	{
+		setZValue(std::numeric_limits<qreal>::max());
+	}
+	else
+	{
+		if (auto draftItem = _draftItem.lock())	// Make sure that the underlying draft item still exists
+			setZValue(draftItem->getZOrder());	// Use the z-order of the draft item
+	}
 }
 
 void DraftItemNode::updateVisibility()
@@ -138,6 +145,8 @@ QVariant DraftItemNode::itemChange(QGraphicsItem::GraphicsItemChange change, con
 		// Show the in-place editor if the item is currently selected
 		if (_inPlaceEditor)
 			_inPlaceEditor->setVisible(value.toBool());
+
+		updateZOrder();
 	}
 
 	return ImageEditorNode::itemChange(change, value);
@@ -151,15 +160,6 @@ void DraftItemNode::updateGeometry()
 	ImageEditorNode::updateGeometry();
 }
 
-std::vector<QAction*> DraftItemNode::getNodesActions(QMenu& menu) const
-{
-	// If multiple items are selected, only show a "delete all" action
-	auto action = UIUtils::createAction(&menu, "&Delete selected items", FILE_ICON_DELETE_SELECTED, nullptr, "Delete the selected items", "Del");
-	action->connect(action, &QAction::triggered, _scene->view(), &ImageEditorView::removeSelectedItems);
-
-	return {action};
-}
-
 void DraftItemNode::deleteNode()
 {
 	_imageEditor->controller().removeSelectedNodes();
diff --git a/Grinder/ui/image/DraftItemNode.h b/Grinder/ui/image/DraftItemNode.h
index e331806a0f81adf197faf397bb64cdc335d72ee4..b59cab44c6dc584269be9923dc48519fca62ffc4 100644
--- a/Grinder/ui/image/DraftItemNode.h
+++ b/Grinder/ui/image/DraftItemNode.h
@@ -50,7 +50,7 @@ namespace grndr
 		virtual void updateGeometry() override;
 
 		virtual std::vector<QAction*> getNodeActions(QMenu& menu) const override { Q_UNUSED(menu); return {nullptr, _deleteAction}; }
-		virtual std::vector<QAction*> getNodesActions(QMenu& menu) const override;
+		virtual std::vector<QAction*> getNodesActions(QMenu& menu) const override { Q_UNUSED(menu); return {nullptr, _deleteAction}; }
 
 	protected slots:
 		void deleteNode();
diff --git a/Grinder/ui/image/ImageEditorDockWidget.cpp b/Grinder/ui/image/ImageEditorDockWidget.cpp
index 456fe4110e77e5e0a9fafc5425ab6e6ed3298967..ea62c751c08cecdda7cfe13ea2645a897f7bfc2e 100644
--- a/Grinder/ui/image/ImageEditorDockWidget.cpp
+++ b/Grinder/ui/image/ImageEditorDockWidget.cpp
@@ -5,7 +5,7 @@
 
 #include "Grinder.h"
 #include "ImageEditorDockWidget.h"
-#include "ImageEditorWidget.h"
+#include "ImageEditorView.h"
 #include "core/GrinderApplication.h"
 #include "ui/StyleSheet.h"
 #include "res/Resources.h"
@@ -32,8 +32,8 @@ void ImageEditorDockWidget::dockShown(bool shown)
 {
 	if (shown)
 	{
-		if (auto editorWidget = findChild<ImageEditorWidget*>())
-			editorWidget->setFocus();
+		if (auto editorView= findChild<ImageEditorView*>())
+			editorView->setFocus();
 	}
 }
 
diff --git a/Grinder/ui/image/ImageEditorManager.cpp b/Grinder/ui/image/ImageEditorManager.cpp
index 0a8646c5e4ed20ae4f75b53ab9c0ddc7a7b4d9be..b3e6b6ced6a49ebecf1ab569a957e18dd3053525 100644
--- a/Grinder/ui/image/ImageEditorManager.cpp
+++ b/Grinder/ui/image/ImageEditorManager.cpp
@@ -10,9 +10,11 @@
 #include "image/ImageExceptions.h"
 #include "pipeline/Pipeline.h"
 
-ImageEditorManager::ImageEditorManager(PipelineManager* pipelineManager) : QObject(pipelineManager)
+ImageEditorManager::ImageEditorManager(Project* project, PipelineManager* pipelineManager) : QObject(pipelineManager)
 {
+	// We need to close editors when an image reference or an entire pipeline is removed
 	connect(pipelineManager, &PipelineManager::pipelineRemoved, this, &ImageEditorManager::pipelineRemoved);
+	connect(project, &Project::imageReferenceRemoved, this, &ImageEditorManager::imageReferenceRemoved);
 }
 
 void ImageEditorManager::showEditor(const Block* block, const std::shared_ptr<grndr::ImageBuild>& imageBuild)
@@ -117,6 +119,21 @@ void ImageEditorManager::blockRemoved(const std::shared_ptr<Block>& block)
 	closeEditor(block.get());
 }
 
+void ImageEditorManager::imageReferenceRemoved(const std::shared_ptr<ImageReference>& imageReference)
+{
+	// Remove all editors that are currently showing the removed image reference
+	std::vector<const Block*> editorsToRemove;
+
+	for (const auto& editor : _editors)
+	{
+		if (editor.second->controller().activeImageBuild()->imageReference() == imageReference.get())
+			editorsToRemove.push_back(editor.first);
+	}
+
+	for (const auto& editorBlock : editorsToRemove)
+		closeEditor(editorBlock);
+}
+
 void ImageEditorManager::updateDockNames()
 {
 	// Update all dock names
diff --git a/Grinder/ui/image/ImageEditorManager.h b/Grinder/ui/image/ImageEditorManager.h
index 52a645fadfcac774b817dcdc1a3a6ab126e81b9e..11922ddfde226493e80f077b4d203c2f5aa2ff36 100644
--- a/Grinder/ui/image/ImageEditorManager.h
+++ b/Grinder/ui/image/ImageEditorManager.h
@@ -21,7 +21,7 @@ namespace grndr
 		Q_OBJECT
 
 	public:
-		ImageEditorManager(PipelineManager* pipelineManager);
+		ImageEditorManager(Project* project, PipelineManager* pipelineManager);
 
 	public:
 		void showEditor(const Block* block, const std::shared_ptr<ImageBuild>& imageBuild);
@@ -42,6 +42,7 @@ namespace grndr
 
 		void pipelineRemoved(const std::shared_ptr<Pipeline>& pipeline);
 		void blockRemoved(const std::shared_ptr<Block>& block);
+		void imageReferenceRemoved(const std::shared_ptr<ImageReference>& imageReference);
 
 		void updateDockNames();
 
diff --git a/Grinder/ui/image/ImageEditorPropertyWidget.h b/Grinder/ui/image/ImageEditorPropertyWidget.h
index f2e772455234f2b4ba37376cf2b2e0afce76abf3..d3d274eadc2961498e5065101788ffa577602200 100644
--- a/Grinder/ui/image/ImageEditorPropertyWidget.h
+++ b/Grinder/ui/image/ImageEditorPropertyWidget.h
@@ -10,7 +10,7 @@
 #include <QGridLayout>
 
 #include "ImageEditorComponent.h"
-#include "common/PropertyVector.h"
+#include "common/properties/PropertyVector.h"
 
 namespace grndr
 {
diff --git a/Grinder/ui/image/ImageEditorTool.h b/Grinder/ui/image/ImageEditorTool.h
index 845ee1087b5eced3c44241494a08c0e80ba8cc3e..80631a5f4f76033cb0f51f3b53c8871a526557a0 100644
--- a/Grinder/ui/image/ImageEditorTool.h
+++ b/Grinder/ui/image/ImageEditorTool.h
@@ -10,7 +10,7 @@
 #include <QCursor>
 #include <QKeySequence>
 
-#include "common/PropertyObject.h"
+#include "common/properties/PropertyObject.h"
 #include "ui/visscene/VisualSceneInputHandler.h"
 #include "ImageEditorComponent.h"
 
diff --git a/Grinder/ui/image/ImageEditorView.cpp b/Grinder/ui/image/ImageEditorView.cpp
index 1b13ef3fae4a08e5453b3c08f519db394a26b017..3fab8df06033ce92560e31a9c8b335e4f806a8f9 100644
--- a/Grinder/ui/image/ImageEditorView.cpp
+++ b/Grinder/ui/image/ImageEditorView.cpp
@@ -7,6 +7,7 @@
 #include "ImageEditorView.h"
 #include "ImageEditorScene.h"
 #include "ImageEditor.h"
+#include "core/GrinderApplication.h"
 #include "controller/ImageEditorController.h"
 #include "util/UIUtils.h"
 #include "res/Resources.h"
@@ -23,13 +24,21 @@ ImageEditorView::ImageEditorView(QWidget* parent) : VisualSceneView(parent)
 	_showDirectionsAction = UIUtils::createAction(this, "&Show direction arrows", FILE_ICON_EDITOR_SHOWDIRECTIONS, SLOT(showDirectionArrows()), "Show direction arrows on items");
 	_showDirectionsAction->setCheckable(true);
 	_showDirectionsAction->setChecked(true);
-
 	_showTagsAction = UIUtils::createAction(this, "&Show tags", FILE_ICON_EDITOR_SHOWTAGS, SLOT(showTags()), "Show tags on items");
 	_showTagsAction->setCheckable(true);
-	_showTagsAction->setChecked(true);
-
+	_showTagsAction->setChecked(true);	
 	_zoomFitAction = UIUtils::createAction(this, "Zoom to &window", FILE_ICON_ZOOMFIT, SLOT(fitImageToWindow()), "Zoom the view to fit the image to its window", "Ctrl+Alt+A");
 
+	_copyDraftItems = UIUtils::createAction(this, "&Copy item(s)", FILE_ICON_COPY, SLOT(copyDraftItems()), "Copy the selected items to the clipboard");
+	_copyDraftItems->setShortcut(QKeySequence{QKeySequence::Copy});
+	_pasteDraftItems = UIUtils::createAction(this, "&Paste item(s)", FILE_ICON_PASTE, SLOT(pasteDraftItems()), "Paste items from the clipboard");
+	_pasteDraftItems->setShortcut(QKeySequence{QKeySequence::Paste});
+	_cutDraftItems = UIUtils::createAction(this, "Cu&t item(s)", FILE_ICON_CUT, SLOT(cutDraftItems()), "Copy the selected items to the clipboard and remove them afterwards");
+	_cutDraftItems->setShortcut(QKeySequence{QKeySequence::Cut});
+
+	// Listen for clipboard changes to update our actions
+	connect(&grinder()->clipboardManager(), &ClipboardManager::dataChanged, this, &ImageEditorView::updateActions);
+
 	updateActions();
 }
 
@@ -62,12 +71,31 @@ void ImageEditorView::removeSelectedItems() const
 		_editorScene->imageEditor()->controller().removeSelectedNodes();
 }
 
+void ImageEditorView::prepareNodeContextMenu(QMenu& menu) const
+{
+	QAction* firstAction = !menu.actions().isEmpty() ? menu.actions().first() : nullptr;
+
+	menu.insertAction(firstAction, _cutDraftItems);
+	menu.insertAction(firstAction, _copyDraftItems);
+	menu.insertAction(firstAction, _pasteDraftItems);
+}
+
+void ImageEditorView::prepareNodesContextMenu(QMenu& menu) const
+{
+	prepareNodeContextMenu(menu);
+}
+
 std::vector<QAction*> ImageEditorView::getActions(AddActionsMode mode) const
 {
 	std::vector<QAction*> actions;
 
 	if (mode != AddActionsMode::Toolbar)
 	{
+		actions.push_back(_cutDraftItems);
+		actions.push_back(_copyDraftItems);
+		actions.push_back(_pasteDraftItems);
+		actions.push_back(nullptr);
+
 		actions.push_back(_selectAllAction);
 		actions.push_back(_deleteSelectedAction);
 		actions.push_back(nullptr);
@@ -102,6 +130,10 @@ void ImageEditorView::updateActions()
 	VisualSceneView::updateActions();
 
 	_zoomFitAction->setEnabled(_scene);
+
+	_copyDraftItems->setEnabled(_scene && !_scene->selectedItems().isEmpty());
+	_pasteDraftItems->setEnabled(_scene && grinder()->clipboardManager().hasData(DraftItemVector::Serialization_Element));
+	_cutDraftItems->setEnabled(_scene && !_scene->selectedItems().isEmpty());
 }
 
 void ImageEditorView::showDirectionArrows()
@@ -149,3 +181,21 @@ void ImageEditorView::showTagsChanged(bool show)
 	_showTagsAction->setChecked(show);
 	update();
 }
+
+void ImageEditorView::copyDraftItems() const
+{
+	if (_editorScene)
+		_editorScene->imageEditor()->controller().copySelectedNodes();
+}
+
+void ImageEditorView::pasteDraftItems() const
+{
+	if (_editorScene)
+		_editorScene->imageEditor()->controller().pasteSelectedNodes();
+}
+
+void ImageEditorView::cutDraftItems() const
+{
+	if (_editorScene)
+		_editorScene->imageEditor()->controller().cutSelectedNodes();
+}
diff --git a/Grinder/ui/image/ImageEditorView.h b/Grinder/ui/image/ImageEditorView.h
index 57f312e09276b0b087106e085e3df1c61528979a..45a32ccc165bbe5993c5e26f20e4e75b6b9d63df 100644
--- a/Grinder/ui/image/ImageEditorView.h
+++ b/Grinder/ui/image/ImageEditorView.h
@@ -32,6 +32,9 @@ namespace grndr
 	public:
 		virtual const ImageEditorStyle& sceneStyle() const override { return _editorStyle; }
 
+		virtual void prepareNodeContextMenu(QMenu& menu) const override;
+		virtual void prepareNodesContextMenu(QMenu& menu) const override;
+
 	protected:
 		virtual std::vector<QAction*> getActions(AddActionsMode mode) const override;
 
@@ -51,6 +54,10 @@ namespace grndr
 		void showDirectionArrowsChanged(bool show);
 		void showTagsChanged(bool show);
 
+		void copyDraftItems() const;
+		void pasteDraftItems() const;
+		void cutDraftItems() const;
+
 	private:
 		ImageEditorScene* _editorScene{nullptr};
 
@@ -60,6 +67,10 @@ namespace grndr
 		QAction* _showDirectionsAction{nullptr};
 		QAction* _showTagsAction{nullptr};
 		QAction* _zoomFitAction{nullptr};
+
+		QAction* _copyDraftItems{nullptr};
+		QAction* _pasteDraftItems{nullptr};
+		QAction* _cutDraftItems{nullptr};
 	};
 }
 
diff --git a/Grinder/ui/image/ImageEditorWidget.cpp b/Grinder/ui/image/ImageEditorWidget.cpp
index 39b3697e292caa3ffcbd80a35845d394584d8b81..f43f7a66e2b6d28a798df564729e6346cb622bfd 100644
--- a/Grinder/ui/image/ImageEditorWidget.cpp
+++ b/Grinder/ui/image/ImageEditorWidget.cpp
@@ -14,16 +14,32 @@
 #include "util/UIUtils.h"
 #include "res/Resources.h"
 
+#include <QMetaType>
+Q_DECLARE_METATYPE(std::shared_ptr<ImageReference>)
+
 ImageEditorWidget::ImageEditorWidget(ImageEditor* imageEditor, QWidget* parent) : QWidget(parent), ImageEditorComponent(imageEditor),
 	ui{new Ui::ImageEditorWidget}
 {
 	if (!imageEditor)
 		throw std::runtime_error{_EXCPT("imageEditor may not be null")};	
 
-	setupUi();
+	// Needed to be able to queue imageReferenceRemoved signals
+	qRegisterMetaType<std::shared_ptr<ImageReference>>();
+
+	// Create editor actions
+	_copyImageBuild = UIUtils::createAction(this, "&Copy image build", FILE_ICON_COPY, SLOT(copyImageBuild()), "Copy the current image build to the clipboard (Ctrl+Shift+C)", "Ctrl+Shift+C", Qt::WidgetWithChildrenShortcut);
+	_pasteImageBuild = UIUtils::createAction(this, "&Paste image build", FILE_ICON_PASTE, SLOT(pasteImageBuild()), "Paste an image build from the clipboard (Ctrl+Shift+V)", "Ctrl+Shift+V", Qt::WidgetWithChildrenShortcut);
+	_duplicateImageBuild = UIUtils::createAction(this, "&Copy the current image build to the next image", FILE_ICON_EDITOR_COPYFROMPREVIOUS, SLOT(duplicateImageBuild()), "Copy the current image build to the next image (Ctrl+D)", "Ctrl+D", Qt::WidgetWithChildrenShortcut);
+
+	setupUi();	
 
 	// Reflect primary color changes
 	connect(&_imageEditor->environment(), &ImageEditorEnvironment::primaryColorChanged, this, &ImageEditorWidget::primaryColorChanged);
+
+	// Listen for various events to update our actions
+	connect(&grinder()->project(), SIGNAL(imageReferenceCreated(const std::shared_ptr<ImageReference>&)), this, SLOT(updateActions()));
+	connect(&grinder()->project(), SIGNAL(imageReferenceRemoved(const std::shared_ptr<ImageReference>&)), this, SLOT(updateActions()), Qt::QueuedConnection);	// Delay this signal so that the image reference has been removed from the image references list
+	connect(&grinder()->clipboardManager(), &ClipboardManager::dataChanged, this, &ImageEditorWidget::updateActions);
 }
 
 ImageEditorWidget::~ImageEditorWidget()
@@ -36,12 +52,21 @@ void ImageEditorWidget::setImageBuild(const std::shared_ptr<ImageBuild>& imageBu
 	if (auto scene = _imageEditor->controller().activeScene())
 		disconnect(scene, nullptr, this, nullptr);
 
+	if (auto imageBuild = _imageEditor->controller().activeImageBuild())
+		disconnect(imageBuild, nullptr, this, nullptr);
+
 	// Let the controller handle showing the new image build
 	_imageEditor->controller().showImageBuild(imageBuild.get());
 
 	// Listen for node selection changes in the new scene to update the primary color
 	if (auto scene = _imageEditor->controller().activeScene())
 		connect(scene, &ImageEditorScene::selectionChanged, this, &ImageEditorWidget::updatePrimaryColor);
+
+	// Listen for various events to update our actions
+	connect(imageBuild.get(), SIGNAL(layerCreated(const std::shared_ptr<Layer>&)), this, SLOT(updateActions()));
+	connect(imageBuild.get(), SIGNAL(layerRemoved(const std::shared_ptr<Layer>&)), this, SLOT(updateActions()));
+
+	updateActions();
 }
 
 void ImageEditorWidget::keyPressEvent(QKeyEvent* event)
@@ -93,6 +118,8 @@ void ImageEditorWidget::setupUi()
 
 	grinder()->settings().getSplitterState("ImageEditorLeft", ui->splitterLeft);
 	grinder()->settings().getSplitterState("ImageEditorRight", ui->splitterRight);
+
+	updateActions();
 }
 
 void ImageEditorWidget::setupImageControlBar()
@@ -104,6 +131,12 @@ void ImageEditorWidget::setupImageControlBar()
 	for (auto toolAction : _imageEditor->editorTools().getActions())
 		ui->imageSceneControlBar->addAction(toolAction, Qt::ToolButtonFollowStyle, Qt::AlignLeft);
 
+	// Add the copy&paste actions
+	ui->imageSceneControlBar->addSeparator(Qt::AlignLeft);
+	ui->imageSceneControlBar->addAction(_duplicateImageBuild, Qt::ToolButtonFollowStyle, Qt::AlignLeft);
+	ui->imageSceneControlBar->addAction(_copyImageBuild, Qt::ToolButtonFollowStyle, Qt::AlignLeft);
+	ui->imageSceneControlBar->addAction(_pasteImageBuild, Qt::ToolButtonFollowStyle, Qt::AlignLeft);
+
 	ui->imageScene->setupUi(static_cast<QMenu*>(nullptr), ui->imageSceneControlBar);
 
 	// Add the zoom level label
@@ -155,6 +188,15 @@ void ImageEditorWidget::on_primaryColorWidget_customContextMenuRequested(const Q
 	menu.exec(ui->primaryColorWidget->mapToGlobal(pos));
 }
 
+void ImageEditorWidget::updateActions()
+{
+	auto imageBuild = _imageEditor->controller().activeImageBuild();
+
+	_copyImageBuild->setEnabled(imageBuild);
+	_duplicateImageBuild->setEnabled(imageBuild && grinder()->project().imageReferences().size() > 1);
+	_pasteImageBuild->setEnabled(imageBuild && grinder()->clipboardManager().hasData(ImageBuildVector::Serialization_Element));
+}
+
 void ImageEditorWidget::updateSceneZoomLevel(qreal zoomLevel)
 {
 	_imageSceneZoomLabel->setText(QString{"%1%"}.arg(static_cast<int>(zoomLevel * 100.0)));
@@ -204,6 +246,21 @@ void ImageEditorWidget::assignPrimaryColorToPreset()
 	}
 }
 
+void ImageEditorWidget::copyImageBuild() const
+{
+	_imageEditor->controller().copyImageBuild();
+}
+
+void ImageEditorWidget::pasteImageBuild() const
+{
+	_imageEditor->controller().pasteImageBuild();
+}
+
+void ImageEditorWidget::duplicateImageBuild() const
+{
+	_imageEditor->controller().duplicateImageBuild();
+}
+
 void ImageEditorWidget::primaryColorChanged(QColor color)
 {
 	ui->primaryColorWidget->setColor(color);
diff --git a/Grinder/ui/image/ImageEditorWidget.h b/Grinder/ui/image/ImageEditorWidget.h
index 3a50dc88cb1ce52ecc9ebc4a59efe800ea2b22ee..0d6e4dcec9dc9b835ff752693f0a8f6b75a741db 100644
--- a/Grinder/ui/image/ImageEditorWidget.h
+++ b/Grinder/ui/image/ImageEditorWidget.h
@@ -51,6 +51,7 @@ namespace grndr
 		void on_primaryColorWidget_customContextMenuRequested(const QPoint &pos);
 
 	private slots:
+		void updateActions();
 		void updateSceneZoomLevel(qreal zoomLevel);
 
 		void updatePrimaryColor();
@@ -59,8 +60,16 @@ namespace grndr
 		void colorPresetSelected(QColor color);
 		void assignPrimaryColorToPreset();
 
+		void copyImageBuild() const;
+		void pasteImageBuild() const;
+		void duplicateImageBuild() const;
+
 	private:
 		QAction* _newLayerAction{nullptr};
+
+		QAction* _copyImageBuild{nullptr};
+		QAction* _pasteImageBuild{nullptr};
+		QAction* _duplicateImageBuild{nullptr};
 	};
 }
 
diff --git a/Grinder/ui/image/LayersListWidget.cpp b/Grinder/ui/image/LayersListWidget.cpp
index b7d0962b44a8b8c8dc0934951618db7ba4459b80..790958495aa5c57bd3a386ad71ddadd4be9cad1f 100644
--- a/Grinder/ui/image/LayersListWidget.cpp
+++ b/Grinder/ui/image/LayersListWidget.cpp
@@ -7,6 +7,7 @@
 #include "LayersListWidget.h"
 #include "ImageEditor.h"
 #include "image/ImageBuild.h"
+#include "core/GrinderApplication.h"
 #include "ui/widget/ControlBar.h"
 #include "util/UIUtils.h"
 #include "util/StringUtils.h"
@@ -18,6 +19,12 @@ LayersListWidget::LayersListWidget(QWidget* parent) : MetaWidget(parent)
 	_renameLayerAction = UIUtils::createAction(this, "Rename layer", FILE_ICON_EDIT, SLOT(renameLayer()), "Rename the selected layer", "F2");
 	_moveUpAction = UIUtils::createAction(this, "Move up", FILE_ICON_MOVEUP, SLOT(moveLayerUp()), "Move the selected layer up", "Ctrl+Shift+Up");
 	_moveDownAction = UIUtils::createAction(this, "Move down", FILE_ICON_MOVEDOWN, SLOT(moveLayerDown()), "Move the selected layer down", "Ctrl+Shift+Down");
+	_copyLayer = UIUtils::createAction(this, "&Copy layer", FILE_ICON_COPY, SLOT(copyLayer()), "Copy the selected layer to the clipboard");
+	_copyLayer->setShortcut(QKeySequence{QKeySequence::Copy});
+	_pasteLayer = UIUtils::createAction(this, "&Paste layer", FILE_ICON_PASTE, SLOT(pasteLayer()), "Paste layer from the clipboard");
+	_pasteLayer->setShortcut(QKeySequence{QKeySequence::Paste});
+	_cutLayer = UIUtils::createAction(this, "Cu&t layer", FILE_ICON_CUT, SLOT(cutLayer()), "Copy the selected layer to the clipboard and remove it afterwards");
+	_cutLayer->setShortcut(QKeySequence{QKeySequence::Cut});
 	_removeLayerAction = UIUtils::createAction(this, "&Remove layer", FILE_ICON_DELETE, SLOT(removeLayer()), "Remove the selected layer", "Del");
 	_removeAllLayersAction = UIUtils::createAction(this, "Remove all layers", "", SLOT(removeAllLayers()), "Remove all layers");
 
@@ -33,6 +40,9 @@ LayersListWidget::LayersListWidget(QWidget* parent) : MetaWidget(parent)
 	// Listen for item selections in order to update the active layer and actions
 	connect(this, &LayersListWidget::itemSelectionChanged, this, &LayersListWidget::selectedItemChanged);
 
+	// Listen for clipboard changes to update our actions
+	connect(&grinder()->clipboardManager(), &ClipboardManager::dataChanged, this, &LayersListWidget::updateActions);
+
 	updateActions();
 }
 
@@ -79,6 +89,10 @@ std::vector<QAction*> LayersListWidget::getActions(MetaWidget::AddActionsMode mo
 		{
 			actions.push_back(_renameLayerAction);
 			actions.push_back(nullptr);
+			actions.push_back(_cutLayer);
+			actions.push_back(_copyLayer);
+			actions.push_back(_pasteLayer);
+			actions.push_back(nullptr);
 		}
 	}
 
@@ -141,6 +155,23 @@ void LayersListWidget::moveLayerDown()
 		_imageEditor->controller().moveLayer(layerSelected->object(), false);
 }
 
+void LayersListWidget::copyLayer()
+{
+	if (auto layerSelected = currentObjectItem())
+		_imageEditor->controller().copyLayer(layerSelected->object());
+}
+
+void LayersListWidget::pasteLayer()
+{
+	_imageEditor->controller().pasteLayer();
+}
+
+void LayersListWidget::cutLayer()
+{
+	if (auto layerSelected = currentObjectItem())
+		_imageEditor->controller().cutLayer(layerSelected->object());
+}
+
 void LayersListWidget::removeLayer()
 {
 	if (auto layer = currentObject())
@@ -206,6 +237,9 @@ void LayersListWidget::updateActions()
 	_renameLayerAction->setEnabled(layerSelected);
 	_moveUpAction->setEnabled(layerSelected && currentRow() > 0);
 	_moveDownAction->setEnabled(layerSelected && currentRow() < count() - 1);
+	_copyLayer->setEnabled(layerSelected);
+	_pasteLayer->setEnabled(grinder()->clipboardManager().hasData(LayerVector::Serialization_Element));
+	_cutLayer->setEnabled(layerSelected);
 	_removeLayerAction->setEnabled(layerSelected);
 	_removeAllLayersAction->setEnabled(count() > 0);
 }
diff --git a/Grinder/ui/image/LayersListWidget.h b/Grinder/ui/image/LayersListWidget.h
index 3d5617c941c585e63212033b34fb100916141311..40a98612e914f4bee8c830af8dba34a44a18f8c7 100644
--- a/Grinder/ui/image/LayersListWidget.h
+++ b/Grinder/ui/image/LayersListWidget.h
@@ -44,6 +44,9 @@ namespace grndr
 		void newLayer();
 		void moveLayerUp();
 		void moveLayerDown();
+		void copyLayer();
+		void pasteLayer();
+		void cutLayer();
 		void removeLayer();
 		void removeAllLayers();
 
@@ -61,6 +64,9 @@ namespace grndr
 		QAction* _newLayerAction{nullptr};
 		QAction* _moveUpAction{nullptr};
 		QAction* _moveDownAction{nullptr};
+		QAction* _copyLayer{nullptr};
+		QAction* _pasteLayer{nullptr};
+		QAction* _cutLayer{nullptr};
 		QAction* _removeLayerAction{nullptr};
 		QAction* _removeAllLayersAction{nullptr};
 	};
diff --git a/Grinder/ui/image/draftitems/BoxDraftItemNode.cpp b/Grinder/ui/image/draftitems/BoxDraftItemNode.cpp
index 8fa9920ddc4540f8521e7e2978450fb7b242885a..649f75259f182612f107fdd04cfb316fe086d6fe 100644
--- a/Grinder/ui/image/draftitems/BoxDraftItemNode.cpp
+++ b/Grinder/ui/image/draftitems/BoxDraftItemNode.cpp
@@ -29,8 +29,7 @@ QRect BoxDraftItemNode::getBoundingRect() const
 		int lineWidth = *draftItem->lineWidth();
 		QSize size = *draftItem->boxSize() + QSize{lineWidth, lineWidth};
 
-		int margin = lineWidth * 2;
-		return QRect{QPoint{-lineWidth / 2, -lineWidth / 2}, size} + QMargins{margin, margin, margin, margin};
+		return QRect{QPoint{-lineWidth / 2, -lineWidth / 2}, size};
 	}
 	else
 		return QRect{};
diff --git a/Grinder/ui/image/draftitems/LineDraftItemNode.cpp b/Grinder/ui/image/draftitems/LineDraftItemNode.cpp
index 49376ae477b02f66d989b1dac5a054997da35ec5..6ab59dc3823e4b739c5f70aa71e3c5b6350bc289 100644
--- a/Grinder/ui/image/draftitems/LineDraftItemNode.cpp
+++ b/Grinder/ui/image/draftitems/LineDraftItemNode.cpp
@@ -32,8 +32,7 @@ QRect LineDraftItemNode::getBoundingRect() const
 		auto width = std::abs(position.x() - endPosition.x());
 		auto height = std::abs(position.y() - endPosition.y());
 
-		int margin = *draftItem->lineWidth() * 2;
-		return QRect{QPoint{0, 0}, QSize{width, height}} + QMargins{margin, margin, margin, margin};
+		return QRect{QPoint{0, 0}, QSize{width, height}};
 	}
 	else
 		return QRect{};
diff --git a/Grinder/ui/image/tools/BoxDraftItemTool.cpp b/Grinder/ui/image/tools/BoxDraftItemTool.cpp
index 4a52472bb0fe04cf28509e2c01b768deec0a663e..5a0c3efd7af8bb981068a1b14adce44480726b74 100644
--- a/Grinder/ui/image/tools/BoxDraftItemTool.cpp
+++ b/Grinder/ui/image/tools/BoxDraftItemTool.cpp
@@ -5,7 +5,6 @@
 
 #include "Grinder.h"
 #include "BoxDraftItemTool.h"
-#include "common/properties/RangeConstraint.h"
 #include "res/Resources.h"
 
 const char* BoxDraftItemTool::tool_type = "BoxDraftItemTool";
diff --git a/Grinder/ui/image/tools/DraftItemTool.cpp b/Grinder/ui/image/tools/DraftItemTool.cpp
index 52c2eedb969e8dd22a2f50e0b741acd75f20492e..43074b3b5b83815379dc950d77bd3a9f3076008d 100644
--- a/Grinder/ui/image/tools/DraftItemTool.cpp
+++ b/Grinder/ui/image/tools/DraftItemTool.cpp
@@ -5,7 +5,6 @@
 
 #include "Grinder.h"
 #include "DraftItemTool.h"
-#include "common/properties/RangeConstraint.h"
 #include "controller/ImageEditorController.h"
 #include "ui/image/ImageEditor.h"
 #include "util/MathUtils.h"
@@ -21,13 +20,6 @@ DraftItemTool::DraftItemTool(ImageEditor* imageEditor, DraftItemType itemType, Q
 
 std::shared_ptr<DraftItem> DraftItemTool::createDraftItem(QPointF pos, bool selectItem, bool setDefaultProperties)
 {
-	// If there are no layers, create a default one and activate it
-	if (!_imageEditor->controller().activeLayer())
-	{		
-		auto layer = _imageEditor->controller().createLayer("Default layer");
-		_imageEditor->controller().switchLayer(layer.get());
-	}
-
 	// Create the draft item
 	auto draftItem = _imageEditor->controller().createDraftItem(_itemType);
 
diff --git a/Grinder/ui/image/tools/LineDraftItemTool.cpp b/Grinder/ui/image/tools/LineDraftItemTool.cpp
index e90b14fd58d456a92d04a8bb3dba146a4f8f4bb8..e07b2fc39ed8034588bf5c6308de0209f4eccc74 100644
--- a/Grinder/ui/image/tools/LineDraftItemTool.cpp
+++ b/Grinder/ui/image/tools/LineDraftItemTool.cpp
@@ -5,7 +5,6 @@
 
 #include "Grinder.h"
 #include "LineDraftItemTool.h"
-#include "common/properties/RangeConstraint.h"
 #include "res/Resources.h"
 
 const char* LineDraftItemTool::tool_type = "LineDraftItemTool";
diff --git a/Grinder/ui/mainwnd/GrinderWindow.cpp b/Grinder/ui/mainwnd/GrinderWindow.cpp
index 53cd011d6ce81260b65cfd03b2e3fe036023aef7..09331d4efb3905bbb4023a979727da96f41151e8 100644
--- a/Grinder/ui/mainwnd/GrinderWindow.cpp
+++ b/Grinder/ui/mainwnd/GrinderWindow.cpp
@@ -116,13 +116,13 @@ void GrinderWindow::on_actExecuteLabel_triggered()
 
 void GrinderWindow::on_actExecuteNextImage_triggered()
 {
-	ui->imageReferencesList->nextImageReference();
+	grinder()->projectController().switchToNextImageReference();
 	on_actExecuteLabel_triggered();
 }
 
 void GrinderWindow::on_actExecutePreviousImage_triggered()
 {
-	ui->imageReferencesList->previousImageReference();
+	grinder()->projectController().switchToPreviousImageReference();
 	on_actExecuteLabel_triggered();
 }
 
@@ -240,8 +240,6 @@ bool GrinderWindow::loadProject(QString fileName)
 {
 	bool result = true;
 
-	setEnabled(false);
-
 	try {
 		grinder()->projectController().loadProject(fileName);
 		ui->statusBar->showMessage(QString{"Loaded project '%1'"}.arg(fileName));
@@ -252,8 +250,6 @@ bool GrinderWindow::loadProject(QString fileName)
 		result = false;
 	}
 
-	setEnabled(true);
-
 	return result;
 }
 
@@ -261,8 +257,6 @@ bool GrinderWindow::saveProject(QString fileName)
 {
 	bool result = true;
 
-	setEnabled(false);
-
 	try {
 		grinder()->projectController().saveProject(fileName);
 		ui->statusBar->showMessage(QString{"Saved the current project to '%1'"}.arg(fileName));
@@ -272,8 +266,6 @@ bool GrinderWindow::saveProject(QString fileName)
 		result = false;
 	}
 
-	setEnabled(true);
-
 	return result;
 }
 
diff --git a/Grinder/ui/mainwnd/GrinderWindow.ui b/Grinder/ui/mainwnd/GrinderWindow.ui
index 151dbb8e43e3703c3762ef44ff488b2ffae03c98..1896b07739778cdcdfe3b30e3b17930a18c2767b 100644
--- a/Grinder/ui/mainwnd/GrinderWindow.ui
+++ b/Grinder/ui/mainwnd/GrinderWindow.ui
@@ -649,7 +649,7 @@
   <customwidget>
    <class>PropertyTreeWidget</class>
    <extends>QTreeWidget</extends>
-   <header>ui/property/PropertyTreeWidget.h</header>
+   <header>ui/properties/PropertyTreeWidget.h</header>
   </customwidget>
   <customwidget>
    <class>GrinderDockWidget</class>
diff --git a/Grinder/ui/mainwnd/ImageReferencesListWidget.cpp b/Grinder/ui/mainwnd/ImageReferencesListWidget.cpp
index 8359d9af6bd4539bf2c3fe4ea8947620e1471d62..28c892818f56e858c4d5cb9fdc3a65f5195f1045 100644
--- a/Grinder/ui/mainwnd/ImageReferencesListWidget.cpp
+++ b/Grinder/ui/mainwnd/ImageReferencesListWidget.cpp
@@ -127,6 +127,16 @@ void ImageReferencesListWidget::dropEvent(QDropEvent* event)
 		event->ignore();
 }
 
+void ImageReferencesListWidget::nextImageReference() const
+{
+	grinder()->projectController().switchToNextImageReference();
+}
+
+void ImageReferencesListWidget::previousImageReference() const
+{
+	grinder()->projectController().switchToPreviousImageReference();
+}
+
 void ImageReferencesListWidget::viewImageReference()
 {
 	if (auto item = currentObjectItem())
@@ -193,24 +203,3 @@ void ImageReferencesListWidget::updateActions()
 	_removeImageReferenceAction->setEnabled(imageSelected);
 	_removeAllImageReferencesAction->setEnabled(count() > 0);
 }
-
-void ImageReferencesListWidget::advanceCurrentImageReference(bool goUp)
-{
-	int index = findObjectItemIndex(activeObjectItem());
-
-	if (index != -1)
-	{
-		if (goUp)
-		{
-			if (--index < 0)
-				index = count() - 1;
-		}
-		else
-		{
-			if (++index >= count())
-				index = 0;
-		}
-
-		switchToObjectItem(objectItem(index), false);
-	}
-}
diff --git a/Grinder/ui/mainwnd/ImageReferencesListWidget.h b/Grinder/ui/mainwnd/ImageReferencesListWidget.h
index 00108585c447fc7784c95de3dbd33b1eeace77fb..aab2839b4d6e21717d037629aabfd6be67e1156c 100644
--- a/Grinder/ui/mainwnd/ImageReferencesListWidget.h
+++ b/Grinder/ui/mainwnd/ImageReferencesListWidget.h
@@ -24,10 +24,6 @@ namespace grndr
 	public:
 		ImageReferencesListWidget(QWidget* parent = nullptr);
 
-	public slots:
-		void nextImageReference() { advanceCurrentImageReference(); }
-		void previousImageReference() { advanceCurrentImageReference(true); }
-
 	protected:
 		virtual std::unique_ptr<QMenu> createContextMenu() const override;
 
@@ -43,6 +39,8 @@ namespace grndr
 
 	private slots:
 		void switchImageReference() { switchToObjectItem(currentObjectItem()); }
+		void nextImageReference() const;
+		void previousImageReference() const;
 		void viewImageReference();
 		void addImageReferences();
 		void addImageReferences(QStringList files);
@@ -53,9 +51,6 @@ namespace grndr
 
 		void updateActions();
 
-	private:
-		void advanceCurrentImageReference(bool goUp = false);
-
 	private:
 		QAction* _switchImageReferenceAction{nullptr};
 		QAction* _nextImageReferenceAction{nullptr};
diff --git a/Grinder/ui/visscene/VisualNode.cpp b/Grinder/ui/visscene/VisualNode.cpp
index dfcf513a91b60c2185e517dcfdd034493d9f8d9b..ea0ed9afbb17c7e47ef056e6378542cec987fc7e 100644
--- a/Grinder/ui/visscene/VisualNode.cpp
+++ b/Grinder/ui/visscene/VisualNode.cpp
@@ -5,6 +5,7 @@
 
 #include "Grinder.h"
 #include "VisualNode.h"
+#include "VisualSceneView.h"
 #include "util/UIUtils.h"
 
 #include <QMenu>
@@ -84,5 +85,18 @@ void VisualNode::showContextMenu(QPoint pos) const
 		}
 	}
 
-	menu.exec(pos);
+	// Iterate over all scene views to add context menu entries from VisualSceneViews
+	for (const auto& view : _graphicsScene->views())
+	{
+		if (auto visualView = dynamic_cast<VisualSceneView*>(view))
+		{
+			if (_graphicsScene->selectedItems().size() > 1)
+				visualView->prepareNodesContextMenu(menu);
+			else
+				visualView->prepareNodeContextMenu(menu);
+		}
+	}
+
+	if (!menu.isEmpty())
+		menu.exec(pos);
 }
diff --git a/Grinder/ui/visscene/VisualSceneView.cpp b/Grinder/ui/visscene/VisualSceneView.cpp
index cb036364bbe7ce9b21d811b5447f8be8bcb34f79..6dec2a52b530698935c760a2f02f2731bed4862f 100644
--- a/Grinder/ui/visscene/VisualSceneView.cpp
+++ b/Grinder/ui/visscene/VisualSceneView.cpp
@@ -19,7 +19,7 @@ VisualSceneView::VisualSceneView(QWidget* parent) : MetaWidget(parent)
 
 	// Create view actions
 	_selectAllAction = UIUtils::createAction(this, "Select &all", FILE_ICON_SELECTALL, SLOT(selectAllItems()), "Select all items", "Ctrl+A");
-	_deleteSelectedAction = UIUtils::createAction(this, "&Delete selected items", FILE_ICON_DELETE_SELECTED, SLOT(removeSelectedItems()), "Delete the selected items", "Del");
+	_deleteSelectedAction = UIUtils::createAction(this, "&Delete selected item(s)", FILE_ICON_DELETE_SELECTED, SLOT(removeSelectedItems()), "Delete the selected items", "Del");
 
 	_zoomInAction = UIUtils::createAction(this, "Zoom &in", FILE_ICON_ZOOMIN, SLOT(zoomIn()), "Zoom the view in", "Ctrl++");
 	_zoomOutAction = UIUtils::createAction(this, "Zoom &out", FILE_ICON_ZOOMOUT, SLOT(zoomOut()), "Zoom the view out", "Ctrl+-");
diff --git a/Grinder/ui/visscene/VisualSceneView.h b/Grinder/ui/visscene/VisualSceneView.h
index e540c18265d844d65c624681662ec1024f26dee7..2dc88892448d13dbd3538c9b32d46faa58879bf1 100644
--- a/Grinder/ui/visscene/VisualSceneView.h
+++ b/Grinder/ui/visscene/VisualSceneView.h
@@ -40,6 +40,9 @@ namespace grndr
 	public:
 		virtual const VisualSceneStyle& sceneStyle() const = 0;
 
+		virtual void prepareNodeContextMenu(QMenu& menu) const { Q_UNUSED(menu); }
+		virtual void prepareNodesContextMenu(QMenu& menu) const { Q_UNUSED(menu); }
+
 	signals:
 		void zoomChanged(qreal);
 
diff --git a/Grinder/util/SerializationUtils.h b/Grinder/util/SerializationUtils.h
index 8190a56f897c44848178e151d96928bbf6db1aee..29dfe7d35c8bd21de940690c4ad5f1f38a5395f0 100644
--- a/Grinder/util/SerializationUtils.h
+++ b/Grinder/util/SerializationUtils.h
@@ -6,8 +6,8 @@
 #ifndef SERIALIZATIONUTILS_H
 #define SERIALIZATIONUTILS_H
 
-#include "project/serialization/SerializationContext.h"
-#include "project/serialization/DeserializationContext.h"
+#include "common/serialization/SerializationContext.h"
+#include "common/serialization/DeserializationContext.h"
 
 namespace grndr
 {
diff --git a/Grinder/util/UIUtils.cpp b/Grinder/util/UIUtils.cpp
index d6cc7433ab9678b6ff4e70d82dfaf3a003bceb77..9e566d49f40e1cf15bb99186ff56c93f0535684a 100644
--- a/Grinder/util/UIUtils.cpp
+++ b/Grinder/util/UIUtils.cpp
@@ -24,11 +24,10 @@ QAction* UIUtils::createAction(QObject* owner, QString name, QString icon, const
 	}
 
 	if (!shortcut.isEmpty())
-	{
 		action->setShortcut(QKeySequence{shortcut});
-		action->setShortcutContext(context);
-		action->setShortcutVisibleInContextMenu(true);
-	}
+
+	action->setShortcutContext(context);
+	action->setShortcutVisibleInContextMenu(true);
 
 	if (callback)
 		owner->connect(action, SIGNAL(triggered(bool)), receiver ? receiver : owner, callback);