diff --git a/org.fortiss.tooling.base.ui/META-INF/MANIFEST.MF b/org.fortiss.tooling.base.ui/META-INF/MANIFEST.MF
index 2492dbdeffc8235f398f3633019d7c9d9dcbe6f0..6b37b9d9578f382a1d425e57f9fe1f65a5b6ae75 100644
--- a/org.fortiss.tooling.base.ui/META-INF/MANIFEST.MF
+++ b/org.fortiss.tooling.base.ui/META-INF/MANIFEST.MF
@@ -23,7 +23,6 @@ Export-Package: org.fortiss.tooling.base.ui,
  org.fortiss.tooling.base.ui.dnd.jface,
  org.fortiss.tooling.base.ui.editor,
  org.fortiss.tooling.base.ui.editor.annotations,
- org.fortiss.tooling.base.ui.editor.fx,
  org.fortiss.tooling.base.ui.editor.fx.controller,
  org.fortiss.tooling.base.ui.editor.fx.model,
  org.fortiss.tooling.base.ui.editor.fx.visual,
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/.ratings b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/.ratings
deleted file mode 100644
index 21108b1edd907c85abeb1a45d9b92e0fcc5e7941..0000000000000000000000000000000000000000
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/.ratings
+++ /dev/null
@@ -1,9 +0,0 @@
-ContextMenuUtil.java 3e04dbd52ee99a59735d39b4087c55b07fb91076 GREEN
-EObjectBasedDiagramControllerBase.java 3564ece9b3cac29caaf9201e0be3451a6a3c2b21 GREEN
-EObjectBasedRectangularResizableContentControllerBase.java a263a6c123ee96015e0a5c37b6e3c13ebd0e8a75 GREEN
-EObjectModelChangeProvider.java 664e4db2e32124832682aff8235ac380ed4db1c0 GREEN
-KernelServiceBasedModelChangeProviderBase.java 7c30347fb0c7cc4aacc79aeaf85167872afc1958 GREEN
-LayoutModelElementModelChangeProvider.java 70c50b789c0088a7e5363bf662bc707a5b5ddf10 GREEN
-LayoutedModelElementBasedContentAnchorageController.java c4dfb9dbcd16078c63321d5dec13a902e4088949 GREEN
-LayoutedModelElementBasedDiagramAnchorageController.java 8e66f7d8207d2b6a03abaa151f94b51449ad66c7 GREEN
-LayoutedModelElementBasedLinkBendPointController.java eb00b6119c02199b53b1a8e3cee2687f835b727c GREEN
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/.ratings b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/.ratings
index 4811e9c2991d2e451beef74b3519eb1eeb70d8b0..1261d17460077cfb5a3faca399aa9f9321a5e0ac 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/.ratings
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/.ratings
@@ -1,10 +1,10 @@
-ContextMenuUtil.java 7be87ce47b775d90c533078e22d4d445d9864caf GREEN
-EObjectDiagramController.java 66b14c1c77953cad7caecae1e04f455958943f28 GREEN
-EObjectModelChangeProvider.java f4b60cebb088a5c81ca92a41614e1a5d40030502 GREEN
-EObjectRectangularResizableContentControllerBase.java 787a540213f29e8daaecd9afe98af8b3f4088db7 GREEN
-KernelServiceBasedModelChangeProviderBase.java 18e48f17ea8dfba90de024a8959fc5a4b0d05d45 GREEN
-LayoutModelChangeProvider.java b5449d02eaf39086909720c43e21bd061005fc9e GREEN
-LayoutedContentAnchorageController.java 3794b41d76e9ce14ead0bd812cde5c1a6d348d5c GREEN
-LayoutedDiagramAnchorageController.java 1e1ac7c5fa26c632736f5023e90f05d09bc0710d GREEN
-LayoutedLinkBendPointController.java a8372485ae96f2abf773d1baeb1f8c7b2b25985f GREEN
-LayoutedRectangularResizableContentController.java 1e18af3ee10dd3754325ed389fed664da65a0b61 GREEN
+ContextMenuUtil.java 36184cc3f383d294463336a8318c61e98fd7d2d4 YELLOW
+EObjectDiagramController.java 9af59e8e586c8251d174108a2ce2fcdee5e75782 YELLOW
+EObjectModelChangeProvider.java f4b60cebb088a5c81ca92a41614e1a5d40030502 YELLOW
+EObjectRectangularResizableContentControllerBase.java f4a967591a60fadb20550ec3eaabccf240c9ec0d YELLOW
+KernelServiceBasedModelChangeProviderBase.java 8d1f8ef79ecd383ff74e5a2bbcf24345aabe70af YELLOW
+LayoutModelChangeProvider.java b5449d02eaf39086909720c43e21bd061005fc9e YELLOW
+LayoutedContentAnchorageController.java 73b103c06edcbf1762654e71a3e524f43c0c50c0 YELLOW
+LayoutedDiagramAnchorageController.java 32d7d77daf252d021458c39ebcfe502f26f29a98 YELLOW
+LayoutedLinkBendPointController.java 6d5de856f513ca82eab805a0ad9cda8194be011d YELLOW
+LayoutedRectangularResizableContentController.java 3232d423572924363702898cf8ba240ce7042b65 YELLOW
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/ContextMenuUtil.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/ContextMenuUtil.java
index 7be87ce47b775d90c533078e22d4d445d9864caf..36184cc3f383d294463336a8318c61e98fd7d2d4 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/ContextMenuUtil.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/ContextMenuUtil.java
@@ -21,8 +21,8 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.eclipse.emf.ecore.EObject;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramViewer;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramViewerFeatures;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerFeatures;
 import org.fortiss.tooling.base.dnd.ElementDropContext;
 import org.fortiss.tooling.base.model.layout.Point;
 import org.fortiss.tooling.kernel.extension.data.IElementCompositionContext;
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/EObjectDiagramController.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/EObjectDiagramController.java
index 66b14c1c77953cad7caecae1e04f455958943f28..9af59e8e586c8251d174108a2ce2fcdee5e75782 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/EObjectDiagramController.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/EObjectDiagramController.java
@@ -22,11 +22,11 @@ import static org.fortiss.tooling.base.ui.editor.fx.controller.ContextMenuUtil.c
 import java.util.List;
 
 import org.eclipse.emf.ecore.EObject;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramCoordinate;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramViewerFeatures;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.base.ControllerBase;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.IModelChangeProvider;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerFeatures;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.ControllerBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelChangeProvider;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
 import org.fortiss.tooling.kernel.extension.data.IElementCompositionContext;
 import org.fortiss.tooling.kernel.service.IElementCompositorService;
 import org.fortiss.tooling.kernel.ui.service.IContextMenuService;
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/EObjectRectangularResizableContentControllerBase.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/EObjectRectangularResizableContentControllerBase.java
index 787a540213f29e8daaecd9afe98af8b3f4088db7..f4a967591a60fadb20550ec3eaabccf240c9ec0d 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/EObjectRectangularResizableContentControllerBase.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/EObjectRectangularResizableContentControllerBase.java
@@ -24,19 +24,19 @@ import java.util.List;
 
 import org.eclipse.emf.common.notify.Notification;
 import org.eclipse.emf.ecore.EObject;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramCoordinate;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramViewerFeatures;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.change.Change;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.IClickController;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.IDragController;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.base.ClickControllerBase;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.rectangular.RectangularContentAnchorageMoveController;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.rectangular.RectangularResizableContentControllerBase;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.IModelChangeProvider;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
 import org.fortiss.tooling.base.model.element.ElementPackage;
 import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerFeatures;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.Change;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IClickController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IDragController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.ClickControllerBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.rectangular.RectangularContentAnchorageMoveController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.rectangular.RectangularResizableContentControllerBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelChangeProvider;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
 import org.fortiss.tooling.kernel.extension.data.IElementCompositionContext;
 import org.fortiss.tooling.kernel.service.IElementCompositorService;
 import org.fortiss.tooling.kernel.ui.extension.IModelElementHandler;
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/KernelServiceBasedModelChangeProviderBase.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/KernelServiceBasedModelChangeProviderBase.java
index 18e48f17ea8dfba90de024a8959fc5a4b0d05d45..8d1f8ef79ecd383ff74e5a2bbcf24345aabe70af 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/KernelServiceBasedModelChangeProviderBase.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/KernelServiceBasedModelChangeProviderBase.java
@@ -23,8 +23,8 @@ import org.eclipse.emf.common.command.CommandStackListener;
 import org.eclipse.emf.common.notify.Adapter;
 import org.eclipse.emf.common.notify.Notification;
 import org.eclipse.emf.ecore.EObject;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.IModelChangeProvider;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.ModelChangeProviderBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelChangeProvider;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.ModelChangeProviderBase;
 import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
 import org.fortiss.tooling.kernel.service.ICommandStackService;
 import org.fortiss.tooling.kernel.service.IPersistencyService;
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/LayoutedContentAnchorageController.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/LayoutedContentAnchorageController.java
index 3794b41d76e9ce14ead0bd812cde5c1a6d348d5c..73b103c06edcbf1762654e71a3e524f43c0c50c0 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/LayoutedContentAnchorageController.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/LayoutedContentAnchorageController.java
@@ -14,16 +14,16 @@
 package org.fortiss.tooling.base.ui.editor.fx.controller;
 
 import static java.util.Objects.requireNonNull;
-import static org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_ALLOWED_TAG;
-import static org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_DENIED_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_ALLOWED_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_DENIED_TAG;
 
 import org.eclipse.emf.ecore.EObject;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramCoordinate;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.base.DelegatingContentAnchorageController;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.IModelChangeProvider;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.MVCBundleTag;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.DelegatingContentAnchorageController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelChangeProvider;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.MVCBundleTag;
 import org.fortiss.tooling.base.model.element.IConnector;
 import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
 import org.fortiss.tooling.kernel.service.IConnectionCompositorService;
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/LayoutedDiagramAnchorageController.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/LayoutedDiagramAnchorageController.java
index 1e1ac7c5fa26c632736f5023e90f05d09bc0710d..32d7d77daf252d021458c39ebcfe502f26f29a98 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/LayoutedDiagramAnchorageController.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/LayoutedDiagramAnchorageController.java
@@ -14,17 +14,17 @@
 package org.fortiss.tooling.base.ui.editor.fx.controller;
 
 import static java.util.Objects.requireNonNull;
-import static org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_ALLOWED_TAG;
-import static org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_DENIED_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_ALLOWED_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_DENIED_TAG;
 import static org.fortiss.tooling.base.utils.LayoutDataUtils.moveNode;
 
 import org.eclipse.emf.ecore.EObject;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramCoordinate;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.FeedbackChange;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.base.MoveControllerBase;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.IModelChangeProvider;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.MVCBundleTag;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.MoveControllerBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelChangeProvider;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.MVCBundleTag;
 import org.fortiss.tooling.base.model.element.IConnector;
 import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
 import org.fortiss.tooling.kernel.service.IConnectionCompositorService;
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/LayoutedLinkBendPointController.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/LayoutedLinkBendPointController.java
index a8372485ae96f2abf773d1baeb1f8c7b2b25985f..6d5de856f513ca82eab805a0ad9cda8194be011d 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/LayoutedLinkBendPointController.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/LayoutedLinkBendPointController.java
@@ -21,13 +21,13 @@ import static org.fortiss.tooling.base.ui.utils.LayoutDataUIUtils.removeConnecti
 import java.util.List;
 
 import org.eclipse.emf.common.util.EList;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramCoordinate;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramViewer;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.IController;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.base.LinkControllerBase;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.IModelChangeProvider;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.LinkControllerBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelChangeProvider;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
 import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
 import org.fortiss.tooling.base.model.layout.Point;
 import org.fortiss.tooling.kernel.service.IConnectionCompositorService;
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/LayoutedRectangularResizableContentController.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/LayoutedRectangularResizableContentController.java
index 1e18af3ee10dd3754325ed389fed664da65a0b61..3232d423572924363702898cf8ba240ce7042b65 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/LayoutedRectangularResizableContentController.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/controller/LayoutedRectangularResizableContentController.java
@@ -22,10 +22,10 @@ import static org.fortiss.tooling.base.utils.LayoutDataUtils.moveNode;
 import static org.fortiss.tooling.base.utils.LayoutDataUtils.setNodeSize;
 import static org.fortiss.tooling.base.utils.LayoutDataUtils.setStickyConnectorLayoutData;
 
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.FeedbackChange;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.IController;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
 import org.fortiss.tooling.base.model.element.IConnector;
 import org.fortiss.tooling.base.model.layout.Dimension;
 import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/model/.ratings b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/model/.ratings
index 1f94f2987b843eba33765b057f6d210e4bbbcbb4..e486bea097082450923a8577024abfdd76030929 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/model/.ratings
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/model/.ratings
@@ -1 +1 @@
-HierarchicElementModelFactoryBase.java 9996bfc1402c27424f0ae69e64560acce0ef81f4 GREEN
+HierarchicElementModelFactoryBase.java 10741fec431df8c05038e73b6b40ba9c7c35fa57 YELLOW
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/model/HierarchicElementModelFactoryBase.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/model/HierarchicElementModelFactoryBase.java
index 9996bfc1402c27424f0ae69e64560acce0ef81f4..10741fec431df8c05038e73b6b40ba9c7c35fa57 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/model/HierarchicElementModelFactoryBase.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/model/HierarchicElementModelFactoryBase.java
@@ -21,7 +21,7 @@ import static org.fortiss.tooling.common.util.LambdaUtils.filterList;
 
 import java.util.List;
 
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.IModelFactory;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelFactory;
 import org.fortiss.tooling.base.model.element.IConnection;
 import org.fortiss.tooling.base.model.element.IHierarchicElement;
 
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/.ratings b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/.ratings
index 5a82836cd656dbc37a323c788161b6ffab643988..a7dd2ab254be3d36a5e8d5b555f759f9f3f9568e 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/.ratings
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/.ratings
@@ -1,9 +1,9 @@
-CoordinateCorrections.java c5cc475b45de38c56fc2e888a2d3093cd2efb52a GREEN
-LayoutedCircularAnchorageContentVisualBase.java 2aa292444671bf644e37bc923b877c92de0235b1 GREEN
-LayoutedCircularAnchorageDiagramVisualBase.java f06e3b908020408e3cd268a3c63edcd75ef74f8b GREEN
-LayoutedLineLinkVisual.java feea85e0bd288590fbe06c152a8a8b138ea85ca2 GREEN
-LayoutedRectangularContentVisualBase.java c2a3937b99284713e0bbcd3ce458874567b25ac5 GREEN
-NamedLayoutedCircularAnchorageContentVisual.java c680002469ce897679fa5a3f4af51d1b19cb53d6 GREEN
-NamedLayoutedCircularAnchorageDiagramVisual.java 714a176a0569a2049efb4009f710ca66bf3a57fb GREEN
-NamedLayoutedLineLinkVisual.java e66e5b2aaa40fe8b22a292e175bb8f3af4539b9d GREEN
-NamedLayoutedRectangularContentVisual.java 122e193ac587857d1dad23b42583a0bcf465f0d0 GREEN
+CoordinateCorrections.java 018bf229e5686afcb8540b61dd9d05b6e4a23e93 YELLOW
+LayoutedCircularAnchorageContentVisualBase.java dba8ae04ecd5813aae9b29eaccea520caa3cf237 YELLOW
+LayoutedCircularAnchorageDiagramVisualBase.java 7634416bcb88a014d985143bf00a8d29ff1e3ff5 YELLOW
+LayoutedLineLinkVisual.java ff1291c57d4ce111d5543d7381a616cd2e096290 YELLOW
+LayoutedRectangularContentVisualBase.java 61698ffd771ee2ad798025df8195d1bc09c2c765 YELLOW
+NamedLayoutedCircularAnchorageContentVisual.java bf06ac6e93d78e98b0359e0f879dccaefc920aa0 YELLOW
+NamedLayoutedCircularAnchorageDiagramVisual.java 53b9d739587d658f65dc03517257d6e29f4ba1fa YELLOW
+NamedLayoutedLineLinkVisual.java 60f5d21f0723dc5d08b2ad43bc6361fca83a16aa YELLOW
+NamedLayoutedRectangularContentVisual.java 8cdc55b306c1db60074fa8c5c240f95d892e1e32 YELLOW
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/CoordinateCorrections.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/CoordinateCorrections.java
index c5cc475b45de38c56fc2e888a2d3093cd2efb52a..018bf229e5686afcb8540b61dd9d05b6e4a23e93 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/CoordinateCorrections.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/CoordinateCorrections.java
@@ -17,8 +17,8 @@ package org.fortiss.tooling.base.ui.editor.fx.visual;
 
 import static org.fortiss.tooling.base.layout.DefaultLayoutConstants.DEFAULT_CONNECTOR_SIZE;
 
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.base.ContentAnchorageVisualBase;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.rectangular.RectangularContentVisualBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base.ContentAnchorageVisualBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular.RectangularContentVisualBase;
 import org.fortiss.tooling.base.layout.DefaultLayoutConstants;
 
 import javafx.geometry.Dimension2D;
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/LayoutedCircularAnchorageContentVisualBase.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/LayoutedCircularAnchorageContentVisualBase.java
index 2aa292444671bf644e37bc923b877c92de0235b1..dba8ae04ecd5813aae9b29eaccea520caa3cf237 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/LayoutedCircularAnchorageContentVisualBase.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/LayoutedCircularAnchorageContentVisualBase.java
@@ -17,18 +17,18 @@ package org.fortiss.tooling.base.ui.editor.fx.visual;
 
 import static javafx.scene.paint.Color.BLACK;
 import static javafx.scene.paint.Color.rgb;
-import static org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_ALLOWED_TAG;
-import static org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_DENIED_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_ALLOWED_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_DENIED_TAG;
 import static org.fortiss.tooling.base.ui.editor.fx.visual.CoordinateCorrections.ANCHOR_DIMENSION;
 import static org.fortiss.tooling.base.ui.editor.fx.visual.CoordinateCorrections.ANCHOR_INSET;
 import static org.fortiss.tooling.base.ui.utils.LWFXEditorUtils.convertEOrientationToSide;
 import static org.fortiss.tooling.base.ui.utils.LayoutDataUIUtils.getConnectorOffsetOrientation;
 
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.layout.ILayout;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.layout.IOffsetLayout;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.layout.ISideLayout;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.elliptic.CircularContentAnchorageVisualBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout.ILayout;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout.IOffsetLayout;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout.ISideLayout;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.elliptic.CircularContentAnchorageVisualBase;
 import org.fortiss.tooling.base.model.layout.EOrientation;
 import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
 import org.fortiss.tooling.base.model.layout.OffsetOrientation;
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/LayoutedCircularAnchorageDiagramVisualBase.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/LayoutedCircularAnchorageDiagramVisualBase.java
index f06e3b908020408e3cd268a3c63edcd75ef74f8b..7634416bcb88a014d985143bf00a8d29ff1e3ff5 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/LayoutedCircularAnchorageDiagramVisualBase.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/LayoutedCircularAnchorageDiagramVisualBase.java
@@ -17,14 +17,14 @@ package org.fortiss.tooling.base.ui.editor.fx.visual;
 
 import static javafx.scene.paint.Color.BLACK;
 import static javafx.scene.paint.Color.rgb;
-import static org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_ALLOWED_TAG;
-import static org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_DENIED_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_ALLOWED_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_DENIED_TAG;
 import static org.fortiss.tooling.base.ui.editor.fx.visual.CoordinateCorrections.ANCHOR_DIMENSION;
 import static org.fortiss.tooling.base.ui.editor.fx.visual.CoordinateCorrections.ANCHOR_INSET;
 import static org.fortiss.tooling.base.utils.LayoutDataUtils.getNodePosition;
 
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IDiagramAnchorageMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.elliptic.CircularDiagramAnchorageVisualBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.elliptic.CircularDiagramAnchorageVisualBase;
 import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
 import org.fortiss.tooling.base.model.layout.Point;
 
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/LayoutedLineLinkVisual.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/LayoutedLineLinkVisual.java
index feea85e0bd288590fbe06c152a8a8b138ea85ca2..ff1291c57d4ce111d5543d7381a616cd2e096290 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/LayoutedLineLinkVisual.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/LayoutedLineLinkVisual.java
@@ -21,9 +21,9 @@ import static org.fortiss.tooling.base.ui.utils.LayoutDataUIUtils.getConnectionP
 
 import java.util.List;
 
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramCoordinate;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.rectangular.LineLinkVisualBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular.LineLinkVisualBase;
 import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
 import org.fortiss.tooling.base.model.layout.Point;
 import org.fortiss.tooling.base.model.layout.Points;
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/LayoutedRectangularContentVisualBase.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/LayoutedRectangularContentVisualBase.java
index c2a3937b99284713e0bbcd3ce458874567b25ac5..61698ffd771ee2ad798025df8195d1bc09c2c765 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/LayoutedRectangularContentVisualBase.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/LayoutedRectangularContentVisualBase.java
@@ -19,13 +19,13 @@ import static java.lang.Math.min;
 import static org.fortiss.tooling.base.ui.editor.fx.visual.CoordinateCorrections.RECTANGLE_INSETS;
 import static org.fortiss.tooling.base.utils.LayoutDataUtils.getNodeBounds;
 
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramCoordinate;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.layout.IOffsetLayout;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.layout.ISideLayout;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.IContentAnchorageVisual;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.rectangular.RectangularBorderLocation;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.rectangular.RectangularContentVisualBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout.IOffsetLayout;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout.ISideLayout;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentAnchorageVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular.RectangularBorderLocation;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular.RectangularContentVisualBase;
 import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
 import org.fortiss.tooling.base.model.layout.Rectangle;
 
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/NamedLayoutedCircularAnchorageContentVisual.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/NamedLayoutedCircularAnchorageContentVisual.java
index c680002469ce897679fa5a3f4af51d1b19cb53d6..bf06ac6e93d78e98b0359e0f879dccaefc920aa0 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/NamedLayoutedCircularAnchorageContentVisual.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/NamedLayoutedCircularAnchorageContentVisual.java
@@ -15,8 +15,8 @@
 +--------------------------------------------------------------------------*/
 package org.fortiss.tooling.base.ui.editor.fx.visual;
 
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.IVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisual;
 import org.fortiss.tooling.base.model.element.IConnector;
 import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
 import org.fortiss.tooling.kernel.model.INamedElement;
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/NamedLayoutedCircularAnchorageDiagramVisual.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/NamedLayoutedCircularAnchorageDiagramVisual.java
index 714a176a0569a2049efb4009f710ca66bf3a57fb..53b9d739587d658f65dc03517257d6e29f4ba1fa 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/NamedLayoutedCircularAnchorageDiagramVisual.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/NamedLayoutedCircularAnchorageDiagramVisual.java
@@ -15,8 +15,8 @@
 +--------------------------------------------------------------------------*/
 package org.fortiss.tooling.base.ui.editor.fx.visual;
 
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IDiagramAnchorageMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.IVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisual;
 import org.fortiss.tooling.base.model.element.IConnector;
 import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
 import org.fortiss.tooling.kernel.model.INamedElement;
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/NamedLayoutedLineLinkVisual.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/NamedLayoutedLineLinkVisual.java
index e66e5b2aaa40fe8b22a292e175bb8f3af4539b9d..60f5d21f0723dc5d08b2ad43bc6361fca83a16aa 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/NamedLayoutedLineLinkVisual.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/NamedLayoutedLineLinkVisual.java
@@ -15,8 +15,8 @@
 +--------------------------------------------------------------------------*/
 package org.fortiss.tooling.base.ui.editor.fx.visual;
 
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.IVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisual;
 import org.fortiss.tooling.base.model.element.IConnection;
 import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
 import org.fortiss.tooling.kernel.model.INamedElement;
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/NamedLayoutedRectangularContentVisual.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/NamedLayoutedRectangularContentVisual.java
index 122e193ac587857d1dad23b42583a0bcf465f0d0..8cdc55b306c1db60074fa8c5c240f95d892e1e32 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/NamedLayoutedRectangularContentVisual.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/fx/visual/NamedLayoutedRectangularContentVisual.java
@@ -15,7 +15,7 @@
 +--------------------------------------------------------------------------*/
 package org.fortiss.tooling.base.ui.editor.fx.visual;
 
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
 import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
 import org.fortiss.tooling.kernel.model.INamedElement;
 
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/utils/.ratings b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/utils/.ratings
index 27fbc6fb9f974296b2e91d4738b5ba7289852c08..d2fc13e9d443fbc1e3005b3723a68a1c72519d3a 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/utils/.ratings
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/utils/.ratings
@@ -1,11 +1,10 @@
 AbstractNameEditingSupport.java c57336a0e0da18711a1610ca667dfea76728807f GREEN
 ActionUtils.java 322f43d4f92f992daef8ac88eb0f9197c840c89b GREEN
-ConstraintsBaseUIUtils.java 713512da44cc0ebd9bcd5c11abee87c86c94088d GREEN
 DragAndDropBaseUtils.java d375377f9124f6113b2a295e6b0e09ac8966e564 GREEN
 EllipseLayoutUIUtils.java 4dd9dbd96a45e8c455c019caa19e4a50f18336af GREEN
 FontUtils.java a167a05bdaa8da9853705cc5134f30f6d81bc9f2 GREEN
 GCStateManager.java 983973a92376b5c757c1253b32e33d0666ccdf7b GREEN
-LWFXEditorUtils.java c628e79fa43c1be8a7b87ee90db6e7264904eafc GREEN
+LWFXEditorUtils.java c624d3f0f6487b6d426b168dad048b2c39e71114 YELLOW
 LayoutDataUIUtils.java c85886ac313a6efb122532218eb134047ffd6631 GREEN
 PropertiesViewUtils.java d345b4501c4092228edf1c98e0189317d53aaf22 GREEN
 RectangleLayoutUIUtils.java ef4b872bb5b4a51174e9a29d9ef05e7cb3bff3a1 GREEN
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/utils/LWFXEditorUtils.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/utils/LWFXEditorUtils.java
index c628e79fa43c1be8a7b87ee90db6e7264904eafc..c624d3f0f6487b6d426b168dad048b2c39e71114 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/utils/LWFXEditorUtils.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/utils/LWFXEditorUtils.java
@@ -19,7 +19,7 @@ import static java.lang.Math.atan2;
 import static java.lang.Math.cos;
 import static java.lang.Math.sin;
 
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
 import org.fortiss.tooling.base.model.layout.EOrientation;
 
 import javafx.geometry.Point2D;
diff --git a/org.fortiss.tooling.common.ui/.classpath b/org.fortiss.tooling.common.ui/.classpath
index d9ed18f5cea4deb235213896eef764feded19dee..0920a6fed0bb5a3313c4f46b95ea1fcb99101429 100644
--- a/org.fortiss.tooling.common.ui/.classpath
+++ b/org.fortiss.tooling.common.ui/.classpath
@@ -3,8 +3,6 @@
 	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="src" path="external-src"/>
 	<classpathentry kind="src" path="res"/>
-	<classpathentry exported="true" kind="lib" path="lib/org.eclipse.systemfocus.kernel.common_0.0.5.20191219.jar"/>
-	<classpathentry exported="true" kind="lib" path="lib/org.eclipse.systemfocus.kernel.common.ui_0.0.5.20191219.jar"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
 		<attributes>
 			<attribute name="module" value="true"/>
diff --git a/org.fortiss.tooling.common.ui/META-INF/MANIFEST.MF b/org.fortiss.tooling.common.ui/META-INF/MANIFEST.MF
index f158eb873beefe27aacb70e4e70c12c3efcf333b..d94aac1c47acf866e4d32c46ffa14022a84a7b3e 100644
--- a/org.fortiss.tooling.common.ui/META-INF/MANIFEST.MF
+++ b/org.fortiss.tooling.common.ui/META-INF/MANIFEST.MF
@@ -24,9 +24,7 @@ Require-Bundle: org.fortiss.tooling.common;visibility:=reexport,
 Bundle-ClassPath: .,
  lib/org.conqat.ide.commons.gef.jar,
  lib/org.conqat.ide.commons.ui.jar,
- lib/swt-grouplayout.jar,
- lib/org.eclipse.systemfocus.kernel.common_0.0.5.20191219.jar,
- lib/org.eclipse.systemfocus.kernel.common.ui_0.0.5.20191219.jar
+ lib/swt-grouplayout.jar
 Export-Package: aerofx,
  aquafx,
  jfxtras,
@@ -74,25 +72,6 @@ Export-Package: aerofx,
  org.eclipse.gmf.runtime.draw2d.ui.figures,
  org.eclipse.gmf.runtime.draw2d.ui.geometry,
  org.eclipse.jface.viewers,
- org.eclipse.systemfocus.kernel.common.ui.javafx.control.treetableview,
- org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef,
- org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.change,
- org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller,
- org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.base,
- org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.elliptic,
- org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.rectangular,
- org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model,
- org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.layout,
- org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc,
- org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.impl,
- org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual,
- org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.base,
- org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.elliptic,
- org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.rectangular,
- org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.widgets,
- org.eclipse.systemfocus.kernel.common.ui.javafx.mesh,
- org.eclipse.systemfocus.kernel.common.ui.javafx.style,
- org.eclipse.systemfocus.kernel.common.ui.javafx.util,
  org.eclipse.ui.actions,
  org.eclipse.wb.swt,
  org.eclipse.wb.swt.layout.grouplayout,
@@ -101,6 +80,21 @@ Export-Package: aerofx,
  org.fortiss.tooling.common.ui.javafx,
  org.fortiss.tooling.common.ui.javafx.control.treetableview,
  org.fortiss.tooling.common.ui.javafx.layout,
+ org.fortiss.tooling.common.ui.javafx.lwfxef,
+ org.fortiss.tooling.common.ui.javafx.lwfxef.change,
+ org.fortiss.tooling.common.ui.javafx.lwfxef.controller,
+ org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base,
+ org.fortiss.tooling.common.ui.javafx.lwfxef.controller.elliptic,
+ org.fortiss.tooling.common.ui.javafx.lwfxef.controller.rectangular,
+ org.fortiss.tooling.common.ui.javafx.lwfxef.model,
+ org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout,
+ org.fortiss.tooling.common.ui.javafx.lwfxef.mvc,
+ org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl,
+ org.fortiss.tooling.common.ui.javafx.lwfxef.visual,
+ org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base,
+ org.fortiss.tooling.common.ui.javafx.lwfxef.visual.elliptic,
+ org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular,
+ org.fortiss.tooling.common.ui.javafx.lwfxef.visual.widgets,
  org.fortiss.tooling.common.ui.javafx.util
 Bundle-Vendor: fortiss GmbH
 Bundle-Activator: org.fortiss.tooling.common.ui.ToolingCommonUIActivator
diff --git a/org.fortiss.tooling.common.ui/build.properties b/org.fortiss.tooling.common.ui/build.properties
index 047e606ce9d79664f75aab85d5fb16ee0ae09b08..ab1e37fb1bbf7fdb879ccf70079b428aacb1e8ff 100644
--- a/org.fortiss.tooling.common.ui/build.properties
+++ b/org.fortiss.tooling.common.ui/build.properties
@@ -5,9 +5,7 @@ bin.includes = .,\
                lib/org.conqat.ide.commons.gef.jar,\
                lib/org.conqat.ide.commons.ui.jar,\
                lib/swt-grouplayout.jar,\
-               res/,\
-               lib/org.eclipse.systemfocus.kernel.common_0.0.5.20191219.jar,\
-               lib/org.eclipse.systemfocus.kernel.common.ui_0.0.5.20191219.jar
+               res/
 jars.compile.order = .
 source.. = src/,\
            res/,\
diff --git a/org.fortiss.tooling.common.ui/lib/org.eclipse.systemfocus.kernel.common.ui_0.0.5.20191219.jar b/org.fortiss.tooling.common.ui/lib/org.eclipse.systemfocus.kernel.common.ui_0.0.5.20191219.jar
deleted file mode 100644
index 395895b74e4f88edcd5118c917fd2ab6b413e057..0000000000000000000000000000000000000000
Binary files a/org.fortiss.tooling.common.ui/lib/org.eclipse.systemfocus.kernel.common.ui_0.0.5.20191219.jar and /dev/null differ
diff --git a/org.fortiss.tooling.common.ui/lib/org.eclipse.systemfocus.kernel.common_0.0.5.20191219.jar b/org.fortiss.tooling.common.ui/lib/org.eclipse.systemfocus.kernel.common_0.0.5.20191219.jar
deleted file mode 100644
index b27db9f8c0e0c56fd798ea2b54927bc768b5ce64..0000000000000000000000000000000000000000
Binary files a/org.fortiss.tooling.common.ui/lib/org.eclipse.systemfocus.kernel.common_0.0.5.20191219.jar and /dev/null differ
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/.ratings b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..daf51f54143284826e353c054984aacc6c4ed77a
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/.ratings
@@ -0,0 +1,12 @@
+DiagramCoordinate.java 6b00aec99054d4cd19003a72bd4e5e774ac6a641 YELLOW
+DiagramLayers.java 155cbb47a5f0aaa0025320ae607e6777f3a2d2e8 YELLOW
+DiagramViewer.java d5ba3f45ddc619132434f9cbefd36f65cce18b65 YELLOW
+DiagramViewerDefaultTags.java 6230763252409c60009ab8887b4ef582cf883229 YELLOW
+DiagramViewerFeatures.java 31e3fb61f915b0d8695005b083c47ce1c5be0b05 YELLOW
+DiagramViewerSelection.java e833f592543bc97077907d980a39b123fc4044e6 YELLOW
+EDragGesture.java 5cfa098d3877db11981c2750e5e103156d62fc5e YELLOW
+FeedbackChange.java b088fa89af648f1674f2f9c1f7f99d585ce801ca YELLOW
+GridCanvasVisual.java 734027d56af342cd01ff445ba9347b8dbb6c83c2 YELLOW
+MVCBundleManager.java 2b4ab114c55b30a3d98d7135458f8f3ddd244d58 YELLOW
+MouseState.java ff90af6d1cca427ef6f3fded76367b535120a5df YELLOW
+SVGExporter.java 2211f06d81c7b0523ae52dc832410a76875a9e07 YELLOW
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramCoordinate.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramCoordinate.java
new file mode 100644
index 0000000000000000000000000000000000000000..6b00aec99054d4cd19003a72bd4e5e774ac6a641
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramCoordinate.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef;
+
+import javafx.geometry.Bounds;
+import javafx.geometry.Point2D;
+import javafx.scene.Node;
+
+/** Extension of Point2D to improve code understandability when working with coordinates. */
+public final class DiagramCoordinate extends Point2D {
+	/** Constructor. */
+	public DiagramCoordinate(double x, double y) {
+		super(x, y);
+	}
+
+	/** Returns the location of this diagram coordinate relative to the given node. */
+	public Point2D getLocal(Node node) {
+		Bounds boundsInLocal = node.getBoundsInLocal();
+		return new Point2D(getX() - boundsInLocal.getMinX(), getY() - boundsInLocal.getMinY());
+	}
+
+	/** Returns the location of this diagram X coordinate relative to the given node. */
+	public double getLocalX(Node node) {
+		return getX() - node.getBoundsInLocal().getMinX();
+	}
+
+	/** Returns the location of this diagram Y coordinate relative to the given node. */
+	public double getLocalY(Node node) {
+		return getY() - node.getBoundsInLocal().getMinY();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate add(double dx, double dy) {
+		return new DiagramCoordinate(getX() + dx, getY() + dy);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate subtract(double dx, double dy) {
+		return add(-dx, -dy);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate subtract(Point2D point) {
+		return subtract(point.getX(), point.getY());
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramLayers.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramLayers.java
new file mode 100644
index 0000000000000000000000000000000000000000..155cbb47a5f0aaa0025320ae607e6777f3a2d2e8
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramLayers.java
@@ -0,0 +1,216 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+
+import javafx.collections.ObservableList;
+import javafx.scene.Group;
+import javafx.scene.Node;
+import javafx.scene.transform.Scale;
+
+/** {@link Group} of layers for the {@link DiagramViewer}. */
+public final class DiagramLayers extends Group {
+	/** The diagram viewer of this layer group. */
+	private final DiagramViewer viewer;
+	/** The bottom layer. */
+	private final Layer bottomLayer = new Layer();
+	/** The content node layer. */
+	private final Layer contentLayer = new Layer();
+	/** The link node layer. */
+	private final Layer linkLayer = new Layer();
+	/** The anchorage node layer. */
+	private final Layer anchorageLayer = new Layer();
+	/** The text node layer. */
+	private final Layer textLayer = new Layer();
+	/** The feedback layer for visual feedback. */
+	private final Layer visualFeedbackLayer = new Layer();
+	/** The feedback layer for interaction with content objects. */
+	private final Layer contentInteractionLayer = new Layer();
+	/** The feedback layer for interaction with anchorage objects. */
+	private final Layer anchorageInteractionLayer = new Layer();
+	/** The feedback layer for interaction with link objects. */
+	private final Layer linkInteractionLayer = new Layer();
+	/** The top layer. */
+	private final Layer topLayer = new Layer();
+	/** The current mouse drag state associated with this bundle's visual. */
+	private final MouseState mouseState;
+	/** The scale to be applied for zooming the content of the layers. */
+	private Scale scale = new Scale();
+
+	/** Constructor. */
+	public DiagramLayers(DiagramViewer viewer) {
+		this.viewer = viewer;
+		ObservableList<Node> c = getChildren();
+		c.add(bottomLayer);
+		c.add(contentLayer);
+		c.add(anchorageLayer);
+		c.add(linkLayer);
+		c.add(visualFeedbackLayer);
+		c.add(textLayer);
+		c.add(contentInteractionLayer);
+		c.add(anchorageInteractionLayer);
+		c.add(linkInteractionLayer);
+		c.add(topLayer);
+		mouseState = new MouseState(viewer);
+	}
+
+	/** Returns mouseState. */
+	public MouseState getMouseState() {
+		return mouseState;
+	}
+
+	/** Returns the bottom layer. */
+	public ILayer getBottomLayer() {
+		return bottomLayer;
+	}
+
+	/** Returns the content layer. */
+	public ILayer getContentLayer() {
+		return contentLayer;
+	}
+
+	/** Returns the anchorage lLayer. */
+	public ILayer getAnchorageLayer() {
+		return anchorageLayer;
+	}
+
+	/** Returns the link layer. */
+	public ILayer getLinkLayer() {
+		return linkLayer;
+	}
+
+	/** Returns the visual feedback layer. */
+	public ILayer getVisualFeedbackLayer() {
+		return visualFeedbackLayer;
+	}
+
+	/** Returns the text layer. */
+	public ILayer getTextLayer() {
+		return textLayer;
+	}
+
+	/**
+	 * Returns the layer for (usually invisible) nodes used for user interactions with content
+	 * nodes.
+	 */
+	public ILayer getContentInteractionLayer() {
+		return contentInteractionLayer;
+	}
+
+	/**
+	 * Returns the layer for (usually invisible) nodes used for user interactions with anchorage
+	 * nodes.
+	 */
+	public ILayer getAnchorageInteractionLayer() {
+		return anchorageInteractionLayer;
+	}
+
+	/**
+	 * Returns the layer for (usually invisible) nodes used for user interactions with link nodes.
+	 */
+	public ILayer getLinkInteractionLayer() {
+		return linkInteractionLayer;
+	}
+
+	/** Returns the top layer. */
+	public ILayer getTopLayer() {
+		return topLayer;
+	}
+
+	/** Returns whether the given node is an interactive feedback node. */
+	public boolean isInteractionFeedback(Node node) {
+		return node.getParent() == contentInteractionLayer;
+	}
+
+	/** Clears all nodes from the layers. */
+	public void clearLayers() {
+		bottomLayer.clear();
+		contentLayer.clear();
+		anchorageLayer.clear();
+		linkLayer.clear();
+		visualFeedbackLayer.clear();
+		textLayer.clear();
+		contentInteractionLayer.clear();
+		anchorageInteractionLayer.clear();
+		linkInteractionLayer.clear();
+		topLayer.clear();
+	}
+
+	/** Registers the scroll listener and the zoom scale transformation. */
+	private void registerScrollListenerAndZoomScale(Node node) {
+		node.setOnScroll(viewer.getScrollingHandler());
+		node.getTransforms().add(scale);
+	}
+
+	/** Unregisters the scroll listener and the zoom scale transformation. */
+	private void unregisterScrollListenerAndZoomScale(Node node) {
+		node.setOnScroll(null);
+		node.getTransforms().remove(scale);
+	}
+
+	/** Scales all nodes in each layer. */
+	/* package */ void setScale(double factor) {
+		scale.setX(factor);
+		scale.setY(factor);
+		scale.setZ(factor);
+	}
+
+	/** Interface for layers. */
+	public static interface ILayer {
+		/**
+		 * Adds the given bundle's node to this layer. Note that the given node's parent must be
+		 * null. In other words, a node must first be explicitly removed from its former layer and
+		 * then added to the new one.
+		 */
+		void add(Node node, IMVCBundle bundle);
+
+		/** Removes the given node from this layer. */
+		void remove(Node node);
+
+		/** Clears this layer removing all nodes. */
+		void clear();
+	}
+
+	/** Implementation of layers. */
+	private class Layer extends Group implements ILayer {
+		/** {@inheritDoc} */
+		@Override
+		public void add(Node node, IMVCBundle bundle) {
+			if(node == null || bundle == null || node.getParent() != null) {
+				return;
+			}
+			getChildren().add(node);
+			registerScrollListenerAndZoomScale(node);
+			mouseState.registerMouseListeners(node, bundle);
+		}
+
+		/** {@inheritDoc} */
+		@Override
+		public void remove(Node node) {
+			if(node == null || node.getParent() != this) {
+				return;
+			}
+			mouseState.unregisterMouseListeners(node);
+			unregisterScrollListenerAndZoomScale(node);
+			getChildren().remove(node);
+		}
+
+		/** {@inheritDoc} */
+		@Override
+		public void clear() {
+			for(Node node : getChildren()) {
+				mouseState.unregisterMouseListeners(node);
+				unregisterScrollListenerAndZoomScale(node);
+			}
+			getChildren().clear();
+		}
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewer.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewer.java
new file mode 100644
index 0000000000000000000000000000000000000000..d5ba3f45ddc619132434f9cbefd36f65cce18b65
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewer.java
@@ -0,0 +1,797 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef;
+
+import static java.lang.Math.ceil;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+import static java.util.Objects.requireNonNull;
+import static javafx.scene.layout.GridPane.setHgrow;
+import static javafx.scene.layout.GridPane.setVgrow;
+import static javafx.scene.layout.Priority.ALWAYS;
+import static javafx.scene.paint.Color.ORANGERED;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_ALLOWED_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_DENIED_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_MAYBE_TAG;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.Change;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.ChangeSet;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.DefaultModelModifier;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IControllerFactory;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IDragController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IKeyPressController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelFactory;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.MVCBundleTag;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisualFactory;
+
+import javafx.event.EventHandler;
+import javafx.geometry.Bounds;
+import javafx.geometry.Orientation;
+import javafx.geometry.Point2D;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Node;
+import javafx.scene.control.ContextMenu;
+import javafx.scene.control.Label;
+import javafx.scene.control.MenuItem;
+import javafx.scene.control.ScrollBar;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.input.ScrollEvent;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.Pane;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Line;
+import javafx.scene.shape.Rectangle;
+
+/**
+ * This class represents the diagram viewer node, which manages the grid
+ * background and the content visuals as well as scrolling, zooming, selection,
+ * context menus and link creation. Due to the number of features and functions,
+ * part of this class delegates to a {@link MVCBundleManager} object used solely
+ * for the purpose of source code size reduction.
+ */
+public class DiagramViewer {
+	/** The bundle manager helper class reference. */
+	private final MVCBundleManager viewerManager;
+	/** The feature state helper class reference. */
+	private final DiagramViewerFeatures features;
+	/** The grid pane for the main pane and scrollbars. */
+	private final GridPane scrolledPane;
+	/** The horizontal scrollbar. */
+	private final ScrollBar horizontalScrollbar;
+	/** The vertical scrollbar. */
+	private final ScrollBar verticalScrollbar;
+	/** The group for layered nodes. */
+	private final DiagramLayers layers;
+	/** The viewer main pane. */
+	private final Pane viewerPane;
+	/** The mouse drag multi-selection feedback rectangle. */
+	private final Rectangle mouseDragRectangle = new Rectangle(0, 0);
+	/** The initial location where the selection rectangle was started. */
+	private Point2D mouseDragRectangleStartLocation = new Point2D(0, 0);
+	/** The line used during link creation gesture. */
+	private final Line linkLineFeedback = new Line();
+	/** The current context menu. */
+	private ContextMenu contextMenu = null;
+
+	/** A debug label. */
+	private Label debugLabel = new Label("");
+
+	/** Constructor. */
+	public DiagramViewer(IModelFactory modelFactory, IVisualFactory visualFactory,
+			IControllerFactory controllerFactory,
+			Consumer<DiagramViewerSelection> selectionChangedCallback) {
+		this(modelFactory, visualFactory, controllerFactory, selectionChangedCallback,
+				new DefaultModelModifier());
+	}
+
+	/** Constructor. */
+	public DiagramViewer(IModelFactory modelFactory, IVisualFactory visualFactory,
+			IControllerFactory controllerFactory,
+			Consumer<DiagramViewerSelection> selectionChangedCallback,
+			Consumer<Change> modelModifier) {
+		features = new DiagramViewerFeatures(this);
+		layers = new DiagramLayers(this);
+		viewerManager = new MVCBundleManager(this, modelFactory, visualFactory, controllerFactory,
+				layers, selectionChangedCallback, requireNonNull(modelModifier));
+
+		// selection feedback rectangle
+		mouseDragRectangle.setFill(Color.TRANSPARENT);
+		mouseDragRectangle.setStroke(Color.ORANGERED);
+		mouseDragRectangle.getStrokeDashArray().addAll(15.0, 5.0);
+		mouseDragRectangle.setMouseTransparent(true);
+		// viewer pane
+		viewerPane = new Pane();
+		layers.boundsInLocalProperty().addListener((obs, ov, nv) -> {
+			updateHorizontalScrollbar();
+			updateVerticalScrollbar();
+		});
+		viewerPane.getChildren().add(layers);
+		viewerPane.widthProperty().addListener(evt -> {
+			updateGridCanvasSize();
+			updateHorizontalScrollbar();
+		});
+		viewerPane.heightProperty().addListener(evt -> {
+			updateGridCanvasSize();
+			updateVerticalScrollbar();
+		});
+		// focus event handling
+		viewerPane.focusedProperty().addListener((obs, ov, nv) -> {
+			viewerManager.handleFocus(nv, getLayers());
+		});
+
+		// grid pane for viewer and scroll bars
+		scrolledPane = new GridPane();
+		scrolledPane.add(viewerPane, 1, 1);
+		setHgrow(viewerPane, ALWAYS);
+		setVgrow(viewerPane, ALWAYS);
+
+		// vertical scrollbar
+		verticalScrollbar = new ScrollBar();
+		verticalScrollbar.setOrientation(Orientation.VERTICAL);
+		scrolledPane.add(verticalScrollbar, 2, 1);
+		verticalScrollbar.valueProperty().addListener((obs, ov, nv) -> {
+			hideContextMenu();
+			double zf = features.getCurrentZoomFactor();
+			double vszf = features.getVerticalSpacing() * zf;
+			double deltaY = nv.doubleValue() * vszf;
+			layers.setTranslateY(-deltaY);
+			viewerManager.gridCanvasVisual.setScrollY(nv.doubleValue());
+		});
+
+		// horizontal scrollbar
+		horizontalScrollbar = new ScrollBar();
+		scrolledPane.add(horizontalScrollbar, 1, 2);
+		horizontalScrollbar.valueProperty().addListener((obs, ov, nv) -> {
+			hideContextMenu();
+			double zf = features.getCurrentZoomFactor();
+			double hszf = features.getHorizontalSpacing() * zf;
+			double deltaX = nv.doubleValue() * hszf;
+			layers.setTranslateX(-deltaX);
+			viewerManager.gridCanvasVisual.setScrollX(nv.doubleValue());
+		});
+
+		viewerPane.getChildren().add(debugLabel);
+		debugLabel.setLayoutX(20);
+		debugLabel.setLayoutY(20);
+		debugLabel.setTextFill(ORANGERED);
+
+		// clipping rectangle for the viewer pane
+		Rectangle viewerClip = new Rectangle();
+		viewerPane.setClip(viewerClip);
+		viewerPane.layoutBoundsProperty().addListener((obs, ov, nv) -> {
+			viewerClip.setWidth(nv.getWidth());
+			viewerClip.setHeight(nv.getHeight());
+		});
+
+		// update the viewer content
+		viewerManager.updateContentObjects();
+	}
+
+	/** Returns viewerPane. */
+	/* package */ Pane getViewerPane() {
+		return viewerPane;
+	}
+
+	/** Returns the {@link IDiagramMVCBundle}. */
+	/* package */ IDiagramMVCBundle getDiagramBundle() {
+		return viewerManager.diagramBundle;
+	}
+
+	/** Shows the context menu when the mouse is pressed. */
+	/* package */ void hideContextMenu() {
+		if(contextMenu != null && contextMenu.isShowing()) {
+			contextMenu.hide();
+			contextMenu = null;
+		}
+	}
+
+	/** Shows the context menu when the mouse is released. */
+	public void showContextMenu(Node node, DiagramCoordinate diagramLocation, IMVCBundle mvcb) {
+		List<MenuItem> items = mvcb.getController().contextMenuContributions(node, diagramLocation);
+		if(items == null || items.isEmpty()) {
+			return;
+		}
+		contextMenu = new ContextMenu();
+		contextMenu.getItems().addAll(items);
+		contextMenu.setAutoHide(true);
+		Point2D locationOnNode = diagramLocation.getLocal(node);
+		Point2D screenLocation;
+		double zf = features.getCurrentZoomFactor();
+		if(viewerManager.gridCanvasVisual.isGridCanvas(node)) {
+			double hsd = features.getHorizontalSpacing() * getHorizontalScrollBarValue() * zf;
+			double vsd = features.getVerticalSpacing() * getVerticalScrollBarValue() * zf;
+			Point2D correction = locationOnNode.multiply(zf).subtract(hsd, vsd);
+			screenLocation = node.localToScreen(correction);
+		} else {
+			Bounds b = node.getBoundsInLocal();
+			screenLocation = node.localToScreen(locationOnNode.getX() + b.getMinX(),
+					locationOnNode.getY() + b.getMinY());
+		}
+		contextMenu.show(node, screenLocation.getX(), screenLocation.getY());
+	}
+
+	/** Returns the root model element displayed by this diagram viewer. */
+	public Object getRootElement() {
+		return viewerManager.getRootModel();
+	}
+
+	/** Returns the features instance. */
+	public DiagramViewerFeatures getFeatures() {
+		return features;
+	}
+
+	/**
+	 * Returns the content of a SVG representation of this viewers content.
+	 * <P>
+	 * Note that the background canvas is not exported.
+	 */
+	public String getSVGExport() {
+		SVGExporter exporter = new SVGExporter(this);
+		return exporter.export();
+	}
+
+	/** Updates the vertical scrollbar. */
+	private void updateVerticalScrollbar() {
+		Bounds vpb = viewerPane.getLayoutBounds();
+		Bounds cb = layers.getLayoutBounds();
+		double vs = features.getVerticalSpacing();
+		double contentHeight = max(ceil(cb.getMaxY() / vs), 2);
+		double viewportHeight = ceil(vpb.getHeight() / vs);
+		double zf = features.getCurrentZoomFactor();
+		// this allows scrolling the content nearly out of sight (two grid spaces)
+		double max = contentHeight - 2 * zf;
+		verticalScrollbar.setMax(max);
+		double visi = max * (viewportHeight / (viewportHeight + contentHeight - 2));
+		verticalScrollbar.setVisibleAmount(visi);
+		verticalScrollbar.setUnitIncrement(1);
+		verticalScrollbar.setBlockIncrement(5);
+		if(verticalScrollbar.getValue() > max) {
+			verticalScrollbar.setValue(max);
+		}
+	}
+
+	/** Updates the horizontal scrollbar. */
+	private void updateHorizontalScrollbar() {
+		Bounds vpb = viewerPane.getLayoutBounds();
+		Bounds cb = layers.getLayoutBounds();
+		double hs = features.getHorizontalSpacing();
+		double contentWidth = max(ceil(cb.getMaxX() / hs), 2);
+		double viewportWidth = ceil(vpb.getWidth() / hs);
+		double zf = features.getCurrentZoomFactor();
+
+		// this allows scrolling the content nearly out of sight (two grid spaces)
+		double max = contentWidth - 2 * zf;
+		horizontalScrollbar.setMax(max);
+		double visi = max * (viewportWidth / (viewportWidth + contentWidth - 2));
+		horizontalScrollbar.setVisibleAmount(visi);
+		horizontalScrollbar.setUnitIncrement(1);
+		horizontalScrollbar.setBlockIncrement(5);
+		if(horizontalScrollbar.getValue() > max) {
+			horizontalScrollbar.setValue(max);
+		}
+	}
+
+	/** Update the size of the grid canvas. */
+	/* package */ void updateGridCanvasSize() {
+		Bounds viewer = viewerPane.getBoundsInLocal();
+		viewerManager.gridCanvasVisual.setWidth(viewer.getWidth());
+		viewerManager.gridCanvasVisual.setHeight(viewer.getHeight());
+	}
+
+	/** Returns the viewer's visual node. */
+	public Pane getVisualNode() {
+		return scrolledPane;
+	}
+
+	/** Returns the current horizontal scroll bar value. */
+	public double getHorizontalScrollBarValue() {
+		return horizontalScrollbar.getValue();
+	}
+
+	/** Returns the current vertical scroll bar value. */
+	public double getVerticalScrollBarValue() {
+		return verticalScrollbar.getValue();
+	}
+
+	/** Returns the model modifier. */
+	public Consumer<Change> getModelModifier() {
+		return viewerManager.getModelModifier();
+	}
+
+	/**
+	 * Calls the delete method of all controllers of currently selected elements and returns a
+	 * corresponding {@link Change}.
+	 */
+	public Change deleteAllSelected() {
+		final DiagramViewerSelection sel = getSelection();
+		return () -> {
+			sel.getPrimarySelection().getController().delete();
+			for(IMVCBundle sec : sel.getSecondarySelections()) {
+				sec.getController().delete();
+			}
+		};
+	}
+
+	/** Returns the current selection. */
+	public DiagramViewerSelection getSelection() {
+		return viewerManager.getSelection();
+	}
+
+	/** Sets the selected {@link IMVCBundle} as single primary selection. */
+	public void setSingleSelectedMVCBundle(IMVCBundle sel) {
+		viewerManager.setSingleSelectedMVCBundle(sel);
+	}
+
+	/** Returns whether this bundle is selected as primary selection. */
+	public boolean isPrimarySelected(IMVCBundle bundle) {
+		return viewerManager.isPrimarySelected(bundle);
+	}
+
+	/** Returns whether this bundle is selected as secondary selection. */
+	public boolean isSecondarySelected(IMVCBundle bundle) {
+		return viewerManager.isSecondarySelected(bundle);
+	}
+
+	/**
+	 * Returns whether this bundle is selected (either primary or secondary
+	 * selection).
+	 */
+	public boolean isSelected(IMVCBundle bundle) {
+		return isPrimarySelected(bundle) || isSecondarySelected(bundle);
+	}
+
+	/**
+	 * Adds the given bundle as primary selection and moves the current selection to
+	 * the secondary selections list.
+	 */
+	public void addSelectedMVCBundle(IMVCBundle sel) {
+		viewerManager.addSelectedMVCBundle(sel);
+	}
+
+	/** Selects all content, diagram anchorage and link {@link IMVCBundle}s of this viewer. */
+	public void selectAll() {
+		viewerManager.selectAll();
+	}
+
+	/** Interprets the effect of shift-clicking the given {@link IMVCBundle}. */
+	public void handleShiftSelectionOf(IMVCBundle sel) {
+		viewerManager.handleShiftSelectionOf(sel);
+	}
+
+	/**
+	 * Removes the given bundle from either the primary selection (selecting next
+	 * the secondary list element) or from the secondary selection list.
+	 */
+	public void removeSelectedMVCBundle(IMVCBundle sel) {
+		viewerManager.removeSelectedMVCBundle(sel);
+	}
+
+	/** Returns the visual factory. */
+	public IVisualFactory getVisualFactory() {
+		return viewerManager.visualFactory;
+	}
+
+	/** Returns the controller factory. */
+	public IControllerFactory getControllerFactory() {
+		return viewerManager.controllerFactory;
+	}
+
+	/** Returns the model factory. */
+	public IModelFactory getModelFactory() {
+		return viewerManager.modelFactory;
+	}
+
+	/** Returns the scroll event handler. */
+	/* package */ EventHandler<? super ScrollEvent> getScrollingHandler() {
+		return evt -> {
+			if(evt.isControlDown()) {
+				if(features.getZoomFactorIndex() == -1) {
+					evt.consume();
+					return;
+				}
+				if(evt.getDeltaY() > 0) {
+					features.zoomIn();
+					evt.consume();
+				} else if(evt.getDeltaY() < 0) {
+					features.zoomOut();
+					evt.consume();
+				}
+			} else if(evt.isShiftDown()) {
+				double hmax = horizontalScrollbar.getMax();
+				double hmin = horizontalScrollbar.getMin();
+				double nval = horizontalScrollbar.getValue() -
+						evt.getDeltaY() / features.getHorizontalSpacing();
+				horizontalScrollbar.setValue(max(hmin, min(hmax, nval)));
+			} else {
+				double vmax = verticalScrollbar.getMax();
+				double vmin = verticalScrollbar.getMin();
+				double nval = verticalScrollbar.getValue() -
+						evt.getDeltaY() / features.getVerticalSpacing();
+				verticalScrollbar.setValue(max(vmin, min(vmax, nval)));
+			}
+		};
+	}
+
+	/** Updates every visual (and only its appearance, not the structure). */
+	public void updateAllVisuals() {
+		viewerManager.updateAllVisuals();
+	}
+
+	/**
+	 * Updates the nodes selectively adhering to the relation given by the
+	 * {@link IMVCBundle}s.
+	 */
+	public void updateVisual(IMVCBundle bundle) {
+		viewerManager.updateVisual(bundle);
+	}
+
+	/**
+	 * Updates the nodes selectively adhering to the relation given by the
+	 * {@link IMVCBundle}s.
+	 */
+	public void updateModelVisual(Object model) {
+		viewerManager.updateModelVisual(model);
+	}
+
+	/**
+	 * Updates the content of the viewer creating, deleting and updating any visuals
+	 * using the underlying model.
+	 */
+	public void updateFromModel() {
+		double vscroll = verticalScrollbar.getValue();
+		double hscroll = horizontalScrollbar.getValue();
+		viewerManager.updateContentObjects();
+		verticalScrollbar.setValue(vscroll);
+		horizontalScrollbar.setValue(hscroll);
+	}
+
+	/**
+	 * Sets the focus to the viewer pane in order to capture key events. This method
+	 * must be called by the container when it receives the focus, e.g. via
+	 * {@code @Focus} annotation.
+	 */
+	public void requestFocus() {
+		viewerPane.requestFocus();
+	}
+
+	/** Returns whether the diagram viewer has the focus or not. */
+	public boolean hasFocus() {
+		return viewerPane.isFocused();
+	}
+
+	/** Sets the debug message. */
+	public void setDebugMessage(String msg) {
+		debugLabel.setText(msg);
+	}
+
+	/** Returns the layer group. */
+	public DiagramLayers getLayers() {
+		return layers;
+	}
+
+	/** Stars the feedback for selection rectangle. */
+	public void startSelectionFeedback(Node node, DiagramCoordinate locationInDiagram) {
+		if(node == null || locationInDiagram == null) {
+			return;
+		}
+		mouseDragRectangleStartLocation = locationInDiagram;
+		mouseDragRectangle.setX(mouseDragRectangleStartLocation.getX());
+		mouseDragRectangle.setY(mouseDragRectangleStartLocation.getY());
+		mouseDragRectangle.setWidth(0);
+		mouseDragRectangle.setHeight(0);
+		if(mouseDragRectangle.getParent() == null) {
+			layers.getVisualFeedbackLayer().add(mouseDragRectangle, viewerManager.diagramBundle);
+		}
+		setSingleSelectedMVCBundle(null);
+	}
+
+	/**
+	 * Converts the given absolute diagram coordinates to node-local coordinates.
+	 */
+	/* package */ DiagramCoordinate convertEventToDiagramCoordinates(Point2D absoluteLocation,
+			Node node) {
+		return convertEventToDiagramCoordinates(absoluteLocation.getX(), absoluteLocation.getY(),
+				node);
+	}
+
+	/** Converts the given mouse event coordinates to node-local coordinates. */
+	/* package */ DiagramCoordinate convertEventToDiagramCoordinates(double evtX, double evtY,
+			Node node) {
+		if(node == viewerManager.gridCanvasVisual.getGridCanvas()) {
+			return convertGridCanvasCoordinate(evtX, evtY);
+		}
+		return new DiagramCoordinate(evtX, evtY);
+	}
+
+	/** Converts the grid canvas coordinate to the diagram coordinate space. */
+	public DiagramCoordinate convertGridCanvasCoordinate(double xOnCanvas, double yOnCanvas) {
+		double zf = features.getCurrentZoomFactor();
+		double x = xOnCanvas / zf + features.getHorizontalSpacing() * getHorizontalScrollBarValue();
+		double y = yOnCanvas / zf + features.getVerticalSpacing() * getVerticalScrollBarValue();
+		return new DiagramCoordinate(x, y);
+	}
+
+	/** Scrolls the diagram to center at the given coordinates. */
+	public void scrollToCenter(DiagramCoordinate center) {
+		double zf = features.getCurrentZoomFactor();
+		Bounds bounds = viewerPane.getLayoutBounds();
+		double upperLeftX = center.getX() - bounds.getMinX() / zf;
+		if(upperLeftX < 0) {
+			upperLeftX = 0;
+		}
+		double newHSBValue = upperLeftX / features.getHorizontalSpacing();
+		double upperLeftY = center.getY() - bounds.getMinY() / zf;
+		if(upperLeftY < 0) {
+			upperLeftY = 0;
+		}
+		double newVSBValue = upperLeftY - features.getVerticalSpacing();
+
+		horizontalScrollbar.setValue(newHSBValue);
+		verticalScrollbar.setValue(newVSBValue);
+	}
+
+	/** Updates the feedback for selection rectangle. */
+	public void updateSelectionFeedback(Node node, Point2D locationInDiagram) {
+		if(node == null || locationInDiagram == null) {
+			return;
+		}
+		Point2D location = locationInDiagram;
+		double x = Math.min(mouseDragRectangleStartLocation.getX(), location.getX());
+		mouseDragRectangle.setX(x);
+		double w = Math.abs(mouseDragRectangleStartLocation.getX() - location.getX());
+		mouseDragRectangle.setWidth(w);
+		double y = Math.min(mouseDragRectangleStartLocation.getY(), location.getY());
+		mouseDragRectangle.setY(y);
+		double h = Math.abs(mouseDragRectangleStartLocation.getY() - location.getY());
+		mouseDragRectangle.setHeight(h);
+	}
+
+	/** Terminates the feedback for selection rectangle. */
+	public void terminateSelectionFeedback(Node node, Point2D locationInDiagram) {
+		if(node == null || locationInDiagram == null) {
+			return;
+		}
+		Point2D location = locationInDiagram;
+		double x = Math.min(mouseDragRectangleStartLocation.getX(), location.getX());
+		double w = Math.abs(mouseDragRectangleStartLocation.getX() - location.getX());
+		double y = Math.min(mouseDragRectangleStartLocation.getY(), location.getY());
+		double h = Math.abs(mouseDragRectangleStartLocation.getY() - location.getY());
+		layers.getVisualFeedbackLayer().remove(mouseDragRectangle);
+		Rectangle2D selectionRect = new Rectangle2D(x, y, w, h);
+		// remove old selection
+		setSingleSelectedMVCBundle(null);
+		for(IContentMVCBundle cmvcb : viewerManager.contentBundles.values()) {
+			Rectangle2D contentRect = cmvcb.getVisual().getCurrentBounds();
+			if(contentRect != null && selectionRect.contains(contentRect)) {
+				addSelectedMVCBundle(cmvcb);
+			}
+		}
+		for(IDiagramAnchorageMVCBundle damvcb : viewerManager.diagramAnchorageBundles.values()) {
+			Rectangle2D daRect = damvcb.getVisual().getCurrentBounds();
+			if(daRect != null && selectionRect.contains(daRect)) {
+				addSelectedMVCBundle(damvcb);
+			}
+		}
+	}
+
+	/**
+	 * Starts the feedback of the link creation line and shows the possible target
+	 * feedbacks.
+	 */
+	public void startNewLinkLineFeedback(IMVCBundle linkStartBundle, Node node,
+			Point2D locationInDiagram) {
+		if(node == null || locationInDiagram == null) {
+			return;
+		}
+		Point2D location = locationInDiagram;
+		linkLineFeedback.setStartX(location.getX());
+		linkLineFeedback.setStartY(location.getY());
+		linkLineFeedback.setEndX(location.getX());
+		linkLineFeedback.setEndY(location.getY());
+		if(linkLineFeedback.getParent() == null) {
+			layers.getVisualFeedbackLayer().add(linkLineFeedback, viewerManager.diagramBundle);
+		}
+		for(IMVCBundle possibleTarget : viewerManager.possibleLinkTargets) {
+			IController controller = possibleTarget.getController();
+			MVCBundleTag effect = controller.getLinkTargetEffectForNewLinkFrom(linkStartBundle);
+			if(effect != null) {
+				possibleTarget.addTag(effect);
+				possibleTarget.getVisual().updateNodes(layers);
+			}
+		}
+	}
+
+	/** Updates the end of the link creation line. */
+	public void updateNewLinkLineFeedback(Point2D locationInDiagram, Paint color) {
+		linkLineFeedback.setEndX(locationInDiagram.getX());
+		linkLineFeedback.setEndY(locationInDiagram.getY());
+		linkLineFeedback.setStroke(color);
+	}
+
+	/** Terminates the link creation line and possible target feedbacks. */
+	public void terminateNewLinkLineFeedback() {
+		layers.getVisualFeedbackLayer().remove(linkLineFeedback);
+		clearLinkTargetFeedback();
+	}
+
+	/** Clears the link target feedback fronm all possible link targets. */
+	private void clearLinkTargetFeedback() {
+		for(IMVCBundle possibleTarget : viewerManager.possibleLinkTargets) {
+			possibleTarget.removeTag(LINK_TARGET_ALLOWED_TAG);
+			possibleTarget.removeTag(LINK_TARGET_DENIED_TAG);
+			possibleTarget.removeTag(LINK_TARGET_MAYBE_TAG);
+			possibleTarget.getVisual().updateNodes(layers);
+		}
+	}
+
+	/** Starts the feedback for relocating a link. */
+	public void startMoveLinkFeedback(ILinkMVCBundle linkBundle,
+			IAnchorageMVCBundle movedStartOrEnd) {
+		for(IMVCBundle possibleTarget : viewerManager.possibleLinkTargets) {
+			IController controller = possibleTarget.getController();
+			MVCBundleTag effect =
+					controller.getLinkTargetEffectForMoveLink(linkBundle, movedStartOrEnd);
+			if(effect != null) {
+				possibleTarget.addTag(effect);
+				possibleTarget.getVisual().updateNodes(layers);
+			}
+		}
+	}
+
+	/** Terminates the link relocation feedbacks. */
+	public void terminateMoveLinkFeedback() {
+		clearLinkTargetFeedback();
+	}
+
+	/** Returns the last known mouse location. */
+	public DiagramCoordinate getLastMouseLocation() {
+		return getLayers().getMouseState().getLastMouseLocation();
+	}
+
+	/** Returns the current drag extent. */
+	public DiagramCoordinate getDragExtent() {
+		return getLayers().getMouseState().getDragExtent();
+	}
+
+	/**
+	 * Creates a drag controller for the given bundle, gesture, mouse location and source node also
+	 * considering the current selection.
+	 */
+	public IDragController createDragController(IMVCBundle bundle, EDragGesture gesture,
+			Node source, DiagramCoordinate lastMouseLocation) {
+		DiagramViewerSelection sel = getSelection();
+		if(!sel.isMultiSelection()) {
+			// single selection delegates to the underlying controller
+			return bundle.getController().getDragController(gesture, source, lastMouseLocation);
+		}
+		// return a composite controller
+		return new CompositeDragController(sel, gesture, source, lastMouseLocation);
+	}
+
+	/**
+	 * Creates a key press controller for the given bundle also considering the current selection.
+	 */
+	public IKeyPressController createKeyPressController(IMVCBundle bundle, Node source,
+			DiagramCoordinate lastMouseLocation) {
+		final DiagramViewerSelection sel = getSelection();
+		if(sel.isMultiSelection()) {
+			return new IKeyPressController() {
+				/** {@inheritDoc} */
+				@Override
+				public Change keyEvent(KeyEvent event, IMVCBundle bundle, Node source,
+						DiagramCoordinate mouseLocation) {
+					ChangeSet changeSet = new ChangeSet();
+					// handle primary selection
+					IMVCBundle primaryBundle = sel.getPrimarySelection();
+					IKeyPressController primaryKPC = primaryBundle.getController()
+							.getKeyPressController(source, lastMouseLocation);
+					Change primaryChg =
+							primaryKPC.keyEvent(event, primaryBundle, source, lastMouseLocation);
+					if(primaryChg != null) {
+						changeSet.add(primaryChg);
+					}
+					// handle secondary selection
+					for(IMVCBundle secondaryBundle : sel.getSecondarySelections()) {
+						IKeyPressController secondaryKPC = secondaryBundle.getController()
+								.getKeyPressController(source, lastMouseLocation);
+						Change secondaryChg = secondaryKPC.keyEvent(event, primaryBundle, source,
+								lastMouseLocation);
+						if(secondaryChg != null) {
+							changeSet.add(secondaryChg);
+						}
+					}
+					if(!changeSet.isEmpty()) {
+						return changeSet;
+					}
+					return null;
+				}
+			};
+		}
+		return bundle.getController().getKeyPressController(source, lastMouseLocation);
+	}
+
+	/** Composite drag controller delegates to a set of {@link IDragController}s. */
+	private static final class CompositeDragController implements IDragController {
+		/** The contained controllers. */
+		private final Map<IDragController, IMVCBundle> controllers = new HashMap<>();
+
+		/** Constructor. */
+		public CompositeDragController(DiagramViewerSelection selection, EDragGesture gesture,
+				Node source, DiagramCoordinate lastMouseLocation) {
+			addDragController(selection.getPrimarySelection(), gesture, source, lastMouseLocation);
+			for(IMVCBundle sec : selection.getSecondarySelections()) {
+				addDragController(sec, gesture, source, lastMouseLocation);
+			}
+		}
+
+		/** Creates a drag controller from the bundle's controller and adds it. */
+		private void addDragController(IMVCBundle bundle, EDragGesture gesture, Node source,
+				DiagramCoordinate lastMouseLocation) {
+			if(bundle == null || bundle.getController() == null) {
+				return;
+			}
+			IDragController dc =
+					bundle.getController().getDragController(gesture, source, lastMouseLocation);
+			if(dc != null) {
+				controllers.put(dc, bundle);
+			}
+		}
+
+		/** {@inheritDoc} */
+		@Override
+		public void dragStarted(IMVCBundle startBundle, Node startNode,
+				DiagramCoordinate lastMouseLocation) {
+			for(IDragController dc : controllers.keySet()) {
+				IMVCBundle bundle = controllers.get(dc);
+				dc.dragStarted(bundle, startNode, lastMouseLocation);
+			}
+		}
+
+		/** {@inheritDoc} */
+		@Override
+		public void dragInProgress(IMVCBundle currentBundle, Node currentNode,
+				DiagramCoordinate lastMouseLocation) {
+			for(IDragController dc : controllers.keySet()) {
+				IMVCBundle bundle = controllers.get(dc);
+				dc.dragInProgress(bundle, currentNode, lastMouseLocation);
+			}
+		}
+
+		/** {@inheritDoc} */
+		@Override
+		public Change dragCompleted(IMVCBundle endBundle, Node endNode,
+				DiagramCoordinate lastMouseLocation) {
+			ChangeSet cset = new ChangeSet();
+			for(IDragController dc : controllers.keySet()) {
+				Change chg = dc.dragCompleted(endBundle, endNode, lastMouseLocation);
+				if(chg != null) {
+					cset.add(chg);
+				}
+			}
+			if(!cset.isEmpty()) {
+				return cset;
+			}
+			return null;
+		}
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewerDefaultTags.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewerDefaultTags.java
new file mode 100644
index 0000000000000000000000000000000000000000..6230763252409c60009ab8887b4ef582cf883229
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewerDefaultTags.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.MVCBundleTag;
+
+/**
+ * Predefined {@link MVCBundleTag}s for focusing, selecting, hovering, link targeting, and
+ * highlighting feedback.
+ */
+public final class DiagramViewerDefaultTags {
+	/** The tagged bundle has the focus. */
+	public static final MVCBundleTag FOCUS_TAG = makeTag("_FOCUS");
+	/** The tagged bundle is the primary selection of the diagram viewer. */
+	public static final MVCBundleTag PRIMARY_SELECTION_TAG = makeTag("_PRIMARY");
+	/** The tagged bundle belongs to the secondary selections of the diagram viewer. */
+	public static final MVCBundleTag SECONDARY_SELECTION_TAG = makeTag("_SECONDARY");
+	/** The tagged bundle is currently hover over by the mouse pointer. */
+	public static final MVCBundleTag HOVER_TAG = makeTag("_HOVER");
+	/** The tagged bundle is a current link target. */
+	public static final MVCBundleTag LINK_TARGET_ALLOWED_TAG = makeTag("_LINKTARGET_ALLOWED");
+	/** The tagged bundle is not a current link target. */
+	public static final MVCBundleTag LINK_TARGET_DENIED_TAG = makeTag("_LINKTARGET_DENIED");
+	/** The tagged bundle might be a current link target. */
+	public static final MVCBundleTag LINK_TARGET_MAYBE_TAG = makeTag("_LINKTARGET_MAYBE");
+	/** The tagged bundle is highlighted as incoming link */
+	public static final MVCBundleTag HIGHLIGHT_INCOMING_LINK_TAG =
+			makeTag("_HIGHLIGHT_INCOMING_LINK");
+	/** The tagged bundle is highlighted as outgoing link */
+	public static final MVCBundleTag HIGHLIGHT_OUTGOING_LINK_TAG =
+			makeTag("_HIGHLIGHT_OUTGOING_LINK");
+
+	/** Creates the tag. */
+	private static MVCBundleTag makeTag(String tagId) {
+		return new MVCBundleTag(DiagramViewerDefaultTags.class, tagId);
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewerFeatures.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewerFeatures.java
new file mode 100644
index 0000000000000000000000000000000000000000..31e3fb61f915b0d8695005b083c47ce1c5be0b05
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewerFeatures.java
@@ -0,0 +1,283 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef;
+
+import static java.lang.Math.floor;
+import static java.lang.Math.max;
+import static java.util.Objects.requireNonNull;
+import static javafx.scene.paint.Color.LIGHTGRAY;
+
+import javafx.geometry.Dimension2D;
+import javafx.geometry.Point2D;
+import javafx.scene.paint.Color;
+
+/**
+ * This class handles the state of all features of the {@link DiagramViewer}, which can be toggled
+ * after instantiating the diagram viewer.
+ */
+public final class DiagramViewerFeatures {
+	/** The diagram viewer. */
+	private final DiagramViewer viewer;
+	/** The zoom factors. */
+	private double[] zoomFactors = new double[] {0.5, 0.75, 1, 1.5, 2};
+	/** The current zoom factor array index. */
+	private int zoomFactorIndex = 2;
+	/** Flag indicating whether link highlighting is enabled. */
+	private boolean linkHighlightingEnabled = false;
+	/** Flag if interaction area shading is active. */
+	private boolean interactionAreaShadingEnabled = false;
+	/** The horizontal and vertical spacing of the grid indicators. */
+	private Dimension2D indicatorSpacing = new Dimension2D(20, 20);
+	/** The flag indicating that this grid is used to snap elements when moving or resizing. */
+	private boolean useSnapToGrid = true;
+	/** The size of the grid indicators. */
+	private double indicatorSize = 2;
+	/** The color of the grid indicator. */
+	private Color indicatorColor = LIGHTGRAY.darker();
+	/** The flag for drawing the outer border, which occupies the first grid row and lane. */
+	private boolean drawOuterBorder = true;
+	/** The indicator type. */
+	private IndicatorType indicatorType = IndicatorType.CROSS;
+	/** The background color. */
+	private Color backgroundColor = LIGHTGRAY;
+
+	/** Constructor. */
+	public DiagramViewerFeatures(DiagramViewer diagramViewer) {
+		this.viewer = diagramViewer;
+	}
+
+	/** Returns the zoom factors. */
+	public double[] getZoomFactors() {
+		return zoomFactors;
+	}
+
+	/** Sets the zoom factors. */
+	public void setZoomFactors(double[] zoomFactors) {
+		this.zoomFactors = zoomFactors;
+		if(zoomFactorIndex >= zoomFactors.length) {
+			zoomFactorIndex = zoomFactors.length - 1;
+		}
+		viewer.updateAllVisuals();
+	}
+
+	/** Returns the zoom factor index. */
+	public int getZoomFactorIndex() {
+		return zoomFactorIndex;
+	}
+
+	/** Returns the current zoom factor. */
+	public double getCurrentZoomFactor() {
+		return zoomFactorIndex == -1 ? 1.0 : zoomFactors[zoomFactorIndex];
+	}
+
+	/** Sets the zoom factor index. */
+	public void setZoomFactorIndex(int zoomFactorIndex) {
+		this.zoomFactorIndex = zoomFactorIndex;
+		viewer.updateAllVisuals();
+	}
+
+	/** Increases zoom factor index by one up to the maximum. */
+	public void zoomIn() {
+		if(zoomFactorIndex < zoomFactors.length - 1) {
+			zoomFactorIndex++;
+			viewer.updateAllVisuals();
+		}
+	}
+
+	/** Decreases zoom factor index by one down to the minimum. */
+	public void zoomOut() {
+		if(zoomFactorIndex > 0) {
+			zoomFactorIndex--;
+			viewer.updateAllVisuals();
+		}
+	}
+
+	/** Returns the link highlighting enabled flag. */
+	public boolean isLinkHighlightingEnabled() {
+		return linkHighlightingEnabled;
+	}
+
+	/** Sets the link highlighting enabled. */
+	public void setLinkHighlightingEnabled(boolean linkHighlightingEnabled) {
+		this.linkHighlightingEnabled = linkHighlightingEnabled;
+		viewer.updateAllVisuals();
+	}
+
+	/** Returns the interaction area shading enabled flag. */
+	public boolean isInteractionAreaShadingEnabled() {
+		return interactionAreaShadingEnabled;
+	}
+
+	/** Sets the interaction area shading enabled flag. */
+	public void setInteractionAreaShadingEnabled(boolean interactionAreaShadingEnabled) {
+		this.interactionAreaShadingEnabled = interactionAreaShadingEnabled;
+		viewer.updateAllVisuals();
+	}
+
+	/** Returns the indicator type. */
+	public IndicatorType getIndicatorType() {
+		return indicatorType;
+	}
+
+	/** Sets the indicator type. */
+	public void setIndicatorType(IndicatorType indicatorType) {
+		this.indicatorType = indicatorType;
+		viewer.updateAllVisuals();
+	}
+
+	/** Returns the background color. */
+	public Color getBackgroundColor() {
+		return backgroundColor;
+	}
+
+	/** Sets the background color. */
+	public void setBackgroundColor(Color backgroundColor) {
+		this.backgroundColor = backgroundColor;
+		viewer.updateAllVisuals();
+	}
+
+	/** Returns the indicator color. */
+	public Color getIndicatorColor() {
+		return indicatorColor;
+	}
+
+	/** Sets the indicator color. */
+	public void setIndicatorColor(Color indicatorColor) {
+		this.indicatorColor = indicatorColor;
+		viewer.updateAllVisuals();
+	}
+
+	/** Returns the indicator size. */
+	public double getIndicatorSize() {
+		return indicatorSize;
+	}
+
+	/** Sets the indicator size. */
+	public void setIndicatorSize(double indicatorSize) {
+		this.indicatorSize = indicatorSize;
+		viewer.updateAllVisuals();
+	}
+
+	/** Returns the indicator spacing. */
+	public Dimension2D getIndicatorSpacing() {
+		return indicatorSpacing;
+	}
+
+	/** Returns the horizontal spacing. */
+	public double getHorizontalSpacing() {
+		return indicatorSpacing.getWidth();
+	}
+
+	/** Returns the vertical spacing. */
+	public double getVerticalSpacing() {
+		return indicatorSpacing.getHeight();
+	}
+
+	/** Sets the indicator spacing. */
+	public void setIndicatorSpacing(Dimension2D indicatorSpacing) {
+		this.indicatorSpacing = requireNonNull(indicatorSpacing);
+		viewer.updateAllVisuals();
+	}
+
+	/** Returns whether to use snap to grid. */
+	public boolean useSnapToGrid() {
+		return useSnapToGrid;
+	}
+
+	/** Sets the use snap to grid flag. */
+	public void setUseSnapToGrid(boolean useSnapToGrid) {
+		this.useSnapToGrid = useSnapToGrid;
+		viewer.updateAllVisuals();
+	}
+
+	/** Computes the closest horizontal grid coordinate of the given point. */
+	public double snapToGridX(double x) {
+		if(useSnapToGrid) {
+			double width = indicatorSpacing.getWidth();
+			return floor(x / width) * width;
+		}
+		return x;
+	}
+
+	/** Computes the closest vertical grid coordinate of the given point. */
+	public double snapToGridY(double y) {
+		if(useSnapToGrid) {
+			double height = indicatorSpacing.getHeight();
+			return floor(y / height) * height;
+		}
+		return y;
+	}
+
+	/** Computes the closest grid location of the given point. */
+	public Point2D snapToGrid(Point2D point) {
+		return snapToGrid(point.getX(), point.getY());
+	}
+
+	/** Computes the closest grid location of the given point. */
+	public Point2D snapToGrid(double x, double y) {
+		return new Point2D(snapToGridX(x), snapToGridY(y));
+	}
+
+	/**
+	 * Computes the closest horizontal coordinate of the given point that lies in the center between
+	 * the grid points.
+	 */
+	public double snapToCenterX(double x) {
+		double hs2 = indicatorSpacing.getWidth() / 2;
+		return snapToGridX(x - hs2) + hs2;
+	}
+
+	/**
+	 * Computes the closest vertical coordinate of the given point that lies in the center between
+	 * the grid points.
+	 */
+	public double snapToCenterY(double y) {
+		double vs2 = indicatorSpacing.getHeight() / 2;
+		return snapToGridY(y - vs2) + vs2;
+	}
+
+	/** Computes the closest center location of the given point. */
+	public DiagramCoordinate snapToCenter(DiagramCoordinate point) {
+		return snapToCenter(point.getX(), point.getY());
+	}
+
+	/** Computes the closest center location of the given point. */
+	public DiagramCoordinate snapToCenter(double x, double y) {
+		return new DiagramCoordinate(snapToCenterX(x), snapToCenterY(y));
+	}
+
+	/** Returns the draw outer border flag. */
+	public boolean isDrawOuterBorder() {
+		return drawOuterBorder;
+	}
+
+	/** Sets the draw outer border. */
+	public void setDrawOuterBorder(boolean drawOuterBorder) {
+		this.drawOuterBorder = drawOuterBorder;
+		viewer.updateAllVisuals();
+	}
+
+	/** The indicator type used for the grid points. */
+	public static enum IndicatorType {
+		/** Invisible indicator. */
+		INVISIBLE,
+		/** Circle indicator. */
+		CIRCLE,
+		/** Cross indicator. */
+		CROSS,
+		/** Square indicator. */
+		SQUARE;
+	}
+
+	/** Returns the maximum of the horizontal and vertical spacing. */
+	public double getMaximumSpacing() {
+		return max(getVerticalSpacing(), getHorizontalSpacing());
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewerSelection.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewerSelection.java
new file mode 100644
index 0000000000000000000000000000000000000000..e833f592543bc97077907d980a39b123fc4044e6
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewerSelection.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.unmodifiableList;
+
+import java.util.List;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+
+/**
+ * A {@link DiagramViewerSelection} wraps {@link IMVCBundle}s, which are currently selected. Each
+ * selection consists of a primary selection and zero or more secondary selections.
+ */
+public final class DiagramViewerSelection {
+	/** The primary selection. */
+	private final IMVCBundle primarySelection;
+	/** The list of secondary selections. */
+	private final List<IMVCBundle> secondarySelections;
+
+	/** Constructor. */
+	public DiagramViewerSelection(IMVCBundle primary) {
+		this(primary, emptyList());
+	}
+
+	/** Constructor. */
+	public DiagramViewerSelection(IMVCBundle primary, List<IMVCBundle> secondary) {
+		this.primarySelection = primary;
+		this.secondarySelections = unmodifiableList(secondary);
+	}
+
+	/** Returns the primary selection. */
+	public IMVCBundle getPrimarySelection() {
+		return primarySelection;
+	}
+
+	/** Returns the secondary selections. */
+	public List<IMVCBundle> getSecondarySelections() {
+		return secondarySelections;
+	}
+
+	/** Returns whether nothing is selected. */
+	public boolean isEmpty() {
+		return primarySelection == null && secondarySelections.isEmpty();
+	}
+
+	/** Returns whether multiple elements are currently selected. */
+	public boolean isMultiSelection() {
+		return primarySelection != null && !secondarySelections.isEmpty();
+	}
+
+	/** Returns whether the given bundle is selected or not. */
+	public boolean isSelected(IMVCBundle bundle) {
+		return primarySelection == bundle || secondarySelections.contains(bundle);
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/EDragGesture.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/EDragGesture.java
new file mode 100644
index 0000000000000000000000000000000000000000..5cfa098d3877db11981c2750e5e103156d62fc5e
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/EDragGesture.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef;
+
+/** Enumeration of gestures provided when the mouse is dragged at certain locations of visuals. */
+public enum EDragGesture {
+	/** No dragging initiated. */
+	NONE,
+	/** Move gesture for diagram content and anchorage visuals. */
+	MOVE,
+	/** Resize vertically gesture. */
+	RESIZE_V,
+	/** Resize horizontally gesture. */
+	RESIZE_H,
+	/** Resize vertical and horizontal gesture. */
+	RESIZE_VH,
+	/** Create a link gesture. */
+	NEW_LINK,
+	/** Moves the start or end point of a link. */
+	RECONNECT_LINK,
+	/** Create a bend point gesture. */
+	NEW_BENDPOINT,
+	/** Move drag gesture for an existing bend point. */
+	MOVE_BENDPOINT,
+	/** Selection drag gesture. */
+	SELECTION;
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/FeedbackChange.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/FeedbackChange.java
new file mode 100644
index 0000000000000000000000000000000000000000..b088fa89af648f1674f2f9c1f7f99d585ce801ca
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/FeedbackChange.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef;
+
+import javafx.geometry.Point2D;
+import javafx.geometry.Rectangle2D;
+
+/**
+ * This class encapsulates the relative position and size changes occurring during feedback
+ * operations, e.g., when the user is dragging a visual to move or resize it with a mouse gesture.
+ */
+public final class FeedbackChange {
+	/** The empty feedback change. */
+	public static final FeedbackChange EMPTY = new FeedbackChange();
+
+	/** The relative X position change of this feedback change. */
+	private final double deltaX;
+	/** The relative Y position change of this feedback change. */
+	private final double deltaY;
+	/** The relative width change of this feedback change. */
+	private final double deltaW;
+	/** The relative height change of this feedback change. */
+	private final double deltaH;
+
+	/** Constructor. */
+	public FeedbackChange(double dx, double dy, double dw, double dh) {
+		this.deltaX = dx;
+		this.deltaY = dy;
+		this.deltaW = dw;
+		this.deltaH = dh;
+	}
+
+	/** Constructor. */
+	public FeedbackChange() {
+		this(0, 0, 0, 0);
+	}
+
+	/** Returns the X position change. */
+	public double getDeltaX() {
+		return deltaX;
+	}
+
+	/** Returns Y position change. */
+	public double getDeltaY() {
+		return deltaY;
+	}
+
+	/** Returns width change. */
+	public double getDeltaW() {
+		return deltaW;
+	}
+
+	/** Returns the height change. */
+	public double getDeltaH() {
+		return deltaH;
+	}
+
+	/** Returns whether this feedback change is empty. */
+	public boolean isEmpty() {
+		return deltaX == 0 && deltaY == 0 && deltaW == 0 && deltaH == 0;
+	}
+
+	/** Applies this change to the given rectangle. */
+	public Rectangle2D applyTo(Rectangle2D b) {
+		return new Rectangle2D(b.getMinX() + deltaX, b.getMinY() + deltaY, b.getWidth() + deltaW,
+				b.getHeight() + deltaH);
+	}
+
+	/** Applies the x and y change to the given point. */
+	public Point2D applyToPoint(Point2D p) {
+		return new Point2D(p.getX() + deltaX, p.getY() + deltaY);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("[").append(deltaX);
+		sb.append(",").append(deltaY);
+		sb.append(",").append(deltaW);
+		sb.append(",").append(deltaH).append("]");
+		return sb.toString();
+	}
+
+	/** Returns a {@link FeedbackChange} with a location change. */
+	public static FeedbackChange locationFeedbackChange(double dx, double dy) {
+		return new FeedbackChange(dx, dy, 0, 0);
+	}
+
+	/** Returns a {@link FeedbackChange} with a size change. */
+	public static FeedbackChange sizeFeedbackChange(double dw, double dh) {
+		return new FeedbackChange(0, 0, dw, dh);
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/GridCanvasVisual.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/GridCanvasVisual.java
new file mode 100644
index 0000000000000000000000000000000000000000..734027d56af342cd01ff445ba9347b8dbb6c83c2
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/GridCanvasVisual.java
@@ -0,0 +1,239 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerFeatures.IndicatorType;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisual;
+
+import javafx.geometry.Bounds;
+import javafx.geometry.Point2D;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Node;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.paint.Color;
+
+/**
+ * {@link IVisual} for the diagram background. Note that the {@link Canvas} used here is always as
+ * big as the diagram viewer's content area and the grid is painted with an according displacement.
+ * Having the canvas as big as the diagram will most certainly result in video memory problems,
+ * which requires to implement the background in this special way.
+ */
+final class GridCanvasVisual implements IVisual {
+	/** The diagram bundle. */
+	private final DiagramViewer viewer;
+	/** The FX canvas to be drawn on. */
+	private final Canvas gridCanvas;
+	/** The displacement caused by scroll bars. */
+	private DiagramCoordinate displacement = new DiagramCoordinate(0, 0);
+
+	/** Constructor. */
+	public GridCanvasVisual(DiagramViewer viewer) {
+		this.viewer = viewer;
+		// the background grid canvas
+		this.gridCanvas = new Canvas(100, 100);
+		gridCanvas.widthProperty().addListener(evt -> {
+			paintGrid();
+		});
+		gridCanvas.heightProperty().addListener(evt -> {
+			paintGrid();
+		});
+	}
+
+	/** Returns gridCanvas. */
+	/* package */Canvas getGridCanvas() {
+		return gridCanvas;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean enableInteraction() {
+		return false;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean enableVisual() {
+		return true;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IMVCBundle getMVCBundle() {
+		return viewer.getDiagramBundle();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void updateNodes(DiagramLayers layers) {
+		if(gridCanvas.getParent() == null) {
+			// grid canvas is added below the diagram layers
+			viewer.getViewerPane().getChildren().add(0, gridCanvas);
+			viewer.getLayers().getMouseState().registerMouseListeners(gridCanvas, getMVCBundle());
+			gridCanvas.setOnScroll(viewer.getScrollingHandler());
+		}
+		paintGrid();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void removeAllVisuals(DiagramLayers layers) {
+		viewer.getLayers().getMouseState().unregisterMouseListeners(gridCanvas);
+		viewer.getViewerPane().getChildren().remove(gridCanvas);
+		gridCanvas.setOnScroll(null);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Rectangle2D getModelBounds() {
+		Bounds b = gridCanvas.getBoundsInParent();
+		return new Rectangle2D(b.getMinX(), b.getMinY(), b.getWidth(), b.getHeight());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate getLinkAnchorage(DiagramCoordinate indication) {
+		DiagramViewerFeatures features = viewer.getFeatures();
+		return indication.add(features.getHorizontalSpacing() / 2,
+				features.getVerticalSpacing() / 2);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Rectangle2D getCurrentBounds() {
+		return getModelBounds();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void requestFocus() {
+		gridCanvas.requestFocus();
+	}
+
+	/** Paints the grid. */
+	public void paintGrid() {
+		DiagramViewerFeatures features = viewer.getFeatures();
+		double zf = features.getCurrentZoomFactor();
+		double width = gridCanvas.getWidth();
+		double height = gridCanvas.getHeight();
+		GraphicsContext gc = gridCanvas.getGraphicsContext2D();
+		// background
+		Color backgroundColor = features.getBackgroundColor();
+		gc.setFill(backgroundColor);
+		gc.setStroke(backgroundColor);
+		gc.fillRect(0, 0, width, height);
+		// outer border and indicators
+		Color indicatorColor = features.getIndicatorColor();
+		gc.setStroke(indicatorColor);
+		gc.setFill(indicatorColor);
+		double hSpacing = features.getHorizontalSpacing();
+		double vSpacing = features.getVerticalSpacing();
+		double dx = displacement.getX() % 1.0;
+		double dy = displacement.getY() % 1.0;
+		double zfhs = zf * hSpacing;
+		double zfvs = zf * vSpacing;
+		if(features.isDrawOuterBorder()) {
+			if(displacement.getX() < 1.0) {
+				gc.fillRect(0, 0, zfhs * (1.0 - dx), height);
+			}
+			if(displacement.getY() < 1.0) {
+				gc.fillRect(0, 0, width, zfvs * (1.0 - dy));
+			}
+		}
+		// abort if no indicators should be drawn
+		IndicatorType indicatorType = features.getIndicatorType();
+		if(indicatorType == IndicatorType.INVISIBLE) {
+			return;
+		}
+		// draw grid indicators
+		double isize = viewer.getFeatures().getIndicatorSize() * zf;
+		for(double x = zfhs * (1.0 - dx); x < width; x += zfhs) {
+			for(double y = zfvs * (1.0 - dy); y < height; y += zfvs) {
+				drawIndicator(gc, x, y, isize, indicatorType);
+			}
+		}
+	}
+
+	/** Draws the indicators. */
+	private void drawIndicator(GraphicsContext gc, double x, double y, double size,
+			IndicatorType indicatorType) {
+		switch(indicatorType) {
+			case CIRCLE:
+				drawCircle(gc, x, y, size);
+				break;
+			case CROSS:
+				drawCross(gc, x, y, size);
+				break;
+			case SQUARE:
+				drawSquare(gc, x, y, size);
+				break;
+			default:
+				break;
+		}
+	}
+
+	/** Draws a small cross at the given point. */
+	private void drawCircle(GraphicsContext gc, double x, double y, double size) {
+		gc.fillOval(x - size, y - size, 2 * size, 2 * size);
+	}
+
+	/** Draws a small cross at the given point. */
+	private void drawCross(GraphicsContext gc, double x, double y, double size) {
+		gc.strokeLine(x - size, y, x + size, y);
+		gc.strokeLine(x, y - size, x, y + size);
+	}
+
+	/** Draws a small square at the given point. */
+	private void drawSquare(GraphicsContext gc, double x, double y, double size) {
+		gc.fillRect(x - size, y - size, 2 * size, 2 * size);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public EDragGesture getDragGesture(Node node, DiagramCoordinate diagramLocation) {
+		if(node == gridCanvas) {
+			return EDragGesture.SELECTION;
+		}
+		return EDragGesture.NONE;
+	}
+
+	/** Sets horizontal scroll displacement. */
+	public void setScrollX(double canvasDeltaX) {
+		displacement = new DiagramCoordinate(canvasDeltaX, displacement.getY());
+		paintGrid();
+	}
+
+	/** Sets vertical scroll displacement. */
+	public void setScrollY(double canvasDeltaY) {
+		displacement = new DiagramCoordinate(displacement.getX(), canvasDeltaY);
+		paintGrid();
+	}
+
+	/** Sets the width of the grid canvas. */
+	public void setWidth(double w) {
+		gridCanvas.setWidth(w);
+	}
+
+	/** Sets the height of the grid canvas. */
+	public void setHeight(double h) {
+		gridCanvas.setHeight(h);
+	}
+
+	/** Relocates the given scene point to local coordinates. */
+	public Point2D sceneToLocal(Point2D locationInScene) {
+		return gridCanvas.sceneToLocal(locationInScene);
+	}
+
+	/** Returns whether the given node is the grid canvas. */
+	public boolean isGridCanvas(Node source) {
+		return source == gridCanvas;
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/MVCBundleManager.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/MVCBundleManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..2b4ab114c55b30a3d98d7135458f8f3ddd244d58
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/MVCBundleManager.java
@@ -0,0 +1,638 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.FOCUS_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.PRIMARY_SELECTION_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.SECONDARY_SELECTION_TAG;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.Change;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IControllerFactory;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelFactory;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundleWithParent;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl.ContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl.ContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl.DiagramAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl.DiagramMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl.LinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl.MVCBundleBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentAnchorageVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IDiagramAnchorageVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.ILinkVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisualFactory;
+
+/**
+ * This class encapsulates all methods for handling {@link IMVCBundle}s (including selection)
+ * management and is solely used by {@link DiagramViewer} (i.e. its purpose is to reduce the size of
+ * the {@link DiagramViewer} class).
+ */
+/* package */final class MVCBundleManager {
+	/** The diagram viewer. */
+	private final DiagramViewer viewer;
+	/** The diagram layers. */
+	private final DiagramLayers layers;
+	/** The selection changed callback used to notify about selection changes. */
+	private final Consumer<DiagramViewerSelection> selectionChangedCallback;
+	/**
+	 * The model modifier is called once a change (or a set of changes) needs to be applied to the
+	 * underlying data model. If this reference is {@code null}, the framework simply calls the
+	 * controller implementations.
+	 */
+	private final Consumer<Change> modelModifier;
+	/** The factory for model elements displayed within this viewer. */
+	/* package */ final IModelFactory modelFactory;
+	/** The factory for visuals of this viewer. */
+	/* package */ final IVisualFactory visualFactory;
+	/** The factory for controllers of this viewer. */
+	/* package */ final IControllerFactory controllerFactory;
+	/** The grid canvas visual. */
+	/* package */ GridCanvasVisual gridCanvasVisual;
+	/** The {@link IMVCBundle}s for content elements. */
+	/* package */ Map<Object, IContentMVCBundle> contentBundles = new HashMap<>();
+	/** The {@link IMVCBundle}s for diagram anchorage elements. */
+	/* package */ Map<Object, IDiagramAnchorageMVCBundle> diagramAnchorageBundles = new HashMap<>();
+	/** The {@link IMVCBundle}s for content anchorage elements. */
+	/* package */ Map<Object, IContentAnchorageMVCBundle> contentAnchorageBundles = new HashMap<>();
+	/** The {@link IMVCBundle}s for link elements. */
+	/* package */ Map<Object, ILinkMVCBundle> linkBundles = new HashMap<>();
+	/** The list of possible link targets. */
+	/* package */ List<IMVCBundle> possibleLinkTargets = new ArrayList<IMVCBundle>();
+	/** The {@link IDiagramMVCBundle} of this viewer. */
+	/* package */ IDiagramMVCBundle diagramBundle;
+	/** The selected {@link IMVCBundle}. */
+	/* package */ IMVCBundle primarySelectedBundle;
+	/** The list of secondary selected {@link IMVCBundle}. */
+	/* package */ List<IMVCBundle> secondarySelectedBundles = new LinkedList<>();
+
+	/** Constructor. */
+	/* package */ MVCBundleManager(DiagramViewer viewer, IModelFactory modelFactory,
+			IVisualFactory visualFactory, IControllerFactory controllerFactory,
+			DiagramLayers layers, Consumer<DiagramViewerSelection> selectionChangedCallback,
+			Consumer<Change> modelModifier) {
+		this.viewer = viewer;
+		this.gridCanvasVisual = new GridCanvasVisual(viewer);
+		this.modelFactory = modelFactory;
+		this.visualFactory = visualFactory;
+		this.controllerFactory = controllerFactory;
+		this.layers = layers;
+		this.selectionChangedCallback = selectionChangedCallback;
+		this.modelModifier = modelModifier;
+	}
+
+	/** Returns the model modifier. */
+	public Consumer<Change> getModelModifier() {
+		return modelModifier;
+	}
+
+	/** Sets the selected {@link IMVCBundle} as single primary selection. */
+	/* package */ void setSingleSelectedMVCBundle(IMVCBundle sel) {
+		if(primarySelectedBundle == sel) {
+			return;
+		}
+		// single selection on other than primary clears multi-selection
+		for(IMVCBundle mvcb : secondarySelectedBundles) {
+			mvcb.removeTag(SECONDARY_SELECTION_TAG);
+			mvcb.getVisual().updateNodes(layers);
+		}
+		secondarySelectedBundles.clear();
+		// single selection clears old selection
+		IMVCBundle oldSelection = primarySelectedBundle;
+		if(oldSelection != null) {
+			oldSelection.removeTag(FOCUS_TAG);
+			oldSelection.removeTag(PRIMARY_SELECTION_TAG);
+			oldSelection.getVisual().updateNodes(layers);
+		}
+		primarySelectedBundle = sel;
+		if(primarySelectedBundle != null) {
+			primarySelectedBundle.getVisual().requestFocus();
+			primarySelectedBundle.addTag(FOCUS_TAG);
+			primarySelectedBundle.addTag(PRIMARY_SELECTION_TAG);
+			primarySelectedBundle.getVisual().updateNodes(layers);
+		}
+		fireSelectionChanged();
+	}
+
+	/**
+	 * Adds the given bundle as primary selection and moves the current selection to the secondary
+	 * selections list.
+	 */
+	/* package */ void addSelectedMVCBundle(IMVCBundle sel) {
+		if(primarySelectedBundle == sel || secondarySelectedBundles.contains(sel)) {
+			return;
+		}
+		// make current primary selection into a secondary selection
+		if(primarySelectedBundle != null) {
+			primarySelectedBundle.removeTag(PRIMARY_SELECTION_TAG);
+			primarySelectedBundle.addTag(SECONDARY_SELECTION_TAG);
+			secondarySelectedBundles.add(primarySelectedBundle);
+			primarySelectedBundle.getVisual().updateNodes(layers);
+		}
+		// set new primary selection
+		primarySelectedBundle = sel;
+		if(primarySelectedBundle != null) {
+			primarySelectedBundle.addTag(PRIMARY_SELECTION_TAG);
+			primarySelectedBundle.getVisual().updateNodes(layers);
+		}
+		fireSelectionChanged();
+	}
+
+	/**
+	 * Removes the given bundle from either the primary selection (selecting next the secondary list
+	 * element) or from the secondary selection list.
+	 */
+	/* package */ void removeSelectedMVCBundle(IMVCBundle sel) {
+		if(primarySelectedBundle == sel) {
+			sel.removeTag(PRIMARY_SELECTION_TAG);
+			if(secondarySelectedBundles.isEmpty()) {
+				primarySelectedBundle = null;
+			} else {
+				primarySelectedBundle =
+						secondarySelectedBundles.remove(secondarySelectedBundles.size() - 1);
+				primarySelectedBundle.removeTag(SECONDARY_SELECTION_TAG);
+				primarySelectedBundle.addTag(PRIMARY_SELECTION_TAG);
+				primarySelectedBundle.getVisual().updateNodes(layers);
+			}
+		} else {
+			sel.removeTag(SECONDARY_SELECTION_TAG);
+			secondarySelectedBundles.remove(sel);
+		}
+		sel.getVisual().updateNodes(layers);
+		fireSelectionChanged();
+	}
+
+	/** Calls the {@link #selectionChangedCallback} to inform it about a selection change. */
+	private void fireSelectionChanged() {
+		if(selectionChangedCallback != null) {
+			selectionChangedCallback.accept(getSelection());
+		}
+	}
+
+	/** Initializes the viewer content. */
+	/* package */ void updateContentObjects() {
+		possibleLinkTargets.clear();
+		// remember old selection
+		Object oldSelection =
+				primarySelectedBundle != null ? primarySelectedBundle.getModel() : null;
+		if(oldSelection == null) {
+			setSingleSelectedMVCBundle(null);
+		}
+		// before creating any content, allow model factory to update
+		modelFactory.update();
+		// initialize the new hash maps
+		Map<Object, IContentMVCBundle> newContentBundles = new HashMap<>();
+		Map<Object, IDiagramAnchorageMVCBundle> newDiagramAnchorageBundles = new HashMap<>();
+		Map<Object, IContentAnchorageMVCBundle> newContentAnchorageBundles = new HashMap<>();
+		Map<Object, ILinkMVCBundle> newLinkBundles = new HashMap<>();
+		// construct model by moving MVCBs from the old maps to the new ones
+		updateDiagramBundle();
+		oldSelection = updateContentAndContentAnchorageBundles(oldSelection, newContentBundles,
+				newContentAnchorageBundles);
+		oldSelection = updateDiagramAnchorages(oldSelection, newDiagramAnchorageBundles);
+		oldSelection = updateLinkBundles(oldSelection, newDiagramAnchorageBundles,
+				newContentAnchorageBundles, newLinkBundles);
+		// select the diagram if the old selection belongs to a bundle which is not
+		// contained in the new bundle maps
+		if(oldSelection != null) {
+			setSingleSelectedMVCBundle(diagramBundle);
+		}
+		// iterate through all old maps and remove MVCB references
+		detachAndRemoveRemainingMVCB();
+		// set new maps active
+		this.contentBundles = newContentBundles;
+		this.diagramAnchorageBundles = newDiagramAnchorageBundles;
+		this.contentAnchorageBundles = newContentAnchorageBundles;
+		this.linkBundles = newLinkBundles;
+		// update visuals
+		updateAllVisuals();
+	}
+
+	/** Performs an update of the link bundles. */
+	private Object updateLinkBundles(Object oldSelection,
+			Map<Object, IDiagramAnchorageMVCBundle> newDiagramAnchorageBundles,
+			Map<Object, IContentAnchorageMVCBundle> newContentAnchorageBundles,
+			Map<Object, ILinkMVCBundle> newLinkBundles) {
+		for(Object link : modelFactory.getLinkModels()) {
+			ILinkMVCBundle bundle = linkBundles.remove(link);
+			if(bundle == null) {
+				bundle = initLinks(link, newDiagramAnchorageBundles, newContentAnchorageBundles);
+			} else {
+				reinitLinks(bundle, newDiagramAnchorageBundles, newContentAnchorageBundles);
+			}
+			newLinkBundles.put(link, bundle);
+			if(link == oldSelection) {
+				setSingleSelectedMVCBundle(bundle);
+				oldSelection = null;
+			}
+		}
+		return oldSelection;
+	}
+
+	/** Performs an update of the diagram anchorage bundles. */
+	private Object updateDiagramAnchorages(Object oldSelection,
+			Map<Object, IDiagramAnchorageMVCBundle> newDiagramAnchorageBundles) {
+		for(Object m : modelFactory.getDiagramAnchorageModels()) {
+			IDiagramAnchorageMVCBundle bundle = diagramAnchorageBundles.remove(m);
+			if(bundle == null) {
+				bundle = initDiagramAnchorages(m);
+			}
+			newDiagramAnchorageBundles.put(m, bundle);
+			if(m == oldSelection) {
+				setSingleSelectedMVCBundle(bundle);
+				oldSelection = null;
+			}
+			if(bundle.getController().isPossibleLinkTarget()) {
+				possibleLinkTargets.add(bundle);
+			}
+		}
+		return oldSelection;
+	}
+
+	/** Performs an update of the parent relationship of content bundles. */
+	private void updateParentRelationship(Map<Object, IContentMVCBundle> newContentBundles,
+			List<?> contentModels) {
+		for(Object m : contentModels) {
+			Object parentModel = modelFactory.getParent(m);
+			if(parentModel != null) {
+				IContentMVCBundle parentBundle = newContentBundles.get(parentModel);
+				IContentMVCBundleWithParent elementBundle =
+						(IContentMVCBundleWithParent)newContentBundles.get(m);
+				if(elementBundle != null && parentBundle != null) {
+					elementBundle.setParentBundle(parentBundle);
+				}
+			}
+		}
+	}
+
+	/** Performs an update of the content bundles and content anchorage bundles. */
+	private Object updateContentAndContentAnchorageBundles(Object oldSelection,
+			Map<Object, IContentMVCBundle> newContentBundles,
+			Map<Object, IContentAnchorageMVCBundle> newContentAnchorageBundles) {
+		List<?> contentModels = modelFactory.getContentModels();
+		for(Object m : contentModels) {
+			IContentMVCBundle bundle = contentBundles.remove(m);
+			if(bundle == null) {
+				bundle = initContentBundle(m);
+			}
+			newContentBundles.put(m, bundle);
+			if(m == oldSelection) {
+				setSingleSelectedMVCBundle(bundle);
+				oldSelection = null;
+			}
+			if(bundle.getController().isPossibleLinkTarget()) {
+				possibleLinkTargets.add(bundle);
+			}
+			// initialize content anchorages
+			for(Object cElement : modelFactory.getContentAnchorageModels(m)) {
+				IContentAnchorageMVCBundle cBundle = contentAnchorageBundles.remove(cElement);
+				if(cBundle == null) {
+					cBundle = initSingleAttachedContentAnchorage(bundle, cElement);
+				}
+				newContentAnchorageBundles.put(cElement, cBundle);
+				if(cElement == oldSelection) {
+					setSingleSelectedMVCBundle(cBundle);
+					oldSelection = null;
+				}
+				if(cBundle.getController().isPossibleLinkTarget()) {
+					possibleLinkTargets.add(cBundle);
+				}
+			}
+			// clear child bundles (will be setup again in the next loop if child has not
+			// disappeared)
+			bundle.getChildren().clear();
+		}
+		// setup parent relationship between content
+		updateParentRelationship(newContentBundles, contentModels);
+
+		return oldSelection;
+	}
+
+	/** Performs an update of the diagram bundle. */
+	private void updateDiagramBundle() {
+		boolean attached = true;
+		if(diagramBundle != null && diagramBundle.getModel() != modelFactory.getRootModel()) {
+			gridCanvasVisual.removeAllVisuals(layers);
+			diagramBundle.detach();
+			diagramBundle.remove();
+			gridCanvasVisual = null;
+			diagramBundle = null;
+		}
+		if(diagramBundle == null) {
+			diagramBundle = new DiagramMVCBundle(modelFactory.getRootModel(), viewer);
+			gridCanvasVisual = new GridCanvasVisual(viewer);
+			diagramBundle.setVisual(gridCanvasVisual);
+			attached = false;
+		}
+		IController diagramController = controllerFactory.createDiagramController(diagramBundle);
+		diagramBundle.setController(diagramController);
+		if(!attached) {
+			diagramBundle.attach();
+		}
+		if(diagramController.isPossibleLinkTarget()) {
+			possibleLinkTargets.add(diagramBundle);
+		}
+	}
+
+	/** Detaches and removes all remaining {@link IMVCBundle}s. */
+	private void detachAndRemoveRemainingMVCB() {
+		for(ILinkMVCBundle b : linkBundles.values()) {
+			b.getVisual().removeAllVisuals(layers);
+			b.detach();
+			b.remove();
+		}
+		for(IContentAnchorageMVCBundle b : contentAnchorageBundles.values()) {
+			b.getVisual().removeAllVisuals(layers);
+			b.detach();
+			b.remove();
+		}
+		for(IDiagramAnchorageMVCBundle b : diagramAnchorageBundles.values()) {
+			b.getVisual().removeAllVisuals(layers);
+			b.detach();
+			b.remove();
+		}
+		for(IContentMVCBundle b : contentBundles.values()) {
+			b.getVisual().removeAllVisuals(layers);
+			b.detach();
+			b.remove();
+		}
+	}
+
+	/**
+	 * Initializes the {@link MVCBundleBase} for the given element and adds it to the
+	 * visuals.
+	 */
+	private IContentMVCBundle initContentBundle(Object m) {
+		IContentMVCBundle mvcb = new ContentMVCBundle(m, diagramBundle, viewer);
+		IContentVisual visual = visualFactory.createContentVisual(mvcb);
+		mvcb.setVisual(visual);
+		IController contentController = controllerFactory.createContentController(mvcb);
+		mvcb.setController(contentController);
+		mvcb.attach();
+		return mvcb;
+	}
+
+	/**
+	 * Initializes the {@link MVCBundleBase} for the given element and adds it to the
+	 * visuals.
+	 */
+	private DiagramAnchorageMVCBundle initDiagramAnchorages(Object m) {
+		DiagramAnchorageMVCBundle mvcb = new DiagramAnchorageMVCBundle(m, diagramBundle, viewer);
+		IDiagramAnchorageVisual visual = visualFactory.createDiagramAnchorageVisual(mvcb);
+		mvcb.setVisual(visual);
+		IController diagramAnchorageController =
+				controllerFactory.createDiagramAnchorageController(mvcb);
+		mvcb.setController(diagramAnchorageController);
+		mvcb.attach();
+		return mvcb;
+	}
+
+	/** Handles the focus reaction when the focus property is set to {@code focus}. */
+	/* package */ void handleFocus(boolean focus, DiagramLayers layers) {
+		if(primarySelectedBundle == null) {
+			return;
+		}
+		if(focus) {
+			primarySelectedBundle.addTag(FOCUS_TAG);
+		} else {
+			primarySelectedBundle.removeTag(FOCUS_TAG);
+		}
+		primarySelectedBundle.getVisual().updateNodes(layers);
+	}
+
+	/** Adds a content anchorage for the given element and content bundle. */
+	private IContentAnchorageMVCBundle initSingleAttachedContentAnchorage(IContentMVCBundle pBundle,
+			Object cElement) {
+		IContentAnchorageMVCBundle cBundle =
+				new ContentAnchorageMVCBundle(cElement, pBundle, viewer);
+		IContentAnchorageVisual cVisual = visualFactory.createContentAnchorageVisual(cBundle);
+		cBundle.setVisual(cVisual);
+		IController contentAnchorageController =
+				controllerFactory.createContentAnchorageController(cBundle);
+		cBundle.setController(contentAnchorageController);
+		cBundle.attach();
+		return cBundle;
+	}
+
+	/** Initializes the link {@link ILinkMVCBundle}s. */
+	private ILinkMVCBundle initLinks(Object link,
+			Map<Object, IDiagramAnchorageMVCBundle> diagramAnchorageBundles,
+			Map<Object, IContentAnchorageMVCBundle> contentAnchorageBundles) {
+		Object startModel = modelFactory.getLinkStart(link);
+		if(startModel == null) {
+			return null;
+		}
+		IAnchorageMVCBundle startBundle = getBundleForAnchorageModel(startModel,
+				diagramAnchorageBundles, contentAnchorageBundles);
+		Object endModel = modelFactory.getLinkEnd(link);
+		if(endModel == null) {
+			return null;
+		}
+		IAnchorageMVCBundle endBundle = getBundleForAnchorageModel(endModel,
+				diagramAnchorageBundles, contentAnchorageBundles);
+		LinkMVCBundle lBundle =
+				new LinkMVCBundle(link, diagramBundle, startBundle, endBundle, viewer);
+		ILinkVisual visual = visualFactory.createLinkVisual(lBundle);
+		lBundle.setVisual(visual);
+		IController linkController = controllerFactory.createLinkController(lBundle);
+		lBundle.setController(linkController);
+		lBundle.attach();
+		if(linkController.isPossibleLinkTarget()) {
+			possibleLinkTargets.add(lBundle);
+		}
+		return lBundle;
+	}
+
+	/** Updates source and target of the link {@link ILinkMVCBundle}s. */
+	private void reinitLinks(ILinkMVCBundle linkBundle,
+			Map<Object, IDiagramAnchorageMVCBundle> diagramAnchorageBundles,
+			Map<Object, IContentAnchorageMVCBundle> contentAnchorageBundles) {
+		Object link = linkBundle.getModel();
+		Object startModel = modelFactory.getLinkStart(link);
+		if(startModel == null) {
+			throw new IllegalArgumentException("Link start model element cannot be null!");
+		}
+		IAnchorageMVCBundle startBundle = getBundleForAnchorageModel(startModel,
+				diagramAnchorageBundles, contentAnchorageBundles);
+		Object endModel = modelFactory.getLinkEnd(link);
+		if(endModel == null) {
+			throw new IllegalArgumentException("Link target model element cannot be null!");
+		}
+		IAnchorageMVCBundle endBundle = getBundleForAnchorageModel(endModel,
+				diagramAnchorageBundles, contentAnchorageBundles);
+		linkBundle.updateLinkBundle(startBundle, endBundle);
+	}
+
+	/** Returns the {@link MVCBundleBase} of the given anchorage node's model. */
+	private IAnchorageMVCBundle getBundleForAnchorageModel(Object m,
+			Map<Object, IDiagramAnchorageMVCBundle> diagramAnchorageBundles,
+			Map<Object, IContentAnchorageMVCBundle> contentAnchorageBundles) {
+		IAnchorageMVCBundle result = diagramAnchorageBundles.get(m);
+		if(result != null) {
+			return result;
+		}
+		result = contentAnchorageBundles.get(m);
+		if(result != null) {
+			return result;
+		}
+		throw new IllegalArgumentException(
+				"Anchorage node model is neither a diagram nor a content anchorage.");
+	}
+
+	/** Updates every visual (and only its appearance, not the structure). */
+	/* package */ void updateAllVisuals() {
+		layers.setScale(viewer.getFeatures().getCurrentZoomFactor());
+		viewer.updateGridCanvasSize();
+		gridCanvasVisual.updateNodes(layers);
+		for(IContentMVCBundle b : contentBundles.values()) {
+			if(b instanceof IContentMVCBundleWithParent) {
+				IContentMVCBundleWithParent wp = (IContentMVCBundleWithParent)b;
+				if(wp.getParentBundle() == null) {
+					updateChildContentVisualsRecursively(b);
+				}
+			} else {
+				updateChildContentVisualsRecursively(b);
+			}
+		}
+		for(IDiagramAnchorageMVCBundle b : diagramAnchorageBundles.values()) {
+			b.getVisual().updateNodes(layers);
+		}
+		for(IContentAnchorageMVCBundle b : contentAnchorageBundles.values()) {
+			b.getVisual().updateNodes(layers);
+		}
+		for(ILinkMVCBundle b : linkBundles.values()) {
+			b.getVisual().updateNodes(layers);
+		}
+	}
+
+	/** Updates all child content visuals recursively (but does not update anchorages and links). */
+	private void updateChildContentVisualsRecursively(IContentMVCBundle bundle) {
+		bundle.getVisual().updateNodes(layers);
+		for(IContentMVCBundle cb : bundle.getChildren()) {
+			updateChildContentVisualsRecursively(cb);
+		}
+	}
+
+	/** Updates the nodes selectively adhering to the relation given by the {@link IMVCBundle}s. */
+	/* package */ void updateVisual(IMVCBundle bundle) {
+		if(bundle instanceof IContentMVCBundle) {
+			updateContentVisual((IContentMVCBundle)bundle);
+		} else if(bundle instanceof IAnchorageMVCBundle) {
+			updateAnchorage((IAnchorageMVCBundle)bundle);
+		} else if(bundle instanceof ILinkMVCBundle) {
+			bundle.getVisual().updateNodes(layers);
+		} else if(bundle instanceof IDiagramMVCBundle) {
+			updateAllVisuals();
+		}
+	}
+
+	/** Updates the nodes selectively adhering to the relation given by the {@link IMVCBundle}s. */
+	/* package */ void updateModelVisual(Object model) {
+		if(contentBundles.containsKey(model)) {
+			updateContentVisual(contentBundles.get(model));
+		} else if(contentAnchorageBundles.containsKey(model)) {
+			updateAnchorage(contentAnchorageBundles.get(model));
+		} else if(diagramAnchorageBundles.containsKey(model)) {
+			updateAnchorage(diagramAnchorageBundles.get(model));
+		} else if(linkBundles.containsKey(model)) {
+			linkBundles.get(model).getVisual().updateNodes(layers);
+		} else if(diagramBundle.getModel() == model) {
+			updateAllVisuals();
+		}
+	}
+
+	/** Updates the given {@link IContentMVCBundle}. */
+	private void updateContentVisual(IContentMVCBundle bundle) {
+		bundle.getVisual().updateNodes(layers);
+		for(IContentAnchorageMVCBundle cab : bundle.getAnchorages()) {
+			updateAnchorage(cab);
+		}
+		for(IContentMVCBundle cb : bundle.getChildren()) {
+			updateContentVisual(cb);
+		}
+	}
+
+	/** Updates the given {@link IContentAnchorageMVCBundle}. */
+	private void updateAnchorage(IAnchorageMVCBundle bundle) {
+		bundle.getVisual().updateNodes(layers);
+		for(ILinkMVCBundle link : bundle.getIncomingLinks()) {
+			link.getVisual().updateNodes(layers);
+		}
+		for(ILinkMVCBundle link : bundle.getOutgoingLinks()) {
+			link.getVisual().updateNodes(layers);
+		}
+	}
+
+	/** Returns the root model element of this diagram. */
+	/* package */ Object getRootModel() {
+		return modelFactory.getRootModel();
+	}
+
+	/** Returns the current selection. */
+	public DiagramViewerSelection getSelection() {
+		return new DiagramViewerSelection(primarySelectedBundle, secondarySelectedBundles);
+	}
+
+	/** Returns whether the given bundle is selected as primary. */
+	public boolean isPrimarySelected(IMVCBundle bundle) {
+		return primarySelectedBundle == bundle;
+	}
+
+	/** Returns whether the given bundle is selected as secondary. */
+	public boolean isSecondarySelected(IMVCBundle bundle) {
+		return secondarySelectedBundles.contains(bundle);
+	}
+
+	/** Performs shift selection of the given bundle. */
+	public void handleShiftSelectionOf(IMVCBundle sel) {
+		if(primarySelectedBundle == sel || secondarySelectedBundles.contains(sel)) {
+			removeSelectedMVCBundle(sel);
+		} else {
+			addSelectedMVCBundle(sel);
+		}
+	}
+
+	/** Selects all content, diagram anchorage and link {@link IMVCBundle}s. */
+	public void selectAll() {
+		secondarySelectedBundles.addAll(contentBundles.values());
+		secondarySelectedBundles.addAll(diagramAnchorageBundles.values());
+		secondarySelectedBundles.addAll(linkBundles.values());
+		if(secondarySelectedBundles.isEmpty()) {
+			if(primarySelectedBundle != null) {
+				primarySelectedBundle.removeTag(PRIMARY_SELECTION_TAG);
+			}
+			primarySelectedBundle = null;
+		} else {
+			primarySelectedBundle = secondarySelectedBundles.remove(0);
+			primarySelectedBundle.removeTag(SECONDARY_SELECTION_TAG);
+			primarySelectedBundle.addTag(PRIMARY_SELECTION_TAG);
+		}
+		for(IMVCBundle s : secondarySelectedBundles) {
+			s.removeTag(PRIMARY_SELECTION_TAG);
+			s.addTag(SECONDARY_SELECTION_TAG);
+		}
+		updateAllVisuals();
+		fireSelectionChanged();
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/MouseState.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/MouseState.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff90af6d1cca427ef6f3fded76367b535120a5df
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/MouseState.java
@@ -0,0 +1,379 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef;
+
+import static javafx.scene.input.MouseEvent.MOUSE_ENTERED;
+import static javafx.scene.input.MouseEvent.MOUSE_EXITED;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.HOVER_TAG;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.Change;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IClickController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IDragController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IKeyPressController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentAnchorageVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisual;
+
+import javafx.event.EventHandler;
+import javafx.scene.Cursor;
+import javafx.scene.Node;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.input.MouseButton;
+import javafx.scene.input.MouseEvent;
+
+/**
+ * Class for monitoring mouse operations including clicking, dragging and linking
+ * gestures. The listeners are installed on every node that is added to the diagram layers.
+ * This class stores a mapping from these nodes to {@link IMVCBundle}s in order to route
+ * mouse gestures, clicks and key typing to the respective controller of the respective bundle.
+ */
+public final class MouseState {
+	/** The diagram viewer. */
+	private final DiagramViewer viewer;
+	/** The inverse map for {@link IMVCBundle} lookup with nodes. */
+	private final Map<Node, IMVCBundle> nodesToBundleMap = new HashMap<Node, IMVCBundle>();
+	/**
+	 * The last mouse down event, which is used for location correction during drag gestures. See
+	 * in-code comment of {@link #mouseDragDetectedHandler}.
+	 */
+	private MouseEvent lastMousePressedEvent = null;
+	/** Stores the last mouse location. */
+	private DiagramCoordinate lastMouseLocation = new DiagramCoordinate(0, 0);
+	/** Stores the drag start or {@code null} if there is no drag currently. */
+	private DiagramCoordinate dragStartLocation = null;
+
+	/** Constructor. */
+	public MouseState(DiagramViewer viewer) {
+		this.viewer = viewer;
+	}
+
+	/** The mouse clicked handler. */
+	private EventHandler<MouseEvent> mouseClickedHandler = new EventHandler<MouseEvent>() {
+		@Override
+		public void handle(MouseEvent event) {
+			if(!nodesToBundleMap.containsKey(event.getSource())) {
+				return;
+			}
+			// wild cast works, since source is in nodesMap
+			Node source = (Node)event.getSource();
+			IMVCBundle bundle = nodesToBundleMap.get(source);
+			lastMouseLocation =
+					viewer.convertEventToDiagramCoordinates(event.getX(), event.getY(), source);
+			IClickController cc =
+					bundle.getController().getClickController(source, lastMouseLocation);
+			if(cc != null) {
+				Change chg = null;
+				if(event.getButton() == MouseButton.SECONDARY) {
+					if(event.getClickCount() == 1) {
+						chg = cc.secondaryClick(event, source, lastMouseLocation);
+					}
+				} else if(event.getButton() == MouseButton.PRIMARY) {
+					if(event.getClickCount() == 1) {
+						chg = cc.singleClick(event, source, lastMouseLocation);
+					} else if(event.getClickCount() == 2) {
+						chg = cc.doubleClick(event, source, lastMouseLocation);
+					}
+				}
+				event.consume();
+				if(chg != null) {
+					viewer.getModelModifier().accept(chg);
+				}
+			}
+		}
+	};
+
+	/** The current drag controller is non-null while a drag gesture is in progress. */
+	private IDragController dragController = null;
+
+	/** The drag detected handler deciding about link drag and normal drag gestures. */
+	private EventHandler<MouseEvent> mouseDragDetectedHandler = new EventHandler<MouseEvent>() {
+		@Override
+		public void handle(MouseEvent event) {
+			if(!nodesToBundleMap.containsKey(event.getSource())) {
+				return;
+			}
+			event.consume();
+			// the drag detected event occurs a view pixels away from the mouse press location
+			// (which is required to detect the drag gesture at all). However, we have to take
+			// the mouse pressed location, since that location's cursor might imply a different
+			// operation when the mouse is dragged the one or the other location (e.g. move cursor
+			// is only a view pixels away from resize cursor). If we do not correct the location to
+			// the initial mouse pressed location the drag controller initiates the wrong operation
+			// (e.g. a resize operation although the user wanted a move operation).
+			double x, y;
+			Node source;
+			if(lastMousePressedEvent != null) {
+				x = lastMousePressedEvent.getX();
+				y = lastMousePressedEvent.getY();
+				source = (Node)lastMousePressedEvent.getSource();
+			} else {
+				x = event.getX();
+				y = event.getY();
+				source = (Node)event.getSource();
+			}
+			// wild cast works, since source is in nodesToBundleMap
+			IMVCBundle bundle = nodesToBundleMap.get(source);
+			IVisual visual = bundle.getVisual();
+			lastMouseLocation = viewer.convertEventToDiagramCoordinates(x, y, source);
+			EDragGesture edg = visual.getDragGesture(source, lastMouseLocation);
+			if(edg == EDragGesture.NONE) {
+				return;
+			}
+			dragController = viewer.createDragController(bundle, edg, source, lastMouseLocation);
+			if(dragController != null) {
+				dragController.dragStarted(bundle, source, lastMouseLocation);
+				source.startFullDrag();
+			}
+		}
+	};
+	/** The drag completed handler for move, resize, and link gestures. */
+	private EventHandler<MouseEvent> mouseDragCompletedHandler = new EventHandler<MouseEvent>() {
+		@Override
+		public void handle(MouseEvent event) {
+			if(!nodesToBundleMap.containsKey(event.getSource())) {
+				return;
+			}
+			if(dragController == null) {
+				return;
+			}
+			event.consume();
+			// wild cast works, since source is in nodesToBundleMap
+			Node source = (Node)event.getSource();
+			IMVCBundle bundle = nodesToBundleMap.get(source);
+			lastMouseLocation =
+					viewer.convertEventToDiagramCoordinates(event.getX(), event.getY(), source);
+			Change chg = dragController.dragCompleted(bundle, source, lastMouseLocation);
+			dragController = null;
+			if(chg != null) {
+				viewer.getModelModifier().accept(chg);
+			}
+		}
+	};
+
+	/** The mouse dragged handler. */
+	private EventHandler<MouseEvent> mouseDraggedHandler = new EventHandler<MouseEvent>() {
+		@Override
+		public void handle(MouseEvent event) {
+			if(!nodesToBundleMap.containsKey(event.getSource())) {
+				return;
+			}
+			if(dragController == null) {
+				// there are drag events even before the drag detected event fired
+				// ignore those phantoms
+				return;
+			}
+			event.consume();
+			// wild cast works, since source is in nodesToBundleMap
+			Node source = (Node)event.getSource();
+			IMVCBundle bundle = nodesToBundleMap.get(source);
+			lastMouseLocation =
+					viewer.convertEventToDiagramCoordinates(event.getX(), event.getY(), source);
+			dragController.dragInProgress(bundle, source, lastMouseLocation);
+		}
+	};
+
+	/** The mouse release handler. */
+	private EventHandler<MouseEvent> mouseReleasedHandler = new EventHandler<MouseEvent>() {
+		@Override
+		public void handle(MouseEvent event) {
+			if(lastMousePressedEvent != null &&
+					event.getButton() == lastMousePressedEvent.getButton()) {
+				lastMousePressedEvent = null;
+			}
+			if(!nodesToBundleMap.containsKey(event.getSource())) {
+				return;
+			}
+			if(dragController == null) {
+				return;
+			}
+			event.consume();
+			// wild cast works, since source is in nodesToBundleMap
+			Node source = (Node)event.getSource();
+			IMVCBundle bundle = nodesToBundleMap.get(source);
+			lastMouseLocation =
+					viewer.convertEventToDiagramCoordinates(event.getX(), event.getY(), source);
+			Change chg = dragController.dragCompleted(bundle, source, lastMouseLocation);
+			dragController = null;
+			if(chg != null) {
+				viewer.getModelModifier().accept(chg);
+			}
+		}
+	};
+
+	/** The mouse enter/exit handler. */
+	private EventHandler<MouseEvent> mouseEnterExitHandler = new EventHandler<MouseEvent>() {
+		@Override
+		public void handle(MouseEvent event) {
+			if(!nodesToBundleMap.containsKey(event.getSource())) {
+				return;
+			}
+			event.consume();
+			// wild cast works, since source is in nodesMap
+			Node source = (Node)event.getSource();
+			IMVCBundle bundle = nodesToBundleMap.get(source);
+			if(event.getEventType() == MOUSE_ENTERED) {
+				bundle.addTag(HOVER_TAG);
+				updateVisualsAfterHoverChange(bundle);
+			} else if(event.getEventType() == MOUSE_EXITED) {
+				bundle.removeTag(HOVER_TAG);
+				updateVisualsAfterHoverChange(bundle);
+			}
+			// prevent cursor from changing during drag operations
+			if(dragController != null) {
+				return;
+			}
+			lastMouseLocation =
+					viewer.convertEventToDiagramCoordinates(event.getX(), event.getY(), source);
+			IController ctrl = bundle.getController();
+			if(ctrl != null) {
+				Cursor cursor = ctrl.getCurrentCursor(source, lastMouseLocation);
+				source.setCursor(cursor);
+			}
+		}
+
+		/** Updates visuals after the hover tag has changed. */
+		private void updateVisualsAfterHoverChange(IMVCBundle bundle) {
+			viewer.updateVisual(bundle);
+			// the following two sections allow hidden anchorages, which are
+			// only shown once the mouse hovers over their parent visual.
+			if(bundle instanceof IContentMVCBundle) {
+				IContentMVCBundle contentBundle = (IContentMVCBundle)bundle;
+				IContentVisual contentVisual = contentBundle.getVisual();
+				if(contentVisual.updateAttachedAnchorageVisualsOnHover()) {
+					// update anchorage visuals
+					contentBundle.getAnchorages().stream()
+							.forEach(anchorageBundle -> viewer.updateVisual(anchorageBundle));
+				}
+			} else if(bundle instanceof IContentAnchorageMVCBundle) {
+				IContentAnchorageMVCBundle anchorageBundle = (IContentAnchorageMVCBundle)bundle;
+				IContentAnchorageVisual anchorageVisual = anchorageBundle.getVisual();
+				if(anchorageVisual.updateSiblingAnchorageVisualsOnHover()) {
+					IContentMVCBundle contentBundle = anchorageBundle.getAttachedTo();
+					// update sibling visuals
+					contentBundle.getAnchorages().stream().filter(ab -> ab != anchorageBundle)
+							.forEach(ab -> viewer.updateVisual(ab));
+				}
+			}
+		}
+	};
+
+	/** The mouse motion handler used for updating the cursor. */
+	private EventHandler<MouseEvent> mouseMotionHandler = new EventHandler<MouseEvent>() {
+		@Override
+		public void handle(MouseEvent event) {
+			if(!nodesToBundleMap.containsKey(event.getSource()) || dragController != null) {
+				return;
+			}
+			event.consume();
+			// wild cast works, since source is in nodesMap
+			Node source = (Node)event.getSource();
+			IMVCBundle bundle = nodesToBundleMap.get(source);
+			IController ctrl = bundle.getController();
+			lastMouseLocation =
+					viewer.convertEventToDiagramCoordinates(event.getX(), event.getY(), source);
+			if(ctrl != null) {
+				Cursor cursor = ctrl.getCurrentCursor(source, lastMouseLocation);
+				source.setCursor(cursor);
+			}
+		}
+	};
+
+	/** The mouse pressed handler for selecting the node under the cursor. */
+	private EventHandler<MouseEvent> mousePressedHandler = new EventHandler<MouseEvent>() {
+		@Override
+		public void handle(MouseEvent event) {
+			// see mouseDragDetectedHandler for explanation of storing the last mouse press event
+			lastMousePressedEvent = event;
+			viewer.hideContextMenu();
+			event.consume();
+		}
+	};
+
+	/** The key event handler. */
+	private final EventHandler<KeyEvent> keyEventHandler = new EventHandler<KeyEvent>() {
+		@Override
+		public void handle(KeyEvent event) {
+			if(!nodesToBundleMap.containsKey(event.getSource())) {
+				return;
+			}
+			// wild cast works, since source is in nodesMap
+			Node source = (Node)event.getSource();
+			IMVCBundle bundle = nodesToBundleMap.get(source);
+			IKeyPressController kpc =
+					viewer.createKeyPressController(bundle, source, lastMouseLocation);
+			if(kpc != null) {
+				Change change = kpc.keyEvent(event, bundle, source, lastMouseLocation);
+				if(change != null) {
+					viewer.getModelModifier().accept(change);
+				}
+			}
+		}
+	};
+
+	/** Registers the mouse listeners on the given node. */
+	/* package */ void registerMouseListeners(Node node, IMVCBundle mvcb) {
+		node.setOnMousePressed(mousePressedHandler);
+		node.setOnMouseEntered(mouseEnterExitHandler);
+		node.setOnMouseExited(mouseEnterExitHandler);
+		node.setOnMouseClicked(mouseClickedHandler);
+		node.setOnDragDetected(mouseDragDetectedHandler);
+		node.setOnMouseDragged(mouseDraggedHandler);
+		node.setOnMouseDragOver(mouseDraggedHandler);
+		node.setOnMouseReleased(mouseReleasedHandler);
+		node.setOnMouseDragReleased(mouseDragCompletedHandler);
+		node.setOnMouseMoved(mouseMotionHandler);
+		node.setOnKeyReleased(keyEventHandler);
+		nodesToBundleMap.put(node, mvcb);
+	}
+
+	/** Unregisters the mouse listeners from the given node. */
+	/* package */ void unregisterMouseListeners(Node node) {
+		node.setOnKeyReleased(null);
+		node.setOnMouseMoved(null);
+		node.setOnMouseDragReleased(null);
+		node.setOnMouseReleased(null);
+		node.setOnMouseDragOver(null);
+		node.setOnMouseDragged(null);
+		node.setOnDragDetected(null);
+		node.setOnMouseClicked(null);
+		node.setOnMouseExited(null);
+		node.setOnMouseEntered(null);
+		node.setOnMousePressed(null);
+		nodesToBundleMap.remove(node);
+	}
+
+	/** Returns the last known mouse location. */
+	public DiagramCoordinate getLastMouseLocation() {
+		return lastMouseLocation;
+	}
+
+	/** Returns the drag start location or {@code null} if no drag is in progress. */
+	public DiagramCoordinate getDragStartLocation() {
+		return dragStartLocation;
+	}
+
+	/**
+	 * Returns the current drag extent, i.e., last mouse position minus drag start location, or
+	 * {@code null} if no drag is in progress.
+	 */
+	public DiagramCoordinate getDragExtent() {
+		if(dragStartLocation == null || lastMouseLocation == null) {
+			return null;
+		}
+		return lastMouseLocation.subtract(dragStartLocation);
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/SVGExporter.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/SVGExporter.java
new file mode 100644
index 0000000000000000000000000000000000000000..2211f06d81c7b0523ae52dc832410a76875a9e07
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/SVGExporter.java
@@ -0,0 +1,368 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef;
+
+import java.util.List;
+
+import javafx.geometry.Bounds;
+import javafx.scene.Group;
+import javafx.scene.Node;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.Region;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Circle;
+import javafx.scene.shape.ClosePath;
+import javafx.scene.shape.CubicCurve;
+import javafx.scene.shape.CubicCurveTo;
+import javafx.scene.shape.Ellipse;
+import javafx.scene.shape.Line;
+import javafx.scene.shape.LineTo;
+import javafx.scene.shape.MoveTo;
+import javafx.scene.shape.Path;
+import javafx.scene.shape.PathElement;
+import javafx.scene.shape.QuadCurve;
+import javafx.scene.shape.QuadCurveTo;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.shape.Shape;
+import javafx.scene.text.Text;
+
+/** Export the {@link DiagramViewer} to a SVG file. */
+final class SVGExporter {
+	/** The diagram viewer node to be exported. */
+	private final Pane diagramNode;
+	/** The width of the diagram computed from the nodes. */
+	private double width = 0;
+	/** The height of the diagram computed from the nodes. */
+	private double height = 0;
+
+	/** Constructor. */
+	public SVGExporter(DiagramViewer viewer) {
+		// first element of the diagram root scrolled pane is the content pane, see DiagramViewer
+		this.diagramNode = (Pane)viewer.getVisualNode().getChildren().get(0);
+	}
+
+	/** Exports the diagram to the target file. */
+	public String export() {
+		width = height = 0;
+		// first convert the nodes in order to compute width and height
+		StringBuilder content = new StringBuilder();
+		for(Node n : diagramNode.getChildrenUnmodifiable()) {
+			transform(n, content);
+		}
+
+		// then build an SVG XML file
+		StringBuilder sb = new StringBuilder();
+		sb.append("<?xml version=\"1.0\" standalone=\"no\"?>\n");
+
+		sb.append("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" ");
+		sb.append("\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
+
+		sb.append("<svg ");
+		setProp("width", width, sb);
+		setProp("height", height, sb);
+		setProp("version", "1.1", sb);
+		setProp("xmlns", "http://www.w3.org/2000/svg", sb);
+		setProp("xmlns:xlink", "http://www.w3.org/1999/xlink", sb);
+		sb.append(">\n");
+
+		sb.append(content);
+		sb.append("</svg>\n");
+		return sb.toString();
+	}
+
+	/** Transforms from {@link Node} to a SVG element. */
+	private void transform(Node node, StringBuilder sb) {
+		Bounds b = node.getBoundsInParent();
+		if(b.getMaxX() > width) {
+			width = b.getMaxX();
+		}
+		if(b.getMaxY() > height) {
+			height = b.getMaxY();
+		}
+		if(node instanceof Region) {
+			transform((Region)node, sb);
+		} else if(node instanceof Group) {
+			transform((Group)node, sb);
+		} else if(node instanceof Rectangle) {
+			transform((Rectangle)node, sb);
+		} else if(node instanceof Ellipse) {
+			transform((Ellipse)node, sb);
+		} else if(node instanceof Circle) {
+			transform((Circle)node, sb);
+		} else if(node instanceof QuadCurve) {
+			transform((QuadCurve)node, sb);
+		} else if(node instanceof CubicCurve) {
+			transform((CubicCurve)node, sb);
+		} else if(node instanceof Line) {
+			transform((Line)node, sb);
+		} else if(node instanceof Text) {
+			transform((Text)node, sb);
+		} else if(node instanceof Path) {
+			transform((Path)node, sb);
+		}
+		// silently ignore the rest
+	}
+
+	/** Writes a SVG path element. */
+	private static void writePath(StringBuilder sb, String op, double... coords) {
+		sb.append(op).append(' ');
+		for(double d : coords) {
+			sb.append(d).append(' ');
+		}
+	}
+
+	/** Transforms from {@link QuadCurve} to SVG Path element. */
+	private void transform(CubicCurve curve, StringBuilder sb) {
+		StyleHelper st = new StyleHelper(curve);
+		if(st.isTransparent()) {
+			return;
+		}
+		StringBuilder db = new StringBuilder();
+		db.append("M ").append(curve.getStartX()).append(' ').append(curve.getStartY()).append(' ');
+		db.append("C ").append(curve.getControlX1()).append(' ').append(curve.getControlY1())
+				.append(' ');
+		db.append(curve.getControlX2()).append(' ').append(curve.getControlY2()).append(' ');
+		db.append(curve.getEndX()).append(' ').append(curve.getEndY());
+		sb.append("<path ");
+		setProp("d", db.toString(), sb);
+		st.applySVGStyle(sb);
+		sb.append("/>\n");
+	}
+
+	/** Transforms from {@link QuadCurve} to SVG Path element. */
+	private void transform(QuadCurve curve, StringBuilder sb) {
+		StyleHelper st = new StyleHelper(curve);
+		if(st.isTransparent()) {
+			return;
+		}
+		StringBuilder db = new StringBuilder();
+		db.append("M ").append(curve.getStartX()).append(' ').append(curve.getStartY()).append(' ');
+		db.append("Q ").append(curve.getControlX()).append(' ').append(curve.getControlY())
+				.append(' ');
+		db.append(curve.getEndX()).append(' ').append(curve.getEndY());
+		sb.append("<path ");
+		setProp("d", db.toString(), sb);
+		st.applySVGStyle(sb);
+		sb.append("/>\n");
+	}
+
+	/** Converts {@link PathElement}s to SVG D specification. */
+	private static String convertPathToD(Path path) {
+		StringBuilder db = new StringBuilder();
+		for(PathElement pe : path.getElements()) {
+			if(pe instanceof MoveTo) {
+				MoveTo mt = (MoveTo)pe;
+				writePath(db, "M", mt.getX(), mt.getY());
+			} else if(pe instanceof LineTo) {
+				LineTo lt = (LineTo)pe;
+				writePath(db, "L", lt.getX(), lt.getY());
+			} else if(pe instanceof QuadCurveTo) {
+				QuadCurveTo qc = (QuadCurveTo)pe;
+				writePath(db, "Q", qc.getControlX(), qc.getControlY(), qc.getX(), qc.getY());
+			} else if(pe instanceof CubicCurveTo) {
+				CubicCurveTo cc = (CubicCurveTo)pe;
+				writePath(db, "C", cc.getControlX1(), cc.getControlY1(), cc.getControlX2(),
+						cc.getControlY2(), cc.getX(), cc.getY());
+			} else if(pe instanceof ClosePath) {
+				writePath(db, "Z");
+			}
+		}
+		return db.toString();
+	}
+
+	/** Transforms from {@link Path} to SVG Path element. */
+	private void transform(Path path, StringBuilder sb) {
+		StyleHelper st = new StyleHelper(path);
+		if(st.isTransparent()) {
+			return;
+		}
+		sb.append("<path ");
+		setProp("d", convertPathToD(path), sb);
+		st.applySVGStyle(sb);
+		sb.append("/>\n");
+	}
+
+	/** Transforms from {@link Text} to SVG Path element. */
+	private void transform(Text text, StringBuilder sb) {
+		StyleHelper st = new StyleHelper(text);
+		if(st.isTransparent()) {
+			return;
+		}
+		sb.append("<text ");
+		setProp("x", text.getX(), sb);
+		setProp("y", text.getY(), sb);
+		setProp("font-family", "Arial, Helvetica, sans-serif", sb);
+		st.applySVGStyle(sb);
+		sb.append(">\n");
+		sb.append(text.getText());
+		sb.append("</text>\n");
+	}
+
+	/** Transforms from {@link Line} to SVG element. */
+	private void transform(Line line, StringBuilder sb) {
+		StyleHelper st = new StyleHelper(line);
+		if(st.isTransparent()) {
+			return;
+		}
+		sb.append("<line ");
+		setProp("x1", line.getStartX(), sb);
+		setProp("y1", line.getStartY(), sb);
+		setProp("x2", line.getEndX(), sb);
+		setProp("y2", line.getEndY(), sb);
+		st.applySVGStyle(sb);
+		sb.append("/>\n");
+	}
+
+	/** Transforms from {@link Circle} to SVG element. */
+	private void transform(Circle circ, StringBuilder sb) {
+		StyleHelper st = new StyleHelper(circ);
+		if(st.isTransparent()) {
+			return;
+		}
+		sb.append("<circle ");
+		setProp("cx", circ.getCenterX(), sb);
+		setProp("cy", circ.getCenterY(), sb);
+		setProp("r", circ.getRadius(), sb);
+		st.applySVGStyle(sb);
+		sb.append("/>\n");
+	}
+
+	/** Transforms from {@link Ellipse} to SVG element. */
+	private void transform(Ellipse ell, StringBuilder sb) {
+		StyleHelper st = new StyleHelper(ell);
+		if(st.isTransparent()) {
+			return;
+		}
+		sb.append("<ellipse ");
+		setProp("cx", ell.getCenterX(), sb);
+		setProp("cy", ell.getCenterY(), sb);
+		setProp("rx", ell.getRadiusX(), sb);
+		setProp("ry", ell.getRadiusY(), sb);
+		st.applySVGStyle(sb);
+		sb.append("/>\n");
+	}
+
+	/** Transforms from {@link Rectangle} to SVG Rect element. */
+	private void transform(Rectangle rect, StringBuilder sb) {
+		StyleHelper st = new StyleHelper(rect);
+		if(st.isTransparent()) {
+			return;
+		}
+		sb.append("<rect ");
+		setProp("x", rect.getX(), sb);
+		setProp("y", rect.getY(), sb);
+		setProp("rx", rect.getArcWidth(), sb);
+		setProp("ry", rect.getArcHeight(), sb);
+		setProp("width", rect.getWidth(), sb);
+		setProp("height", rect.getHeight(), sb);
+		st.applySVGStyle(sb);
+		sb.append("/>\n");
+	}
+
+	/** Transforms from {@link Group} to SVG G element. */
+	private void transform(Group g, StringBuilder sb) {
+		transformToG(null, g.getChildrenUnmodifiable(), sb);
+	}
+
+	/** Write a SVG G element. */
+	private void transformToG(String transform, List<Node> children, StringBuilder sb) {
+		sb.append("<g ");
+		if(transform != null) {
+			sb.append("transform=\"").append(transform).append("\" ");
+		}
+		sb.append(">\n");
+		for(Node n : children) {
+			if(n.isVisible()) {
+				transform(n, sb);
+			}
+		}
+		sb.append("</g>\n");
+	}
+
+	/** Transforms from {@link Region} to SVG G element. */
+	private void transform(Region r, StringBuilder sb) {
+		Bounds b = r.getBoundsInParent();
+		String transform = "translate(" + b.getMinX() + "," + b.getMinY() + ")";
+		transformToG(transform, r.getChildrenUnmodifiable(), sb);
+	}
+
+	/** Writes the given property and value to the string builder. */
+	private static void setProp(String prop, String value, StringBuilder sb) {
+		sb.append(prop).append("=\"").append(value).append("\" ");
+	}
+
+	/** Writes the given property and value to the string builder. */
+	private static void setProp(String prop, double value, StringBuilder sb) {
+		sb.append(prop).append("=\"").append(value).append("\" ");
+	}
+
+	/** Helper class for handling fill and stroke styles. */
+	private static class StyleHelper {
+		/** RGB hex of the fill color. */
+		private String fill;
+		/** Opacity of the fill color. */
+		private double fillAlpha;
+		/** RGB hex of the stroke color. */
+		private String stroke;
+		/** Opacity of the stroke color. */
+		private double strokeAlpha;
+		/** Width of the stroke. */
+		private double strokeWidth;
+
+		/** Constructor. */
+		public StyleHelper(Paint back, Paint fore, double sw) {
+			fill = rgbHex(back);
+			fillAlpha = alpha(back);
+			stroke = rgbHex(fore);
+			strokeAlpha = alpha(fore);
+			strokeWidth = sw;
+		}
+
+		/** Constructor. */
+		public StyleHelper(Shape shape) {
+			this(shape.getFill(), shape.getStroke(), shape.getStrokeWidth());
+		}
+
+		/** Returns the SVG string for this style. */
+		public void applySVGStyle(StringBuilder sb) {
+			setProp("fill", fill != null ? fill : "none", sb);
+			setProp("fill-opacity", fillAlpha, sb);
+			setProp("stroke", stroke != null ? stroke : "none", sb);
+			setProp("stroke-width", strokeWidth, sb);
+			setProp("stroke-opacity", strokeAlpha, sb);
+		}
+
+		/** Returns whether the style has transparent colors. */
+		public boolean isTransparent() {
+			return (fill == null && stroke == null) || (fillAlpha == 0 && strokeAlpha == 0);
+		}
+
+		/** Converts from {@link Paint} to SVG hex-decimal color specification. */
+		private static String rgbHex(Paint p) {
+			if(p instanceof Color) {
+				Color c = (Color)p;
+				int r = (int)(c.getRed() * 255);
+				int g = (int)(c.getGreen() * 255);
+				int b = (int)(c.getBlue() * 255);
+				return String.format("#%02X%02X%02X", r, g, b);
+			}
+			return null;
+		}
+
+		/** Converts from Paint to alpha value. */
+		private static double alpha(Paint p) {
+			if(p instanceof Color) {
+				return ((Color)p).getOpacity();
+			}
+			return 0.0;
+		}
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/change/.ratings b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/change/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..1758ea0a359923dd644757977226edcf4ad6064a
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/change/.ratings
@@ -0,0 +1,3 @@
+Change.java e907a516f1369013cbb3d5d002b1ddb69cd5cc25 YELLOW
+ChangeSet.java 363c4fcdad1709e6f7ccad4205a53b22c976b50d YELLOW
+DefaultModelModifier.java bfa32a76ce226ec84a173201553b87d6317b84b1 YELLOW
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/change/Change.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/change/Change.java
new file mode 100644
index 0000000000000000000000000000000000000000..e907a516f1369013cbb3d5d002b1ddb69cd5cc25
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/change/Change.java
@@ -0,0 +1,17 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.change;
+
+/** This interface encapsulates a changes to be applied to the underlying data model. */
+@FunctionalInterface
+public interface Change {
+	/** Applies this change to the underlying model. */
+	public void applyChange();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/change/ChangeSet.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/change/ChangeSet.java
new file mode 100644
index 0000000000000000000000000000000000000000..363c4fcdad1709e6f7ccad4205a53b22c976b50d
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/change/ChangeSet.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.change;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** This class encapsulates a set of {@link Change}s, which need to be performed atomically. */
+public final class ChangeSet implements Change {
+	/** The contained changes of this change set. */
+	private final List<Change> changeSet = new ArrayList<>();
+
+	/** Adds the given {@link Change} to this change set. */
+	public void add(Change chg) {
+		changeSet.add(chg);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void applyChange() {
+		for(Change c : changeSet) {
+			c.applyChange();
+		}
+	}
+
+	/** Returns whether this change set contains any changes. **/
+	public boolean isEmpty() {
+		return changeSet.isEmpty();
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/change/DefaultModelModifier.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/change/DefaultModelModifier.java
new file mode 100644
index 0000000000000000000000000000000000000000..bfa32a76ce226ec84a173201553b87d6317b84b1
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/change/DefaultModelModifier.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.change;
+
+import java.util.function.Consumer;
+
+/** The default model modifier simply applies all changes without any transaction mechanism. */
+public final class DefaultModelModifier implements Consumer<Change> {
+	/** {@inheritDoc} */
+	@Override
+	public void accept(Change change) {
+		change.applyChange();
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/.ratings b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..b36ac7444c8875c8a6f059a120545746e2acdf95
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/.ratings
@@ -0,0 +1,5 @@
+IClickController.java c0270e99d918aef14612d46f3e84905d3a6bdd8c YELLOW
+IController.java 6ba069977e7588f97187916c23a0e37f7cb91059 YELLOW
+IControllerFactory.java 85b86eda643489f2a03454eae5383915ecf27f83 YELLOW
+IDragController.java c1f311d2ae9ed684c8a7cd85e9ed1f85e79658d1 YELLOW
+IKeyPressController.java dc8fe2a7c441866122e8c7b3114fd12d17f0b051 YELLOW
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/IClickController.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/IClickController.java
new file mode 100644
index 0000000000000000000000000000000000000000..c0270e99d918aef14612d46f3e84905d3a6bdd8c
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/IClickController.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.Change;
+
+import javafx.scene.Node;
+import javafx.scene.input.MouseEvent;
+
+/**
+ * Interface for controllers, which handle click gestures provided by the visuals. Each operation
+ * may result in {@link Change} to the underlying model.
+ */
+public interface IClickController {
+	/** Handles a single-click at the given mouse location. */
+	Change singleClick(MouseEvent event, Node node, DiagramCoordinate lastMouseLocation);
+
+	/** Handles a double-click at the given mouse location. */
+	Change doubleClick(MouseEvent event, Node node, DiagramCoordinate lastMouseLocation);
+
+	/**
+	 * Handles a single click at the given mouse location when the secondary mouse button is used.
+	 */
+	Change secondaryClick(MouseEvent event, Node node, DiagramCoordinate lastMouseLocation);
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/IController.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/IController.java
new file mode 100644
index 0000000000000000000000000000000000000000..6ba069977e7588f97187916c23a0e37f7cb91059
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/IController.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller;
+
+import java.util.List;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelChangeProvider;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundlePart;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.MVCBundleTag;
+
+import javafx.scene.Cursor;
+import javafx.scene.Node;
+import javafx.scene.control.MenuItem;
+
+/**
+ * Interface for controllers which are informed about gestures or clicks and provide context menus
+ * and cursor shapes.
+ */
+public interface IController extends IMVCBundlePart {
+	/**
+	 * Returns whether the {@link IMVCBundle} should be selected if the mouse is pressed on one of
+	 * its nodes.
+	 */
+	boolean selectOnMousePress();
+
+	/**
+	 * Returns whether the {@link IMVCBundle} should be added to the selection if the mouse is
+	 * pressed on one of its nodes while the shift key is down.
+	 */
+	boolean selectOnShiftMousePress();
+
+	/** Returns an {@link IDragController} for the given node and last mouse location. */
+	IDragController getDragController(EDragGesture gesture, Node node,
+			DiagramCoordinate lastMouseLocation);
+
+	/** Returns an {@link IClickController} for the given node and last mouse location. */
+	IClickController getClickController(Node node, DiagramCoordinate lastMouseLocation);
+
+	/** Returns an {@link IKeyPressController} for the given node and last mouse location. */
+	IKeyPressController getKeyPressController(Node node, DiagramCoordinate lastMouseLocation);
+
+	/**
+	 * Returns the menu items when the underlying visual is requesting a context menu. May return
+	 * {@code null} to avoid showing any context menu. The given location is relative to the
+	 * diagram origin.
+	 */
+	List<MenuItem> contextMenuContributions(Node node, DiagramCoordinate diagramLocation);
+
+	/**
+	 * Returns whether this bundle represents a possible link target. If this method returns
+	 * {@code false} this bundle is considered neither when a new link gesture is started nor
+	 * when a move-link gesture is started. If {@code true} is returned
+	 * {@link #getLinkTargetEffectForNewLinkFrom(IMVCBundle)} and
+	 * {@link #getLinkTargetEffectForMoveLink(ILinkMVCBundle, IAnchorageMVCBundle)} are called to
+	 * determine the user feedback for the concrete situation. The default is {@code false}.
+	 */
+	boolean isPossibleLinkTarget();
+
+	/** Returns the link target effect tag for a new link gesture starting at the given bundle. */
+	MVCBundleTag getLinkTargetEffectForNewLinkFrom(IMVCBundle linkStartBundle);
+
+	/**
+	 * Returns the link target effect tag for a move link gesture of the given link and the given
+	 * previous bundle, from which the link is currently removed.
+	 */
+	MVCBundleTag getLinkTargetEffectForMoveLink(ILinkMVCBundle linkBundle,
+			IAnchorageMVCBundle movedAnchorageBundle);
+
+	/**
+	 * Returns the mouse cursor to be used when the pointer is over the given node at the given
+	 * diagram location.
+	 */
+	Cursor getCurrentCursor(Node node, DiagramCoordinate diagramLocation);
+
+	/** Called when the delete operation of the underlying model should be performed. */
+	void delete();
+
+	/**
+	 * Allows the controller to provide a helper object instead of implementing
+	 * {@link IModelChangeProvider} directly.
+	 */
+	IModelChangeProvider getModelChangeProvider();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/IControllerFactory.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/IControllerFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..85b86eda643489f2a03454eae5383915ecf27f83
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/IControllerFactory.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+
+/**
+ * Interface for factories which create controllers for model elements in a {@link DiagramViewer}.
+ */
+public interface IControllerFactory {
+	/** Returns the controller instance for the given content model. */
+	IController createContentController(IContentMVCBundle model);
+
+	/** Returns the controller instance for the given diagram anchorage model. */
+	IController createDiagramAnchorageController(IDiagramAnchorageMVCBundle modelBundle);
+
+	/**
+	 * Returns the controller instance for the given content anchorage model. Sub-classes may assume
+	 * that {@code model.getAttachedTo()} points to the parent {@link IContentMVCBundle}.
+	 */
+	IController createContentAnchorageController(IContentAnchorageMVCBundle modelBundle);
+
+	/**
+	 * Returns the controller instance for the given link model. Sub-classes may assume that
+	 * {@code modelBundle.getAttachedTo()} points to the parent {@link IDiagramMVCBundle}.
+	 */
+	IController createLinkController(ILinkMVCBundle modelBundle);
+
+	/**
+	 * Returns the controller instance for the given diagram bundle. This method is called only once
+	 * when the diagram is initialized.
+	 */
+	IController createDiagramController(IDiagramMVCBundle diagramBundle);
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/IDragController.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/IDragController.java
new file mode 100644
index 0000000000000000000000000000000000000000..c1f311d2ae9ed684c8a7cd85e9ed1f85e79658d1
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/IDragController.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.Change;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+
+import javafx.scene.Node;
+
+/** Interface for controllers, which handle standard drag gestures provided by the visuals. */
+public interface IDragController {
+	/**
+	 * Informs the drag controller that the drag gesture has started on the given bundle, node and
+	 * diagram location.
+	 */
+	void dragStarted(IMVCBundle startBundle, Node startNode, DiagramCoordinate lastMouseLocation);
+
+	/** Updates the drag controller with the new drag bundle, node, and diagram location. */
+	void dragInProgress(IMVCBundle currentBundle, Node currentNode,
+			DiagramCoordinate lastMouseLocation);
+
+	/**
+	 * Informs the drag controller about the final result: the target bundle, target node and
+	 * diagram location. This operation may result in a {@link Change} to the underlying model.
+	 */
+	Change dragCompleted(IMVCBundle endBundle, Node endNode, DiagramCoordinate lastMouseLocation);
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/IKeyPressController.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/IKeyPressController.java
new file mode 100644
index 0000000000000000000000000000000000000000..dc8fe2a7c441866122e8c7b3114fd12d17f0b051
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/IKeyPressController.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.Change;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+
+import javafx.scene.Node;
+import javafx.scene.input.KeyEvent;
+
+/** Interface for controllers that handle key interactions on the selected node. */
+public interface IKeyPressController {
+	/**
+	 * The given key event occurred on the bundle, source node and at the given location. Its effect
+	 * may result in a {@link Change} to the underlying model.
+	 */
+	Change keyEvent(KeyEvent event, IMVCBundle bundle, Node source,
+			DiagramCoordinate mouseLocation);
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/.ratings b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..2fb55495d45c3c21a244d8e23cb04ab51a9f5314
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/.ratings
@@ -0,0 +1,11 @@
+AnchorageContentControllerBase.java da56b10cbf2711b5da69f0b59f43eacbe54f4eea YELLOW
+ClickControllerBase.java 8e5861ed5f9318008ad0fdd5497ed320cd5bd647 YELLOW
+ContentAnchorageMoveControllerBase.java c18e7915ce23e124757f5b736086ecc46694800a YELLOW
+ControllerBase.java 309e9ee3f3a255b5a06fed8f1b4d4ec8bf88f101 YELLOW
+DefaultDiagramController.java 0e083b89a08f63967102a384d66ebc1d64d203af YELLOW
+DelegatingContentAnchorageController.java 2e3b1b4e14402a3503233f816b21ef3e4aa09edc YELLOW
+DragControllerBase.java b15ff874304f679fe494d85f57cc8cbe4d0d1d15 YELLOW
+DraggingUtils.java 95117e2ea4c36b6c6a31f8088bb95b484e0e6612 YELLOW
+LinkControllerBase.java 392cb79cb42e9f878c665d47053b0795c3768603 YELLOW
+MoveControllerBase.java 38d632e31f5e27d112ecdd4933e3a331378180d0 YELLOW
+ResizableContentControllerBase.java 898500d389b035f8138308d496d2d24be501c719 YELLOW
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/AnchorageContentControllerBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/AnchorageContentControllerBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..da56b10cbf2711b5da69f0b59f43eacbe54f4eea
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/AnchorageContentControllerBase.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IDragController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+
+/**
+ * Base class providing dragging implementation for visuals which move and resize in the
+ * {@link DiagramViewer} and which have movable anchorages attached. Sub-classes must implement
+ * {@link #resize(FeedbackChange)} and {@link #move(FeedbackChange)} in order to change the
+ * underlying model with the respective coordinate changes. Furthermore, sub-classes must implement
+ * {@link #createAnchorageMoveController(IContentAnchorageMVCBundle)} and create
+ * {@link IDragController}s for the given anchorages.
+ */
+public abstract class AnchorageContentControllerBase extends ResizableContentControllerBase {
+	/** Constructor. */
+	public AnchorageContentControllerBase(IContentMVCBundle mvcb) {
+		super(mvcb);
+	}
+
+	/** Stores the anchorage move controllers. */
+	private final Map<IContentAnchorageMVCBundle, IDragController> moveAnchorageControllerMap =
+			new HashMap<IContentAnchorageMVCBundle, IDragController>();
+
+	/**
+	 * Returns an {@link IDragController} responsible for positioning the anchorage visual during
+	 * the move feedback and for committing the new position to the model.
+	 */
+	protected final IDragController
+			getAnchorageMoveController(IContentAnchorageMVCBundle anchorage) {
+		if(!canMoveAnchorage(anchorage)) {
+			return null;
+		}
+		IDragController ctrl = moveAnchorageControllerMap.get(anchorage);
+		if(ctrl == null) {
+			ctrl = createAnchorageMoveController(anchorage);
+			moveAnchorageControllerMap.put(anchorage, ctrl);
+		}
+		return ctrl;
+	}
+
+	/**
+	 * Returns whether the given content anchorage should be movable.
+	 * 
+	 * @param anchorage
+	 *            the {@link IMVCBundle} of the anchorage to be moved
+	 * @return whether the anchorage can be moved
+	 */
+	protected boolean canMoveAnchorage(IContentAnchorageMVCBundle anchorage) {
+		return true;
+	}
+
+	/**
+	 * Creates the move controller for the given anchorage.
+	 * 
+	 * @param anchorage
+	 *            the {@link IMVCBundle} of the anchorage to be moved
+	 * @return a drag controller for moving the anchorage
+	 */
+	protected abstract IDragController
+			createAnchorageMoveController(IContentAnchorageMVCBundle anchorage);
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/ClickControllerBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/ClickControllerBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..8e5861ed5f9318008ad0fdd5497ed320cd5bd647
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/ClickControllerBase.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.Change;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IClickController;
+
+import javafx.scene.Node;
+import javafx.scene.input.MouseEvent;
+
+/** Base class implementing {@link IClickController}. */
+public abstract class ClickControllerBase implements IClickController {
+	/** {@inheritDoc} */
+	@Override
+	public Change singleClick(MouseEvent event, Node node, DiagramCoordinate diagramLocation) {
+		// ignore by default
+		return null;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Change doubleClick(MouseEvent event, Node node, DiagramCoordinate diagramLocation) {
+		// ignore by default
+		return null;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Change secondaryClick(MouseEvent event, Node node, DiagramCoordinate diagramLocation) {
+		// ignore by default
+		return null;
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/ContentAnchorageMoveControllerBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/ContentAnchorageMoveControllerBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..c18e7915ce23e124757f5b736086ecc46694800a
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/ContentAnchorageMoveControllerBase.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.Change;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+
+import javafx.geometry.Point2D;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Node;
+
+/** Base class for move controllers of content anchorages. */
+public abstract class ContentAnchorageMoveControllerBase extends DragControllerBase {
+	/** The anchorage. */
+	protected final IContentAnchorageMVCBundle anchorage;
+
+	/** Constructor. */
+	public ContentAnchorageMoveControllerBase(IContentAnchorageMVCBundle anchorage) {
+		this.anchorage = anchorage;
+	}
+
+	/** Returns the location of the anchorage when dragged by the given delta. */
+	protected abstract Point2D getAnchorageFeedbackLocation(DiagramCoordinate delta);
+
+	/** Sets the angle of the given anchorage when move operation has completed. */
+	protected abstract void moveAnchorage(DiagramCoordinate delta);
+
+	/** {@inheritDoc} */
+	@Override
+	public final void safeDragInProgress(IMVCBundle bundle, Node node,
+			DiagramCoordinate diagramLocation) {
+		DiagramCoordinate delta = diagramLocation.subtract(startDiagramLocation);
+		Point2D c = getAnchorageFeedbackLocation(delta);
+		Rectangle2D anchorageBounds = anchorage.getVisual().getModelBounds();
+		FeedbackChange change = new FeedbackChange(c.getX() - anchorageBounds.getMinX(),
+				c.getY() - anchorageBounds.getMinY(), 0, 0);
+		anchorage.setFeedbackChange(change);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public final Change safeDragCompleted(IMVCBundle bundle, Node node,
+			DiagramCoordinate diagramLocation) {
+		DiagramCoordinate delta = diagramLocation.subtract(startDiagramLocation);
+		anchorage.setFeedbackChange(FeedbackChange.EMPTY);
+		bundle.getViewer().updateVisual(bundle);
+		return () -> moveAnchorage(delta);
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/ControllerBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/ControllerBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..309e9ee3f3a255b5a06fed8f1b4d4ec8bf88f101
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/ControllerBase.java
@@ -0,0 +1,282 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base;
+
+import static java.util.Collections.emptyList;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture.NEW_LINK;
+
+import java.util.List;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.Change;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IClickController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IDragController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IKeyPressController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelChangeProvider;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.MVCBundleTag;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl.MVCBundlePartBase;
+
+import javafx.scene.Cursor;
+import javafx.scene.Node;
+import javafx.scene.control.MenuItem;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+
+/** Base class for {@link IController} implementations with move and link operation. */
+public abstract class ControllerBase extends MVCBundlePartBase implements IController {
+	/** Constructor. */
+	public ControllerBase(IMVCBundle mvcb) {
+		super(mvcb);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean selectOnMousePress() {
+		return true;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean selectOnShiftMousePress() {
+		return true;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public List<MenuItem> contextMenuContributions(Node node, DiagramCoordinate diagramLocation) {
+		return emptyList();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IClickController getClickController(Node node, DiagramCoordinate diagramLocation) {
+		return new ClickControllerBase() {
+			/** {@inheritDoc} */
+			@Override
+			public Change singleClick(MouseEvent event, Node node,
+					DiagramCoordinate diagramLocation) {
+				if(!selectOnClick()) {
+					return null;
+				}
+				if(event.isShiftDown()) {
+					getViewer().handleShiftSelectionOf(getMVCBundle());
+				} else {
+					getViewer().setSingleSelectedMVCBundle(getMVCBundle());
+				}
+				return null;
+			}
+
+			/** {@inheritDoc} */
+			@Override
+			public Change secondaryClick(MouseEvent event, Node node,
+					DiagramCoordinate diagramLocation) {
+				DiagramViewer viewer = getViewer();
+				if(selectOnClick()) {
+					viewer.setSingleSelectedMVCBundle(getMVCBundle());
+				}
+				if(event.isControlDown()) {
+					return () -> delete();
+				}
+				viewer.showContextMenu(node, diagramLocation, getMVCBundle());
+				return null;
+			}
+		};
+	}
+
+	/** Returns whether the visual should be selected on single click. */
+	protected boolean selectOnClick() {
+		return true;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IDragController getDragController(EDragGesture gesture, Node node,
+			DiagramCoordinate diagramLocation) {
+		if(allowLink() && gesture == NEW_LINK) {
+			return getLinkCreationController();
+		}
+		return null;
+	}
+
+	/** Returns whether this visual can be linked. */
+	protected boolean allowLink() {
+		return true;
+	}
+
+	/** Returns the default {@link IDragController} for creating links. */
+	protected IDragController getLinkCreationController() {
+		return defaultLinkController;
+	}
+
+	/** The default {@link IDragController} for link creation. */
+	private final IDragController defaultLinkController = new DragControllerBase() {
+		@Override
+		public void safeDragStarted(IMVCBundle bundle, Node node,
+				DiagramCoordinate diagramLocation) {
+			diagramLocation = getVisual().getLinkAnchorage(diagramLocation);
+			getViewer().startNewLinkLineFeedback(bundle, node, diagramLocation);
+		}
+
+		@Override
+		protected void safeDragInProgress(IMVCBundle bundle, Node node,
+				DiagramCoordinate diagramLocation) {
+			diagramLocation = bundle.getVisual().getLinkAnchorage(diagramLocation);
+			getViewer().updateNewLinkLineFeedback(diagramLocation,
+					newLinkFeedback(getMVCBundle(), startDiagramLocation, bundle, diagramLocation));
+		}
+
+		@Override
+		protected Change safeDragCompleted(IMVCBundle endBundle, Node node,
+				DiagramCoordinate endDiagramLocation) {
+			IMVCBundle startBundle = getMVCBundle();
+			getViewer().terminateNewLinkLineFeedback();
+			if(canLink(startBundle, startDiagramLocation, endBundle, endDiagramLocation)) {
+				return () -> link(startBundle, startDiagramLocation, endBundle, endDiagramLocation);
+			}
+			return null;
+		}
+	};
+
+	/**
+	 * Returns whether a new link can be created between the given bundles and locations.
+	 * 
+	 * @param startBundle
+	 *            the {@link IMVCBundle} of the start node of the link
+	 * @param startLocation
+	 *            the location of the start of the link
+	 * @param endBundle
+	 *            the {@link IMVCBundle} of the end node of the link
+	 * @param endLocation
+	 *            the location of the end of the link
+	 * @return if the link can be created; default is true
+	 */
+	protected boolean canLink(IMVCBundle startBundle, DiagramCoordinate startLocation,
+			IMVCBundle endBundle, DiagramCoordinate endLocation) {
+		return true;
+	}
+
+	/**
+	 * Creates a link between the bundles at the given, respective locations.
+	 * 
+	 * @param startBundle
+	 *            the {@link IMVCBundle} of the start node of the link
+	 * @param startLocation
+	 *            the location of the start of the link
+	 * @param endBundle
+	 *            the {@link IMVCBundle} of the end node of the link
+	 * @param endLocation
+	 *            the location of the end of the link
+	 */
+	protected void link(IMVCBundle startBundle, DiagramCoordinate startLocation,
+			IMVCBundle endBundle, DiagramCoordinate endLocation) {
+		// the default does nothing
+	}
+
+	/**
+	 * Determines the feedback color of for creating new links between the given bundles.
+	 * 
+	 * @param startBundle
+	 *            the {@link IMVCBundle} of the start node of the link
+	 * @param startLocation
+	 *            the location of the start of the link
+	 * @param endBundle
+	 *            the {@link IMVCBundle} of the end node of the link
+	 * @param endLocation
+	 *            the location of the end of the link
+	 * @return the feedback line color
+	 */
+	protected Paint newLinkFeedback(IMVCBundle startBundle, DiagramCoordinate startLocation,
+			IMVCBundle endBundle, DiagramCoordinate endLocation) {
+		return Color.BLACK;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean isPossibleLinkTarget() {
+		return false;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public MVCBundleTag getLinkTargetEffectForNewLinkFrom(IMVCBundle linkStartBundle) {
+		return null;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public MVCBundleTag getLinkTargetEffectForMoveLink(ILinkMVCBundle linkBundle,
+			IAnchorageMVCBundle movedAnchorageBundle) {
+		return null;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void delete() {
+		// the default does nothing
+	}
+
+	/**
+	 * The default implementation for key press controller allowing move with cursor keys and delete
+	 * with the delete key.
+	 */
+	private final IKeyPressController defaultKeyPressController = new IKeyPressController() {
+		@Override
+		public Change keyEvent(KeyEvent event, IMVCBundle bundle, Node source,
+				DiagramCoordinate mouseLocation) {
+			DiagramViewer viewer = getViewer();
+			KeyCode code = event.getCode();
+			if(code == KeyCode.DELETE) {
+				return new Change() {
+					@Override
+					public void applyChange() {
+						delete();
+					}
+				};
+			} else if(code == KeyCode.A && event.isControlDown()) {
+				viewer.selectAll();
+			}
+			return null;
+		}
+	};
+
+	/** {@inheritDoc} */
+	@Override
+	public IKeyPressController getKeyPressController(Node node,
+			DiagramCoordinate lastMouseLocation) {
+		return defaultKeyPressController;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Cursor getCurrentCursor(Node node, DiagramCoordinate diagramLocation) {
+		EDragGesture edg = getVisual().getDragGesture(node, diagramLocation);
+		switch(edg) {
+			case NEW_LINK:
+				return allowLink() ? Cursor.CROSSHAIR : Cursor.DEFAULT;
+			default:
+				return Cursor.DEFAULT;
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IModelChangeProvider getModelChangeProvider() {
+		return null;
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/DefaultDiagramController.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/DefaultDiagramController.java
new file mode 100644
index 0000000000000000000000000000000000000000..0e083b89a08f63967102a384d66ebc1d64d203af
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/DefaultDiagramController.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerFeatures;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.Change;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IClickController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IDragController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+
+import javafx.scene.Node;
+import javafx.scene.control.Menu;
+import javafx.scene.control.MenuItem;
+
+/**
+ * Default implementation for diagram controller, which provides a {@code Display...} context menu
+ * with the options to enable and disable link highlighting and interactive area shading.
+ */
+public class DefaultDiagramController extends ControllerBase {
+	/** Flag for ignoring mouse click event after dragging gesture. */
+	private boolean ignoreNextMouseClick = false;
+
+	/** Constructor. */
+	public DefaultDiagramController(IMVCBundle mvcb) {
+		super(mvcb);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public List<MenuItem> contextMenuContributions(Node node, DiagramCoordinate diagramLocation) {
+		Menu menu = new Menu("Display ...");
+		DiagramViewerFeatures features = getViewer().getFeatures();
+		// link highlighting
+		if(features.isLinkHighlightingEnabled()) {
+			MenuItem disableLinkHighlighting = new MenuItem("Disable Link Highlighting");
+			disableLinkHighlighting.setOnAction(evt -> {
+				features.setLinkHighlightingEnabled(false);
+			});
+			menu.getItems().add(disableLinkHighlighting);
+		} else {
+			MenuItem enableLinkHighlighting = new MenuItem("Enable Link Highlighting");
+			enableLinkHighlighting.setOnAction(evt -> {
+				features.setLinkHighlightingEnabled(true);
+			});
+			menu.getItems().add(enableLinkHighlighting);
+		}
+		// interaction area shading
+		if(features.isInteractionAreaShadingEnabled()) {
+			MenuItem disableInteractionShading = new MenuItem("Disable Interactive Area Shading");
+			disableInteractionShading.setOnAction(evt -> {
+				features.setInteractionAreaShadingEnabled(false);
+			});
+			menu.getItems().add(disableInteractionShading);
+		} else {
+			MenuItem enableInteractionShading = new MenuItem("Enable Interactive Area Shading");
+			enableInteractionShading.setOnAction(evt -> {
+				features.setInteractionAreaShadingEnabled(true);
+			});
+			menu.getItems().add(enableInteractionShading);
+		}
+
+		List<MenuItem> menuList = new LinkedList<>();
+		menuList.add(menu);
+		return menuList;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IDragController getDragController(EDragGesture gesture, Node node,
+			DiagramCoordinate diagramLocation) {
+		if(gesture == EDragGesture.SELECTION) {
+			return dragSelectionRectangleController;
+		}
+		return super.getDragController(gesture, node, diagramLocation);
+	}
+
+	/** {@link IDragController} for the selection rectangle. */
+	private final IDragController dragSelectionRectangleController = new DragControllerBase() {
+		@Override
+		protected void safeDragStarted(IMVCBundle bundle, Node node,
+				DiagramCoordinate diagramLocation) {
+			getViewer().startSelectionFeedback(node, diagramLocation);
+			ignoreNextMouseClick = true;
+		}
+
+		@Override
+		protected void safeDragInProgress(IMVCBundle bundle, Node node,
+				DiagramCoordinate diagramLocation) {
+			getViewer().updateSelectionFeedback(node, diagramLocation);
+		}
+
+		@Override
+		protected Change safeDragCompleted(IMVCBundle bundle, Node node,
+				DiagramCoordinate diagramLocation) {
+			getViewer().terminateSelectionFeedback(node, diagramLocation);
+			return null;
+		}
+	};
+
+	/** {@inheritDoc} */
+	@Override
+	public IClickController getClickController(Node node, DiagramCoordinate diagramLocation) {
+		if(ignoreNextMouseClick) {
+			ignoreNextMouseClick = false;
+			return null;
+		}
+		return super.getClickController(node, diagramLocation);
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/DelegatingContentAnchorageController.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/DelegatingContentAnchorageController.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e3b1b4e14402a3503233f816b21ef3e4aa09edc
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/DelegatingContentAnchorageController.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture.MOVE;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IDragController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+
+import javafx.scene.Cursor;
+import javafx.scene.Node;
+
+/**
+ * Default {@link IController} implementation for moving anchorage visuals attached to a parent
+ * visual. This controller delegates to the parent controller and tries to acquire an anchorage move
+ * controller from it, because in most cases the shape of the parent determines the possible
+ * locations of the anchorages.
+ */
+public class DelegatingContentAnchorageController extends ControllerBase {
+	/** Constructor. */
+	public DelegatingContentAnchorageController(IContentAnchorageMVCBundle mvcb) {
+		super(mvcb);
+	}
+
+	/** Returns the {@link IContentAnchorageMVCBundle}. */
+	private IContentAnchorageMVCBundle getContentAnchorageBundle() {
+		// Wild cast works: see constructor
+		return (IContentAnchorageMVCBundle)getMVCBundle();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IDragController getDragController(EDragGesture gesture, Node node,
+			DiagramCoordinate diagramLocation) {
+		if(gesture == MOVE && allowMove()) {
+			IContentAnchorageMVCBundle contentAnchorageBundle = getContentAnchorageBundle();
+			IContentMVCBundle parent = contentAnchorageBundle.getAttachedTo();
+			if(parent != null && parent.getController() instanceof AnchorageContentControllerBase) {
+				AnchorageContentControllerBase parentController =
+						(AnchorageContentControllerBase)parent.getController();
+				return parentController.getAnchorageMoveController(contentAnchorageBundle);
+			}
+			return null;
+		}
+		return super.getDragController(gesture, node, diagramLocation);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Cursor getCurrentCursor(Node node, DiagramCoordinate diagramLocation) {
+		EDragGesture edg = getVisual().getDragGesture(node, diagramLocation);
+		if(edg == EDragGesture.MOVE) {
+			return allowMove() ? Cursor.MOVE : Cursor.DEFAULT;
+		}
+		return super.getCurrentCursor(node, diagramLocation);
+	}
+
+	/** Returns whether the model element can be moved. */
+	protected boolean allowMove() {
+		return true;
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/DragControllerBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/DragControllerBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..b15ff874304f679fe494d85f57cc8cbe4d0d1d15
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/DragControllerBase.java
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange.locationFeedbackChange;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.Change;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IDragController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+
+import javafx.scene.Node;
+
+/** Base implementation of {@link IDragController}. */
+public abstract class DragControllerBase implements IDragController {
+	/** Stores the start bundle of the drag gesture. */
+	protected IMVCBundle startBundle = null;
+	/** Stores the start node of the drag gesture. */
+	protected Node startNode;
+	/** Stores the start location of the drag gesturein diagram coordinates. */
+	protected DiagramCoordinate startDiagramLocation = null;
+
+	/** {@inheritDoc} */
+	@Override
+	public final void dragStarted(IMVCBundle bundle, Node node, DiagramCoordinate diagramLocation) {
+		this.startBundle = bundle;
+		this.startNode = node;
+		this.startDiagramLocation = diagramLocation;
+		safeDragStarted(bundle, node, startDiagramLocation);
+	}
+
+	/**
+	 * Informs the drag controller that a drag has started.
+	 * 
+	 * @param bundle
+	 *            the final {@link IMVCBundle}
+	 * @param node
+	 *            the final node
+	 * @param diagramLocation
+	 *            the current absolute mouse location
+	 */
+	protected void safeDragStarted(IMVCBundle bundle, Node node,
+			DiagramCoordinate diagramLocation) {
+		// the default does nothing
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public final void dragInProgress(IMVCBundle bundle, Node node,
+			DiagramCoordinate diagramLocation) {
+		// filter drag events that arrive before drag started was called.
+		if(isDragInProgress()) {
+			safeDragInProgress(bundle, node, diagramLocation);
+		}
+	}
+
+	/**
+	 * Updates the drag controller with the new drag location guaranteeing that its only called
+	 * after {@link #dragStarted(IMVCBundle, Node, DiagramCoordinate)} was called and before
+	 * {@link #dragCompleted(IMVCBundle, Node, DiagramCoordinate)} is
+	 * called.
+	 * 
+	 * @param bundle
+	 *            the current {@link IMVCBundle}
+	 * @param node
+	 *            the current node under the drag gesture pointer
+	 * @param diagramLocation
+	 *            the current diagram location of the drag gesture
+	 */
+	protected void safeDragInProgress(IMVCBundle bundle, Node node,
+			DiagramCoordinate diagramLocation) {
+		// the default does nothing
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public final Change dragCompleted(IMVCBundle bundle, Node node,
+			DiagramCoordinate diagramLocation) {
+		Change result = null;
+		if(isDragInProgress()) {
+			result = safeDragCompleted(bundle, node, diagramLocation);
+		}
+		startBundle = null;
+		startNode = null;
+		startDiagramLocation = null;
+		return result;
+	}
+
+	/**
+	 * Updates the drag controller with the final drag location guaranteeing that its only called
+	 * after {@link #dragStarted(IMVCBundle, Node, DiagramCoordinate)} was called and any number of
+	 * {@link #safeDragInProgress(IMVCBundle, Node, DiagramCoordinate)} occurred.
+	 * 
+	 * @param bundle
+	 *            the final {@link IMVCBundle}
+	 * @param node
+	 *            the final node under the gesture pointer
+	 * @param diagramLocation
+	 *            the final diagram location of the drag gesture
+	 * @return the model change which was caused by this drag operation.
+	 */
+	protected Change safeDragCompleted(IMVCBundle bundle, Node node,
+			DiagramCoordinate diagramLocation) {
+		// the default does nothing
+		return null;
+	}
+
+	/**
+	 * Returns whether a drag gesture is in progress, i.e., dragStarted() was called and
+	 * dragCompleted() was not yet called.
+	 */
+	protected final boolean isDragInProgress() {
+		return startBundle != null;
+	}
+
+	/** Returns the current drag delta. */
+	protected final FeedbackChange getDragDelta(DiagramCoordinate endDiagramLocation) {
+		double dx = endDiagramLocation.getX() - startDiagramLocation.getX();
+		double dy = endDiagramLocation.getY() - startDiagramLocation.getY();
+		return locationFeedbackChange(dx, dy);
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/DraggingUtils.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/DraggingUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..95117e2ea4c36b6c6a31f8088bb95b484e0e6612
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/DraggingUtils.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange.locationFeedbackChange;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerFeatures;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+
+/** Utility methods used for computation of locations of mouse dragging operations. */
+/* package */ final class DraggingUtils {
+	/** Clamps the given location and snaps it to the closest grid point. */
+	public static FeedbackChange clampAndSnapMoveLocationToGridPoint(double x, double y, double dx,
+			double dy, DiagramViewerFeatures features) {
+		return clampAndSnapMoveLocation(x, y, dx, dy, features, false);
+	}
+
+	/** Clamps the given location and snaps it to the closest center between four grid points. */
+	public static FeedbackChange clampAndSnapMoveLocationToGridCenterPoint(double x, double y,
+			double dx, double dy, DiagramViewerFeatures features) {
+		return clampAndSnapMoveLocation(x, y, dx, dy, features, true);
+	}
+
+	/** Clamps the given location and snaps it to the grid (possibly centered). */
+	private static FeedbackChange clampAndSnapMoveLocation(double x, double y, double dx, double dy,
+			DiagramViewerFeatures features, boolean snapToCenter) {
+		// add 10 to compensate the mouse cursor hit point
+		// and make the anchorage stick closer to the cursor
+		double newx = x + dx + 10;
+		double newy = y + dy + 10;
+		// clamp
+		if(features.isDrawOuterBorder()) {
+			// clamp one space from origin
+			newx = Math.max(newx, features.getHorizontalSpacing());
+			newy = Math.max(newy, features.getVerticalSpacing());
+		} else {
+			// clamp negativ coordinates
+			newx = Math.max(newx, 0);
+			newy = Math.max(newy, 0);
+		}
+		// snap to grid
+		if(features.useSnapToGrid()) {
+			newx = snapToCenter ? features.snapToCenterX(newx) : features.snapToGridX(newx);
+			newy = snapToCenter ? features.snapToCenterY(newy) : features.snapToGridY(newy);
+		}
+		dx = newx - x;
+		dy = newy - y;
+		return locationFeedbackChange(dx, dy);
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/LinkControllerBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/LinkControllerBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..392cb79cb42e9f878c665d47053b0795c3768603
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/LinkControllerBase.java
@@ -0,0 +1,470 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base;
+
+import static java.util.Collections.emptyList;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange.locationFeedbackChange;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.DraggingUtils.clampAndSnapMoveLocationToGridCenterPoint;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.visual.ILinkVisual.END_OF_LINK_BEND_POINT_INDEX;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.visual.ILinkVisual.NO_BEND_POINT_INDEX;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.visual.ILinkVisual.SPECIAL_BEND_POINT_INDEX_CAP;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.visual.ILinkVisual.START_OF_LINK_BEND_POINT_INDEX;
+
+import java.util.List;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerFeatures;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerSelection;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.Change;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IClickController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IDragController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IKeyPressController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelChangeProvider;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.MVCBundleTag;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl.MVCBundlePartBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.ILinkVisual;
+
+import javafx.geometry.Point2D;
+import javafx.scene.Cursor;
+import javafx.scene.Node;
+import javafx.scene.control.MenuItem;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.input.MouseEvent;
+
+/**
+ * Base class implementing {@link IController} for links. It provides dragging implementation for
+ * visuals of bend-points and of the start or end of the link. It also handles creation and removal
+ * of bend-points (including keyboard interaction).
+ * <P>
+ * Sub-classes must implement the abstract methods, which are called once the respective operation
+ * is completed, and write the results back into the underlying data model.
+ */
+public abstract class LinkControllerBase extends MVCBundlePartBase implements IController {
+	/** Stores the index of the currently moving bend-point. */
+	private int currentBendPointIndex = 0;
+
+	/** Constructor. */
+	public LinkControllerBase(ILinkMVCBundle mvcb) {
+		super(mvcb);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IModelChangeProvider getModelChangeProvider() {
+		return null;
+	}
+
+	/** Create a new bend point with the given mouse click coordinates. */
+	protected abstract void createBendPointAt(int bpIndex, DiagramCoordinate location);
+
+	/**
+	 * Moves the model element.
+	 * 
+	 * @param bpIndex
+	 *            the index of moved bend-point
+	 * @param dx
+	 *            the x delta
+	 * @param dy
+	 *            the y delta
+	 */
+	protected abstract void updateModelAfterBendPointMove(int bpIndex, double dx, double dy);
+
+	/** Deletes the current bend-point. */
+	protected abstract void deleteBendPoint(int bpIndex);
+
+	/**
+	 * Moves the given end of the link to the new anchorage. The given bend-point
+	 */
+	protected abstract void updateModelAfterLinkMove(ILinkMVCBundle linkBundle,
+			IMVCBundle startBundle, IMVCBundle endBundle);
+
+	/** Removes the given bend point. */
+	protected abstract void deleteLink();
+
+	/** Sets the current bend-point index. */
+	protected final void setCurrentBendPointIndex(int bendPointIndex) {
+		currentBendPointIndex = bendPointIndex;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public ILinkMVCBundle getMVCBundle() {
+		// wild cast works: see constructor
+		return (ILinkMVCBundle)super.getMVCBundle();
+	}
+
+	/** Returns the link visual. */
+	protected ILinkVisual getLinkVisual() {
+		return getMVCBundle().getVisual();
+	}
+
+	/** Returns the {@link IClickController} for creating bend-points and selecting the link. */
+	protected IClickController getBendPointCreateAndLinkSelectController() {
+		return new ClickControllerBase() {
+			@Override
+			public Change singleClick(MouseEvent event, Node node,
+					DiagramCoordinate diagramLocation) {
+				DiagramViewer viewer = getViewer();
+				if(event.isControlDown()) {
+					// link must be primary selected
+					if(!viewer.isPrimarySelected(getMVCBundle())) {
+						return null;
+					}
+					// control+click on visible or selection line creates a bend point
+					DiagramViewerFeatures features = viewer.getFeatures();
+					DiagramCoordinate snappedLocation = features.snapToCenter(diagramLocation);
+					return () -> createBendPointAt(currentBendPointIndex, snappedLocation);
+				}
+				if(event.isShiftDown()) {
+					viewer.handleShiftSelectionOf(getMVCBundle());
+				} else {
+					viewer.setSingleSelectedMVCBundle(getMVCBundle());
+				}
+				return null;
+			}
+
+			@Override
+			public Change secondaryClick(MouseEvent event, Node node,
+					DiagramCoordinate locationOnNode) {
+				if(event.isControlDown()) {
+					// link must be primary selected
+					DiagramViewer viewer = getViewer();
+					if(viewer.isPrimarySelected(getMVCBundle())) {
+						// control+right-click deletes the link
+						return () -> deleteLink();
+					}
+				}
+				return null;
+			}
+		};
+	}
+
+	/** Returns the {@link IDragController} for moving bend-points. */
+	protected IDragController getBendPointMoveController() {
+		return new DragControllerBase() {
+			@Override
+			public void safeDragInProgress(IMVCBundle bundle, Node node,
+					DiagramCoordinate diagramLocation) {
+				Point2D delta = diagramLocation.subtract(startDiagramLocation);
+				moveBendPointVisually(currentBendPointIndex, delta);
+				getViewer().updateVisual(getMVCBundle());
+			}
+
+			@Override
+			public Change safeDragCompleted(IMVCBundle bundle, Node node,
+					DiagramCoordinate diagramLocation) {
+				Point2D delta = diagramLocation.subtract(startDiagramLocation);
+				return () -> moveBendPointInModel(currentBendPointIndex, delta);
+			}
+		};
+	}
+
+	/**
+	 * Updates the bend-point visual(s) during the dragging phase of the move operation. Sub-classes
+	 * may override to move other bend-points than only the current bend-point.
+	 */
+	protected void moveBendPointVisually(int bpIndex, Point2D delta) {
+		FeedbackChange chg = getMoveDeltaFeedback(bpIndex, delta.getX(), delta.getY());
+		getLinkVisual().setFeedbackChangeForBendPoint(bpIndex, chg);
+	}
+
+	/**
+	 * Updates the bend-point visual(s) during the dragging phase of the move operation. Sub-classes
+	 * may override to move other bend-points than only the current bend-point.
+	 */
+	protected void moveBendPointVisually(int bpIndex, FeedbackChange change) {
+		FeedbackChange chg = getMoveDeltaFeedback(bpIndex, change.getDeltaX(), change.getDeltaY());
+		getLinkVisual().setFeedbackChangeForBendPoint(bpIndex, chg);
+	}
+
+	/** Returns the clamped and snapped move delta feedback change for the given bend point. */
+	protected final FeedbackChange getMoveDeltaFeedback(int bpIndex, Point2D delta) {
+		return getMoveDeltaFeedback(bpIndex, delta.getX(), delta.getY());
+	}
+
+	/** Returns the clamped and snapped move delta feedback change for the given bend point. */
+	protected final FeedbackChange getMoveDeltaFeedback(int bpIndex, double dx, double dy) {
+		DiagramCoordinate bpModelLocation = getLinkVisual().getBendPointLocation(bpIndex);
+		DiagramViewerFeatures features = getViewer().getFeatures();
+		if(clampAndSnapBendPointToGridCenter()) {
+			return clampAndSnapMoveLocationToGridCenterPoint(bpModelLocation.getX(),
+					bpModelLocation.getY(), dx, dy, features);
+		}
+		if(clampAndSnapBendPointToGrid()) {
+			return DraggingUtils.clampAndSnapMoveLocationToGridPoint(bpModelLocation.getX(),
+					bpModelLocation.getY(), dx, dy, features);
+		}
+		return locationFeedbackChange(dx, dy);
+	}
+
+	/**
+	 * Returns whether bend point positions should be snapped to the center of four grid points.
+	 * This mode is the default and has priority over {@link #clampAndSnapBendPointToGrid()}.
+	 */
+	protected boolean clampAndSnapBendPointToGridCenter() {
+		return true;
+	}
+
+	/** Returns whether bend point positions should be snapped to grid points. */
+	protected boolean clampAndSnapBendPointToGrid() {
+		return false;
+	}
+
+	/**
+	 * Updates the bend-point model element(s) after the dragging of the move operation is complete.
+	 */
+	protected void moveBendPointInModel(int bpIndex, Point2D delta) {
+		FeedbackChange chg = getMoveDeltaFeedback(bpIndex, delta.getX(), delta.getY());
+		getLinkVisual().setFeedbackChangeForBendPoint(bpIndex, null);
+		updateModelAfterBendPointMove(bpIndex, chg.getDeltaX(), chg.getDeltaY());
+	}
+
+	/**
+	 * Updates the bend-point model element(s) after the dragging of the move operation is complete.
+	 */
+	protected void moveBendPointInModel(int bpIndex, FeedbackChange delta) {
+		FeedbackChange chg = getMoveDeltaFeedback(bpIndex, delta.getDeltaX(), delta.getDeltaY());
+		getLinkVisual().setFeedbackChangeForBendPoint(bpIndex, null);
+		updateModelAfterBendPointMove(bpIndex, chg.getDeltaX(), chg.getDeltaY());
+	}
+
+	/** Returns the {@link IClickController} for deleting bend-points. */
+	protected IClickController getBendPointDeleteController() {
+		return new ClickControllerBase() {
+			@Override
+			public Change secondaryClick(MouseEvent event, Node node,
+					DiagramCoordinate diagramLocation) {
+				if(event.isControlDown()) {
+					// bundle must be primary selection
+					DiagramViewer viewer = getViewer();
+					if(viewer.isPrimarySelected(getMVCBundle())) {
+						// control+right-click on selection handle of bend point removes it
+						return () -> deleteBendPoint(currentBendPointIndex);
+					}
+				}
+				return null;
+			}
+		};
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IDragController getDragController(EDragGesture gesture, Node node,
+			DiagramCoordinate diagramLocation) {
+		DiagramViewerSelection selection = getViewer().getSelection();
+		ILinkMVCBundle linkBundle = getMVCBundle();
+		if(selection.getPrimarySelection() == linkBundle) {
+			// links with primary selection show move handles
+			ILinkVisual linkVisual = getLinkVisual();
+			int bpIndex = linkVisual.getBendPointIndex(node);
+			if(bpIndex == START_OF_LINK_BEND_POINT_INDEX ||
+					bpIndex == END_OF_LINK_BEND_POINT_INDEX) {
+				return getLinkMoveController(bpIndex);
+			}
+			if(bpIndex == NO_BEND_POINT_INDEX) {
+				return null;
+			}
+			setCurrentBendPointIndex(bpIndex);
+			return getBendPointMoveController();
+		}
+		if(selection.isSelected(linkBundle)) {
+			// selected links may be moved by group move operation
+			return new DragControllerBase() {
+				/** {@inheritDoc} */
+				@Override
+				protected void safeDragInProgress(IMVCBundle bundle, Node node,
+						DiagramCoordinate diagramLocation) {
+					FeedbackChange fc = getDragDelta(diagramLocation);
+					int numerOfBendPoints = getNumerOfBendPoints();
+					for(int i = 0; i < numerOfBendPoints; i++) {
+						moveBendPointVisually(i, fc);
+					}
+				}
+
+				/** {@inheritDoc} */
+				@Override
+				protected Change safeDragCompleted(IMVCBundle bundle, Node node,
+						DiagramCoordinate diagramLocation) {
+					FeedbackChange fc = getDragDelta(diagramLocation);
+					return () -> {
+						int numerOfBendPoints = getNumerOfBendPoints();
+						for(int i = 0; i < numerOfBendPoints; i++) {
+							moveBendPointInModel(i, fc);
+						}
+					};
+				}
+			};
+		}
+		return null;
+	}
+
+	/**
+	 * Returns an {@link IDragController} for relocating the end of this link. {@code bpIndex} is
+	 * either {@link ILinkVisual#START_OF_LINK_BEND_POINT_INDEX} or
+	 * {@link ILinkVisual#END_OF_LINK_BEND_POINT_INDEX} depending on which part of the link is
+	 * moved.
+	 */
+	protected IDragController getLinkMoveController(final int bpIndex) {
+		return new DragControllerBase() {
+			@Override
+			protected void safeDragStarted(IMVCBundle bundle, Node node,
+					DiagramCoordinate diagramLocation) {
+				ILinkMVCBundle linkBundle = getMVCBundle();
+				IAnchorageMVCBundle anchorage;
+				if(bpIndex == START_OF_LINK_BEND_POINT_INDEX) {
+					anchorage = linkBundle.getStartAnchorage();
+				} else {
+					anchorage = linkBundle.getEndAnchorage();
+				}
+				getLinkVisual().disableFeedback();
+				getViewer().startMoveLinkFeedback(linkBundle, anchorage);
+			}
+
+			@Override
+			public void safeDragInProgress(IMVCBundle bundle, Node node,
+					DiagramCoordinate diagramLocation) {
+				Point2D linkAnchorageLocation =
+						bundle.getVisual().getLinkAnchorage(diagramLocation);
+				Point2D delta;
+				if(linkAnchorageLocation != null) {
+					delta = linkAnchorageLocation.subtract(startDiagramLocation);
+				} else {
+					delta = diagramLocation.subtract(startDiagramLocation);
+				}
+				FeedbackChange chg = locationFeedbackChange(delta.getX(), delta.getY());
+				getLinkVisual().setFeedbackChangeForBendPoint(bpIndex, chg);
+				getViewer().updateVisual(getMVCBundle());
+			}
+
+			@Override
+			public Change safeDragCompleted(IMVCBundle bundle, Node node,
+					DiagramCoordinate diagramLocation) {
+				getLinkVisual().enableFeedback();
+				getLinkVisual().setFeedbackChangeForBendPoint(bpIndex, null);
+				DiagramViewer viewer = getViewer();
+				viewer.terminateMoveLinkFeedback();
+				ILinkMVCBundle linkBundle = getMVCBundle();
+				if(bpIndex == START_OF_LINK_BEND_POINT_INDEX) {
+					return () -> updateModelAfterLinkMove(linkBundle, bundle,
+							linkBundle.getEndAnchorage());
+				}
+				return () -> updateModelAfterLinkMove(linkBundle, linkBundle.getStartAnchorage(),
+						bundle);
+			}
+		};
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IClickController getClickController(Node node, DiagramCoordinate diagramLocation) {
+		int bpIndex = getLinkVisual().getBendPointIndex(node);
+		if(bpIndex < SPECIAL_BEND_POINT_INDEX_CAP) {
+			return null;
+		}
+		setCurrentBendPointIndex(bpIndex);
+		if(getLinkVisual().isBendPointHandle(node)) {
+			return getBendPointDeleteController();
+		}
+		return getBendPointCreateAndLinkSelectController();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean selectOnMousePress() {
+		return true;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean selectOnShiftMousePress() {
+		return true;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IKeyPressController getKeyPressController(Node node,
+			DiagramCoordinate lastMouseLocation) {
+		return new IKeyPressController() {
+			@Override
+			public Change keyEvent(KeyEvent event, IMVCBundle bundle, Node source,
+					DiagramCoordinate mouseLocation) {
+				KeyCode code = event.getCode();
+				if(code == KeyCode.DELETE) {
+					return () -> deleteLink();
+				} else if(code == KeyCode.A && event.isControlDown()) {
+					getViewer().selectAll();
+				}
+				return null;
+			}
+		};
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public List<MenuItem> contextMenuContributions(Node node, DiagramCoordinate diagramLocation) {
+		return emptyList();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean isPossibleLinkTarget() {
+		return false;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public MVCBundleTag getLinkTargetEffectForNewLinkFrom(IMVCBundle linkStartBundle) {
+		return null;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public MVCBundleTag getLinkTargetEffectForMoveLink(ILinkMVCBundle linkBundle,
+			IAnchorageMVCBundle movedAnchorageBundle) {
+		return null;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Cursor getCurrentCursor(Node node, DiagramCoordinate diagramLocation) {
+		EDragGesture edg = getVisual().getDragGesture(node, diagramLocation);
+		switch(edg) {
+			case MOVE_BENDPOINT:
+				return allowBendPointMove() ? Cursor.MOVE : Cursor.DEFAULT;
+			case NEW_BENDPOINT:
+				return Cursor.CROSSHAIR;
+			case RECONNECT_LINK:
+				return allowLinkMove() ? Cursor.MOVE : Cursor.DEFAULT;
+			default:
+				return Cursor.DEFAULT;
+		}
+	}
+
+	/** Determines whether bend points can be moved. */
+	protected boolean allowBendPointMove() {
+		return true;
+	}
+
+	/** Determines whether link start and end can be relocated. */
+	protected boolean allowLinkMove() {
+		return true;
+	}
+
+	/** Returns the number of bend-points in the model. */
+	protected abstract int getNumerOfBendPoints();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/MoveControllerBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/MoveControllerBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..38d632e31f5e27d112ecdd4933e3a331378180d0
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/MoveControllerBase.java
@@ -0,0 +1,192 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture.MOVE;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange.locationFeedbackChange;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.DraggingUtils.clampAndSnapMoveLocationToGridPoint;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerFeatures;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.Change;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IDragController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IKeyPressController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Cursor;
+import javafx.scene.Node;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+
+/**
+ * Controller implementation, which allows to move the underlying model element. Sub-classes must
+ * implement {@link #move(FeedbackChange)} in order to change the underlying model with the
+ * respective coordinate changes.
+ */
+public abstract class MoveControllerBase extends ControllerBase {
+	/** Constructor. */
+	public MoveControllerBase(IMVCBundle mvcb) {
+		super(mvcb);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IDragController getDragController(EDragGesture gesture, Node node,
+			DiagramCoordinate diagramLocation) {
+		if(allowMove() && gesture == MOVE) {
+			return moveController;
+		}
+		return super.getDragController(gesture, node, diagramLocation);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Cursor getCurrentCursor(Node node, DiagramCoordinate diagramLocation) {
+		EDragGesture edg = getVisual().getDragGesture(node, diagramLocation);
+		if(edg == EDragGesture.MOVE) {
+			return allowMove() ? Cursor.MOVE : Cursor.DEFAULT;
+		}
+		return super.getCurrentCursor(node, diagramLocation);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IKeyPressController getKeyPressController(Node node,
+			DiagramCoordinate lastMouseLocation) {
+		if(allowMove() && allowKeyboardMove()) {
+			return new IKeyPressController() {
+				@Override
+				public Change keyEvent(KeyEvent event, IMVCBundle bundle, Node source,
+						DiagramCoordinate mouseLocation) {
+					DiagramViewerFeatures features = getViewer().getFeatures();
+					double hSpacing = features.getHorizontalSpacing();
+					double vSpacing = features.getVerticalSpacing();
+					FeedbackChange fbChange = null;
+					KeyCode code = event.getCode();
+					if(code == KeyCode.RIGHT) {
+						fbChange = locationFeedbackChange(hSpacing, 0);
+					} else if(code == KeyCode.LEFT) {
+						fbChange = locationFeedbackChange(-hSpacing, 0);
+					} else if(code == KeyCode.UP) {
+						fbChange = locationFeedbackChange(0, -vSpacing);
+					} else if(code == KeyCode.DOWN) {
+						fbChange = locationFeedbackChange(0, vSpacing);
+					} else {
+						return MoveControllerBase.super.getKeyPressController(node,
+								lastMouseLocation).keyEvent(event, bundle, source, mouseLocation);
+					}
+					if(fbChange != null && !fbChange.isEmpty()) {
+						return new MoveChange(fbChange);
+					}
+					return null;
+				}
+			};
+		}
+		return super.getKeyPressController(node, lastMouseLocation);
+	}
+
+	/**
+	 * Perform the move operation by altering the underlying model. The default does nothing.
+	 * 
+	 * @param deltaChange
+	 *            the (possibly altered) delta the model should be moved.
+	 */
+	protected void move(FeedbackChange deltaChange) {
+		// default does nothing
+	}
+
+	/** Returns whether the model element can be moved. */
+	protected boolean allowMove() {
+		return true;
+	}
+
+	/** Returns whether the model element can be moved by keyboard commands. */
+	protected boolean allowKeyboardMove() {
+		return true;
+	}
+
+	/**
+	 * Returns whether move operations should be clamped to positive diagram area and snapped to the
+	 * grid.
+	 */
+	protected boolean clampAndSnapMove() {
+		return true;
+	}
+
+	/** Returns whether child elements should be moved also. */
+	protected boolean moveChildren() {
+		return true;
+	}
+
+	/** Sets the current feedback change for the given bundle and possibly its children. */
+	private void setMoveFeedbackChange(FeedbackChange change, IMVCBundle bundle) {
+		bundle.setFeedbackChange(change);
+		if(moveChildren() && bundle instanceof IContentMVCBundle) {
+			IContentMVCBundle cb = (IContentMVCBundle)bundle;
+			for(IMVCBundle ch : cb.getChildren()) {
+				setMoveFeedbackChange(change, ch);
+			}
+		}
+	}
+
+	/**
+	 * Alters the move feedback to apply some constraints to it (e.g. no negative result location).
+	 */
+	protected FeedbackChange alterMoveFeedback(FeedbackChange change) {
+		if(!clampAndSnapMove()) {
+			return change;
+		}
+		Rectangle2D modelBounds = getVisual().getModelBounds();
+		return clampAndSnapMoveLocationToGridPoint(modelBounds.getMinX(), modelBounds.getMinY(),
+				change.getDeltaX(), change.getDeltaY(), getViewer().getFeatures());
+	}
+
+	/** The drag controller managing the move operation. */
+	private final IDragController moveController = new DragControllerBase() {
+		@Override
+		protected final void safeDragInProgress(IMVCBundle bundle, Node node,
+				DiagramCoordinate diagramLocation) {
+			FeedbackChange altered = alterMoveFeedback(getDragDelta(diagramLocation));
+			setMoveFeedbackChange(altered, startBundle);
+		}
+
+		@Override
+		protected final Change safeDragCompleted(IMVCBundle bundle, Node node,
+				DiagramCoordinate diagramLocation) {
+			setMoveFeedbackChange(FeedbackChange.EMPTY, startBundle);
+			final FeedbackChange change = alterMoveFeedback(getDragDelta(diagramLocation));
+			if(change.isEmpty()) {
+				return null;
+			}
+			return new MoveChange(change);
+		}
+	};
+
+	/** A {@link Change} that calls {@link #move(FeedbackChange)}. */
+	private final class MoveChange implements Change {
+		/** The feedback change to be applied. */
+		private final FeedbackChange change;
+
+		/** Constructor. */
+		public MoveChange(FeedbackChange change) {
+			this.change = change;
+		}
+
+		/** {@inheritDoc} */
+		@Override
+		public void applyChange() {
+			move(change);
+		}
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/ResizableContentControllerBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/ResizableContentControllerBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..898500d389b035f8138308d496d2d24be501c719
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/base/ResizableContentControllerBase.java
@@ -0,0 +1,269 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base;
+
+import static java.lang.Math.max;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture.RESIZE_H;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture.RESIZE_V;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange.sizeFeedbackChange;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerFeatures;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.Change;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IClickController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IDragController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentVisual;
+
+import javafx.geometry.Dimension2D;
+import javafx.geometry.Point2D;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Cursor;
+import javafx.scene.Node;
+import javafx.scene.input.MouseEvent;
+
+/**
+ * Base class providing dragging implementation for visuals which move and resize in the
+ * {@link DiagramViewer}. Sub-classes must implement {@link #resize(FeedbackChange)} and
+ * {@link #move(FeedbackChange)} in order to change the underlying model with the respective
+ * coordinate changes.
+ */
+public abstract class ResizableContentControllerBase extends MoveControllerBase {
+	/** Constructor. */
+	public ResizableContentControllerBase(IContentMVCBundle mvcb) {
+		super(mvcb);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IClickController getClickController(Node node, DiagramCoordinate diagramLocation) {
+		// wild cast works: see constructor
+		IContentVisual visual = (IContentVisual)getVisual();
+		if(allowExpandCollapse() && visual.isExpandCollapseWidget(node)) {
+			return new ClickControllerBase() {
+				/** {@inheritDoc} */
+				@Override
+				public Change singleClick(MouseEvent event, Node node,
+						DiagramCoordinate diagramLocation) {
+					return () -> expandCollapse();
+				}
+			};
+		}
+		return super.getClickController(node, diagramLocation);
+	}
+
+	/** Returns whether the expand/collapse feature is active. */
+	public boolean allowExpandCollapse() {
+		return false;
+	}
+
+	/** Triggers the expand/collapse operation. */
+	public void expandCollapse() {
+		// the default does nothing
+	}
+
+	/** Returns whether the horizontal resize is available. */
+	public boolean allowHorizontalResize() {
+		return true;
+	}
+
+	/** Returns whether the vertical resize is available. */
+	public boolean allowVerticalResize() {
+		return true;
+	}
+
+	/** Returns whether child elements should be resized also. */
+	protected boolean resizeChildren() {
+		return true;
+	}
+
+	/** Sets the current feedback change for the given bundle and possibly its children. */
+	private void setResizeFeedbackChange(FeedbackChange change, IMVCBundle bundle) {
+		bundle.setFeedbackChange(change);
+		if(resizeChildren() && bundle instanceof IContentMVCBundle) {
+			IContentMVCBundle cb = (IContentMVCBundle)bundle;
+			for(IMVCBundle ch : cb.getChildren()) {
+				setResizeFeedbackChange(change, ch);
+			}
+		}
+	}
+
+	/**
+	 * The minimum dimension of the diagram element in model coordinates. It will not be resizable
+	 * below this value. The default value is a single grid spacing.
+	 */
+	protected Dimension2D getMinimumSize() {
+		DiagramViewerFeatures features = getViewer().getFeatures();
+		return new Dimension2D(features.getHorizontalSpacing(), features.getVerticalSpacing());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IDragController getDragController(EDragGesture gesture, Node n,
+			DiagramCoordinate diagramLocation) {
+		if(gesture == RESIZE_H && allowHorizontalResize()) {
+			return getHorizontalResizeDragController();
+		}
+		if(gesture == RESIZE_V && allowVerticalResize()) {
+			return getVerticalResizeDragController();
+		}
+		if(gesture == EDragGesture.RESIZE_VH && allowHorizontalResize() && allowVerticalResize()) {
+			return getHorizontalVerticalResizeDragController();
+		}
+		return super.getDragController(gesture, n, diagramLocation);
+	}
+
+	/** Returns the {@link IDragController} for the horizontal resize. */
+	public IDragController getHorizontalResizeDragController() {
+		return defaultHorizontalResizeDragController;
+	}
+
+	/** The default controller for horizontal resize. */
+	private IDragController defaultHorizontalResizeDragController = new DragControllerBase() {
+		@Override
+		public void safeDragInProgress(IMVCBundle bundle, Node node,
+				DiagramCoordinate diagramLocation) {
+			Point2D delta = diagramLocation.subtract(startDiagramLocation);
+			// use startBundle since bundle and node might differ
+			handleResizeDragInProgress(startBundle, delta.getX(), 0);
+		}
+
+		@Override
+		public Change safeDragCompleted(IMVCBundle bundle, Node node,
+				DiagramCoordinate diagramLocation) {
+			Point2D delta = diagramLocation.subtract(startDiagramLocation);
+			// use startBundle since bundle and node might differ
+			return handleResizeCompleted(startBundle, delta.getX(), 0);
+		}
+	};
+
+	/** Applies the feedback change for the given delta. */
+	private void handleResizeDragInProgress(IMVCBundle bundle, double dw, double dh) {
+		FeedbackChange altered = alterResizeFeedback(sizeFeedbackChange(dw, dh));
+		setResizeFeedbackChange(altered, bundle);
+	}
+
+	/** Handles the completion of the resize operation. */
+	private Change handleResizeCompleted(IMVCBundle bundle, double dw, double dh) {
+		bundle.setFeedbackChange(FeedbackChange.EMPTY);
+		final FeedbackChange altered = alterResizeFeedback(sizeFeedbackChange(dw, dh));
+		if(altered.isEmpty()) {
+			return null;
+		}
+		return () -> resize(altered);
+	}
+
+	/**
+	 * Returns whether resize operations should be clamped to positive diagram area and snapped to
+	 * the grid.
+	 */
+	protected boolean clampAndSnapResize() {
+		return true;
+	}
+
+	/** Alters the given resize feedback (e.g. to uphold minimum size. */
+	protected FeedbackChange alterResizeFeedback(FeedbackChange change) {
+		IContentVisual visual = (IContentVisual)getMVCBundle().getVisual();
+		Rectangle2D orig = visual.getModelBounds();
+		double dw = change.getDeltaW();
+		double dh = change.getDeltaH();
+		Dimension2D minDim = getMinimumSize();
+		double newMaxX = orig.getMinX() + max(minDim.getWidth(), orig.getWidth() + dw);
+		double newMaxY = orig.getMinY() + max(minDim.getHeight(), orig.getHeight() + dh);
+		if(clampAndSnapResize()) {
+			DiagramViewerFeatures features = getViewer().getFeatures();
+			if(features.useSnapToGrid()) {
+				newMaxX = features.snapToGridX(newMaxX);
+				newMaxY = features.snapToGridY(newMaxY);
+			}
+		}
+		return sizeFeedbackChange(newMaxX - orig.getMaxX(), newMaxY - orig.getMaxY());
+	}
+
+	/** Returns the {@link IDragController} for the vertical resize. */
+	public IDragController getVerticalResizeDragController() {
+		return defaultVerticalResizeController;
+	}
+
+	/** The default controller for vertical resize. */
+	private IDragController defaultVerticalResizeController = new DragControllerBase() {
+		@Override
+		public void safeDragInProgress(IMVCBundle bundle, Node node,
+				DiagramCoordinate diagramLocation) {
+			Point2D delta = diagramLocation.subtract(startDiagramLocation);
+			// use startBundle since bundle and node might differ
+			handleResizeDragInProgress(startBundle, 0, delta.getY());
+		}
+
+		@Override
+		public Change safeDragCompleted(IMVCBundle bundle, Node node,
+				DiagramCoordinate diagramLocation) {
+			Point2D delta = diagramLocation.subtract(startDiagramLocation);
+			// use startBundle since bundle and node might differ
+			return handleResizeCompleted(startBundle, 0, delta.getY());
+		}
+	};
+
+	/** Returns the {@link IDragController} for the resize. */
+	public IDragController getHorizontalVerticalResizeDragController() {
+		return defaultHorizontalVerticalResizeDragController;
+	}
+
+	/** The default controller for horizontal and vertical resize. */
+	private IDragController defaultHorizontalVerticalResizeDragController =
+			new DragControllerBase() {
+				@Override
+				public void safeDragInProgress(IMVCBundle bundle, Node node,
+						DiagramCoordinate diagramLocation) {
+					Point2D delta = diagramLocation.subtract(startDiagramLocation);
+					// use startBundle since bundle and node might differ
+					handleResizeDragInProgress(startBundle, delta.getX(), delta.getY());
+				}
+
+				@Override
+				public Change safeDragCompleted(IMVCBundle bundle, Node node,
+						DiagramCoordinate diagramLocation) {
+					Point2D delta = diagramLocation.subtract(startDiagramLocation);
+					// use startBundle since bundle and node might differ
+					return handleResizeCompleted(startBundle, delta.getX(), delta.getY());
+				}
+			};
+
+	/**
+	 * Resizes the model element.
+	 * 
+	 * @param delta
+	 *            the delta width and height to be applied to the model element
+	 */
+	protected void resize(FeedbackChange delta) {
+		// default does nothing
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Cursor getCurrentCursor(Node node, DiagramCoordinate diagramLocation) {
+		EDragGesture edg = getVisual().getDragGesture(node, diagramLocation);
+		switch(edg) {
+			case RESIZE_H:
+				return allowHorizontalResize() ? Cursor.H_RESIZE : Cursor.DEFAULT;
+			case RESIZE_V:
+				return allowVerticalResize() ? Cursor.V_RESIZE : Cursor.DEFAULT;
+			case RESIZE_VH:
+				return allowHorizontalResize() && allowVerticalResize() ? Cursor.SE_RESIZE
+						: Cursor.DEFAULT;
+			default:
+		}
+		return super.getCurrentCursor(node, diagramLocation);
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/elliptic/.ratings b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/elliptic/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..ca7cd95560997655fec8af345c644cec0bccbc37
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/elliptic/.ratings
@@ -0,0 +1,3 @@
+CircularResizableContentControllerBase.java daf05a58eac298462a5f092503e506575b31dff1 YELLOW
+CurveLinkBendPointControllerBase.java 4d119cd640b864f2193ea5c1a7f816310b7a57a4 YELLOW
+EllipticResizableContentControllerBase.java 42bcbdbf29c1cf2b71177e55f74358d1517d623f YELLOW
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/elliptic/CircularResizableContentControllerBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/elliptic/CircularResizableContentControllerBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..daf05a58eac298462a5f092503e506575b31dff1
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/elliptic/CircularResizableContentControllerBase.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.elliptic;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.visual.elliptic.EllipticBorderLocation.getClosestLocationOnBounds;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IDragController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.AnchorageContentControllerBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.ContentAnchorageMoveControllerBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.elliptic.EllipticBorderLocation;
+
+import javafx.geometry.Dimension2D;
+import javafx.geometry.Point2D;
+import javafx.geometry.Rectangle2D;
+
+/** Base class for controllers with content visuals represented by ellipses. */
+public abstract class CircularResizableContentControllerBase
+		extends AnchorageContentControllerBase {
+	/** Constructor. */
+	public CircularResizableContentControllerBase(IContentMVCBundle mvcb) {
+		super(mvcb);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected IDragController createAnchorageMoveController(IContentAnchorageMVCBundle anchorage) {
+		return new CircularAnchorageMoveController(anchorage);
+	}
+
+	/** Move controller for anchorages moving on the ellipse border. */
+	private final class CircularAnchorageMoveController extends ContentAnchorageMoveControllerBase {
+		/** Constructor. */
+		public CircularAnchorageMoveController(IContentAnchorageMVCBundle anchorage) {
+			super(anchorage);
+		}
+
+		/** Returns the border location on the ellipse shape. */
+		private EllipticBorderLocation getBorderLocation(DiagramCoordinate delta) {
+			Rectangle2D contentBounds = getVisual().getModelBounds();
+			Rectangle2D anchorageBounds = anchorage.getVisual().getModelBounds();
+			Dimension2D span =
+					new Dimension2D(anchorageBounds.getWidth(), anchorageBounds.getHeight());
+			Point2D p = new Point2D(anchorageBounds.getMinX() + delta.getX(),
+					anchorageBounds.getMinY() + delta.getY());
+			EllipticBorderLocation rbl = getClosestLocationOnBounds(p, contentBounds, span);
+			return rbl;
+		}
+
+		/** {@inheritDoc} */
+		@Override
+		protected Point2D getAnchorageFeedbackLocation(DiagramCoordinate delta) {
+			EllipticBorderLocation rbl = getBorderLocation(delta);
+			return rbl.getLocation();
+		}
+
+		/** {@inheritDoc} */
+		@Override
+		protected void moveAnchorage(DiagramCoordinate delta) {
+			EllipticBorderLocation rbl = getBorderLocation(delta);
+			moveAnchorageToAngle(anchorage, rbl.getAngleInDegree());
+		}
+	}
+
+	/** Sets the angle of the given anchorage when move operation has completed. */
+	protected abstract void moveAnchorageToAngle(IContentAnchorageMVCBundle anchorage,
+			double angleInDegree);
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/elliptic/CurveLinkBendPointControllerBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/elliptic/CurveLinkBendPointControllerBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..4d119cd640b864f2193ea5c1a7f816310b7a57a4
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/elliptic/CurveLinkBendPointControllerBase.java
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.elliptic;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange.locationFeedbackChange;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerFeatures;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.LinkControllerBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.ILinkVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.elliptic.CurveLinkVisualBase;
+
+import javafx.geometry.Point2D;
+
+/** Base class for {@link CurveLinkVisualBase} dragging implementation. */
+public abstract class CurveLinkBendPointControllerBase extends LinkControllerBase {
+	/** Constructor. */
+	public CurveLinkBendPointControllerBase(ILinkMVCBundle mvcb) {
+		super(mvcb);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected final void moveBendPointVisually(int bpIndex, Point2D delta) {
+		super.moveBendPointVisually(bpIndex, delta);
+		if(bpIndex <= 0 || bpIndex >= getNumerOfBendPoints() - 1) {
+			// first and last bend-point move unconnected
+			return;
+		}
+		ILinkVisual linkVisual = getLinkVisual();
+		FeedbackChange centerPointChange = getMoveDeltaFeedback(bpIndex, delta);
+		int indexMod3 = bpIndex % 3;
+		if(indexMod3 == 2) {
+			// bend-points between curve segments also drag the previous and next point
+			linkVisual.setFeedbackChangeForBendPoint(bpIndex - 1, centerPointChange);
+			linkVisual.setFeedbackChangeForBendPoint(bpIndex + 1, centerPointChange);
+		} else {
+			// otherwise the connected bend-point is moved in the opposite direction
+			FeedbackChange inv = locationFeedbackChange(-centerPointChange.getDeltaX(),
+					-centerPointChange.getDeltaY());
+			int otherIndex = (indexMod3 == 1) ? bpIndex + 2 : bpIndex - 2;
+			linkVisual.setFeedbackChangeForBendPoint(otherIndex, inv);
+		}
+		// linkVisual.update();
+		return;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected final void moveBendPointInModel(int bpIndex, Point2D delta) {
+		FeedbackChange chg = getMoveDeltaFeedback(bpIndex, delta);
+		getLinkVisual().setFeedbackChangeForBendPoint(bpIndex, null);
+		updateModelAfterBendPointMove(bpIndex, chg.getDeltaX(), chg.getDeltaY());
+		if(bpIndex <= 0 || bpIndex >= getNumerOfBendPoints() - 1) {
+			// first and last bend-point move unconnected
+			return;
+		}
+		ILinkVisual linkVisual = getLinkVisual();
+		int indexMod3 = bpIndex % 3;
+		if(indexMod3 == 2) {
+			// bend-points between curve segments also drag the previous and next point
+			linkVisual.setFeedbackChangeForBendPoint(bpIndex - 1, null);
+			updateModelAfterBendPointMove(bpIndex - 1, chg.getDeltaX(), chg.getDeltaY());
+			linkVisual.setFeedbackChangeForBendPoint(bpIndex + 1, null);
+			updateModelAfterBendPointMove(bpIndex + 1, chg.getDeltaX(), chg.getDeltaY());
+		} else {
+			// otherwise the connected bend-point is moved in the opposite direction
+			int otherIndex = (indexMod3 == 1) ? bpIndex + 2 : bpIndex - 2;
+			linkVisual.setFeedbackChangeForBendPoint(otherIndex, null);
+			updateModelAfterBendPointMove(otherIndex, -chg.getDeltaX(), -chg.getDeltaY());
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected final void deleteBendPoint(int bpIndex) {
+		if(bpIndex <= 0 || bpIndex >= getNumerOfBendPoints() - 1) {
+			// never delete first and last bend-points (i.e. control points)
+			return;
+		}
+		int indexMod3 = bpIndex % 3;
+		if(indexMod3 == 0) {
+			deleteFromCurve(bpIndex - 2, bpIndex - 1, bpIndex);
+		} else if(indexMod3 == 1) {
+			deleteFromCurve(bpIndex, bpIndex + 1, bpIndex + 2);
+		} else {
+			deleteFromCurve(bpIndex - 1, bpIndex, bpIndex + 1);
+		}
+	}
+
+	/** Deletes the given three bend-points from the model. */
+	protected abstract void deleteFromCurve(int ctrlPoint2Index, int segmentPointIndex,
+			int ctrlPoint1Index);
+
+	/** {@inheritDoc} */
+	@Override
+	protected final void createBendPointAt(int bpIndex, DiagramCoordinate location) {
+		DiagramViewerFeatures features = getViewer().getFeatures();
+		DiagramCoordinate c2 = new DiagramCoordinate(
+				location.getX() - 2 * features.getHorizontalSpacing(), location.getY());
+		DiagramCoordinate c1 = new DiagramCoordinate(
+				location.getX() + 2 * features.getHorizontalSpacing(), location.getY());
+		createCurveSegment(bpIndex - 1, c2, location, c1);
+	}
+
+	/** Creates the new curve segment with the given locations after the given index. */
+	protected abstract void createCurveSegment(int afterIndex, DiagramCoordinate newCtrlPt2,
+			DiagramCoordinate newSegmentPoint, DiagramCoordinate newCtrlPt1);
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/elliptic/EllipticResizableContentControllerBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/elliptic/EllipticResizableContentControllerBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..42bcbdbf29c1cf2b71177e55f74358d1517d623f
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/elliptic/EllipticResizableContentControllerBase.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.elliptic;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.visual.elliptic.EllipticBorderLocation.getClosestLocationOnBounds;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IDragController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.AnchorageContentControllerBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.ContentAnchorageMoveControllerBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.elliptic.EllipticBorderLocation;
+
+import javafx.geometry.Dimension2D;
+import javafx.geometry.Point2D;
+import javafx.geometry.Rectangle2D;
+
+/** Base class for controllers with content visuals represented by ellipses. */
+public abstract class EllipticResizableContentControllerBase
+		extends AnchorageContentControllerBase {
+	/** Constructor. */
+	public EllipticResizableContentControllerBase(IContentMVCBundle mvcb) {
+		super(mvcb);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected final IDragController
+			createAnchorageMoveController(IContentAnchorageMVCBundle anchorage) {
+		return new EllipticAnchorageMoveController(anchorage);
+	}
+
+	/** Move controller for anchorages moving on the ellipse border. */
+	private final class EllipticAnchorageMoveController extends ContentAnchorageMoveControllerBase {
+		/** Constructor. */
+		public EllipticAnchorageMoveController(IContentAnchorageMVCBundle anchorage) {
+			super(anchorage);
+		}
+
+		/** Returns the border location on the ellipse shape. */
+		private EllipticBorderLocation getBorderLocation(DiagramCoordinate delta) {
+			Rectangle2D contentBounds = getVisual().getModelBounds();
+			Rectangle2D anchorageBounds = anchorage.getVisual().getModelBounds();
+			Dimension2D span =
+					new Dimension2D(anchorageBounds.getWidth(), anchorageBounds.getHeight());
+			Point2D p = new Point2D(anchorageBounds.getMinX() + delta.getX(),
+					anchorageBounds.getMinY() + delta.getY());
+			EllipticBorderLocation rbl = getClosestLocationOnBounds(p, contentBounds, span);
+			return rbl;
+		}
+
+		/** {@inheritDoc} */
+		@Override
+		protected Point2D getAnchorageFeedbackLocation(DiagramCoordinate delta) {
+			EllipticBorderLocation rbl = getBorderLocation(delta);
+			return rbl.getLocation();
+		}
+
+		/** {@inheritDoc} */
+		@Override
+		protected void moveAnchorage(DiagramCoordinate delta) {
+			EllipticBorderLocation rbl = getBorderLocation(delta);
+			moveAnchorageToAngle(anchorage, rbl.getAngleInDegree());
+		}
+	}
+
+	/** Sets the angle of the given anchorage when move operation has completed. */
+	protected abstract void moveAnchorageToAngle(IContentAnchorageMVCBundle anchorage,
+			double angleInDegree);
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/.ratings b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..47f0dc98d33d9c0b1565094a64e13befc63d6696
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/.ratings
@@ -0,0 +1,5 @@
+DiamondResizableContentControllerBase.java 31f227e88205bd2e131326bdddd8421991b47d79 YELLOW
+RectangularContentAnchorageMoveController.java c119b011f5a38933cee07b8082fcd44c6c3b0037 YELLOW
+RectangularResizableContentControllerBase.java 262b083e4e0ce842b0bb8fbb594585e9f0d2f678 YELLOW
+RhomboidContentAnchorageMoveController.java fbd40ce483f99c18cfa94076374f572bb72c2646 YELLOW
+RhomboidResizableContentControllerBase.java 863b9eae49bad18f71b20c8cde4b0f350348aa27 YELLOW
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/DiamondResizableContentControllerBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/DiamondResizableContentControllerBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..31f227e88205bd2e131326bdddd8421991b47d79
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/DiamondResizableContentControllerBase.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.rectangular;
+
+import static java.lang.Math.hypot;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IDragController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.AnchorageContentControllerBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.ContentAnchorageMoveControllerBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+
+import javafx.geometry.Point2D;
+import javafx.geometry.Rectangle2D;
+import javafx.geometry.Side;
+
+/** Base class for controllers with content visuals represented by diamonds. */
+public abstract class DiamondResizableContentControllerBase extends AnchorageContentControllerBase {
+	/** Relative X coordinates to be checked for anchorage locations. */
+	private static final double[] XCOORDS = {0.5, 0.5, 0, 1};
+	/** Relative Y coordinates to be checked for anchorage locations. */
+	private static final double[] YCOORDS = {0, 1, 0.5, 0.5};
+
+	/** Constructor. */
+	public DiamondResizableContentControllerBase(IContentMVCBundle mvcb) {
+		super(mvcb);
+	}
+
+	/** Finds the closest corner of the diamond shape relative to the given indicator position. */
+	private Side getClosestBorderLocation(double indicatorX, double indicatorY,
+			Rectangle2D bounds) {
+		Side resultSide = Side.values()[0];
+		double hypo = Double.MAX_VALUE;
+		double boundsX = bounds.getMinX();
+		double boundsY = bounds.getMinY();
+		for(int cur = 0; cur < XCOORDS.length; cur++) {
+			double anchorageX = boundsX + getXCoord(cur, bounds);
+			double anchorageY = boundsY + getYCoord(cur, bounds);
+			double curHypo = hypot(indicatorX - anchorageX, indicatorY - anchorageY);
+			if(curHypo < hypo) {
+				hypo = curHypo;
+				resultSide = Side.values()[cur];
+			}
+		}
+		return resultSide;
+	}
+
+	/** Computes X coordinate. */
+	private double getXCoord(int cur, Rectangle2D b) {
+		return b.getWidth() * XCOORDS[cur];
+	}
+
+	/** Computes Y coordinate. */
+	private double getYCoord(int cur, Rectangle2D b) {
+		return b.getHeight() * YCOORDS[cur];
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected IDragController createAnchorageMoveController(IContentAnchorageMVCBundle anchorage) {
+		return new ContentAnchorageMoveControllerBase(anchorage) {
+			@Override
+			protected Point2D getAnchorageFeedbackLocation(DiagramCoordinate delta) {
+				Rectangle2D cBounds = getVisual().getModelBounds();
+				Rectangle2D aBounds = anchorage.getVisual().getModelBounds();
+				double indicatorX = cBounds.getMinX() + aBounds.getMinX() + delta.getX();
+				double indicatorY = cBounds.getMinY() + aBounds.getMinY() + delta.getY();
+				Side closest = getClosestBorderLocation(indicatorX, indicatorY, cBounds);
+				int idx = closest.ordinal();
+				double x = cBounds.getWidth() * XCOORDS[idx] - aBounds.getWidth() / 2;
+				double y = cBounds.getHeight() * YCOORDS[idx] - aBounds.getHeight() / 2;
+				return new Point2D(x, y);
+			}
+
+			@Override
+			protected void moveAnchorage(DiagramCoordinate delta) {
+				Rectangle2D cBounds = getVisual().getModelBounds();
+				Rectangle2D aBounds = anchorage.getVisual().getModelBounds();
+				double indicatorX = cBounds.getMinX() + aBounds.getMinX() + delta.getX();
+				double indicatorY = cBounds.getMinY() + aBounds.getMinY() + delta.getY();
+				moveAnchorageToSideOffset(anchorage,
+						getClosestBorderLocation(indicatorX, indicatorY, cBounds), 0);
+			}
+		};
+	}
+
+	/** Sets the side and offset of the given anchorage when move operation has completed. */
+	protected abstract void moveAnchorageToSideOffset(IContentAnchorageMVCBundle anchorage,
+			Side side, double offset);
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/RectangularContentAnchorageMoveController.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/RectangularContentAnchorageMoveController.java
new file mode 100644
index 0000000000000000000000000000000000000000..c119b011f5a38933cee07b8082fcd44c6c3b0037
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/RectangularContentAnchorageMoveController.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.rectangular;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular.RectangularBorderLocation.getClosestLocationOnBounds;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.ContentAnchorageMoveControllerBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular.RectangularBorderLocation;
+
+import javafx.geometry.Dimension2D;
+import javafx.geometry.Point2D;
+import javafx.geometry.Rectangle2D;
+
+/**
+ * Default implementation for moving content anchorages on the border of a rectangular content
+ * model element visual.
+ */
+public class RectangularContentAnchorageMoveController extends ContentAnchorageMoveControllerBase {
+	/** The controller of the rectangular content visual. */
+	private final RectangularResizableContentControllerBase contentController;
+
+	/** Constructor. */
+	public RectangularContentAnchorageMoveController(
+			RectangularResizableContentControllerBase controller,
+			IContentAnchorageMVCBundle anchorage) {
+		super(anchorage);
+		this.contentController = controller;
+	}
+
+	/** Returns the location clamped to the border of the parent visual. */
+	private RectangularBorderLocation getLocationOnBorder(IContentAnchorageMVCBundle anchorage,
+			Point2D delta) {
+		Rectangle2D contentBounds = contentController.getVisual().getModelBounds();
+		Rectangle2D anchorageBounds = anchorage.getVisual().getModelBounds();
+		Dimension2D anchorageSize =
+				new Dimension2D(anchorageBounds.getWidth(), anchorageBounds.getHeight());
+		Dimension2D borderSnap = overrideBorderSnap(anchorageSize);
+		DiagramCoordinate p = new DiagramCoordinate(anchorageBounds.getMinX() + delta.getX(),
+				anchorageBounds.getMinY() + delta.getY());
+		return getClosestLocationOnBounds(p, contentBounds, anchorageSize, borderSnap);
+	}
+
+	/** Allows sub-classes to override the default span calculation. */
+	protected Dimension2D overrideBorderSnap(Dimension2D anchorageBounds) {
+		return anchorageBounds;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected Point2D getAnchorageFeedbackLocation(DiagramCoordinate delta) {
+		RectangularBorderLocation rbl = getLocationOnBorder(anchorage, delta);
+		return rbl.getLocation();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected void moveAnchorage(DiagramCoordinate delta) {
+		RectangularBorderLocation rbl = getLocationOnBorder(anchorage, delta);
+		contentController.moveAnchorageToSideOffset(anchorage, rbl.getSide(), rbl.getOffset());
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/RectangularResizableContentControllerBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/RectangularResizableContentControllerBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..262b083e4e0ce842b0bb8fbb594585e9f0d2f678
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/RectangularResizableContentControllerBase.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.rectangular;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IDragController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.AnchorageContentControllerBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+
+import javafx.geometry.Side;
+
+/** Base class for controllers with content visuals represented by rectangles. */
+public abstract class RectangularResizableContentControllerBase
+		extends AnchorageContentControllerBase {
+	/** Constructor. */
+	public RectangularResizableContentControllerBase(IContentMVCBundle mvcb) {
+		super(mvcb);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected IDragController createAnchorageMoveController(IContentAnchorageMVCBundle anchorage) {
+		return new RectangularContentAnchorageMoveController(this, anchorage);
+	}
+
+	/** Sets the side and offset of the given anchorage when move operation has completed. */
+	protected abstract void moveAnchorageToSideOffset(IContentAnchorageMVCBundle anchorage,
+			Side side, double offset);
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/RhomboidContentAnchorageMoveController.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/RhomboidContentAnchorageMoveController.java
new file mode 100644
index 0000000000000000000000000000000000000000..fbd40ce483f99c18cfa94076374f572bb72c2646
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/RhomboidContentAnchorageMoveController.java
@@ -0,0 +1,147 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.rectangular;
+
+import static java.lang.Math.hypot;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.ContentAnchorageMoveControllerBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentAnchorageVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular.RectangularBorderLocation;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular.RhomboidContentVisualBase;
+
+import javafx.geometry.Dimension2D;
+import javafx.geometry.Point2D;
+import javafx.geometry.Rectangle2D;
+import javafx.geometry.Side;
+
+/**
+ * Default implementation for moving content anchorages on the border of a rhomboid content
+ * model element visual.
+ */
+public class RhomboidContentAnchorageMoveController extends ContentAnchorageMoveControllerBase {
+	/** The controller of the rhomboid content visual. */
+	private final RhomboidResizableContentControllerBase contentController;
+
+	/** Constructor. */
+	public RhomboidContentAnchorageMoveController(RhomboidResizableContentControllerBase controller,
+			IContentAnchorageMVCBundle anchorage) {
+		super(anchorage);
+		this.contentController = controller;
+		if(!(contentController.getVisual() instanceof RhomboidContentVisualBase)) {
+			throw new IllegalArgumentException("Expected RhomboidContentVisualBase.");
+		}
+	}
+
+	/** Returns the location clamped to the border of the parent visual. */
+	private Pair<Side, Double> getLocationOnBorder(IContentAnchorageMVCBundle anchorage,
+			Point2D delta) {
+		IContentAnchorageVisual anchorageVisual = anchorage.getVisual();
+		Rectangle2D anchorageBounds = anchorageVisual.getModelBounds();
+		double awidth = anchorageBounds.getWidth();
+		double aheight = anchorageBounds.getHeight();
+
+		IVisual contentVisual = contentController.getVisual();
+		Rectangle2D contentBounds = contentVisual.getModelBounds();
+		double rhomboidOffset = ((RhomboidContentVisualBase)contentVisual).getOffset();
+
+		double topX = contentBounds.getMinX();
+		double indicatorX = topX + rhomboidOffset + delta.getX() + awidth / 2;
+		double widthLessOffset = contentBounds.getWidth() - rhomboidOffset;
+		double topY = contentBounds.getMinY();
+		double indicatorY = topY + delta.getY() + aheight / 2;
+		double botY = contentBounds.getMaxY();
+
+		Side resultSide = Side.TOP;
+		double resultOffset = rhomboidOffset;
+		double hypo = Double.MAX_VALUE; // hypot(indicatorX - topX, indicatorY - topY);
+
+		// search TOP and BOTTOM locations
+		for(double fx = 0; fx <= 1; fx += 0.25) {
+			double tx = topX + rhomboidOffset + fx * widthLessOffset;
+			double topHypo = hypot(indicatorX - tx, indicatorY - topY);
+			if(topHypo < hypo) {
+				hypo = topHypo;
+				resultOffset = tx - topX;
+				resultSide = Side.TOP;
+			}
+			double bx = topX + fx * widthLessOffset;
+			double botHypo = hypot(indicatorX - bx, indicatorY - botY);
+			if(botHypo < hypo) {
+				hypo = botHypo;
+				resultOffset = bx - topX;
+				resultSide = Side.BOTTOM;
+			}
+		}
+
+		double height2 = contentBounds.getHeight() / 2;
+		double halfHeightY = topY + height2;
+		// search LEFT center
+		double lx = topX + rhomboidOffset / 2;
+		double leftHypo = hypot(indicatorX - lx, indicatorY - halfHeightY);
+		if(leftHypo < hypo) {
+			hypo = leftHypo;
+			resultOffset = height2;
+			resultSide = Side.LEFT;
+		}
+		// search RIGHT center
+		double rx = topX + contentBounds.getWidth() - rhomboidOffset / 2;
+		double rightHypo = hypot(indicatorX - rx, indicatorY - halfHeightY);
+		if(rightHypo < hypo) {
+			resultOffset = height2;
+			resultSide = Side.RIGHT;
+		}
+		// return closest test point
+		return new ImmutablePair<Side, Double>(resultSide, resultOffset - awidth / 2);
+	}
+
+	/** Allows sub-classes to override the default span calculation. */
+	protected Dimension2D overrideBorderSnap(Dimension2D anchorageBounds) {
+		return anchorageBounds;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected Point2D getAnchorageFeedbackLocation(DiagramCoordinate delta) {
+		RhomboidContentVisualBase contentVisual =
+				(RhomboidContentVisualBase)contentController.getVisual();
+		Rectangle2D contentBounds = contentVisual.getModelBounds();
+		Rectangle2D anchorageBounds = anchorage.getVisual().getModelBounds();
+		Dimension2D anchorageSize =
+				new Dimension2D(anchorageBounds.getWidth(), anchorageBounds.getHeight());
+		double rhomboidOffset = contentVisual.getOffset();
+
+		Pair<Side, Double> lob = getLocationOnBorder(anchorage, delta);
+		Side side = lob.getLeft();
+		if(side == Side.LEFT) {
+			double lx = rhomboidOffset / 2 - anchorageSize.getWidth() / 2;
+			double ly = (contentBounds.getHeight() - anchorageSize.getHeight()) / 2;
+			return new DiagramCoordinate(lx, ly);
+		} else if(side == Side.RIGHT) {
+			double lx = contentBounds.getWidth() - (rhomboidOffset + anchorageSize.getWidth()) / 2;
+			double ly = (contentBounds.getHeight() - anchorageSize.getHeight()) / 2;
+			return new DiagramCoordinate(lx, ly);
+		}
+		RectangularBorderLocation rbl =
+				new RectangularBorderLocation(side, lob.getRight(), contentBounds, anchorageSize);
+		return rbl.getLocation();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected void moveAnchorage(DiagramCoordinate delta) {
+		Pair<Side, Double> lob = getLocationOnBorder(anchorage, delta);
+		contentController.moveAnchorageToSideOffset(anchorage, lob.getLeft(), lob.getRight());
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/RhomboidResizableContentControllerBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/RhomboidResizableContentControllerBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..863b9eae49bad18f71b20c8cde4b0f350348aa27
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/controller/rectangular/RhomboidResizableContentControllerBase.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.controller.rectangular;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IDragController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.base.AnchorageContentControllerBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+
+import javafx.geometry.Side;
+
+/** Base class for controllers with content visuals represented by rhomboids. */
+public abstract class RhomboidResizableContentControllerBase
+		extends AnchorageContentControllerBase {
+	/** Constructor. */
+	public RhomboidResizableContentControllerBase(IContentMVCBundle mvcb) {
+		super(mvcb);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected IDragController createAnchorageMoveController(IContentAnchorageMVCBundle anchorage) {
+		return new RhomboidContentAnchorageMoveController(this, anchorage);
+	}
+
+	/** Sets the side and offset of the given anchorage when move operation has completed. */
+	protected abstract void moveAnchorageToSideOffset(IContentAnchorageMVCBundle anchorage,
+			Side side, double offset);
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/.ratings b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..64f28f835bfdcd13cc22512f260cf8d9d693a7ca
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/.ratings
@@ -0,0 +1,5 @@
+IModelChangeListener.java 241a583a3517ad504ad0a2d5a8f4d98afee8fd8b YELLOW
+IModelChangeProvider.java bc7622c4af211e3ee470d12b80d04a92880f0d38 YELLOW
+IModelFactory.java 3b6a1cb2779af0215a2637c40428b42600ef4ffb YELLOW
+ModelChangeProviderBase.java eb5ad0363e30f727a70c42edb3d781acb6e6add6 YELLOW
+ModelFactoryBase.java 6d0e1e5658c592f3f98d3bcf68bdbb54a506c489 YELLOW
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/IModelChangeListener.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/IModelChangeListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..241a583a3517ad504ad0a2d5a8f4d98afee8fd8b
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/IModelChangeListener.java
@@ -0,0 +1,17 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.model;
+
+/** Functional interface for listeners of model changes. */
+@FunctionalInterface
+public interface IModelChangeListener {
+	/** Called when the change occurred. */
+	public void changed(Object element);
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/IModelChangeProvider.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/IModelChangeProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..bc7622c4af211e3ee470d12b80d04a92880f0d38
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/IModelChangeProvider.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.model;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl.MVCBundleBase;
+
+/** Interface for classes providing model changes to the {@link MVCBundleBase}s. */
+public interface IModelChangeProvider {
+	/** Adds the given listener. */
+	public void addListener(IModelChangeListener l);
+
+	/** Removes the given listener. */
+	public void removeListener(IModelChangeListener l);
+
+	/** Fires the change event to all listeners. */
+	public void fireUpdateComplete();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/IModelFactory.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/IModelFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..3b6a1cb2779af0215a2637c40428b42600ef4ffb
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/IModelFactory.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.model;
+
+import java.util.List;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+
+/** Factory class for providing model data to the {@link DiagramViewer}. */
+public interface IModelFactory {
+	/** The root model element, which is presented as the background of the diagram. */
+	Object getRootModel();
+
+	/**
+	 * The content model elements, which are represented by their visuals. Usually, the parent model
+	 * element of these is the root element.
+	 */
+	List<?> getContentModels();
+
+	/**
+	 * The anchorage model elements of the root element, which are presented as freely positioned
+	 * visuals. The parent of these elements is usually the root element.
+	 */
+	List<?> getDiagramAnchorageModels();
+
+	/**
+	 * The anchorage model elements of the given content model elements, which are presented as
+	 * visuals sticking to their content parent visual's border.
+	 */
+	List<?> getContentAnchorageModels(Object parent);
+
+	/**
+	 * The link model elements of the root element, which connect between interface model
+	 * elements of both the root and/or content model elements.
+	 */
+	List<?> getLinkModels();
+
+	/**
+	 * Returns the model element, which is the start of the given link element. The returned
+	 * element must be contained in either {@link #getDiagramAnchorageModels()} or
+	 * {@link #getContentAnchorageModels(Object)}.
+	 */
+	Object getLinkStart(Object link);
+
+	/**
+	 * Returns the model element, which is the end of the given link element. The returned
+	 * element must be contained in either {@link #getDiagramAnchorageModels()} or
+	 * {@link #getContentAnchorageModels(Object)}.
+	 */
+	Object getLinkEnd(Object link);
+
+	/**
+	 * Returns the model element, which is considered the parent content model of the given model
+	 * element.
+	 */
+	Object getParent(Object element);
+
+	/** Update the model factory's auxiliary data before calling any of the other methods. */
+	void update();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/ModelChangeProviderBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/ModelChangeProviderBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..eb5ad0363e30f727a70c42edb3d781acb6e6add6
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/ModelChangeProviderBase.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Base class for model change listeners. */
+public abstract class ModelChangeProviderBase implements IModelChangeProvider {
+	/** The registered listeners. */
+	private final List<IModelChangeListener> listeners = new ArrayList<>();
+
+	/** {@inheritDoc} */
+	@Override
+	public void addListener(IModelChangeListener l) {
+		if(!listeners.contains(l)) {
+			listeners.add(l);
+			connectToModel();
+		}
+	}
+
+	/** Called when the first listener is added. */
+	protected void connectToModel() {
+		// does nothing
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void removeListener(IModelChangeListener l) {
+		listeners.remove(l);
+		if(listeners.isEmpty()) {
+			disconnectFromModel();
+		}
+	}
+
+	/** Called when the last listener is removed. */
+	protected void disconnectFromModel() {
+		// does nothing
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void fireUpdateComplete() {
+		// use a copy to avoid concurrent modification exception
+		ArrayList<IModelChangeListener> iterationSafeCopy = new ArrayList<>(listeners);
+		for(IModelChangeListener l : iterationSafeCopy) {
+			l.changed(this);
+		}
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/ModelFactoryBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/ModelFactoryBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..6d0e1e5658c592f3f98d3bcf68bdbb54a506c489
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/ModelFactoryBase.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.model;
+
+import static java.util.Collections.emptyList;
+
+import java.util.List;
+
+/** Base implementation for {@link IModelFactory}. */
+public abstract class ModelFactoryBase implements IModelFactory {
+	/** {@inheritDoc} */
+	@Override
+	public List<?> getContentModels() {
+		return emptyList();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public List<?> getDiagramAnchorageModels() {
+		return emptyList();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public List<?> getContentAnchorageModels(Object parent) {
+		return emptyList();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public List<?> getLinkModels() {
+		return emptyList();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Object getLinkStart(Object link) {
+		return null;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Object getLinkEnd(Object link) {
+		return null;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Object getParent(Object element) {
+		return null;
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/.ratings b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..e16ac9b565554bdc3997e7fda8cf824f4f77e9aa
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/.ratings
@@ -0,0 +1,6 @@
+IAngleLayout.java 4993d53870c2a71dc63e8a99ceb47df6cfec3801 YELLOW
+ILayout.java e6fd2a1ac953a18c8ea8951e594e44a1c0fc8d92 YELLOW
+IOffsetLayout.java 440551f07a2af2ceb000b14177cc2351cb1b7f52 YELLOW
+IPointsLayout.java be1c706ae6e782d6ac960e02e6855f8d2118914b YELLOW
+ISideLayout.java 20b14640a7424a013338e6683b9f4cf6e62f01e1 YELLOW
+IXYLayout.java e3e007db33846621bf6844621360b5881f7f6023 YELLOW
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/IAngleLayout.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/IAngleLayout.java
new file mode 100644
index 0000000000000000000000000000000000000000..4993d53870c2a71dc63e8a99ceb47df6cfec3801
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/IAngleLayout.java
@@ -0,0 +1,16 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout;
+
+/** {@link ILayout} defined by an angle in degree. */
+public interface IAngleLayout extends ILayout {
+	/** Returns the angle in degree. */
+	double getAngleInDegree();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/ILayout.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/ILayout.java
new file mode 100644
index 0000000000000000000000000000000000000000..e6fd2a1ac953a18c8ea8951e594e44a1c0fc8d92
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/ILayout.java
@@ -0,0 +1,15 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout;
+
+/** Base interface for all pieces of layout information of model elements. */
+public interface ILayout {
+	// nothing to do here
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/IOffsetLayout.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/IOffsetLayout.java
new file mode 100644
index 0000000000000000000000000000000000000000..440551f07a2af2ceb000b14177cc2351cb1b7f52
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/IOffsetLayout.java
@@ -0,0 +1,16 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout;
+
+/** {@link ILayout} defined by an offset. */
+public interface IOffsetLayout extends ILayout {
+	/** Returns the offset. */
+	double getOffset();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/IPointsLayout.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/IPointsLayout.java
new file mode 100644
index 0000000000000000000000000000000000000000..be1c706ae6e782d6ac960e02e6855f8d2118914b
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/IPointsLayout.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout;
+
+/** {@link ILayout} defined by a sequence of X and Y coordinates. */
+public interface IPointsLayout extends ILayout {
+	/** Returns the number of points in the layout data. */
+	int getNumberOfPoints();
+
+	/**
+	 * Returns the X coordinate of the point at the given index [0 <= index < getNumberOfPoints()].
+	 */
+	double getX(int index);
+
+	/**
+	 * Returns the Y coordinate of the point at the given index [0 <= index < getNumberOfPoints()].
+	 */
+	double getY(int index);
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/ISideLayout.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/ISideLayout.java
new file mode 100644
index 0000000000000000000000000000000000000000..20b14640a7424a013338e6683b9f4cf6e62f01e1
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/ISideLayout.java
@@ -0,0 +1,18 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout;
+
+import javafx.geometry.Side;
+
+/** {@link ILayout} defined by a {@link Side}. */
+public interface ISideLayout extends ILayout {
+	/** Returns the side. */
+	Side getSide();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/IXYLayout.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/IXYLayout.java
new file mode 100644
index 0000000000000000000000000000000000000000..e3e007db33846621bf6844621360b5881f7f6023
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/model/layout/IXYLayout.java
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout;
+
+/** {@link ILayout} defined by X and Y. */
+public interface IXYLayout extends ILayout {
+	/** Returns the X coordinate. */
+	double getX();
+
+	/** Returns the Y coordinate. */
+	double getY();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/.ratings b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..2cbebcd4f0eb0c2a87624fda33091a00b464d1d6
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/.ratings
@@ -0,0 +1,10 @@
+IAnchorageMVCBundle.java 17671d1ef6ecc4d1b4b98cf9d83016b31455346d YELLOW
+IContentAnchorageMVCBundle.java c3bd9eb5266d0f87b16cdd16aeaadbfa6eeb49de YELLOW
+IContentMVCBundle.java f84f8e59af85d022c2019d271d957821c5c55d3f YELLOW
+IContentMVCBundleWithParent.java 890ffcacb493fda5ead5b7f74dc7ae1637077526 YELLOW
+IDiagramAnchorageMVCBundle.java c49ef7ca7457ea9d4557251263779b403f79e69c YELLOW
+IDiagramMVCBundle.java 354facfc00887726a6d7553d40824b7171865330 YELLOW
+ILinkMVCBundle.java a2b1527f98ed642def2346190f6a71016ca674af YELLOW
+IMVCBundle.java 19a6cc6bc96c42dd98dca24288a050ff0ba04734 YELLOW
+IMVCBundlePart.java 6ddc90167a6ee7410876150e1785840f6ad32912 YELLOW
+MVCBundleTag.java cf428833f24caccd74945119cf906b19daa3d63b YELLOW
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IAnchorageMVCBundle.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IAnchorageMVCBundle.java
new file mode 100644
index 0000000000000000000000000000000000000000..17671d1ef6ecc4d1b4b98cf9d83016b31455346d
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IAnchorageMVCBundle.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc;
+
+import java.util.List;
+
+/**
+ * Interface for {@link IMVCBundle}s, which represent anchorages and thus can have directed links
+ * attached to them.
+ */
+public interface IAnchorageMVCBundle extends IMVCBundle {
+	/** Returns the {@link IMVCBundle} this anchorage is attached to. */
+	IMVCBundle getAttachedTo();
+
+	/** Returns the list of incoming links. */
+	List<ILinkMVCBundle> getIncomingLinks();
+
+	/** Returns the list of outgoing links. */
+	List<ILinkMVCBundle> getOutgoingLinks();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IContentAnchorageMVCBundle.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IContentAnchorageMVCBundle.java
new file mode 100644
index 0000000000000000000000000000000000000000..c3bd9eb5266d0f87b16cdd16aeaadbfa6eeb49de
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IContentAnchorageMVCBundle.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentAnchorageVisual;
+
+/** Interface for anchorage {@link IMVCBundle}s attached to {@link IContentMVCBundle}s. */
+public interface IContentAnchorageMVCBundle extends IAnchorageMVCBundle {
+	/** Returns the {@link IContentMVCBundle} this anchorage is attached to. */
+	@Override
+	IContentMVCBundle getAttachedTo();
+
+	/** {@inheritDoc} */
+	@Override
+	IContentAnchorageVisual getVisual();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IContentMVCBundle.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IContentMVCBundle.java
new file mode 100644
index 0000000000000000000000000000000000000000..f84f8e59af85d022c2019d271d957821c5c55d3f
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IContentMVCBundle.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc;
+
+import java.util.List;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentVisual;
+
+/** Interface for content elements of the diagram viewer. */
+public interface IContentMVCBundle extends IMVCBundle {
+	/** Returns the {@link IDiagramMVCBundle} this content is attached to. */
+	IDiagramMVCBundle getDiagramBundle();
+
+	/** Returns the list of attached anchorages. */
+	List<IContentAnchorageMVCBundle> getAnchorages();
+
+	/** Returns the children of this content bundle. */
+	List<IContentMVCBundleWithParent> getChildren();
+
+	/** {@inheritDoc} */
+	@Override
+	IContentVisual getVisual();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IContentMVCBundleWithParent.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IContentMVCBundleWithParent.java
new file mode 100644
index 0000000000000000000000000000000000000000..890ffcacb493fda5ead5b7f74dc7ae1637077526
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IContentMVCBundleWithParent.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc;
+
+/**
+ * Interface for content elements of diagrams, which have a reference to another
+ * {@link IContentMVCBundle}.
+ */
+public interface IContentMVCBundleWithParent extends IContentMVCBundle {
+	/** Returns the parent {@link IContentMVCBundle}. */
+	IContentMVCBundle getParentBundle();
+
+	/** Sets the parent of this {@link IContentMVCBundleWithParent}. */
+	void setParentBundle(IContentMVCBundle parent);
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IDiagramAnchorageMVCBundle.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IDiagramAnchorageMVCBundle.java
new file mode 100644
index 0000000000000000000000000000000000000000..c49ef7ca7457ea9d4557251263779b403f79e69c
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IDiagramAnchorageMVCBundle.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IDiagramAnchorageVisual;
+
+/** Interface for {@link IAnchorageMVCBundle}s attached to the diagram background. */
+public interface IDiagramAnchorageMVCBundle extends IAnchorageMVCBundle {
+	/** Returns the {@link IDiagramMVCBundle} this anchorage is attached to. */
+	@Override
+	IDiagramMVCBundle getAttachedTo();
+
+	/** {@inheritDoc} */
+	@Override
+	IDiagramAnchorageVisual getVisual();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IDiagramMVCBundle.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IDiagramMVCBundle.java
new file mode 100644
index 0000000000000000000000000000000000000000..354facfc00887726a6d7553d40824b7171865330
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IDiagramMVCBundle.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc;
+
+import java.util.List;
+
+/** Interface for the {@link IMVCBundle} representing the diagram (i.e. its background). */
+public interface IDiagramMVCBundle extends IMVCBundle {
+	/** Returns the list of content elements. */
+	List<IContentMVCBundle> getContents();
+
+	/** Returns the list of links. */
+	List<ILinkMVCBundle> getLinks();
+
+	/** Returns the list of attached diagram anchorages. */
+	List<IDiagramAnchorageMVCBundle> getAnchorages();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/ILinkMVCBundle.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/ILinkMVCBundle.java
new file mode 100644
index 0000000000000000000000000000000000000000..a2b1527f98ed642def2346190f6a71016ca674af
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/ILinkMVCBundle.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.ILinkVisual;
+
+/** Interface for directed links anchored to {@link IAnchorageMVCBundle}s. */
+public interface ILinkMVCBundle extends IMVCBundle {
+	/** Returns the parent {@link IDiagramMVCBundle}. */
+	IDiagramMVCBundle getDiagramMVCBundle();
+
+	/** Returns the start anchorage of this link. */
+	IAnchorageMVCBundle getStartAnchorage();
+
+	/** Returns the end anchorage of this link. */
+	IAnchorageMVCBundle getEndAnchorage();
+
+	/** Returns the visual. */
+	@Override
+	ILinkVisual getVisual();
+
+	/** Updates the {@link ILinkMVCBundle}'s start and end anchorages. */
+	void updateLinkBundle(IAnchorageMVCBundle start, IAnchorageMVCBundle end);
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IMVCBundle.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IMVCBundle.java
new file mode 100644
index 0000000000000000000000000000000000000000..19a6cc6bc96c42dd98dca24288a050ff0ba04734
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IMVCBundle.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisual;
+
+/** Interface for bundles composed of a model, a view, and a controller. */
+public interface IMVCBundle {
+	/** Returns the model. */
+	Object getModel();
+
+	/** Returns the visual. */
+	IVisual getVisual();
+
+	/** Sets the visual. */
+	void setVisual(IVisual v);
+
+	/** Returns the controller. */
+	IController getController();
+
+	/** Sets the controller. */
+	void setController(IController c);
+
+	/** Returns the diagram viewer. */
+	DiagramViewer getViewer();
+
+	/** Returns the feedback change (temporary location or size changes during user gestures). */
+	FeedbackChange getFeedbackChange();
+
+	/** Sets the feedback change (temporary location or size changes during user gestures). */
+	void setFeedbackChange(FeedbackChange fc);
+
+	/** Attaches listeners to the visual and the underlying model. */
+	void attach();
+
+	/** Detaches listeners from the visual and from the underlying model. */
+	void detach();
+
+	/** Adds the given tag to this bundle. */
+	void addTag(MVCBundleTag tag);
+
+	/** Returns the tag of this bundle matching the given tags identifier. */
+	MVCBundleTag getTag(MVCBundleTag tag);
+
+	/** Returns whether this bundle is tagged with the given tag (using equals()). */
+	boolean hasTag(MVCBundleTag tag);
+
+	/** Removes the tag from this bundle. */
+	void removeTag(MVCBundleTag tag);
+
+	/** Removes the bundle from any other bundle it is related to. */
+	void remove();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IMVCBundlePart.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IMVCBundlePart.java
new file mode 100644
index 0000000000000000000000000000000000000000..6ddc90167a6ee7410876150e1785840f6ad32912
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/IMVCBundlePart.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl.MVCBundleBase;
+
+/**
+ * Interface for classes that take part in the model-visual-controller setup and reference an
+ * {@link IMVCBundle}.
+ */
+public interface IMVCBundlePart {
+	/** Returns the {@link MVCBundleBase}. */
+	IMVCBundle getMVCBundle();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/MVCBundleTag.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/MVCBundleTag.java
new file mode 100644
index 0000000000000000000000000000000000000000..cf428833f24caccd74945119cf906b19daa3d63b
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/MVCBundleTag.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc;
+
+/** Class encapsulating a String tag and, optionally, a user object. */
+public final class MVCBundleTag {
+	/** The tag identifier. */
+	private final String tagIdentifier;
+	/** The tag user object. */
+	private final Object userObject;
+
+	/** Constructor. */
+	private MVCBundleTag(String id, Object userObject) {
+		this.tagIdentifier = id;
+		this.userObject = userObject;
+	}
+
+	/** Constructor. */
+	private MVCBundleTag(String id) {
+		this(id, null);
+	}
+
+	/** Constructor. */
+	public MVCBundleTag(Class<?> clazz, String id) {
+		this(clazz.getName() + "_" + id);
+	}
+
+	/** Returns the tag identifier. */
+	public String getTagIdentifier() {
+		return tagIdentifier;
+	}
+
+	/** Returns the user object. */
+	public Object getUserObject() {
+		return userObject;
+	}
+
+	/** Creates a tag object with the same identifier and given user object. */
+	public MVCBundleTag getCopy(Object userObject) {
+		return new MVCBundleTag(tagIdentifier, userObject);
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/.ratings b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..75dd39150629f014e42f0ffb5515d36228037152
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/.ratings
@@ -0,0 +1,8 @@
+AnchorageMVCBundleBase.java 63dc967da3342d402603adcc005344ce89386ea8 YELLOW
+ContentAnchorageMVCBundle.java 1a517903c6400203cb7850502671e01d715f2453 YELLOW
+ContentMVCBundle.java ee8a2c7780b64d61cf3e196437ce5ab07ad29e7c YELLOW
+DiagramAnchorageMVCBundle.java b0f12259bbb8ccbf68b0774eb76c060188f4fea2 YELLOW
+DiagramMVCBundle.java 46860e820c67f985cd75704b9084948df1939ee8 YELLOW
+LinkMVCBundle.java b8246cd0e6f6ff53b377a9a0c4d1c8e4aa6371c4 YELLOW
+MVCBundleBase.java 0a1b561e6ed5a5b4a3d2fdf52e2e5839c770193b YELLOW
+MVCBundlePartBase.java 9e56b3799f14fe63649a09130edbdef083b87fdc YELLOW
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/AnchorageMVCBundleBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/AnchorageMVCBundleBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..63dc967da3342d402603adcc005344ce89386ea8
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/AnchorageMVCBundleBase.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+
+/** Base class for {@link IAnchorageMVCBundle}s. */
+abstract class AnchorageMVCBundleBase extends MVCBundleBase implements IAnchorageMVCBundle {
+	/** The parent bundle this bundle is attached to. */
+	private IMVCBundle attachedTo;
+	/** The list of incoming links. */
+	private final List<ILinkMVCBundle> incomingLinks = new LinkedList<>();
+	/** The list of outgoing links. */
+	private final List<ILinkMVCBundle> outgoingLinks = new LinkedList<>();
+
+	/** Constructor. */
+	public AnchorageMVCBundleBase(Object m, IMVCBundle attachedTo, DiagramViewer v) {
+		super(m, v);
+		this.attachedTo = requireNonNull(attachedTo);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IMVCBundle getAttachedTo() {
+		return attachedTo;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public List<ILinkMVCBundle> getIncomingLinks() {
+		return incomingLinks;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public List<ILinkMVCBundle> getOutgoingLinks() {
+		return outgoingLinks;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void changed(Object element) {
+		// Anchorages have no children, so only the properties can have changed.
+		// Therefore, update just the view and the connected links.
+		getViewer().updateVisual(this);
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/ContentAnchorageMVCBundle.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/ContentAnchorageMVCBundle.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a517903c6400203cb7850502671e01d715f2453
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/ContentAnchorageMVCBundle.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentAnchorageVisual;
+
+/** Implementation of {@link IContentAnchorageMVCBundle}. */
+public final class ContentAnchorageMVCBundle extends AnchorageMVCBundleBase
+		implements IContentAnchorageMVCBundle {
+	/** Constructor. */
+	public ContentAnchorageMVCBundle(Object m, IContentMVCBundle attachedTo, DiagramViewer v) {
+		super(m, attachedTo, v);
+		attachedTo.getAnchorages().add(this);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void remove() {
+		getAttachedTo().getAnchorages().remove(this);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IContentMVCBundle getAttachedTo() {
+		// wild cast works: see constructor
+		return (IContentMVCBundle)super.getAttachedTo();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IContentAnchorageVisual getVisual() {
+		return (IContentAnchorageVisual)super.getVisual();
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/ContentMVCBundle.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/ContentMVCBundle.java
new file mode 100644
index 0000000000000000000000000000000000000000..ee8a2c7780b64d61cf3e196437ce5ab07ad29e7c
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/ContentMVCBundle.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundleWithParent;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentVisual;
+
+/** Implementation of {@link IContentMVCBundle}. */
+public final class ContentMVCBundle extends MVCBundleBase implements IContentMVCBundleWithParent {
+	/** The diagram bundle this content is attached to. */
+	private final IDiagramMVCBundle diagramBundle;
+	/** The list of attached anchorages. */
+	private final List<IContentAnchorageMVCBundle> anchorages = new ArrayList<>();
+	/** The parent bundle. */
+	private IContentMVCBundle parent;
+	/** The child bundles. */
+	private final List<IContentMVCBundleWithParent> children =
+			new LinkedList<IContentMVCBundleWithParent>();
+
+	/** Constructor. */
+	public ContentMVCBundle(Object m, IDiagramMVCBundle diagramBundle, DiagramViewer v) {
+		super(m, v);
+		this.diagramBundle = diagramBundle;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void remove() {
+		// nothing to do here
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IDiagramMVCBundle getDiagramBundle() {
+		return diagramBundle;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public List<IContentAnchorageMVCBundle> getAnchorages() {
+		return anchorages;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IContentMVCBundle getParentBundle() {
+		return parent;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IContentVisual getVisual() {
+		return (IContentVisual)super.getVisual();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void setParentBundle(IContentMVCBundle p) {
+		if(parent != null) {
+			parent.getChildren().remove(this);
+		}
+		this.parent = p;
+		if(parent != null) {
+			parent.getChildren().add(this);
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public List<IContentMVCBundleWithParent> getChildren() {
+		return children;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void changed(Object element) {
+		// Check if the anchorages have changed.
+		boolean anchoragesChanged = false;
+		List<Object> modelAnchorages = new ArrayList<>(
+				getViewer().getModelFactory().getContentAnchorageModels(getModel()));
+		for(IContentAnchorageMVCBundle a : getAnchorages()) {
+			anchoragesChanged |= !modelAnchorages.remove(a.getModel());
+		}
+		anchoragesChanged |= modelAnchorages.size() > 0;
+
+		// If the anchorages have changed, invoke the generic case from the superclass
+		if(anchoragesChanged) {
+			super.changed(element);
+			return;
+		}
+
+		// Otherwise, only the properties of the element can have changed.
+		// Update the view of the element, its anchorages and the connected links.
+		getViewer().updateVisual(this);
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/DiagramAnchorageMVCBundle.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/DiagramAnchorageMVCBundle.java
new file mode 100644
index 0000000000000000000000000000000000000000..b0f12259bbb8ccbf68b0774eb76c060188f4fea2
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/DiagramAnchorageMVCBundle.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IDiagramAnchorageVisual;
+
+/** Implementation of {@link IContentAnchorageMVCBundle}. */
+public final class DiagramAnchorageMVCBundle extends AnchorageMVCBundleBase
+		implements IDiagramAnchorageMVCBundle {
+
+	/** Constructor. */
+	public DiagramAnchorageMVCBundle(Object m, IDiagramMVCBundle attachedTo, DiagramViewer v) {
+		super(m, attachedTo, v);
+		attachedTo.getAnchorages().add(this);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void remove() {
+		getAttachedTo().getAnchorages().remove(this);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IDiagramMVCBundle getAttachedTo() {
+		// wild cast works: see constructor
+		return (IDiagramMVCBundle)super.getAttachedTo();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IDiagramAnchorageVisual getVisual() {
+		return (IDiagramAnchorageVisual)super.getVisual();
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/DiagramMVCBundle.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/DiagramMVCBundle.java
new file mode 100644
index 0000000000000000000000000000000000000000..46860e820c67f985cd75704b9084948df1939ee8
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/DiagramMVCBundle.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+
+/** Implementation of {@link IDiagramMVCBundle}. */
+public final class DiagramMVCBundle extends MVCBundleBase implements IDiagramMVCBundle {
+	/** The list of content elements. */
+	private final List<IContentMVCBundle> contents = new ArrayList<>();
+	/** The list of link elements. */
+	private final List<ILinkMVCBundle> links = new ArrayList<>();
+	/** The list of attached anchorages. */
+	private final List<IDiagramAnchorageMVCBundle> anchorages = new ArrayList<>();
+
+	/** Constructor. */
+	public DiagramMVCBundle(Object m, DiagramViewer v) {
+		super(m, v);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void remove() {
+		// nothing to do here
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public List<IContentMVCBundle> getContents() {
+		return contents;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public List<ILinkMVCBundle> getLinks() {
+		return links;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public List<IDiagramAnchorageMVCBundle> getAnchorages() {
+		return anchorages;
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/LinkMVCBundle.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/LinkMVCBundle.java
new file mode 100644
index 0000000000000000000000000000000000000000..b8246cd0e6f6ff53b377a9a0c4d1c8e4aa6371c4
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/LinkMVCBundle.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.ILinkVisual;
+
+/**
+ * {@link MVCBundleBase} for connections providing access to both end-point {@link MVCBundleBase}s.
+ */
+public class LinkMVCBundle extends MVCBundleBase implements ILinkMVCBundle {
+	/** The {@link IDiagramMVCBundle}, which is the parent of all links. */
+	private final IDiagramMVCBundle parent;
+	/** The start-point {@link IAnchorageMVCBundle}. */
+	private IAnchorageMVCBundle start;
+	/** The end-point {@link IAnchorageMVCBundle}. */
+	private IAnchorageMVCBundle end;
+
+	/** Constructor. */
+	public LinkMVCBundle(Object m, IDiagramMVCBundle parent, IAnchorageMVCBundle start,
+			IAnchorageMVCBundle end, DiagramViewer v) {
+		super(m, v);
+		this.parent = parent;
+		parent.getLinks().add(this);
+		this.start = start;
+		start.getOutgoingLinks().add(this);
+		this.end = end;
+		end.getIncomingLinks().add(this);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public final void remove() {
+		parent.getLinks().remove(this);
+		start.getOutgoingLinks().remove(this);
+		end.getIncomingLinks().remove(this);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IAnchorageMVCBundle getStartAnchorage() {
+		return start;
+	}
+
+	/** Sets the start bundle. */
+	public void setStartAnchorage(IAnchorageMVCBundle start) {
+		this.start = start;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IAnchorageMVCBundle getEndAnchorage() {
+		return end;
+	}
+
+	/** Sets the end bundle. */
+	public void setEndAnchorage(IAnchorageMVCBundle end) {
+		this.end = end;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IDiagramMVCBundle getDiagramMVCBundle() {
+		return parent;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public ILinkVisual getVisual() {
+		return (ILinkVisual)super.getVisual();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public final void updateLinkBundle(IAnchorageMVCBundle newStart, IAnchorageMVCBundle newEnd) {
+		if(start != null && start != newStart) {
+			start.getOutgoingLinks().remove(this);
+			start = newStart;
+			start.getOutgoingLinks().add(this);
+		}
+		if(end != null && end != newEnd) {
+			end.getIncomingLinks().remove(this);
+			end = newEnd;
+			end.getIncomingLinks().add(this);
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void changed(Object element) {
+		getViewer().updateVisual(this);
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/MVCBundleBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/MVCBundleBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..0a1b561e6ed5a5b4a3d2fdf52e2e5839c770193b
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/MVCBundleBase.java
@@ -0,0 +1,179 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelChangeListener;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelChangeProvider;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.MVCBundleTag;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisual;
+
+/**
+ * This class bundles all objects used by the model-view-controller paradigm.
+ * <p>
+ * It is also responsible for propagating model changes to changes of the visual.
+ * Model changes may be received directly from the model element (if it implements
+ * {@link IModelChangeProvider}) or from the assigned controller (if that one
+ * implements {@link IModelChangeProvider}).
+ */
+public abstract class MVCBundleBase implements IModelChangeListener, IMVCBundle {
+	/** The model. */
+	private final Object model;
+	/** The diagram viewer to be used for selection handling. */
+	private final DiagramViewer viewer;
+	/** The visual. */
+	private IVisual visual;
+	/** The controller. */
+	private IController controller;
+	/** The current feedback change applied to this bundle's visual. */
+	private FeedbackChange feedbackChange = new FeedbackChange();
+	/** The set of {@link MVCBundleTag}s. */
+	private final Map<String, MVCBundleTag> tagMap = new HashMap<>();
+
+	/** Constructor. */
+	public MVCBundleBase(Object m, DiagramViewer v) {
+		model = requireNonNull(m, "Model must not be null.");
+		viewer = requireNonNull(v, "Viewer must not be null.");
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public final void setVisual(IVisual visual) {
+		this.visual = requireNonNull(visual, "Visual must not be null.");
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void attach() {
+		if(model instanceof IModelChangeProvider) {
+			IModelChangeProvider mcp = (IModelChangeProvider)model;
+			mcp.addListener(this);
+		}
+		if(controller instanceof IModelChangeProvider) {
+			IModelChangeProvider mcp = (IModelChangeProvider)controller;
+			mcp.addListener(this);
+		}
+		IModelChangeProvider adapted = controller.getModelChangeProvider();
+		if(adapted != null) {
+			adapted.addListener(this);
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public final void detach() {
+		if(model instanceof IModelChangeProvider) {
+			IModelChangeProvider mcp = (IModelChangeProvider)model;
+			mcp.removeListener(this);
+		}
+		if(controller instanceof IModelChangeProvider) {
+			IModelChangeProvider mcp = (IModelChangeProvider)controller;
+			mcp.removeListener(this);
+		}
+		IModelChangeProvider adapted = controller.getModelChangeProvider();
+		if(adapted != null) {
+			adapted.removeListener(this);
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public final void setController(IController controller) {
+		this.controller = requireNonNull(controller, "Controller must not be null.");
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Object getModel() {
+		return model;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IVisual getVisual() {
+		return visual;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IController getController() {
+		return controller;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramViewer getViewer() {
+		return viewer;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void changed(Object element) {
+		viewer.updateFromModel();
+	}
+
+	/**
+	 * {@inheritDoc}
+	 * <p>
+	 * Updates the visual after setting the feedback change.
+	 */
+	@Override
+	public final void setFeedbackChange(FeedbackChange feedbackChange) {
+		this.feedbackChange = requireNonNull(feedbackChange);
+		viewer.updateVisual(this);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public final FeedbackChange getFeedbackChange() {
+		return feedbackChange;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public String toString() {
+		return getClass().getSimpleName() + " MVC bundle of " + getModel().toString();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void addTag(MVCBundleTag tag) {
+		tagMap.put(tag.getTagIdentifier(), tag);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public MVCBundleTag getTag(MVCBundleTag tag) {
+		return tagMap.get(tag.getTagIdentifier());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean hasTag(MVCBundleTag tag) {
+		if(tag == null) {
+			return false;
+		}
+		return tagMap.get(tag.getTagIdentifier()) != null;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void removeTag(MVCBundleTag tag) {
+		tagMap.remove(tag.getTagIdentifier());
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/MVCBundlePartBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/MVCBundlePartBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..9e56b3799f14fe63649a09130edbdef083b87fdc
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/mvc/impl/MVCBundlePartBase.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundlePart;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisual;
+
+/** Base class implementing {@link IMVCBundlePart}. */
+public class MVCBundlePartBase implements IMVCBundlePart {
+	/** The {@link MVCBundleBase}. */
+	private IMVCBundle mvcb;
+
+	/** Constructor. */
+	public MVCBundlePartBase(IMVCBundle mvcb) {
+		this.mvcb = mvcb;
+	}
+
+	/** Returns the model object. */
+	public final Object getModel() {
+		return mvcb.getModel();
+	}
+
+	/** Returns the visual. */
+	public final IVisual getVisual() {
+		return mvcb.getVisual();
+	}
+
+	/** Returns the controller. */
+	public final IController getController() {
+		return mvcb.getController();
+	}
+
+	/** Returns the diagram viewer. */
+	public final DiagramViewer getViewer() {
+		return mvcb.getViewer();
+	}
+
+	/** Returns the feedback change. */
+	public final FeedbackChange getFeedbackChange() {
+		return mvcb.getFeedbackChange();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IMVCBundle getMVCBundle() {
+		return mvcb;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("MVCB part ").append(getClass().getSimpleName());
+		sb.append(" of ").append(mvcb.toString());
+		return sb.toString();
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/.ratings b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..eac3ce8aa6f5915c3c45dd6ddaee84beae2392df
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/.ratings
@@ -0,0 +1,7 @@
+IAnchorageVisual.java e742212c132db1e2309650f5d6e40bc79c2ff85a YELLOW
+IContentAnchorageVisual.java 3a3ceb675820f151aa678b6caa0f1ae833da1b7f YELLOW
+IContentVisual.java 1052ece1f6172799204b1d7524398766c3b7a12e YELLOW
+IDiagramAnchorageVisual.java ae0895912fe735147ccd11b7f466a7a5b61c3ca5 YELLOW
+ILinkVisual.java 8f9bd6cf7c423a78d67fab0a20ba2dad2dfbd55e YELLOW
+IVisual.java 400569502788bf2efff3de84f2c53b65256b3fc4 YELLOW
+IVisualFactory.java 39253dc705f7f73ddbb83d050232a19219d54e71 YELLOW
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IAnchorageVisual.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IAnchorageVisual.java
new file mode 100644
index 0000000000000000000000000000000000000000..e742212c132db1e2309650f5d6e40bc79c2ff85a
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IAnchorageVisual.java
@@ -0,0 +1,18 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual;
+
+import javafx.geometry.Dimension2D;
+
+/** {@link IVisual} interface for anchorages. */
+public interface IAnchorageVisual extends IVisual {
+	/** Returns the dimension of the visual. */
+	Dimension2D getDimensions();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IContentAnchorageVisual.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IContentAnchorageVisual.java
new file mode 100644
index 0000000000000000000000000000000000000000..3a3ceb675820f151aa678b6caa0f1ae833da1b7f
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IContentAnchorageVisual.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout.ILayout;
+
+/** Interface for anchorage visuals which stick to a content node in the diagram. */
+public interface IContentAnchorageVisual extends IAnchorageVisual {
+	/**
+	 * Returns the layout information for this content anchorage. Callers must provide an actual
+	 * type of the requested layout information.
+	 */
+	<T extends ILayout> T getLayout(Class<T> type);
+
+	/**
+	 * Returns whether sibling anchorage visuals (i.e. those attached to the same content visual as
+	 * this anchorage visual) should be updated when this visual is hovered over by the mouse
+	 * pointer. The default is {@code false}.
+	 * 
+	 * @see IContentVisual#updateAttachedAnchorageVisualsOnHover()
+	 */
+	boolean updateSiblingAnchorageVisualsOnHover();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IContentVisual.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IContentVisual.java
new file mode 100644
index 0000000000000000000000000000000000000000..1052ece1f6172799204b1d7524398766c3b7a12e
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IContentVisual.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+
+import javafx.scene.Node;
+
+/**
+ * {@link IVisual} for content nodes moving freely on the diagram background.
+ * Content nodes can normally be resized and have anchorage visuals.
+ */
+public interface IContentVisual extends IVisual {
+	/**
+	 * Returns the location on the border of this content visual for the given
+	 * {@link IContentAnchorageVisual}.
+	 */
+	DiagramCoordinate getAnchorageLocation(IContentAnchorageVisual visual);
+
+	/** Returns whether the given node is the expand/collapse widget. */
+	boolean isExpandCollapseWidget(Node node);
+
+	/**
+	 * Returns whether anchorage visuals attached to this visual should be updated when this content
+	 * visual is hovered over by the mouse pointer. The default is {@code false}.
+	 * 
+	 * @see IContentAnchorageVisual#updateSiblingAnchorageVisualsOnHover()
+	 */
+	boolean updateAttachedAnchorageVisualsOnHover();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IDiagramAnchorageVisual.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IDiagramAnchorageVisual.java
new file mode 100644
index 0000000000000000000000000000000000000000..ae0895912fe735147ccd11b7f466a7a5b61c3ca5
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IDiagramAnchorageVisual.java
@@ -0,0 +1,15 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual;
+
+/** {@link IVisual} for anchorages moving freely on the diagram background. */
+public interface IDiagramAnchorageVisual extends IAnchorageVisual {
+	// marker interface
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/ILinkVisual.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/ILinkVisual.java
new file mode 100644
index 0000000000000000000000000000000000000000..8f9bd6cf7c423a78d67fab0a20ba2dad2dfbd55e
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/ILinkVisual.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+
+import javafx.scene.Node;
+
+/**
+ * Interface for visuals encapsulating a {@link Node}, which usually displays a link or connection,
+ * e.g. a Bezier curve.
+ */
+public interface ILinkVisual extends IVisual {
+	/** Bend-point index indicating the start of the link point. */
+	public static final int START_OF_LINK_BEND_POINT_INDEX = Integer.MIN_VALUE;
+	/** Bend-point index indicating the end of the link point. */
+	public static final int END_OF_LINK_BEND_POINT_INDEX = Integer.MIN_VALUE + 1;
+	/** Bend-point index indicating that no bend-point is hit. */
+	public static final int NO_BEND_POINT_INDEX = Integer.MIN_VALUE + 2;
+	/** Index cap for special bend-point indices. */
+	public static final int SPECIAL_BEND_POINT_INDEX_CAP = Integer.MIN_VALUE + 3;
+
+	/** Returns the location of the start anchor point. */
+	DiagramCoordinate getStartAnchorPoint();
+
+	/** Returns the location of the end anchor point. */
+	DiagramCoordinate getEndAnchorPoint();
+
+	/** Sets the feedback change for the bend-point at the given index. */
+	void setFeedbackChangeForBendPoint(int index, FeedbackChange change);
+
+	/**
+	 * Returns the index of the bend-point represented by the given node or
+	 * {@link #START_OF_LINK_BEND_POINT_INDEX}, if the node represents the start of the link, or
+	 * {@link #END_OF_LINK_BEND_POINT_INDEX}, if the node represents the end of the link, or
+	 * {@link #NO_BEND_POINT_INDEX}, if the node does not represent any bend point.
+	 */
+	int getBendPointIndex(Node node);
+
+	/** Returns whether the node represents a bend-point handle or the link. */
+	boolean isBendPointHandle(Node node);
+
+	/**
+	 * Returns the location of the bend point in the diagram coordinate space derived from the
+	 * current model state.
+	 */
+	DiagramCoordinate getBendPointLocation(int bpIndex);
+
+	/** Enable all feedback nodes (at least make them mouse opaque). */
+	void enableFeedback();
+
+	/** Disable all feedback nodes (at least make them mouse transparent). */
+	void disableFeedback();
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IVisual.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IVisual.java
new file mode 100644
index 0000000000000000000000000000000000000000..400569502788bf2efff3de84f2c53b65256b3fc4
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IVisual.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundlePart;
+
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Node;
+
+/**
+ * Interface for visuals (encapsulating a {@link Node}), which are created by
+ * {@link IVisualFactory} and usually represent a single model element.
+ */
+public interface IVisual extends IMVCBundlePart {
+	/**
+	 * Updates the visual's nodes possibly adding or removing nodes from the scene graph using the
+	 * diagram layers.
+	 */
+	void updateNodes(DiagramLayers layers);
+
+	/**
+	 * Removes any nodes from the layers before the {@link IMVCBundle} is removed from the diagram,
+	 * e.g., because the model element was deleted.
+	 */
+	void removeAllVisuals(DiagramLayers layers);
+
+	/** Returns whether this visual is currently visible. */
+	boolean enableVisual();
+
+	/** Returns whether this visual is currently receiving interactions. */
+	boolean enableInteraction();
+
+	/**
+	 * Returns the current bounds computed from model and feedback coordinates relative to the
+	 * diagram origin.
+	 */
+	Rectangle2D getCurrentBounds();
+
+	/**
+	 * Returns the bounds of the visual in the diagram coordinate space derived from the current
+	 * model state.
+	 */
+	Rectangle2D getModelBounds();
+
+	/**
+	 * Returns the point used to start or end the link creation line feedback using the given
+	 * location as an indication.
+	 */
+	DiagramCoordinate getLinkAnchorage(DiagramCoordinate indication);
+
+	/** Requests the focus on a specific node determined by this visual. */
+	void requestFocus();
+
+	/**
+	 * Returns the {@link EDragGesture} for the given node and diagram location.
+	 * 
+	 * @param node
+	 *            the node to be checked
+	 * @param diagramLocation
+	 *            the location on the diagram
+	 * @return the drag gesture that can be initiated at this location
+	 */
+	EDragGesture getDragGesture(Node node, DiagramCoordinate diagramLocation);
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IVisualFactory.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IVisualFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..39253dc705f7f73ddbb83d050232a19219d54e71
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/IVisualFactory.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+
+/**
+ * Interface for factories which create {@link IVisual}s from model elements for the
+ * {@link DiagramViewer}. The factory creates four types of visuals: diagram anchorage, content,
+ * content anchorage, and link visuals.
+ */
+public interface IVisualFactory {
+	/** Returns a visual node for the given content model element bundle. */
+	IContentVisual createContentVisual(IContentMVCBundle modelBundle);
+
+	/** Returns a visual node for the given diagram anchorage model element bundle. */
+	IDiagramAnchorageVisual createDiagramAnchorageVisual(IDiagramAnchorageMVCBundle modelBundle);
+
+	/**
+	 * Returns a visual node for the given content anchorage model element bundle. Sub-classes may
+	 * assume that {@code modelBundle.getAttachedTo()} points to the parent bundle.
+	 */
+	IContentAnchorageVisual createContentAnchorageVisual(IContentAnchorageMVCBundle modelBundle);
+
+	/**
+	 * Returns a visual node for the given link model element bundle. Sub-classes may assume
+	 * that {@code modelBundle.getDiagramMVCBundle()} points to the parent bundle.
+	 */
+	ILinkVisual createLinkVisual(ILinkMVCBundle modelBundle);
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/.ratings b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..46f793ff95f84ccfb94b335d55d982ac19faa129
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/.ratings
@@ -0,0 +1,6 @@
+ContentAnchorageVisualBase.java 6722629a940e9f8d973d2176bc3855932d7fa35a YELLOW
+ContentVisualBase.java b061d42fc27e980023980d070961d0814702237d YELLOW
+DiagramAnchorageVisualBase.java 05c235152bc79187f0fc9b041435da7968654a78 YELLOW
+LinkVisualBase.java 909b933b38b7651cac901d767115e173983bef26 YELLOW
+MVCBundlePartWithEffectsBase.java 6f6fbbb065950ad3acd4dc1fbfdd1348874e51d2 YELLOW
+VisualBase.java a2daf2d8f8450559450ddf5bbc93d3f948c189dd YELLOW
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/ContentAnchorageVisualBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/ContentAnchorageVisualBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..6722629a940e9f8d973d2176bc3855932d7fa35a
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/ContentAnchorageVisualBase.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers.ILayer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerFeatures;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentAnchorageVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentVisual;
+
+import javafx.geometry.Dimension2D;
+import javafx.geometry.Point2D;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Shape;
+
+/** Base implementation of {@link IContentAnchorageVisual}. */
+public abstract class ContentAnchorageVisualBase extends VisualBase
+		implements IContentAnchorageVisual {
+	/** Constructor. */
+	public ContentAnchorageVisualBase(IContentAnchorageMVCBundle mvcb, Shape visual, Shape hit) {
+		super(mvcb, visual, hit);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IContentAnchorageMVCBundle getMVCBundle() {
+		// wild cast works: see constructor
+		return (IContentAnchorageMVCBundle)super.getMVCBundle();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected ILayer getLayerForVisualShape(DiagramLayers layers) {
+		return layers.getAnchorageLayer();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected ILayer getLayerForHitShape(DiagramLayers layers) {
+		return layers.getAnchorageInteractionLayer();
+	}
+
+	/** Returns the location of the visual in its parent coordinate system. */
+	protected Point2D getLocationOnParent() {
+		IContentVisual cv = getMVCBundle().getAttachedTo().getVisual();
+		return cv.getAnchorageLocation(this);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Rectangle2D getCurrentBounds() {
+		Rectangle2D parentBounds = getMVCBundle().getAttachedTo().getVisual().getCurrentBounds();
+		Point2D modelLocationOnParent = getLocationOnParent();
+		Dimension2D size = getDimensions();
+		FeedbackChange chg = getFeedbackChange();
+		double x = parentBounds.getMinX() + modelLocationOnParent.getX() + chg.getDeltaX();
+		double y = parentBounds.getMinY() + modelLocationOnParent.getY() + chg.getDeltaY();
+		return new Rectangle2D(x, y, size.getWidth(), size.getHeight());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Dimension2D getDimensions() {
+		DiagramViewerFeatures features = getViewer().getFeatures();
+		return new Dimension2D(features.getHorizontalSpacing(), features.getVerticalSpacing());
+	}
+
+	/** Returns the color to be used when the port is highlighted as incoming port. */
+	protected Paint getHighlightIncomingLinkColor() {
+		return Color.GREENYELLOW;
+	}
+
+	/** Returns the color to be used when the port is highlighted as outgoing port. */
+	protected Paint getHighlightOutgoingLinkColor() {
+		return Color.TOMATO;
+	}
+
+	/** Returns the color of the border to be used when the port is highlighted as incoming port. */
+	protected Paint getHighlightIncomingLinkBorderColor() {
+		return Color.GREENYELLOW.darker();
+	}
+
+	/** Returns the color of the border to be used when the port is highlighted as outgoing port. */
+	protected Paint getHighlightOutgoingLinkBorderColor() {
+		return Color.TOMATO.darker();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean updateSiblingAnchorageVisualsOnHover() {
+		return false;
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/ContentVisualBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/ContentVisualBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..b061d42fc27e980023980d070961d0814702237d
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/ContentVisualBase.java
@@ -0,0 +1,280 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.HIGHLIGHT_INCOMING_LINK_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.HIGHLIGHT_OUTGOING_LINK_TAG;
+
+import java.util.List;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.MVCBundleTag;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.widgets.ExpandCollapseWidget;
+
+import javafx.geometry.Insets;
+import javafx.geometry.Rectangle2D;
+import javafx.geometry.VPos;
+import javafx.scene.Node;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.shape.Shape;
+import javafx.scene.text.Text;
+import javafx.scene.text.TextAlignment;
+
+/** Base implementation for {@link IContentVisual}. */
+public abstract class ContentVisualBase extends VisualBase implements IContentVisual {
+	/** The text of this visual. */
+	protected final Text text = new Text();
+	/** The icon of this visual. */
+	protected final ImageView icon = new ImageView();
+	/** The expanded / collapsed indicator widget. */
+	protected final ExpandCollapseWidget expandCollapseWidget =
+			new ExpandCollapseWidget(0, 0, 20, 20, 3);
+
+	/** Constructor. */
+	public ContentVisualBase(IContentMVCBundle mvcb, Shape visual, Shape hit) {
+		super(mvcb, visual, hit);
+	}
+
+	/** Determines whether the icon is enabled or not. */
+	protected boolean enableIcon() {
+		return true;
+	}
+
+	/** Determines whether the text of this visual is enabled or not. */
+	protected boolean enableText() {
+		return true;
+	}
+
+	/** Determines whether the expand/collapse widget of this visual is enabled or not. */
+	protected boolean enableExpandCollapseWidget() {
+		return false;
+	}
+
+	/** Returns the name of the content. */
+	protected String getName() {
+		return "";
+	}
+
+	/** Returns the anchor location relative to the parent bounds. */
+	protected DiagramCoordinate getTextAnchorLocation() {
+		Insets i = getTextInsets();
+		double croppedHeight = getCurrentBounds().getHeight() - i.getTop() - i.getBottom();
+		return new DiagramCoordinate(0, croppedHeight / 2);
+	}
+
+	/** Returns the insets of the text label. */
+	protected Insets getTextInsets() {
+		return new Insets(10, 10, 10, 10);
+	}
+
+	/** Returns the vertical alignment of the text label. */
+	protected VPos getVerticalTextAlignment() {
+		return VPos.CENTER;
+	}
+
+	/** Returns the horizontal alignment of the text label. */
+	protected TextAlignment getHorizontalTextAlignment() {
+		return TextAlignment.LEFT;
+	}
+
+	/**
+	 * Returns the icon image for the content. Sub-classes should use an image cache instead of
+	 * creating a new image every time this method is called.
+	 */
+	protected Image getIcon() {
+		return null;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public final void applySelectedFocusedEffect(DiagramLayers layers) {
+		super.applySelectedFocusedEffect(layers);
+
+		if(getViewer().getFeatures().isLinkHighlightingEnabled()) {
+			IContentMVCBundle bundle = getMVCBundle();
+			List<IContentAnchorageMVCBundle> anchoragesBundles = bundle.getAnchorages();
+			for(IContentAnchorageMVCBundle aBundle : anchoragesBundles) {
+				List<ILinkMVCBundle> incomingLinksBundles = aBundle.getIncomingLinks();
+				for(ILinkMVCBundle ilBundle : incomingLinksBundles) {
+					addTag(ilBundle, HIGHLIGHT_INCOMING_LINK_TAG, layers);
+					addTag(ilBundle.getStartAnchorage(), HIGHLIGHT_INCOMING_LINK_TAG, layers);
+					addTag(ilBundle.getEndAnchorage(), HIGHLIGHT_INCOMING_LINK_TAG, layers);
+				}
+				List<ILinkMVCBundle> outgoingLinksBundles = aBundle.getOutgoingLinks();
+				for(ILinkMVCBundle olBundle : outgoingLinksBundles) {
+					addTag(olBundle, HIGHLIGHT_OUTGOING_LINK_TAG, layers);
+					addTag(olBundle.getEndAnchorage(), HIGHLIGHT_OUTGOING_LINK_TAG, layers);
+					addTag(olBundle.getStartAnchorage(), HIGHLIGHT_OUTGOING_LINK_TAG, layers);
+				}
+			}
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected void applyNotSelectedEffect(DiagramLayers layers) {
+		super.applyNotSelectedEffect(layers);
+
+		IContentMVCBundle bundle = getMVCBundle();
+		List<IContentAnchorageMVCBundle> anchoragesBundles = bundle.getAnchorages();
+		for(IContentAnchorageMVCBundle aBundle : anchoragesBundles) {
+			List<ILinkMVCBundle> incomingLinksBundles = aBundle.getIncomingLinks();
+			for(ILinkMVCBundle ilBundle : incomingLinksBundles) {
+				removeTag(ilBundle, HIGHLIGHT_INCOMING_LINK_TAG, layers);
+				removeTag(ilBundle.getStartAnchorage(), HIGHLIGHT_INCOMING_LINK_TAG, layers);
+				removeTag(ilBundle.getEndAnchorage(), HIGHLIGHT_INCOMING_LINK_TAG, layers);
+			}
+			List<ILinkMVCBundle> outgoingLinksBundles = aBundle.getOutgoingLinks();
+			for(ILinkMVCBundle olBundle : outgoingLinksBundles) {
+				removeTag(olBundle, HIGHLIGHT_OUTGOING_LINK_TAG, layers);
+				removeTag(olBundle.getEndAnchorage(), HIGHLIGHT_OUTGOING_LINK_TAG, layers);
+				removeTag(olBundle.getStartAnchorage(), HIGHLIGHT_OUTGOING_LINK_TAG, layers);
+			}
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Rectangle2D getCurrentBounds() {
+		FeedbackChange fb = getFeedbackChange();
+		Rectangle2D mb = getModelBounds();
+		return fb.applyTo(mb);
+	}
+
+	/** Adds a tag for the given bundle and updates the visual. */
+	private void addTag(IMVCBundle bundle, MVCBundleTag tag, DiagramLayers layers) {
+		bundle.addTag(tag);
+		bundle.getVisual().updateNodes(layers);
+	}
+
+	/** Removes a tag from the given bundle and updates the visual. */
+	private void removeTag(IMVCBundle bundle, MVCBundleTag tag, DiagramLayers layers) {
+		bundle.removeTag(tag);
+		bundle.getVisual().updateNodes(layers);
+	}
+
+	/**
+	 * {@inheritDoc}
+	 * <p>
+	 * Sub-classes may override, but must call super implementation to update children properly.
+	 */
+	@Override
+	public void updateNodes(DiagramLayers layers) {
+		super.updateNodes(layers);
+
+		IContentMVCBundle bundle = getMVCBundle();
+		Rectangle2D bounds = getCurrentBounds();
+		double leftX = bounds.getMinX();
+		double rightX = bounds.getMaxX();
+		double upperY = bounds.getMinY();
+
+		Insets i = getTextInsets();
+		DiagramCoordinate anchorLocation = getTextAnchorLocation();
+		double anchorX = leftX + anchorLocation.getX() + i.getLeft();
+		double anchorY = upperY + anchorLocation.getY() + i.getTop();
+
+		if(enableIcon()) {
+			if(icon.getParent() == null) {
+				layers.getTextLayer().add(icon, bundle);
+			}
+			Image img = getIcon();
+			icon.setImage(img);
+			if(img != null) {
+				icon.setX(anchorX);
+				anchorX += img.getWidth() + i.getLeft();
+				icon.setY(anchorY - img.getHeight() / 2);
+			}
+		} else {
+			layers.getTextLayer().remove(icon);
+		}
+
+		if(enableText()) {
+			if(text.getParent() == null) {
+				layers.getTextLayer().add(text, bundle);
+			}
+			text.setX(anchorX);
+			text.setY(anchorY);
+			text.setText(getName());
+			text.setWrappingWidth(rightX - anchorX - i.getRight());
+			text.setTextAlignment(getHorizontalTextAlignment());
+			text.setTextOrigin(getVerticalTextAlignment());
+		} else {
+			layers.getTextLayer().remove(text);
+		}
+
+		if(enableExpandCollapseWidget()) {
+			if(expandCollapseWidget.getParent() == null) {
+				layers.getContentInteractionLayer().add(expandCollapseWidget, bundle);
+			}
+			Rectangle2D ecWidgetLocation = getExpandCollapseWidgetRectangle();
+			double inset = getExpandCollapseWidgetInset();
+			double x = leftX + ecWidgetLocation.getMinX();
+			double y = upperY + ecWidgetLocation.getMinY();
+			expandCollapseWidget.update(x, y, ecWidgetLocation.getWidth(),
+					ecWidgetLocation.getHeight(), inset);
+			expandCollapseWidget.setState(getExpandCollapseWidgetState());
+		} else {
+			layers.getContentInteractionLayer().remove(expandCollapseWidget);
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void removeAllVisuals(DiagramLayers layers) {
+		super.removeAllVisuals(layers);
+
+		layers.getContentInteractionLayer().remove(expandCollapseWidget);
+		layers.getTextLayer().remove(text);
+		layers.getTextLayer().remove(icon);
+	}
+
+	/** Returns the current state of the expand/collapse widget. */
+	protected boolean getExpandCollapseWidgetState() {
+		return true;
+	}
+
+	/** Returns the inset for the plus/minus sign of the expand/collapse widget. */
+	protected double getExpandCollapseWidgetInset() {
+		return 3;
+	}
+
+	/** Returns the location for the expand/collapse widget. */
+	protected Rectangle2D getExpandCollapseWidgetRectangle() {
+		Rectangle2D b = getCurrentBounds();
+		return new Rectangle2D(b.getWidth() - 20, 6, 14, 14);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IContentMVCBundle getMVCBundle() {
+		// wild cast works: see constructor
+		return (IContentMVCBundle)super.getMVCBundle();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean isExpandCollapseWidget(Node widget) {
+		return expandCollapseWidget.isHit(widget);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean updateAttachedAnchorageVisualsOnHover() {
+		return false;
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/DiagramAnchorageVisualBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/DiagramAnchorageVisualBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..05c235152bc79187f0fc9b041435da7968654a78
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/DiagramAnchorageVisualBase.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IDiagramAnchorageVisual;
+
+import javafx.geometry.Rectangle2D;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Shape;
+
+/** Base implementation of {@link IDiagramAnchorageVisual}. */
+public abstract class DiagramAnchorageVisualBase extends VisualBase
+		implements IDiagramAnchorageVisual {
+	/** Constructor. */
+	public DiagramAnchorageVisualBase(IDiagramAnchorageMVCBundle mvcb, Shape visual, Shape hit) {
+		super(mvcb, visual, hit);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IDiagramAnchorageMVCBundle getMVCBundle() {
+		// wild cast works: see constructor
+		return (IDiagramAnchorageMVCBundle)super.getMVCBundle();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Rectangle2D getCurrentBounds() {
+		FeedbackChange fb = getMVCBundle().getFeedbackChange();
+		Rectangle2D mb = getModelBounds();
+		return fb.applyTo(mb);
+	}
+
+	/** Returns the color to be used when the port is highlighted as incoming port. */
+	protected Paint getHighlightIncomingLinkColor() {
+		return Color.GREENYELLOW;
+	}
+
+	/** Returns the color to be used when the port is highlighted as outgoing port. */
+	protected Paint getHighlightOutgoingLinkColor() {
+		return Color.TOMATO;
+	}
+
+	/** Returns the color of the border to be used when the port is highlighted as incoming port. */
+	protected Paint getHighlightIncomingLinkBorderColor() {
+		return Color.GREENYELLOW.darker();
+	}
+
+	/** Returns the color of the border to be used when the port is highlighted as outgoing port. */
+	protected Paint getHighlightOutgoingLinkBorderColor() {
+		return Color.TOMATO.darker();
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/LinkVisualBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/LinkVisualBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..909b933b38b7651cac901d767115e173983bef26
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/LinkVisualBase.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base;
+
+import static java.lang.Math.abs;
+import static java.lang.Math.atan2;
+import static java.lang.Math.cos;
+import static java.lang.Math.sin;
+import static javafx.scene.paint.Color.RED;
+import static javafx.scene.paint.Color.TRANSPARENT;
+import static javafx.scene.paint.Color.color;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.ILinkVisual;
+
+import javafx.geometry.Point2D;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Node;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Rectangle;
+
+/** Base implementation for {@link ILinkVisual}. */
+public abstract class LinkVisualBase extends MVCBundlePartWithEffectsBase implements ILinkVisual {
+	/** Stores the feedback change for each bend point. */
+	protected final Map<Integer, FeedbackChange> feedbackChanges = new HashMap<>();
+	/** The selection feedback handle rectangle for the start of the line link. */
+	protected final Rectangle startFeedbackHandle;
+	/** The selection feedback handle rectangle for the end of the line link. */
+	protected final Rectangle endFeedbackHandle;
+
+	/** Constructor. */
+	public LinkVisualBase(ILinkMVCBundle mvcb) {
+		super(mvcb);
+		double feedbackSize = getFeedbackMarkerSize();
+		double fs2 = feedbackSize / 2;
+		Point2D startAnchorPoint = getStartAnchorPoint();
+		startFeedbackHandle = new Rectangle(startAnchorPoint.getX() - fs2,
+				startAnchorPoint.getY() - fs2, feedbackSize, feedbackSize);
+		startFeedbackHandle.setFill(TRANSPARENT);
+		startFeedbackHandle.setStroke(RED);
+		Point2D endAnchorPoint = getEndAnchorPoint();
+		endFeedbackHandle = new Rectangle(endAnchorPoint.getX() - fs2, endAnchorPoint.getY() - fs2,
+				feedbackSize, feedbackSize);
+		endFeedbackHandle.setFill(TRANSPARENT);
+		endFeedbackHandle.setStroke(RED);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public final Rectangle2D getModelBounds() {
+		// links do not need model bounds
+		return null;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void setFeedbackChangeForBendPoint(int index, FeedbackChange change) {
+		if(change == null) {
+			feedbackChanges.remove(index);
+		} else {
+			feedbackChanges.put(index, change);
+		}
+		getViewer().updateVisual(getMVCBundle());
+	}
+
+	/**
+	 * Returns the current {@link FeedbackChange} for the bend-point at the given index. If there is
+	 * no entry at the given index then {@link FeedbackChange#EMPTY} is returned.
+	 */
+	protected FeedbackChange getFeedbackChangeForBendPoint(int index) {
+		FeedbackChange result = feedbackChanges.get(index);
+		if(result == null) {
+			return FeedbackChange.EMPTY;
+		}
+		return result;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate getLinkAnchorage(DiagramCoordinate indication) {
+		return indication;
+	}
+
+	/** Returns the color to be used when the connection is highlighted as incoming link. */
+	protected Paint getHighlightIncomingLinkColor() {
+		return Color.GREENYELLOW;
+	}
+
+	/** Returns the color to be used when the connection is highlighted as outgoing link. */
+	protected Paint getHighlightOutgoingLinkColor() {
+		return Color.TOMATO;
+	}
+
+	/**
+	 * Returns the point on a circle centered in the current bounds intersecting with a line between
+	 * the center point and the given point.
+	 */
+	protected DiagramCoordinate getCircleLocation(Rectangle2D bounds, Point2D target) {
+		double sx = bounds.getMinX() + bounds.getWidth() / 2;
+		double sy = bounds.getMinY() + bounds.getHeight() / 2;
+
+		double a = atan2(target.getY() - sy, target.getX() - sx);
+		double nx = sx + cos(a) * (bounds.getWidth() / 2 - 1);
+		double ny = sy + sin(a) * (bounds.getHeight() / 2 - 1);
+		return new DiagramCoordinate(nx, ny);
+	}
+
+	/** Returns the middle point of the rectangle border closest to the target point. */
+	protected final DiagramCoordinate getRectangleLocation(Rectangle2D ib, Point2D target) {
+		double cx = ib.getMinX() + ib.getWidth() / 2;
+		double cy = ib.getMinY() + ib.getHeight() / 2;
+		double dx = cx - target.getX();
+		double dy = cy - target.getY();
+		if(abs(dx) > abs(dy)) {
+			if(cx < target.getX()) {
+				return new DiagramCoordinate(ib.getMaxX(), cy);
+			}
+			return new DiagramCoordinate(ib.getMinX(), cy);
+		}
+		if(cy < target.getY()) {
+			return new DiagramCoordinate(cx, ib.getMaxY());
+		}
+		return new DiagramCoordinate(cx, ib.getMinY());
+	}
+
+	/** The interaction shading color. */
+	private static final Color INTERACTION_SHADING = color(0, 0, 0, .1);
+
+	/** Returns the color for the interaction area. */
+	protected final Paint getInteractionColor() {
+		return getViewer().getFeatures().isInteractionAreaShadingEnabled() ? INTERACTION_SHADING
+				: Color.TRANSPARENT;
+	}
+
+	/** Returns the size of the feedback markers. */
+	protected double getFeedbackMarkerSize() {
+		return getMVCBundle().getViewer().getFeatures().getHorizontalSpacing() / 2;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public int getBendPointIndex(Node node) {
+		if(node == startFeedbackHandle) {
+			return START_OF_LINK_BEND_POINT_INDEX;
+		}
+		if(node == endFeedbackHandle) {
+			return END_OF_LINK_BEND_POINT_INDEX;
+		}
+		return NO_BEND_POINT_INDEX;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean isBendPointHandle(Node node) {
+		return node == startFeedbackHandle || node == endFeedbackHandle;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void disableFeedback() {
+		startFeedbackHandle.setMouseTransparent(true);
+		endFeedbackHandle.setMouseTransparent(true);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void enableFeedback() {
+		startFeedbackHandle.setMouseTransparent(false);
+		endFeedbackHandle.setMouseTransparent(false);
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/MVCBundlePartWithEffectsBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/MVCBundlePartWithEffectsBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..6f6fbbb065950ad3acd4dc1fbfdd1348874e51d2
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/MVCBundlePartWithEffectsBase.java
@@ -0,0 +1,173 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.FOCUS_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.HOVER_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_ALLOWED_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_DENIED_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.LINK_TARGET_MAYBE_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.PRIMARY_SELECTION_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.SECONDARY_SELECTION_TAG;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl.MVCBundlePartBase;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisual;
+
+/** {@link MVCBundlePartBase} base class extended with effect methods from {@link IVisual}. */
+public abstract class MVCBundlePartWithEffectsBase extends MVCBundlePartBase implements IVisual {
+	/** Constructor. */
+	public MVCBundlePartWithEffectsBase(IMVCBundle mvcb) {
+		super(mvcb);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void updateNodes(DiagramLayers layers) {
+		IMVCBundle bundle = getMVCBundle();
+		// selection and focus effects
+		if(bundle.hasTag(PRIMARY_SELECTION_TAG)) {
+			if(bundle.hasTag(FOCUS_TAG)) {
+				applySelectedFocusedEffect(layers);
+			} else {
+				applySelectedNotFocusedEffect(layers);
+			}
+		} else if(bundle.hasTag(SECONDARY_SELECTION_TAG)) {
+			applySecondarySelectedEffect(layers);
+		} else {
+			applyNotSelectedEffect(layers);
+		}
+		// hover effects
+		if(bundle.hasTag(HOVER_TAG)) {
+			createHoverEffect(layers);
+		} else {
+			removeHoverEffect(layers);
+		}
+		// link target effects
+		if(bundle.hasTag(LINK_TARGET_ALLOWED_TAG)) {
+			createLinkTargetAllowedEffect(layers);
+		} else if(bundle.hasTag(LINK_TARGET_DENIED_TAG)) {
+			createLinkTargetDeniedEffect(layers);
+		} else if(bundle.hasTag(LINK_TARGET_MAYBE_TAG)) {
+			createLinkTargetMaybeEffect(layers);
+		} else {
+			createLinkTargetDisabledEffect(layers);
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void removeAllVisuals(DiagramLayers layers) {
+		applyNotSelectedEffect(layers);
+		removeHoverEffect(layers);
+		createLinkTargetDisabledEffect(layers);
+	}
+
+	/**
+	 * Applies the effect when not selected and not focused.
+	 * 
+	 * @param layers
+	 *            the layers of the diagram
+	 */
+	protected void applyNotSelectedEffect(DiagramLayers layers) {
+		// the default does nothing
+	}
+
+	/**
+	 * Applies the effect when selected and focused.
+	 * 
+	 * @param layers
+	 *            the layers of the diagram
+	 */
+	protected void applySelectedFocusedEffect(DiagramLayers layers) {
+		// the default does nothing
+	}
+
+	/**
+	 * Applies the effect when selected, but not focused.
+	 * 
+	 * @param layers
+	 *            the layers of the diagram
+	 */
+	protected void applySelectedNotFocusedEffect(DiagramLayers layers) {
+		// the default does nothing
+	}
+
+	/**
+	 * Applies the effect when secondary selected.
+	 * 
+	 * @param layers
+	 *            the layers of the diagram
+	 */
+	protected void applySecondarySelectedEffect(DiagramLayers layers) {
+		// the default does nothing
+	}
+
+	/**
+	 * Creates the hover effect.
+	 * 
+	 * @param layers
+	 *            the layers of the diagram
+	 */
+	protected void createHoverEffect(DiagramLayers layers) {
+		// the default does nothing
+	}
+
+	/**
+	 * Removes the hover effect.
+	 * 
+	 * @param layers
+	 *            the layers of the diagram
+	 */
+	protected void removeHoverEffect(DiagramLayers layers) {
+		// the default does nothing
+	}
+
+	/**
+	 * Creates the link target allowed effect.
+	 * 
+	 * @param layers
+	 *            the layers of the diagram
+	 */
+	protected void createLinkTargetAllowedEffect(DiagramLayers layers) {
+		// do nothing
+	}
+
+	/**
+	 * Creates the link target denied effect.
+	 * 
+	 * @param layers
+	 *            the layers of the diagram
+	 */
+	protected void createLinkTargetDeniedEffect(DiagramLayers layers) {
+		// do nothing
+	}
+
+	/**
+	 * Creates the link target maybe effect.
+	 * 
+	 * @param layers
+	 *            the layers of the diagram
+	 */
+	protected void createLinkTargetMaybeEffect(DiagramLayers layers) {
+		// do nothing
+	}
+
+	/**
+	 * Creates the link target disabled effect.
+	 * 
+	 * @param layers
+	 *            the layers of the diagram
+	 */
+	protected void createLinkTargetDisabledEffect(DiagramLayers layers) {
+		// do nothing
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/VisualBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/VisualBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..a2daf2d8f8450559450ddf5bbc93d3f948c189dd
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/base/VisualBase.java
@@ -0,0 +1,369 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base;
+
+import static javafx.scene.paint.Color.CORNFLOWERBLUE;
+import static javafx.scene.paint.Color.DEEPSKYBLUE;
+import static javafx.scene.paint.Color.ORANGE;
+import static javafx.scene.paint.Color.RED;
+import static javafx.scene.paint.Color.TRANSPARENT;
+import static javafx.scene.paint.Color.color;
+import static javafx.scene.shape.StrokeType.INSIDE;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture.MOVE;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture.NEW_LINK;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture.NONE;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture.RESIZE_H;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture.RESIZE_V;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture.RESIZE_VH;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers.ILayer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisual;
+
+import javafx.geometry.Point2D;
+import javafx.geometry.Rectangle2D;
+import javafx.geometry.VPos;
+import javafx.scene.Node;
+import javafx.scene.effect.DropShadow;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.shape.Shape;
+import javafx.scene.shape.StrokeType;
+import javafx.scene.text.Text;
+import javafx.scene.text.TextAlignment;
+
+/**
+ * Base class implementing the {@link IVisual} interface.
+ * <P>
+ * This implementation provides a simple {@link DropShadow} hover effect and a hover label.
+ */
+public abstract class VisualBase extends MVCBundlePartWithEffectsBase implements IVisual {
+	/** The visible shape of this visual. */
+	private final Shape visualShape;
+	/** The invisible hit area of this visual. */
+	private final Shape hitAreaShape;
+	/** The selection rectangle. */
+	protected final Rectangle selectionRectangle = new Rectangle();
+	/** The hover text label. */
+	private final Text hoverText = new Text("");
+
+	/** Constructor. */
+	public VisualBase(IMVCBundle mvcb, Shape visual, Shape hit) {
+		super(mvcb);
+		this.visualShape = visual;
+		this.hitAreaShape = hit;
+		hitAreaShape.setFill(TRANSPARENT);
+		hitAreaShape.setStroke(TRANSPARENT);
+		selectionRectangle.setFill(TRANSPARENT);
+		selectionRectangle.setStrokeWidth(2);
+		hoverText.setMouseTransparent(true);
+	}
+
+	/** Returns the hit area shape. */
+	protected final Shape getHitAreaShape() {
+		return hitAreaShape;
+	}
+
+	/** Returns visual shape. */
+	protected final Shape getVisualShape() {
+		return visualShape;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected void createHoverEffect(DiagramLayers layers) {
+		Color shadow = getHoverShadowColor();
+		if(shadow != null && visualShape != null) {
+			visualShape.setEffect(new DropShadow(10, 3, 3, shadow));
+		}
+		// handle hover text
+		String text = getHoverText();
+		if(text == null) {
+			return;
+		}
+		hoverText.setText(text);
+		Point2D loc = getHoverTextLocation();
+		Rectangle2D bounds = getCurrentBounds();
+		double tx = bounds.getMinX() + loc.getX();
+		hoverText.setX(tx);
+		double ty = bounds.getMinY() + loc.getY();
+		hoverText.setY(ty);
+		hoverText.setTextAlignment(TextAlignment.LEFT);
+		hoverText.setTextOrigin(VPos.TOP);
+		hoverText.setMouseTransparent(true);
+		layers.getVisualFeedbackLayer().add(hoverText, getMVCBundle());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected void removeHoverEffect(DiagramLayers layers) {
+		if(visualShape != null) {
+			visualShape.setEffect(null);
+		}
+		layers.getVisualFeedbackLayer().remove(hoverText);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void removeAllVisuals(DiagramLayers layers) {
+		super.removeAllVisuals(layers);
+
+		getLayerForHitShape(layers).remove(hitAreaShape);
+		getLayerForVisualShape(layers).remove(visualShape);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void updateNodes(DiagramLayers layers) {
+		super.updateNodes(layers);
+
+		IMVCBundle bundle = getMVCBundle();
+		if(visualShape != null) {
+			if(enableVisual()) {
+				if(visualShape.getParent() == null) {
+					getLayerForVisualShape(layers).add(visualShape, bundle);
+				}
+			} else {
+				getLayerForVisualShape(layers).remove(visualShape);
+			}
+		}
+		if(hitAreaShape != null) {
+			if(enableInteraction()) {
+				if(hitAreaShape.getParent() == null) {
+					getLayerForHitShape(layers).add(hitAreaShape, bundle);
+				}
+			} else {
+				getLayerForHitShape(layers).remove(hitAreaShape);
+			}
+		}
+	}
+
+	/**
+	 * Returns the layer to be used for the {@link #getVisualShape() visual shape}. The default is
+	 * {@link DiagramLayers#getContentLayer()}
+	 */
+	protected ILayer getLayerForVisualShape(DiagramLayers layers) {
+		return layers.getContentLayer();
+	}
+
+	/**
+	 * Returns the layer to be used for the {@link #getHitAreaShape() hit area shape}. The default
+	 * is {@link DiagramLayers#getContentInteractionLayer()}
+	 */
+	protected ILayer getLayerForHitShape(DiagramLayers layers) {
+		return layers.getContentInteractionLayer();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean enableVisual() {
+		return true;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean enableInteraction() {
+		return true;
+	}
+
+	/** Updates and adds the selection feedback rectangle. */
+	protected void updateSelectionFeedbackRectangle(Paint color, DiagramLayers layers) {
+		Rectangle2D bounds = getCurrentBounds();
+		if(bounds == null) {
+			return;
+		}
+		selectionRectangle.setX(bounds.getMinX());
+		selectionRectangle.setY(bounds.getMinY());
+		selectionRectangle.setWidth(bounds.getWidth());
+		selectionRectangle.setHeight(bounds.getHeight());
+		selectionRectangle.setStroke(color);
+		layers.getVisualFeedbackLayer().add(selectionRectangle, getMVCBundle());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected void applySelectedNotFocusedEffect(DiagramLayers layers) {
+		updateSelectionFeedbackRectangle(getSelectedNotFocusedColor(), layers);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected void applySecondarySelectedEffect(DiagramLayers layers) {
+		updateSelectionFeedbackRectangle(getSecondarySelectedColor(), layers);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected void applySelectedFocusedEffect(DiagramLayers layers) {
+		updateSelectionFeedbackRectangle(getSelectedFocusedColor(), layers);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected void applyNotSelectedEffect(DiagramLayers layers) {
+		super.applyNotSelectedEffect(layers);
+		layers.getVisualFeedbackLayer().remove(selectionRectangle);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public EDragGesture getDragGesture(Node node, DiagramCoordinate diagramLocation) {
+		boolean notSelected = !getViewer().isPrimarySelected(getMVCBundle());
+		EDragGesture gesture = dragGestureHitTest(node, diagramLocation);
+		if(gesture == NEW_LINK && requireSelectionForNewLinkGesture() && notSelected) {
+			// override when not selected
+			return NONE;
+		}
+		if(gesture == MOVE && requireSelectionForMoveGesture() && notSelected) {
+			// override when not selected
+			return NONE;
+		}
+		if(gesture == RESIZE_H || gesture == RESIZE_V || gesture == RESIZE_VH) {
+			if(requireSelectionForResizeGesture() && notSelected) {
+				// override when not selected
+				return NONE;
+			}
+		}
+		return gesture;
+	}
+
+	/**
+	 * Performs a hit test on the given node returning the expected gesture. Note that the result
+	 * may be overridden if the node is not selected and
+	 * {@link #requireSelectionForNewLinkGesture()} or {@link #requireSelectionForMoveGesture()}
+	 * return {@code true}, respectively. The default returns {@link EDragGesture#NONE}.
+	 * 
+	 * @param node
+	 *            the node currently under the mouse pointer
+	 * @param diagramLocation
+	 *            the location in diagram coordinates
+	 * @return the drag gesture
+	 */
+	protected EDragGesture dragGestureHitTest(Node node, DiagramCoordinate diagramLocation) {
+		return NONE;
+	}
+
+	/**
+	 * Returns whether the visual must be selected when creating links is allowed. The default is
+	 * {@code false}.
+	 */
+	protected boolean requireSelectionForNewLinkGesture() {
+		return false;
+	}
+
+	/**
+	 * Returns whether the visual must be selected when moving is allowed. The default is
+	 * {@code true}.
+	 */
+	protected boolean requireSelectionForMoveGesture() {
+		return true;
+	}
+
+	/**
+	 * Returns whether the visual must be selected when resizing is allowed. The default is
+	 * {@code true}.
+	 */
+	protected boolean requireSelectionForResizeGesture() {
+		return true;
+	}
+
+	/** Returns the opacity of the content visual. */
+	protected double getOpacity() {
+		return 0.8;
+	}
+
+	/** Returns the color used to paint the border of the visual. */
+	protected Paint getBorderColor() {
+		return CORNFLOWERBLUE;
+	}
+
+	/** Returns the width of the border. */
+	protected double getBorderWidth() {
+		return 2;
+	}
+
+	/** Returns the type of the border. */
+	protected StrokeType getBorderType() {
+		return INSIDE;
+	}
+
+	/** Returns the color for the interior of the visual. */
+	protected Paint getFillColor() {
+		return DEEPSKYBLUE;
+	}
+
+	/** Returns the color of the selection rectangle if it also has focus. */
+	protected Color getSelectedFocusedColor() {
+		return RED;
+	}
+
+	/** Returns the color of the selection rectangle if it part of a secondary selection. */
+	protected Color getSecondarySelectedColor() {
+		return ORANGE;
+	}
+
+	/** Returns the color of the selection rectangle if it does not have focus. */
+	protected Color getSelectedNotFocusedColor() {
+		return RED;
+	}
+
+	/** Returns the size of the hit area as an outset of the visible rectangle. */
+	protected double getHitAreaOutset() {
+		return 10;
+	}
+
+	/** The interaction shading color. */
+	protected static final Color INTERACTION_SHADING = color(0, 0, 0, .1);
+
+	/** Returns the color for the interaction area. */
+	protected Color getInteractionColor() {
+		return getViewer().getFeatures().isInteractionAreaShadingEnabled() ? INTERACTION_SHADING
+				: Color.TRANSPARENT;
+	}
+
+	/** Returns the color of the hover drop shadow. */
+	protected Color getHoverShadowColor() {
+		return Color.DARKGRAY.darker();
+	}
+
+	/** Returns the hover text. */
+	protected String getHoverText() {
+		return null;
+	}
+
+	/** Returns the hover text location . */
+	protected Point2D getHoverTextLocation() {
+		return new Point2D(0, getCurrentBounds().getHeight());
+	}
+
+	/** Returns the center point of the current bounds. */
+	protected final DiagramCoordinate getCurrentBoundsCenter() {
+		Rectangle2D b = getVisual().getCurrentBounds();
+		double x = b.getMinX() + b.getWidth() / 2;
+		double y = b.getMinY() + b.getHeight() / 2;
+		return new DiagramCoordinate(x, y);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate getLinkAnchorage(DiagramCoordinate indication) {
+		return getCurrentBoundsCenter();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void requestFocus() {
+		getVisualShape().requestFocus();
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/.ratings b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..c2c5235bd3b9deb31fdbf82757057360aa46d36b
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/.ratings
@@ -0,0 +1,7 @@
+CircularContentAnchorageVisualBase.java de227ce8a2a14eb4df7bdcf43b82d98f6ff17045 YELLOW
+CircularContentVisualBase.java cc3caea328e36e90069b915e413c8e7e9522a939 YELLOW
+CircularDiagramAnchorageVisualBase.java 7a3b92fb1b135c218b9a5e16506acfc74a6b5468 YELLOW
+CurveLinkVisualBase.java 0b8706214320d715966c86a5242ad21c8bf5a315 YELLOW
+CurveSegment.java 445bc2607cb70ae1c788c27ba9fc637dd7df4956 YELLOW
+EllipticBorderLocation.java 1e9b3d42c7fcd5495004fb30b0c499096a839967 YELLOW
+EllipticContentVisualBase.java dc2fddc9cfe5605bc8a5d09dd862845e360b23f5 YELLOW
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/CircularContentAnchorageVisualBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/CircularContentAnchorageVisualBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..de227ce8a2a14eb4df7bdcf43b82d98f6ff17045
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/CircularContentAnchorageVisualBase.java
@@ -0,0 +1,124 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.elliptic;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.HIGHLIGHT_INCOMING_LINK_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.HIGHLIGHT_OUTGOING_LINK_TAG;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentAnchorageVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base.ContentAnchorageVisualBase;
+
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Node;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Circle;
+
+/** Base class for {@link IContentAnchorageVisual}s depicted as circles. */
+public abstract class CircularContentAnchorageVisualBase extends ContentAnchorageVisualBase {
+	/** Constructor. */
+	public CircularContentAnchorageVisualBase(IContentAnchorageMVCBundle mvcb) {
+		super(mvcb, new Circle(), new Circle());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void updateNodes(DiagramLayers layers) {
+		super.updateNodes(layers);
+		updateCircleProperties();
+	}
+
+	/** Sets all properties of the main node. */
+	private void updateCircleProperties() {
+		Rectangle2D b = getCurrentBounds();
+		if(enableVisual()) {
+			Circle circle = getVisualCircle();
+			circle.setCenterX(b.getMinX() + b.getWidth() / 2);
+			circle.setCenterY(b.getMinY() + b.getHeight() / 2);
+			circle.setRadius(getInnerRadius(b));
+			circle.setOpacity(getOpacity());
+			circle.setStrokeWidth(getBorderWidth());
+			circle.setStrokeType(getBorderType());
+			if(getMVCBundle().hasTag(HIGHLIGHT_INCOMING_LINK_TAG)) {
+				circle.setFill(getHighlightIncomingLinkColor());
+				circle.setStroke(getHighlightIncomingLinkBorderColor());
+			} else if(getMVCBundle().hasTag(HIGHLIGHT_OUTGOING_LINK_TAG)) {
+				circle.setFill(getHighlightOutgoingLinkColor());
+				circle.setStroke(getHighlightOutgoingLinkBorderColor());
+			} else {
+				circle.setFill(getFillColor());
+				circle.setStroke(getBorderColor());
+			}
+		}
+
+		if(enableInteraction()) {
+			Circle ha = getHitAreaCircle();
+			ha.setCenterX(b.getMinX() + b.getWidth() / 2);
+			ha.setCenterY(b.getMinY() + b.getHeight() / 2);
+			ha.setRadius(getOuterRadius(b));
+			Color interactionColor = getInteractionColor();
+			ha.setStroke(interactionColor);
+			ha.setFill(interactionColor);
+		}
+	}
+
+	/** Helper function to compute outer radius. */
+	private double getOuterRadius(Rectangle2D b) {
+		return getInnerRadius(b) + getHitAreaOutset();
+	}
+
+	/** Helper function to compute inner radius. */
+	private double getInnerRadius(Rectangle2D b) {
+		return b.getWidth() / 2 - getInset();
+	}
+
+	/** Returns the insets of the filled circle subtracted from {@link #getDimensions()}. */
+	protected double getInset() {
+		return 2;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected EDragGesture dragGestureHitTest(Node n, DiagramCoordinate diagramLocation) {
+		if(n == getHitAreaCircle()) {
+			Circle ha = getHitAreaCircle();
+			double dx = Math.abs(diagramLocation.getX() - ha.getCenterX());
+			double dy = Math.abs(diagramLocation.getY() - ha.getCenterY());
+			Rectangle2D bounds = getCurrentBounds();
+			double innerRadius = getInnerRadius(bounds);
+			double outerRadius = getOuterRadius(bounds);
+			double squaredLocation = dx * dx + dy * dy;
+			if(squaredLocation > innerRadius * innerRadius &&
+					squaredLocation <= outerRadius * outerRadius) {
+				return EDragGesture.NEW_LINK;
+			}
+			return EDragGesture.MOVE;
+		}
+		if(n == getVisualShape()) {
+			return EDragGesture.MOVE;
+		}
+		return EDragGesture.NONE;
+	}
+
+	/** Returns the visual circle. */
+	protected final Circle getVisualCircle() {
+		// wild cast works: see constructor
+		return (Circle)getVisualShape();
+	}
+
+	/** Returns the hit area circle. */
+	protected final Circle getHitAreaCircle() {
+		// wild cast works: see constructor
+		return (Circle)getHitAreaShape();
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/CircularContentVisualBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/CircularContentVisualBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..cc3caea328e36e90069b915e413c8e7e9522a939
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/CircularContentVisualBase.java
@@ -0,0 +1,182 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.elliptic;
+
+import static java.lang.Math.cos;
+import static java.lang.Math.hypot;
+import static java.lang.Math.max;
+import static java.lang.Math.sin;
+import static java.lang.Math.toRadians;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout.IAngleLayout;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentAnchorageVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base.ContentVisualBase;
+
+import javafx.geometry.Bounds;
+import javafx.geometry.Dimension2D;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Node;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Circle;
+
+/** Base class for {@link ContentVisualBase content visuals} depicted by rectangles. */
+public abstract class CircularContentVisualBase extends ContentVisualBase {
+	/** Constructor. */
+	public CircularContentVisualBase(IContentMVCBundle mvcb) {
+		super(mvcb, new Circle(), new Circle());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void updateNodes(DiagramLayers layers) {
+		super.updateNodes(layers);
+
+		Rectangle2D bounds = getCurrentBounds();
+		double leftX = bounds.getMinX();
+		double upperY = bounds.getMinY();
+		double width = bounds.getWidth();
+		double height = bounds.getHeight();
+		double w2 = width / 2.0;
+		double cx = leftX + w2;
+		double h2 = height / 2.0;
+		double cy = upperY + h2;
+		double radius = max(w2, h2);
+		if(enableVisual()) {
+			Circle circle = getVisualCircleShape();
+			circle.setCenterX(cx);
+			circle.setCenterY(cy);
+			circle.setRadius(radius);
+			circle.setOpacity(getOpacity());
+			circle.setStroke(getBorderColor());
+			circle.setStrokeWidth(getBorderWidth());
+			circle.setStrokeType(getBorderType());
+			circle.setFill(getFillColor());
+		}
+
+		if(enableInteraction()) {
+			Circle ha = getHitAreaCircleShape();
+			double lnk = getHitAreaStartLinkSize();
+			double resize = getHitAreaResizeSize();
+			radius = lnk + resize + radius;
+			ha.setCenterX(cx);
+			ha.setCenterY(cy);
+			ha.setRadius(radius);
+
+			Color interactionColor = getInteractionColor();
+			ha.setStroke(interactionColor);
+			ha.setFill(interactionColor);
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate getAnchorageLocation(IContentAnchorageVisual visual) {
+		Rectangle2D pb = getCurrentBounds();
+		double w2 = pb.getWidth() / 2.0;
+		double h2 = pb.getHeight() / 2.0;
+		double radius = max(w2, h2);
+		double angleDeg = visual.getLayout(IAngleLayout.class).getAngleInDegree();
+		double angleRad = toRadians(angleDeg);
+		double nx = cos(angleRad);
+		double ny = sin(angleRad);
+		double x = pb.getMinX() + w2 + radius * nx;
+		double y = pb.getMinY() + h2 + radius * ny;
+		return new DiagramCoordinate(x, y);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate getLinkAnchorage(DiagramCoordinate indication) {
+		return getLocationOnCircle(indication);
+	}
+
+	/** Returns the location on the circle closest to the given indication coordinate. */
+	private DiagramCoordinate getLocationOnCircle(DiagramCoordinate indication) {
+		Circle circ = getVisualCircleShape();
+		double radius = getRadius(circ);
+		double cx = circ.getCenterX();
+		double dx = indication.getX() - cx;
+		double cy = circ.getCenterY();
+		double dy = indication.getY() - cy;
+		double len = hypot(dx, dy);
+		double nx = radius * dx / len;
+		double ny = radius * dy / len;
+		return new DiagramCoordinate(cx + nx, cy + ny);
+	}
+
+	/** Returns the dimensions of the corner arcs. */
+	protected Dimension2D getCornerArcDimensions() {
+		return new Dimension2D(15, 15);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected EDragGesture dragGestureHitTest(Node node, DiagramCoordinate diagramLocation) {
+		Circle circ = getHitAreaCircleShape();
+		if(node == circ) {
+			double dx = diagramLocation.getX() - circ.getCenterX();
+			double dy = diagramLocation.getY() - circ.getCenterY();
+			double radius = getRadius(circ);
+			double hypot = hypot(dx, dy);
+			if(hypot < radius - getHitAreaResizeSize() - getHitAreaStartLinkSize()) {
+				return EDragGesture.MOVE;
+			}
+			if(hypot < radius - getHitAreaStartLinkSize()) {
+				return EDragGesture.RESIZE_VH;
+			}
+			if(hypot < radius) {
+				return EDragGesture.NEW_LINK;
+			}
+		}
+		if(node == getVisualShape() || node == text) {
+			return EDragGesture.MOVE;
+		}
+		return EDragGesture.NONE;
+	}
+
+	/** Returns the radius of the given circle computed from node bounds. */
+	private double getRadius(Circle circ) {
+		Bounds bounds = circ.getBoundsInLocal();
+		double w2 = bounds.getWidth() / 2.0;
+		double h2 = bounds.getHeight() / 2.0;
+		double radius = max(w2, h2);
+		return radius;
+	}
+
+	/** Returns the visual circle shape. */
+	protected final Circle getVisualCircleShape() {
+		// wild cast works: see constructor
+		return (Circle)getVisualShape();
+	}
+
+	/** Returns the hit area circle shape. */
+	protected final Circle getHitAreaCircleShape() {
+		// wild cast works: see constructor
+		return (Circle)getHitAreaShape();
+	}
+
+	/**
+	 * Returns the extension of the hit area in pixel used to trigger link creation. This number
+	 * specifies how many pixels beyond the visible border are used to trigger link creation by a
+	 * mouse drag gesture.
+	 */
+	protected double getHitAreaStartLinkSize() {
+		return getViewer().getFeatures().getMaximumSpacing();
+	}
+
+	/** Returns the size of the hit area in pixel used to resize the content visual. */
+	protected double getHitAreaResizeSize() {
+		return getViewer().getFeatures().getMaximumSpacing();
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/CircularDiagramAnchorageVisualBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/CircularDiagramAnchorageVisualBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a3b92fb1b135c218b9a5e16506acfc74a6b5468
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/CircularDiagramAnchorageVisualBase.java
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.elliptic;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.HIGHLIGHT_INCOMING_LINK_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.HIGHLIGHT_OUTGOING_LINK_TAG;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base.DiagramAnchorageVisualBase;
+
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Node;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Circle;
+
+/** Base class for {@link DiagramAnchorageVisualBase free interface visuals} depicted as circles. */
+public abstract class CircularDiagramAnchorageVisualBase extends DiagramAnchorageVisualBase {
+	/** Constructor. */
+	public CircularDiagramAnchorageVisualBase(IDiagramAnchorageMVCBundle mvcb) {
+		super(mvcb, new Circle(), new Circle());
+	}
+
+	/** Returns the visual circle. */
+	protected final Circle getVisualCircle() {
+		// wild cast works: see constructor
+		return (Circle)getVisualShape();
+	}
+
+	/** Returns the hit area circle. */
+	protected final Circle getHitAreaCircle() {
+		// wild cast works: see constructor
+		return (Circle)getHitAreaShape();
+	}
+
+	/** Sets the properties of the circle node. */
+	private void updateCircleProperties() {
+		Rectangle2D b = getCurrentBounds();
+		double cx = b.getMinX() + b.getWidth() / 2;
+		double cy = b.getMinY() + b.getHeight() / 2;
+
+		if(enableVisual()) {
+			Circle circle = getVisualCircle();
+			circle.setCenterX(cx);
+			circle.setCenterY(cy);
+			circle.setRadius(getInnerRadius(b));
+			circle.setOpacity(getOpacity());
+			circle.setStrokeWidth(getBorderWidth());
+			circle.setStrokeType(getBorderType());
+			if(getMVCBundle().hasTag(HIGHLIGHT_INCOMING_LINK_TAG)) {
+				circle.setFill(getHighlightIncomingLinkColor());
+				circle.setStroke(getHighlightIncomingLinkBorderColor());
+			} else if(getMVCBundle().hasTag(HIGHLIGHT_OUTGOING_LINK_TAG)) {
+				circle.setFill(getHighlightOutgoingLinkColor());
+				circle.setStroke(getHighlightOutgoingLinkBorderColor());
+			} else {
+				circle.setFill(getFillColor());
+				circle.setStroke(getBorderColor());
+			}
+		}
+
+		if(enableInteraction()) {
+			Circle ha = getHitAreaCircle();
+			ha.setCenterX(cx);
+			ha.setCenterY(cy);
+			ha.setRadius(getOuterRadius(b));
+			Color interactionColor = getInteractionColor();
+			ha.setStroke(interactionColor);
+			ha.setFill(interactionColor);
+		}
+	}
+
+	/** Helper function to compute outer radius. */
+	private double getOuterRadius(Rectangle2D b) {
+		return getInnerRadius(b) + getHitAreaOutset();
+	}
+
+	/** Helper function to compute inner radius. */
+	private double getInnerRadius(Rectangle2D b) {
+		return b.getWidth() / 2 - getInset();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void updateNodes(DiagramLayers layers) {
+		super.updateNodes(layers);
+		updateCircleProperties();
+	}
+
+	/** Returns the insets of the filled circle subtracted from {@link #getDimensions()}. */
+	protected double getInset() {
+		return 2;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected EDragGesture dragGestureHitTest(Node n, DiagramCoordinate diagramLocation) {
+		if(n == getHitAreaCircle()) {
+			Circle ha = getHitAreaCircle();
+			double dx = Math.abs(diagramLocation.getX() - ha.getCenterX());
+			double dy = Math.abs(diagramLocation.getY() - ha.getCenterY());
+			Rectangle2D bounds = getCurrentBounds();
+			double innerRadius = getInnerRadius(bounds);
+			double outerRadius = getOuterRadius(bounds);
+			double squaredLocation = dx * dx + dy * dy;
+			if(squaredLocation > innerRadius * innerRadius &&
+					squaredLocation <= outerRadius * outerRadius) {
+				return EDragGesture.NEW_LINK;
+			}
+			return EDragGesture.MOVE;
+		}
+		if(n == getVisualCircle()) {
+			return EDragGesture.MOVE;
+		}
+		return EDragGesture.NONE;
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/CurveLinkVisualBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/CurveLinkVisualBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..0b8706214320d715966c86a5242ad21c8bf5a315
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/CurveLinkVisualBase.java
@@ -0,0 +1,451 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.elliptic;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.FOCUS_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.HIGHLIGHT_INCOMING_LINK_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.HIGHLIGHT_OUTGOING_LINK_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.PRIMARY_SELECTION_TAG;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers.ILayer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl.LinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base.LinkVisualBase;
+
+import javafx.geometry.Point2D;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Node;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+
+/** Base class for {@link LinkVisualBase link visuals} with Bezier curve lines. */
+public abstract class CurveLinkVisualBase extends LinkVisualBase {
+	/** Stores the visual segments. */
+	private final List<CurveSegment> segments = new LinkedList<>();
+	/** Flag storing if the lines have been added. */
+	private boolean curvesAddedToSceneGraph = false;
+
+	/** Constructor. */
+	public CurveLinkVisualBase(ILinkMVCBundle mvcb) {
+		super(mvcb);
+	}
+
+	/** Adds the curve segments to the scene graph. */
+	private void addCurveSegments(DiagramLayers layers) {
+		LinkMVCBundle linkBundle = getLinkBundle();
+		double markerSize = getFeedbackMarkerSize();
+		double ms2 = markerSize / 2;
+		// start point
+		Point2D sp = getFeedbackChangeForBendPoint(START_OF_LINK_BEND_POINT_INDEX)
+				.applyToPoint(getStartAnchorPoint());
+		Point2D cp1 = getBendPointLocation(0);
+		Point2D cp2 = getBendPointLocation(1);
+		startFeedbackHandle.setX(sp.getX() - ms2);
+		startFeedbackHandle.setY(sp.getY() - ms2);
+		startFeedbackHandle.setWidth(markerSize);
+		startFeedbackHandle.setHeight(markerSize);
+		// handle bend points
+		int pts = getNumberOfBendPoints();
+		for(int i = 2; i < pts; i += 3) {
+			Point2D ep = getBendPointLocation(i);
+			makeCurve(sp, cp1, cp2, ep, getInvisibleSelectionLineWidth(), markerSize, markerSize,
+					false, true, 0);
+			sp = ep;
+			cp1 = getBendPointLocation(i + 1);
+			cp2 = getBendPointLocation(i + 2);
+			// bpModel = getBendPointModel();
+		}
+		// end point
+		Point2D ep = getFeedbackChangeForBendPoint(END_OF_LINK_BEND_POINT_INDEX)
+				.applyToPoint(getEndAnchorPoint());
+		// Last bend point is not selectable => set its feedback rectangle size to 0
+		makeCurve(sp, cp1, cp2, ep, getInvisibleSelectionLineWidth(), 0, markerSize,
+				showArrowOnLastSegment(), useLineArrow(), getArrowLength());
+		endFeedbackHandle.setX(ep.getX() - ms2);
+		endFeedbackHandle.setY(ep.getY() - ms2);
+		endFeedbackHandle.setWidth(markerSize);
+		endFeedbackHandle.setHeight(markerSize);
+
+		for(CurveSegment cs : segments) {
+			cs.addLinkNodes(layers, linkBundle);
+		}
+		curvesAddedToSceneGraph = true;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void updateNodes(DiagramLayers layers) {
+		super.updateNodes(layers);
+		// if no longer enabled => remove
+		if(!enableVisual()) {
+			if(curvesAddedToSceneGraph) {
+				removeCurveSegments(layers);
+			}
+			return;
+		}
+		// check for model updates (e.g new bend-point)
+		int pts = getNumberOfBendPoints();
+		int expectedSegments = 1 + (pts - 2) / 3;
+		if(!curvesAddedToSceneGraph) {
+			addCurveSegments(layers);
+		}
+		if(segments.size() != expectedSegments) {
+			removeCurveSegments(layers);
+			addCurveSegments(layers);
+			if(getViewer().getSelection().getPrimarySelection() == getLinkBundle()) {
+				if(getViewer().hasFocus()) {
+					applySelectedFocusedEffect(layers);
+				} else {
+					applySelectedNotFocusedEffect(layers);
+				}
+			}
+			return;
+		}
+
+		int segIndex = 0;
+		double markerSize = getFeedbackMarkerSize();
+		double ms2 = markerSize / 2;
+		// start point
+		Point2D sp = getFeedbackChangeForBendPoint(START_OF_LINK_BEND_POINT_INDEX)
+				.applyToPoint(getStartAnchorPoint());
+		startFeedbackHandle.setX(sp.getX() - ms2);
+		startFeedbackHandle.setY(sp.getY() - ms2);
+		startFeedbackHandle.setWidth(markerSize);
+		startFeedbackHandle.setHeight(markerSize);
+		Point2D cp1 = getFeedbackChangeForBendPoint(0).applyToPoint(getBendPointLocation(0));
+		Point2D cp2 = getFeedbackChangeForBendPoint(1).applyToPoint(getBendPointLocation(1));
+		// handle bend points
+		for(int i = 2; i < pts; i += 3) {
+			Point2D ep = getFeedbackChangeForBendPoint(i).applyToPoint(getBendPointLocation(i));
+			segments.get(segIndex).update(sp, cp1, cp2, ep, markerSize, markerSize, 0);
+			sp = ep;
+			cp1 = getFeedbackChangeForBendPoint(i + 1).applyToPoint(getBendPointLocation(i + 1));
+			cp2 = getFeedbackChangeForBendPoint(i + 2).applyToPoint(getBendPointLocation(i + 2));
+			segIndex++;
+		}
+		// end point
+		Point2D ep = getFeedbackChangeForBendPoint(END_OF_LINK_BEND_POINT_INDEX)
+				.applyToPoint(getEndAnchorPoint());
+		segments.get(segIndex).update(sp, cp1, cp2, ep, 0, markerSize, getArrowLength());
+		endFeedbackHandle.setX(ep.getX() - ms2);
+		endFeedbackHandle.setY(ep.getY() - ms2);
+		endFeedbackHandle.setWidth(markerSize);
+		endFeedbackHandle.setHeight(markerSize);
+
+		// coloring
+		Paint color = getLineColor();
+		IMVCBundle mvcBundle = getMVCBundle();
+		if(mvcBundle.hasTag(HIGHLIGHT_INCOMING_LINK_TAG)) {
+			color = getHighlightIncomingLinkColor();
+		} else if(mvcBundle.hasTag(HIGHLIGHT_OUTGOING_LINK_TAG)) {
+			color = getHighlightOutgoingLinkColor();
+		} else if(mvcBundle.hasTag(PRIMARY_SELECTION_TAG)) {
+			if(mvcBundle.hasTag(FOCUS_TAG)) {
+				color = getSelectionFocusedColor();
+			} else {
+				color = getSelectionNotFocusedColor();
+			}
+		}
+		for(CurveSegment seg : segments) {
+			seg.setStrokeColor(color);
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void removeAllVisuals(DiagramLayers layers) {
+		super.removeAllVisuals(layers);
+		removeCurveSegments(layers);
+	}
+
+	/** Removes the curve segments from the scene graph. */
+	private void removeCurveSegments(DiagramLayers layers) {
+		for(CurveSegment cs : segments) {
+			cs.removeLinkNodes(layers);
+			cs.removeFeedbackNodes(layers);
+		}
+		ILayer l = layers.getLinkInteractionLayer();
+		l.remove(startFeedbackHandle);
+		l.remove(endFeedbackHandle);
+		segments.clear();
+		curvesAddedToSceneGraph = false;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean enableVisual() {
+		return true;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean enableInteraction() {
+		return true;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate getStartAnchorPoint() {
+		IAnchorageMVCBundle start = getLinkBundle().getStartAnchorage();
+		Rectangle2D sb = start.getVisual().getCurrentBounds();
+
+		Point2D target;
+		int pts = getNumberOfBendPoints();
+		if(pts == 0) {
+			IAnchorageMVCBundle end = getLinkBundle().getEndAnchorage();
+			Rectangle2D eb = end.getVisual().getCurrentBounds();
+			target = new Point2D(eb.getMinX() + eb.getWidth() / 2,
+					eb.getMinY() + eb.getHeight() / 2);
+		} else {
+			target = getCurrentBendPointLocation(0);
+		}
+		return getStartAnchorLocation(sb, target);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate getEndAnchorPoint() {
+		IAnchorageMVCBundle end = getLinkBundle().getEndAnchorage();
+		Rectangle2D eb = end.getVisual().getCurrentBounds();
+
+		Point2D target;
+		int pts = getNumberOfBendPoints();
+		if(pts == 0) {
+			IAnchorageMVCBundle start = getLinkBundle().getStartAnchorage();
+			Rectangle2D sb = start.getVisual().getCurrentBounds();
+			target = new Point2D(sb.getMinX() + sb.getWidth() / 2,
+					sb.getMinY() + sb.getHeight() / 2);
+		} else {
+			target = getCurrentBendPointLocation(pts - 1);
+		}
+		return getEndAnchorLocation(eb, target);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void applySelectedFocusedEffect(DiagramLayers layers) {
+		ILinkMVCBundle bundle = getLinkBundle();
+		Paint selectionFocusedColor = getSelectionFocusedColor();
+		Paint interactionColor = getInteractionColor();
+		for(CurveSegment seg : segments) {
+			seg.setStrokeColor(selectionFocusedColor);
+			seg.setClickableLinesColor(interactionColor);
+			seg.addFeedbackNodes(layers, bundle);
+		}
+		ILayer linkInteractionLayer = layers.getLinkInteractionLayer();
+		linkInteractionLayer.add(startFeedbackHandle, bundle);
+		linkInteractionLayer.add(endFeedbackHandle, bundle);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void applySelectedNotFocusedEffect(DiagramLayers layers) {
+		removeFeedback(layers, getSelectionNotFocusedColor());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected void applyNotSelectedEffect(DiagramLayers layers) {
+		removeFeedback(layers, getInteractionColor());
+	}
+
+	/** Removes the feedback handles and sets the link to the given color. */
+	private void removeFeedback(DiagramLayers layers, Paint lineColor) {
+		Paint selectionNotFocusedColor = lineColor;
+		Paint interactionColor = getInteractionColor();
+		for(CurveSegment seg : segments) {
+			seg.setStrokeColor(selectionNotFocusedColor);
+			seg.setClickableLinesColor(interactionColor);
+			seg.removeFeedbackNodes(layers);
+		}
+		ILayer linkInteractionLayer = layers.getLinkInteractionLayer();
+		linkInteractionLayer.remove(startFeedbackHandle);
+		linkInteractionLayer.remove(endFeedbackHandle);
+	}
+
+	/**
+	 * Returns the anchor location w.r.t. the interface node bonds and the given target location
+	 * (e.g. the first bend-point or the end interface node center point).
+	 */
+	protected abstract DiagramCoordinate getStartAnchorLocation(Rectangle2D ifcBounds,
+			Point2D target);
+
+	/**
+	 * Returns the anchor location w.r.t. the interface node bonds and the given target location
+	 * (e.g. the last bend-point or the start interface node center point).
+	 */
+	protected abstract DiagramCoordinate getEndAnchorLocation(Rectangle2D ifcBounds,
+			Point2D target);
+
+	/** Returns the color to be used when the connection is selected. */
+	protected Paint getSelectionFocusedColor() {
+		return Color.RED;
+	}
+
+	/** Returns the color to be used when the connection is selected, but not focused. */
+	protected Paint getSelectionNotFocusedColor() {
+		return Color.ORANGE;
+	}
+
+	/** Returns the color to be used when the connection is not selected. */
+	protected Paint getLineColor() {
+		return Color.BLACK;
+	}
+
+	/**
+	 * Returns the point of the bend-point at the given index altered by its current feedback
+	 * change.
+	 */
+	private Point2D getCurrentBendPointLocation(int bendPointIndex) {
+		if(bendPointIndex >= 0 && bendPointIndex < getNumberOfBendPoints()) {
+			Point2D bendPoint = getBendPointLocation(bendPointIndex);
+			FeedbackChange chg = getFeedbackChangeForBendPoint(bendPointIndex);
+			double x = bendPoint.getX() + chg.getDeltaX();
+			double y = bendPoint.getY() + chg.getDeltaY();
+			return new Point2D(x, y);
+		}
+		throw new IndexOutOfBoundsException("No bend-point exists at index " + bendPointIndex);
+	}
+
+	/**
+	 * Returns the model of the bend-point at the given index. Each bend-point must be identified by
+	 * a unique object, which must not change as long as the bend-point exists.
+	 */
+	protected abstract Object getBendPointModel(int i);
+
+	/** Returns the number of bend-points. */
+	protected abstract int getNumberOfBendPoints();
+
+	/** Returns the width of the invisible selection line. */
+	protected abstract double getInvisibleSelectionLineWidth();
+
+	/** Creates the curve segment. */
+	private void makeCurve(Point2D sp, Point2D cp1, Point2D cp2, Point2D ep, double selWidth,
+			double bpMarkerSize, double cpMarkerSize, boolean showArrow, boolean useLineArrow,
+			double arrowLength) {
+		CurveSegment segVis = new CurveSegment(sp.getX(), sp.getY(), cp1.getX(), cp1.getY(),
+				cp2.getX(), cp2.getY(), ep.getX(), ep.getY(), selWidth, bpMarkerSize, cpMarkerSize,
+				showArrow, useLineArrow, arrowLength);
+		segments.add(segVis);
+	}
+
+	/** Returns the {@link LinkMVCBundle}. */
+	private LinkMVCBundle getLinkBundle() {
+		return (LinkMVCBundle)getMVCBundle();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Rectangle2D getCurrentBounds() {
+		// connections do not need bounds
+		return Rectangle2D.EMPTY;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public int getBendPointIndex(Node node) {
+		int superResult = super.getBendPointIndex(node);
+		if(superResult != NO_BEND_POINT_INDEX) {
+			return superResult;
+		}
+		for(int i = 0; i < segments.size(); i++) {
+			CurveSegment cs = segments.get(i);
+			if(node == cs.getFirstControlPointHandle()) {
+				return i * 3;
+			} else if(node == cs.getSecondControlPointHandle()) {
+				return 1 + i * 3;
+			} else if(node == cs.getBendPointHandle()) {
+				return 2 + i * 3;
+			} else if(node == cs.getVisibleCurve() || node == cs.getClickableCurve()) {
+				return 2 + i * 3;
+			}
+		}
+		return NO_BEND_POINT_INDEX;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean isBendPointHandle(Node node) {
+		for(CurveSegment cs : segments) {
+			if(node == cs.getBendPointHandle() || node == cs.getFirstControlPointHandle() ||
+					node == cs.getSecondControlPointHandle()) {
+				return true;
+			}
+		}
+		return super.isBendPointHandle(node);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void requestFocus() {
+		if(!segments.isEmpty()) {
+			// segment list was tested for emptiness => get(0) works
+			segments.get(0).getVisibleCurve().requestFocus();
+		}
+	}
+
+	/** Returns whether the last line segment should show an arrow head marker. */
+	protected abstract boolean showArrowOnLastSegment();
+
+	/** Returns the type of the arrow to be used (line vs. filled). The default is filled. */
+	protected boolean useLineArrow() {
+		return false;
+	}
+
+	/** Returns the length of the arrow. */
+	protected double getArrowLength() {
+		return 10;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public EDragGesture getDragGesture(Node node, DiagramCoordinate diagramLocation) {
+		if(node == startFeedbackHandle || node == endFeedbackHandle) {
+			return EDragGesture.RECONNECT_LINK;
+		}
+		for(CurveSegment cs : segments) {
+			if(node == cs.getBendPointHandle() || node == cs.getFirstControlPointHandle() ||
+					node == cs.getSecondControlPointHandle()) {
+				return EDragGesture.MOVE_BENDPOINT;
+			}
+			if(node == cs.getClickableCurve()) {
+				return EDragGesture.NEW_BENDPOINT;
+			}
+		}
+		return EDragGesture.NONE;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void enableFeedback() {
+		super.enableFeedback();
+		for(CurveSegment cs : segments) {
+			cs.setMouseTransparent(false);
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void disableFeedback() {
+		super.disableFeedback();
+		for(CurveSegment cs : segments) {
+			cs.setMouseTransparent(true);
+		}
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/CurveSegment.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/CurveSegment.java
new file mode 100644
index 0000000000000000000000000000000000000000..445bc2607cb70ae1c788c27ba9fc637dd7df4956
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/CurveSegment.java
@@ -0,0 +1,250 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.elliptic;
+
+import static javafx.scene.paint.Color.BLACK;
+import static javafx.scene.paint.Color.GRAY;
+import static javafx.scene.paint.Color.RED;
+import static javafx.scene.paint.Color.TRANSPARENT;
+import static javafx.scene.shape.StrokeLineCap.BUTT;
+import static javafx.scene.shape.StrokeLineJoin.ROUND;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers.ILayer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.widgets.LinkArrowWidget;
+
+import javafx.geometry.Point2D;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.CubicCurve;
+import javafx.scene.shape.Line;
+import javafx.scene.shape.Rectangle;
+
+/**
+ * This class encapsulates {@link CubicCurve}s usually needed for every segment: a visible one, a
+ * bigger and invisible one for mouse pointer hit tests.
+ */
+final class CurveSegment {
+	/** The visible curve. */
+	private final CubicCurve visibleCurve;
+	/** The clickable curve. */
+	private final CubicCurve clickableCurve;
+	/** The selection feedback handle rectangle for the first control point. */
+	private final Rectangle firstControlPointHandle;
+	/** The selection feedback handle rectangle for the second control point. */
+	private final Rectangle secondControlPointHandle;
+	/** The selection feedback handle rectangle for the bend point. */
+	private final Rectangle bendPointHandle;
+	/** The visual helper line from the start to the first control point. */
+	private final Line helperStart;
+	/** The visual helper line from the second control point to the end point. */
+	private final Line helperEnd;
+	/** The arrow widget for curved links. */
+	private final LinkArrowWidget arrowWidget;
+
+	/** Constructor. */
+	public CurveSegment(double sx, double sy, double c1x, double c1y, double c2x, double c2y,
+			double ex, double ey, double selectionStrokeWidth, double bendPointFeedbackSize,
+			double controlPointFeedbackSize, boolean showArrow, boolean useLineArrow,
+			double arrowLength) {
+		visibleCurve = new CubicCurve(sx, sy, c1x, c1y, c2x, c2y, ex, ey);
+		visibleCurve.setStroke(BLACK);
+		visibleCurve.setFill(null);
+
+		clickableCurve = new CubicCurve(sx, sy, c1x, c1y, c2x, c2y, ex, ey);
+		clickableCurve.setStrokeWidth(selectionStrokeWidth);
+		clickableCurve.setFill(null);
+		clickableCurve.setStrokeLineJoin(ROUND);
+		clickableCurve.setStrokeLineCap(BUTT);
+		clickableCurve.setStroke(TRANSPARENT);
+
+		double fs2 = bendPointFeedbackSize / 2;
+		bendPointHandle =
+				new Rectangle(ex - fs2, ey - fs2, bendPointFeedbackSize, bendPointFeedbackSize);
+		bendPointHandle.setFill(TRANSPARENT);
+		bendPointHandle.setStroke(RED);
+
+		double cps2 = controlPointFeedbackSize / 2;
+		firstControlPointHandle = new Rectangle(c1x - cps2, c1y - cps2, controlPointFeedbackSize,
+				controlPointFeedbackSize);
+		firstControlPointHandle.setFill(TRANSPARENT);
+		firstControlPointHandle.setStroke(GRAY);
+
+		secondControlPointHandle = new Rectangle(c2x - cps2, c2y - cps2, controlPointFeedbackSize,
+				controlPointFeedbackSize);
+		secondControlPointHandle.setFill(TRANSPARENT);
+		secondControlPointHandle.setStroke(GRAY);
+
+		helperStart = new Line(sx, sy, c1x, c1y);
+		helperStart.setStroke(GRAY);
+
+		helperEnd = new Line(c2x, c2y, ex, ey);
+		helperEnd.setStroke(GRAY);
+
+		if(showArrow) {
+			arrowWidget = new LinkArrowWidget(useLineArrow, ex, ey, c2x, c2y, arrowLength);
+		} else {
+			arrowWidget = null;
+		}
+	}
+
+	/** Returns the visible curve. */
+	public CubicCurve getVisibleCurve() {
+		return visibleCurve;
+	}
+
+	/** Returns the clickable curve. */
+	public CubicCurve getClickableCurve() {
+		return clickableCurve;
+	}
+
+	/** Returns the first control point feedback handle. */
+	public Rectangle getFirstControlPointHandle() {
+		return firstControlPointHandle;
+	}
+
+	/** Returns the second control point feedback handle. */
+	public Rectangle getSecondControlPointHandle() {
+		return secondControlPointHandle;
+	}
+
+	/** Returns the bend-point feedback handle. */
+	public Rectangle getBendPointHandle() {
+		return bendPointHandle;
+	}
+
+	/** Returns the helper line of the start. */
+	public Line getHelperStart() {
+		return helperStart;
+	}
+
+	/** Returns the helper line of the end. */
+	public Line getHelperEnd() {
+		return helperEnd;
+	}
+
+	/** Sets the color of the visible lines. */
+	public void setStrokeColor(Paint color) {
+		visibleCurve.setStroke(color);
+		if(arrowWidget != null) {
+			arrowWidget.setColor(color);
+		}
+	}
+
+	/** Sets the color of the invisible, clickable curves. */
+	public void setClickableLinesColor(Paint color) {
+		clickableCurve.setStroke(color);
+	}
+
+	/** Updates the segment coordinates. */
+	public void update(Point2D sp, Point2D cp1, Point2D cp2, Point2D ep,
+			double bendPointFeedbackSize, double controlPointFeedbackSize, double arrowLength) {
+		double sx = sp.getX();
+		double sy = sp.getY();
+		visibleCurve.setStartX(sx);
+		visibleCurve.setStartY(sy);
+		double c1x = cp1.getX();
+		double c1y = cp1.getY();
+		visibleCurve.setControlX1(c1x);
+		visibleCurve.setControlY1(c1y);
+		double c2x = cp2.getX();
+		double c2y = cp2.getY();
+		visibleCurve.setControlX2(c2x);
+		visibleCurve.setControlY2(c2y);
+		double ex = ep.getX();
+		double ey = ep.getY();
+		visibleCurve.setEndX(ex);
+		visibleCurve.setEndY(ey);
+
+		clickableCurve.setStartX(sx);
+		clickableCurve.setStartY(sy);
+		clickableCurve.setControlX1(c1x);
+		clickableCurve.setControlY1(c1y);
+		clickableCurve.setControlX2(c2x);
+		clickableCurve.setControlY2(c2y);
+		clickableCurve.setEndX(ex);
+		clickableCurve.setEndY(ey);
+
+		double cps2 = controlPointFeedbackSize / 2;
+		firstControlPointHandle.setX(c1x - cps2);
+		firstControlPointHandle.setY(c1y - cps2);
+
+		secondControlPointHandle.setX(c2x - cps2);
+		secondControlPointHandle.setY(c2y - cps2);
+
+		double bps2 = bendPointFeedbackSize / 2;
+		bendPointHandle.setX(ex - bps2);
+		bendPointHandle.setY(ey - bps2);
+
+		helperStart.setStartX(sx);
+		helperStart.setStartY(sy);
+		helperStart.setEndX(c1x);
+		helperStart.setEndY(c1y);
+
+		helperEnd.setStartX(c2x);
+		helperEnd.setStartY(c2y);
+		helperEnd.setEndX(ex);
+		helperEnd.setEndY(ey);
+
+		if(arrowWidget != null) {
+			arrowWidget.update(ex, ey, c2x, c2y, arrowLength);
+		}
+	}
+
+	/** Adds the link nodes of this segment to the link layer node. */
+	public void addLinkNodes(DiagramLayers layers, ILinkMVCBundle bundle) {
+		ILayer linkLayer = layers.getLinkLayer();
+		linkLayer.add(visibleCurve, bundle);
+		linkLayer.add(clickableCurve, bundle);
+		if(arrowWidget != null) {
+			linkLayer.add(arrowWidget, bundle);
+		}
+	}
+
+	/** Removes the link nodes of this segment from the link layer node. */
+	public void removeLinkNodes(DiagramLayers layers) {
+		ILayer linkLayer = layers.getLinkLayer();
+		linkLayer.remove(visibleCurve);
+		linkLayer.remove(clickableCurve);
+		if(arrowWidget != null) {
+			linkLayer.remove(arrowWidget);
+		}
+	}
+
+	/** Adds the feedback nodes of this segment to the feedback layer node. */
+	public void addFeedbackNodes(DiagramLayers layers, ILinkMVCBundle bundle) {
+		layers.getVisualFeedbackLayer().add(helperStart, bundle);
+		layers.getVisualFeedbackLayer().add(helperEnd, bundle);
+		layers.getLinkInteractionLayer().add(firstControlPointHandle, bundle);
+		layers.getLinkInteractionLayer().add(bendPointHandle, bundle);
+		layers.getLinkInteractionLayer().add(secondControlPointHandle, bundle);
+	}
+
+	/** Removes the feedback nodes of this segment from the feedback layer node. */
+	public void removeFeedbackNodes(DiagramLayers layers) {
+		layers.getVisualFeedbackLayer().remove(helperStart);
+		layers.getVisualFeedbackLayer().remove(helperEnd);
+		layers.getLinkInteractionLayer().remove(firstControlPointHandle);
+		layers.getLinkInteractionLayer().remove(bendPointHandle);
+		layers.getLinkInteractionLayer().remove(secondControlPointHandle);
+	}
+
+	/** Sets all elements to the given mouse transparency. */
+	public void setMouseTransparent(boolean transparent) {
+		visibleCurve.setMouseTransparent(transparent);
+		clickableCurve.setMouseTransparent(transparent);
+		bendPointHandle.setMouseTransparent(transparent);
+		firstControlPointHandle.setMouseTransparent(transparent);
+		secondControlPointHandle.setMouseTransparent(transparent);
+		if(arrowWidget != null) {
+			arrowWidget.setMouseTransparent(transparent);
+		}
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/EllipticBorderLocation.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/EllipticBorderLocation.java
new file mode 100644
index 0000000000000000000000000000000000000000..1e9b3d42c7fcd5495004fb30b0c499096a839967
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/EllipticBorderLocation.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.elliptic;
+
+import static java.lang.Math.atan2;
+import static java.lang.Math.cos;
+import static java.lang.Math.sin;
+import static java.util.Objects.requireNonNull;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+
+import javafx.geometry.Dimension2D;
+import javafx.geometry.Point2D;
+import javafx.geometry.Rectangle2D;
+
+/** This class encapsulates locations on the border of an ellipse defined by the angular value. */
+public final class EllipticBorderLocation {
+	/** The angle in degrees. */
+	private final double angleInDegree;
+	/** The radiuses of the ellipse. */
+	private final Dimension2D radiuses;
+	/**
+	 * The correction values used to compute the final location (usually half the size of the object
+	 * located on the border so that it looks centered.
+	 */
+	private final Dimension2D correction;
+
+	/** Constructor. */
+	public EllipticBorderLocation(double angleInDegree, Dimension2D radiuses,
+			Dimension2D correction) {
+		this.angleInDegree = angleInDegree;
+		this.radiuses = requireNonNull(radiuses);
+		this.correction = requireNonNull(correction);
+	}
+
+	/** Returns the angle in degree. */
+	public double getAngleInDegree() {
+		return angleInDegree;
+	}
+
+	/** Returns the radiuses. */
+	public Dimension2D getRadiuses() {
+		return radiuses;
+	}
+
+	/**
+	 * Returns the location computed from this {@link EllipticBorderLocation} relative to the
+	 * ellipse center.
+	 */
+	public DiagramCoordinate getLocation() {
+		double rw = radiuses.getWidth();
+		double rh = radiuses.getHeight();
+		double angle = Math.PI * angleInDegree / 180;
+		double x = rw - correction.getWidth() / 2 + rw * cos(angle);
+		double y = rh - correction.getHeight() / 2 - rh * sin(angle);
+		return new DiagramCoordinate(x, y);
+	}
+
+	/**
+	 * Returns the closest point on the ellipse bounds inside the given rectangle when pointing at
+	 * the given location.
+	 */
+	public static final EllipticBorderLocation getClosestLocationOnBounds(Point2D point,
+			Rectangle2D ellipse, Dimension2D size) {
+		double w2 = ellipse.getWidth() / 2;
+		double h2 = ellipse.getHeight() / 2;
+		double dx = point.getX() - w2;
+		double dy = h2 - point.getY();
+
+		double angle = atan2(dy, dx) * 180.0 / Math.PI;
+		Dimension2D radiuses = new Dimension2D(w2, h2);
+		return new EllipticBorderLocation(angle, radiuses, size);
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/EllipticContentVisualBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/EllipticContentVisualBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..dc2fddc9cfe5605bc8a5d09dd862845e360b23f5
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/elliptic/EllipticContentVisualBase.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.elliptic;
+
+import static java.lang.Math.atan2;
+import static java.lang.Math.toDegrees;
+import static javafx.scene.paint.Color.DARKORANGE;
+import static javafx.scene.paint.Color.ORANGE;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.visual.elliptic.EllipticBorderLocation.getClosestLocationOnBounds;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout.IAngleLayout;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentAnchorageVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base.ContentVisualBase;
+
+import javafx.geometry.Bounds;
+import javafx.geometry.Dimension2D;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Node;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Ellipse;
+
+/** Base class for {@link ContentVisualBase content visuals} depicted by ellipses. */
+public abstract class EllipticContentVisualBase extends ContentVisualBase {
+	/** Constructor. */
+	public EllipticContentVisualBase(IContentMVCBundle mvcb) {
+		super(mvcb, new Ellipse(), new Ellipse());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void updateNodes(DiagramLayers layers) {
+		super.updateNodes(layers);
+		updateNodeProperties();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate getAnchorageLocation(IContentAnchorageVisual visual) {
+		Rectangle2D pb = getCurrentBounds();
+		Dimension2D ell = new Dimension2D(pb.getWidth() / 2, pb.getHeight() / 2);
+		Dimension2D dim = visual.getDimensions();
+		double angleDeg = visual.getLayout(IAngleLayout.class).getAngleInDegree();
+		EllipticBorderLocation ebl = new EllipticBorderLocation(angleDeg, ell, dim);
+		return ebl.getLocation();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate getLinkAnchorage(DiagramCoordinate indication) {
+		Rectangle2D pb = getCurrentBounds();
+		EllipticBorderLocation ebl = getClosestLocationOnBounds(
+				indication.add(-pb.getMinX(), -pb.getMinY()), pb, new Dimension2D(1, 1));
+		return ebl.getLocation().add(pb.getMinX(), pb.getMinY());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected Paint getBorderColor() {
+		return DARKORANGE.darker();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected Paint getFillColor() {
+		return ORANGE.brighter();
+	}
+
+	/** Updates the properties of the visual nodes. */
+	private void updateNodeProperties() {
+		Rectangle2D bounds = getCurrentBounds();
+		double w2 = bounds.getWidth() / 2;
+		double h2 = bounds.getHeight() / 2;
+		Ellipse ellipse = getVisualEllipse();
+		ellipse.setCenterX(bounds.getMinX() + w2);
+		ellipse.setCenterY(bounds.getMinY() + h2);
+		ellipse.setRadiusX(w2);
+		ellipse.setRadiusY(h2);
+		ellipse.setOpacity(getOpacity());
+		ellipse.setStroke(getBorderColor());
+		ellipse.setStrokeWidth(getBorderWidth());
+		ellipse.setStrokeType(getBorderType());
+		ellipse.setFill(getFillColor());
+
+		double lnk = getHitAreaStartLinkSize();
+		double res = getHitAreaResizeSize();
+		// wild cast works: see constructor
+		Ellipse hitShape = getHitAreaEllipse();
+		hitShape.setCenterX(bounds.getMinX() + w2);
+		hitShape.setCenterY(bounds.getMinY() + h2);
+		hitShape.setRadiusX(w2 + lnk + res / 2);
+		hitShape.setRadiusY(h2 + lnk + res / 2);
+		Paint interactionColor = getInteractionColor();
+		hitShape.setStroke(interactionColor);
+		hitShape.setFill(interactionColor);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected EDragGesture dragGestureHitTest(Node node, DiagramCoordinate diagramLocation) {
+		if(node == getHitAreaShape()) {
+			Bounds bounds = node.getBoundsInLocal();
+			double w = bounds.getWidth();
+			double h = bounds.getHeight();
+
+			double x = diagramLocation.getLocalX(node) - w / 2;
+			double y = diagramLocation.getLocalY(node) - h / 2;
+			double res = getHitAreaResizeSize();
+			double lnk = getHitAreaStartLinkSize();
+			// check for move area
+			double a = w / 2 - res - lnk;
+			double b = h / 2 - res - lnk;
+			double hitMoveArea = (x * x / (a * a) + y * y / (b * b));
+			if(hitMoveArea <= 1.0) {
+				return EDragGesture.MOVE;
+			}
+			// check for resize area
+			a = w / 2 - lnk;
+			b = h / 2 - lnk;
+			double hitResizeArea = (x * x / (a * a) + y * y / (b * b));
+			if(hitResizeArea <= 1.0) {
+				// check angle
+				double angle = (360.0 + toDegrees(atan2(-y, x))) % 360.0;
+				if(angle <= 45.0 || angle >= 345.0) {
+					return EDragGesture.RESIZE_H;
+				}
+				if(angle >= 300.0) {
+					return EDragGesture.RESIZE_VH;
+				}
+				if(angle >= 225.0) {
+					return EDragGesture.RESIZE_V;
+				}
+				return EDragGesture.NONE;
+			}
+			// else startlink
+			return EDragGesture.NEW_LINK;
+		}
+		if(node == getVisualEllipse() || node == text) {
+			return EDragGesture.MOVE;
+		}
+		return EDragGesture.NONE;
+	}
+
+	/**
+	 * Returns the extension of the hit area in pixel used to trigger link creation. This number
+	 * specifies how many pixels beyond the visible border are used to trigger link creation by a
+	 * mouse drag gesture.
+	 */
+	protected double getHitAreaStartLinkSize() {
+		return getViewer().getFeatures().getMaximumSpacing();
+	}
+
+	/** Returns the size of the hit area in pixel used to resize the content visual. */
+	protected double getHitAreaResizeSize() {
+		return getViewer().getFeatures().getMaximumSpacing();
+	}
+
+	/** Returns the ellipse visual. */
+	protected final Ellipse getVisualEllipse() {
+		// wild cast works: see constructor
+		return (Ellipse)getVisualShape();
+	}
+
+	/** Returns the ellipse of the hit area. */
+	protected final Ellipse getHitAreaEllipse() {
+		return (Ellipse)getHitAreaShape();
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/.ratings b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..1f339261e0cee18516e6bb05f07035ad012a2ef4
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/.ratings
@@ -0,0 +1,9 @@
+DiamondContentVisualBase.java 214dc886dba3f26a6404db5c521715466ce85522 YELLOW
+LineLinkGraph.java 85a06a553f88f7b9fb4bd9c06411725d9fb160fc YELLOW
+LineLinkVisualBase.java 41cee7c8258cb65080bfce2a5d785772305a8119 YELLOW
+LineSegment.java a8658ec5bcd930d417a148861831b9ebb70bb37d YELLOW
+RectangularBorderLocation.java 824472c353534d1094ae4f735a30a231b885f050 YELLOW
+RectangularContentAnchorageVisualBase.java 39981dc29cac42d77c6ffe855ecc8ccad1689230 YELLOW
+RectangularContentVisualBase.java ccd23400a7ac47573127a39bc68c55c82baa44ad YELLOW
+RectangularDiagramAnchorageVisualBase.java 1259d6d110becca9ae02c754036c6693f00de683 YELLOW
+RhomboidContentVisualBase.java 7a91e401034acc629179fb1a45416cd519f617d7 YELLOW
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/DiamondContentVisualBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/DiamondContentVisualBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..214dc886dba3f26a6404db5c521715466ce85522
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/DiamondContentVisualBase.java
@@ -0,0 +1,195 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular.RectangularBorderLocation.getClosestLocationOnBounds;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout.ISideLayout;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentAnchorageVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base.ContentVisualBase;
+
+import javafx.collections.ObservableList;
+import javafx.geometry.Bounds;
+import javafx.geometry.Dimension2D;
+import javafx.geometry.Rectangle2D;
+import javafx.geometry.Side;
+import javafx.scene.Node;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.ClosePath;
+import javafx.scene.shape.LineTo;
+import javafx.scene.shape.MoveTo;
+import javafx.scene.shape.Path;
+import javafx.scene.shape.PathElement;
+
+/** Base class for {@link ContentVisualBase content visuals} depicted by a diamond shape. */
+public abstract class DiamondContentVisualBase extends ContentVisualBase {
+	/** Constructor. */
+	public DiamondContentVisualBase(IContentMVCBundle mvcb) {
+		super(mvcb, new Path(), new Path());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void updateNodes(DiagramLayers layers) {
+		super.updateNodes(layers);
+
+		Rectangle2D bounds = getCurrentBounds();
+		double leftX = bounds.getMinX();
+		double upperY = bounds.getMinY();
+		double width = bounds.getWidth();
+		double height = bounds.getHeight();
+		if(enableVisual()) {
+			Path path = getVisualPathShape();
+			path.setOpacity(getOpacity());
+			path.setStroke(getBorderColor());
+			path.setStrokeWidth(getBorderWidth());
+			path.setStrokeType(getBorderType());
+			path.setFill(getFillColor());
+			makePath(leftX, upperY, width, height, path);
+		}
+
+		if(enableInteraction()) {
+			Path ha = getHitAreaPathShape();
+			double lnk = getHitAreaStartLinkSize();
+			double resize = getHitAreaResizeSize();
+			double lr = lnk + resize;
+			makePath(leftX - lr, upperY - lr, width + 2 * lr, height + 2 * lr, ha);
+			Color interactionColor = getInteractionColor();
+			ha.setStroke(interactionColor);
+			ha.setFill(interactionColor);
+		}
+	}
+
+	/** Creates the rhomboid shape. */
+	private void makePath(double x, double y, double w, double h, Path p) {
+		double w2 = w / 2;
+		double h2 = h / 2;
+		ObservableList<PathElement> pes = p.getElements();
+		pes.clear();
+		pes.add(new MoveTo(x, y + h2));
+		pes.add(new LineTo(x + w2, y + h));
+		pes.add(new LineTo(x + w, y + h2));
+		pes.add(new LineTo(x + w2, y));
+		pes.add(new ClosePath());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate getAnchorageLocation(IContentAnchorageVisual visual) {
+		Rectangle2D pb = getCurrentBounds();
+		double pbw2 = pb.getWidth() / 2;
+		double pbh2 = pb.getHeight() / 2;
+		Dimension2D anchorageSize = visual.getDimensions();
+		double aw2 = anchorageSize.getWidth() / 2;
+		double ah2 = anchorageSize.getHeight() / 2;
+		Side side = visual.getLayout(ISideLayout.class).getSide();
+		// Side.TOP
+		double x = pbw2 - aw2;
+		double y = -ah2;
+		if(side == Side.BOTTOM) {
+			y = pb.getHeight() - ah2;
+		} else if(side == Side.LEFT) {
+			x = -aw2;
+			y = pbh2 - ah2;
+		} else if(side == Side.RIGHT) {
+			x = pb.getWidth() - aw2;
+			y = pbh2 - ah2;
+		}
+		return new DiagramCoordinate(x, y);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate getLinkAnchorage(DiagramCoordinate indication) {
+		Rectangle2D b = getCurrentBounds();
+		Dimension2D dummy = new Dimension2D(1, 1);
+		RectangularBorderLocation loc = getClosestLocationOnBounds(
+				indication.add(-b.getMinX(), -b.getMinY()), b, dummy, dummy);
+		return loc.getLocation().add(b.getMinX(), b.getMinY());
+	}
+
+	/** Returns the dimensions of the corner arcs. */
+	protected Dimension2D getCornerArcDimensions() {
+		return new Dimension2D(15, 15);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected EDragGesture dragGestureHitTest(Node node, DiagramCoordinate diagramLocation) {
+		if(node == getHitAreaShape()) {
+			double x = diagramLocation.getLocalX(node);
+			double y = diagramLocation.getLocalY(node);
+			double l = getHitAreaStartLinkSize();
+			double r = getHitAreaResizeSize();
+			Bounds bounds = node.getBoundsInLocal();
+			// check for move area
+			double inset = l + r;
+			Rectangle2D moveArea = new Rectangle2D(inset, inset, bounds.getWidth() - 2 * inset,
+					bounds.getHeight() - 2 * inset);
+			if(moveArea.contains(x, y)) {
+				return EDragGesture.MOVE;
+			}
+			if(x < l || y < l || x > bounds.getWidth() - l || y > bounds.getHeight() - l) {
+				return EDragGesture.NEW_LINK;
+			}
+			if(x > bounds.getWidth() - inset && y >= l && y <= bounds.getHeight() - inset) {
+				return EDragGesture.RESIZE_H;
+			}
+			if(y > bounds.getHeight() - inset && x >= l && x <= bounds.getWidth() - inset) {
+				return EDragGesture.RESIZE_V;
+			}
+			if(x > bounds.getWidth() - inset && y > bounds.getHeight() - inset) {
+				return EDragGesture.RESIZE_VH;
+			}
+			return EDragGesture.NONE;
+		}
+		if(node == getVisualShape() || node == text) {
+			return EDragGesture.MOVE;
+		}
+		return EDragGesture.NONE;
+	}
+
+	/** Returns the visual path shape. */
+	protected final Path getVisualPathShape() {
+		// wild cast works: see constructor
+		return (Path)getVisualShape();
+	}
+
+	/** Returns the hit area path shape. */
+	protected final Path getHitAreaPathShape() {
+		// wild cast works: see constructor
+		return (Path)getHitAreaShape();
+	}
+
+	/**
+	 * Returns the extension of the hit area in pixel used to trigger link creation. This number
+	 * specifies how many pixels beyond the visible border are used to trigger link creation by a
+	 * mouse drag gesture.
+	 */
+	protected double getHitAreaStartLinkSize() {
+		return getViewer().getFeatures().getMaximumSpacing();
+	}
+
+	/** Returns the size of the hit area in pixel used to resize the content visual. */
+	protected double getHitAreaResizeSize() {
+		return getViewer().getFeatures().getMaximumSpacing();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected DiagramCoordinate getTextAnchorLocation() {
+		DiagramCoordinate textAnchorLocation = super.getTextAnchorLocation();
+		return textAnchorLocation.add(getCurrentBounds().getWidth() / 5, 0);
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/LineLinkGraph.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/LineLinkGraph.java
new file mode 100644
index 0000000000000000000000000000000000000000..85a06a553f88f7b9fb4bd9c06411725d9fb160fc
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/LineLinkGraph.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javafx.scene.shape.Line;
+import javafx.util.Pair;
+
+/** This class represents all rectangular lines. */
+public class LineLinkGraph {
+	/** Maps the LineSegment to its Lines. */
+	private Map<LineSegment, List<Line>> segment2lines = new HashMap<>();
+	/** Maps the Line to its LineSegment. */
+	private Map<Line, LineSegment> line2segment = new HashMap<>();
+	/** Maps the Line to every Line Pair that is interrupted by it. */
+	private Map<Line, Set<Pair<Line, Line>>> line2interruptedLine = new HashMap<>();
+
+	/** Adds a new LineSegment to the graph. */
+	public void addSegment(LineSegment seg, List<Line> lines) {
+		segment2lines.put(seg, lines);
+		for(Line l : lines) {
+			line2segment.put(l, seg);
+		}
+	}
+
+	/** Removes the LineSegment from the graph. */
+	public void removeSegment(LineSegment seg, List<Line> lines) {
+		segment2lines.remove(seg);
+		for(Line l : lines) {
+			line2segment.remove(l);
+		}
+	}
+
+	/** Adds a new interrupted line. */
+	public void addInterruptedLine(Line line, Line interruptedLine1, Line interruptedLine2) {
+		Set<Pair<Line, Line>> set = line2interruptedLine.get(line);
+		if(set == null)
+			set = new HashSet<>();
+		set.add(new Pair<>(interruptedLine1, interruptedLine2));
+		line2interruptedLine.put(line, set);
+	}
+
+	/** Adds new interrupted lines. */
+	public void addInterruptedLines(Map<Line, Pair<Line, Line>> lines) {
+		for(Line line : lines.keySet()) {
+			Pair<Line, Line> pair = lines.get(line);
+			addInterruptedLine(line, pair.getKey(), pair.getValue());
+		}
+	}
+
+	/** Returns line2segment. */
+	public Map<Line, LineSegment> getLine2segment() {
+		return line2segment;
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/LineLinkVisualBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/LineLinkVisualBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..41cee7c8258cb65080bfce2a5d785772305a8119
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/LineLinkVisualBase.java
@@ -0,0 +1,447 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.FOCUS_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.HIGHLIGHT_INCOMING_LINK_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.HIGHLIGHT_OUTGOING_LINK_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.PRIMARY_SELECTION_TAG;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers.ILayer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.FeedbackChange;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.impl.LinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base.LinkVisualBase;
+
+import javafx.geometry.Point2D;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Node;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+
+/** Base class for {@link LinkVisualBase link visuals} with straight lines. */
+public abstract class LineLinkVisualBase extends LinkVisualBase {
+	/** Stores the visual segments. */
+	private final List<LineSegment> segments = new LinkedList<>();
+	/** Flag storing if the lines have been added. */
+	private boolean linesAddedToSceneGraph = false;
+
+	/** Constructor. */
+	public LineLinkVisualBase(ILinkMVCBundle mvcb) {
+		super(mvcb);
+	}
+
+	/** Creates the line segments and adds them to the scene graph. */
+	private void addLineSegments(DiagramLayers layers) {
+		Object bpModel = null;
+		double feedbackSize = getFeedbackMarkerSize();
+		double fs2 = feedbackSize / 2;
+		// start point
+		Point2D startAnchorPoint = getFeedbackChangeForBendPoint(START_OF_LINK_BEND_POINT_INDEX)
+				.applyToPoint(getStartAnchorPoint());
+		double sx = startAnchorPoint.getX();
+		double sy = startAnchorPoint.getY();
+		startFeedbackHandle.setX(sx - fs2);
+		startFeedbackHandle.setY(sy - fs2);
+		startFeedbackHandle.setWidth(feedbackSize);
+		startFeedbackHandle.setHeight(feedbackSize);
+		// handle bend points
+		int pts = getNumberOfBendPoints();
+		for(int i = 0; i < pts; i++) {
+			Point2D bp = getBendPointLocation(i);
+			FeedbackChange chg = getFeedbackChangeForBendPoint(i);
+			double nx = bp.getX() + chg.getDeltaX();
+			double ny = bp.getY() + chg.getDeltaY();
+			segments.add(new LineSegment(bpModel, sx, sy, nx, ny, getInvisibleSelectionLineWidth(),
+					feedbackSize, false, false, 0, getLabelText(i, pts + 1)));
+			sx = nx;
+			sy = ny;
+			bpModel = getBendPointModel(i);
+		}
+		// end point
+		Point2D endPoint = getFeedbackChangeForBendPoint(END_OF_LINK_BEND_POINT_INDEX)
+				.applyToPoint(getEndAnchorPoint());
+		segments.add(new LineSegment(bpModel, sx, sy, endPoint.getX(), endPoint.getY(),
+				getInvisibleSelectionLineWidth(), 0, showArrowOnLastSegment(), useLineArrow(),
+				getArrowLength(), getLabelText(pts, pts + 1)));
+		endFeedbackHandle.setX(endPoint.getX() - fs2);
+		endFeedbackHandle.setY(endPoint.getY() - fs2);
+		endFeedbackHandle.setWidth(feedbackSize);
+		endFeedbackHandle.setHeight(feedbackSize);
+
+		ILinkMVCBundle bundle = getLinkBundle();
+		int numberOfSegs = segments.size();
+		for(int i = 0; i < numberOfSegs; i++) {
+			LineSegment ls = segments.get(i);
+			ls.addLinkNodes(layers, bundle, getLabelText(i, numberOfSegs));
+		}
+		linesAddedToSceneGraph = true;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void removeAllVisuals(DiagramLayers layers) {
+		super.removeAllVisuals(layers);
+		removeLineSegments(layers);
+	}
+
+	/** Removes the line segments from the scene graph. */
+	private void removeLineSegments(DiagramLayers layers) {
+		for(LineSegment ls : segments) {
+			ls.removeLinkNodes(layers);
+			ls.removeFeedbackNodes(layers);
+		}
+		ILayer linkInteractionLayer = layers.getLinkInteractionLayer();
+		linkInteractionLayer.remove(startFeedbackHandle);
+		linkInteractionLayer.remove(endFeedbackHandle);
+		segments.clear();
+		linesAddedToSceneGraph = false;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void updateNodes(DiagramLayers layers) {
+		super.updateNodes(layers);
+		if(!enableVisual()) {
+			if(linesAddedToSceneGraph) {
+				removeLineSegments(layers);
+			}
+			return;
+		}
+
+		int pts = getNumberOfBendPoints();
+		int expectedSegments = 1 + pts;
+		if(!linesAddedToSceneGraph) {
+			addLineSegments(layers);
+		}
+		if(segments.size() != expectedSegments) {
+			removeLineSegments(layers);
+			addLineSegments(layers);
+			if(getViewer().getSelection().getPrimarySelection() == getLinkBundle()) {
+				if(getViewer().hasFocus()) {
+					applySelectedFocusedEffect(layers);
+				} else {
+					applySelectedNotFocusedEffect(layers);
+				}
+			}
+			return;
+		}
+		int segIndex = 0;
+		double feedbackSize = getFeedbackMarkerSize();
+		double fs2 = feedbackSize / 2;
+		// start point
+		Point2D sp = getFeedbackChangeForBendPoint(START_OF_LINK_BEND_POINT_INDEX)
+				.applyToPoint(getStartAnchorPoint());
+		startFeedbackHandle.setX(sp.getX() - fs2);
+		startFeedbackHandle.setY(sp.getY() - fs2);
+		startFeedbackHandle.setWidth(feedbackSize);
+		startFeedbackHandle.setHeight(feedbackSize);
+		// handle bend points
+		for(int i = 0; i < pts; i++) {
+			Point2D ep = getFeedbackChangeForBendPoint(i).applyToPoint(getBendPointLocation(i));
+			segments.get(segIndex).update(sp, ep, feedbackSize, 0, getLabelText(i, pts));
+			sp = ep;
+			segIndex++;
+		}
+		// end point
+		Point2D ep = getFeedbackChangeForBendPoint(END_OF_LINK_BEND_POINT_INDEX)
+				.applyToPoint(getEndAnchorPoint());
+		segments.get(segIndex).update(sp, ep, 0, getArrowLength(), getLabelText(pts, pts));
+		endFeedbackHandle.setX(ep.getX() - fs2);
+		endFeedbackHandle.setY(ep.getY() - fs2);
+		endFeedbackHandle.setWidth(feedbackSize);
+		endFeedbackHandle.setHeight(feedbackSize);
+
+		Paint color = getLineColor();
+		IMVCBundle mvcBundle = getMVCBundle();
+		if(mvcBundle.hasTag(HIGHLIGHT_INCOMING_LINK_TAG)) {
+			color = getHighlightIncomingLinkColor();
+		} else if(mvcBundle.hasTag(HIGHLIGHT_OUTGOING_LINK_TAG)) {
+			color = getHighlightOutgoingLinkColor();
+		} else if(mvcBundle.hasTag(PRIMARY_SELECTION_TAG)) {
+			if(mvcBundle.hasTag(FOCUS_TAG)) {
+				color = getSelectionFocusedColor();
+			} else {
+				color = getSelectionNotFocusedColor();
+			}
+		}
+		for(LineSegment seg : segments) {
+			seg.setStrokeColor(color);
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean enableVisual() {
+		return true;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean enableInteraction() {
+		return true;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate getStartAnchorPoint() {
+		IAnchorageMVCBundle start = getLinkBundle().getStartAnchorage();
+		Rectangle2D sb = start.getVisual().getCurrentBounds();
+
+		DiagramCoordinate target;
+		int pts = getNumberOfBendPoints();
+		if(pts == 0) {
+			IAnchorageMVCBundle end = getLinkBundle().getEndAnchorage();
+			Rectangle2D eb = end.getVisual().getCurrentBounds();
+			target = new DiagramCoordinate(eb.getMinX() + eb.getWidth() / 2,
+					eb.getMinY() + eb.getHeight() / 2);
+		} else {
+			target = getCurrentBendPointLocation(0);
+		}
+		return getStartAnchorLocation(sb, target);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate getEndAnchorPoint() {
+		IAnchorageMVCBundle end = getLinkBundle().getEndAnchorage();
+		Rectangle2D eb = end.getVisual().getCurrentBounds();
+
+		DiagramCoordinate target;
+		int pts = getNumberOfBendPoints();
+		if(pts == 0) {
+			IAnchorageMVCBundle start = getLinkBundle().getStartAnchorage();
+			Rectangle2D sb = start.getVisual().getCurrentBounds();
+			target = new DiagramCoordinate(sb.getMinX() + sb.getWidth() / 2,
+					sb.getMinY() + sb.getHeight() / 2);
+		} else {
+			target = getCurrentBendPointLocation(pts - 1);
+		}
+		return getEndAnchorLocation(eb, target);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void applySelectedFocusedEffect(DiagramLayers layers) {
+		ILinkMVCBundle bundle = getLinkBundle();
+		Paint interactionColor = getInteractionColor();
+		Paint selectionFocusedColor = getSelectionFocusedColor();
+		for(LineSegment seg : segments) {
+			seg.setStrokeColor(selectionFocusedColor);
+			seg.setClickableLinesColor(interactionColor);
+			seg.addFeedbackNodes(layers, bundle);
+		}
+		ILayer linkInteractionLayer = layers.getLinkInteractionLayer();
+		linkInteractionLayer.add(startFeedbackHandle, bundle);
+		linkInteractionLayer.add(endFeedbackHandle, bundle);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void applySelectedNotFocusedEffect(DiagramLayers layers) {
+		removeFeedback(layers, getSelectionNotFocusedColor());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected void applyNotSelectedEffect(DiagramLayers layers) {
+		removeFeedback(layers, getLineColor());
+	}
+
+	/** Removes the feedback handles and sets the link to the given color. */
+	private void removeFeedback(DiagramLayers layers, Paint lineColor) {
+		Paint interactionColor = getInteractionColor();
+		for(LineSegment seg : segments) {
+			seg.setStrokeColor(lineColor);
+			seg.setClickableLinesColor(interactionColor);
+			seg.removeFeedbackNodes(layers);
+		}
+		ILayer linkInteractionLayer = layers.getLinkInteractionLayer();
+		linkInteractionLayer.remove(startFeedbackHandle);
+		linkInteractionLayer.remove(endFeedbackHandle);
+	}
+
+	/**
+	 * Returns the anchor location w.r.t. the interface node bonds and the given target location
+	 * (e.g. the first bend-point or the end interface node center point).
+	 */
+	protected abstract DiagramCoordinate getStartAnchorLocation(Rectangle2D ifcBounds,
+			DiagramCoordinate target);
+
+	/**
+	 * Returns the anchor location w.r.t. the interface node bonds and the given target location
+	 * (e.g. the last bend-point or the start interface node center point).
+	 */
+	protected abstract DiagramCoordinate getEndAnchorLocation(Rectangle2D ifcBounds,
+			DiagramCoordinate target);
+
+	/** Returns the color to be used when the connection is selected and focused. */
+	protected Paint getSelectionFocusedColor() {
+		return Color.RED;
+	}
+
+	/** Returns the color to be used when the connection is selected, but not focused. */
+	protected Paint getSelectionNotFocusedColor() {
+		return Color.ORANGE;
+	}
+
+	/** Returns the color to be used when the connection is not selected. */
+	protected Paint getLineColor() {
+		return Color.BLACK;
+	}
+
+	/**
+	 * Returns the point of the bend-point at the given index altered by its current feedback
+	 * change.
+	 */
+	private DiagramCoordinate getCurrentBendPointLocation(int bendPointIndex) {
+		if(bendPointIndex >= 0 && bendPointIndex < getNumberOfBendPoints()) {
+			Point2D bendPoint = getBendPointLocation(bendPointIndex);
+			FeedbackChange chg = getFeedbackChangeForBendPoint(bendPointIndex);
+			double x = bendPoint.getX() + chg.getDeltaX();
+			double y = bendPoint.getY() + chg.getDeltaY();
+			return new DiagramCoordinate(x, y);
+		}
+		throw new IndexOutOfBoundsException("No bend-point exists at index " + bendPointIndex);
+	}
+
+	/**
+	 * Returns the model of the bend-point at the given index. Each bend-point must be identified by
+	 * a unique object, which must not change as long as the bend-point exists.
+	 */
+	protected abstract Object getBendPointModel(int i);
+
+	/** Returns the number of bend-points. */
+	protected abstract int getNumberOfBendPoints();
+
+	/** Returns whether the last line segment should show an arrow head marker. */
+	protected abstract boolean showArrowOnLastSegment();
+
+	/** Returns the type of the arrow to be used (line vs. filled). The default is filled. */
+	protected boolean useLineArrow() {
+		return false;
+	}
+
+	/** Returns the length of the arrow. */
+	protected double getArrowLength() {
+		return 10;
+	}
+
+	/** Returns the width of the invisible selection line. */
+	protected abstract double getInvisibleSelectionLineWidth();
+
+	/**
+	 * Returns the label text.
+	 * 
+	 * @param currentSegment
+	 *            the current segment
+	 * @param lastSegment
+	 *            the last index of a segment
+	 * @return the label text for the current segment or null for no label
+	 */
+	protected String getLabelText(int currentSegment, int lastSegment) {
+		return null;
+	}
+
+	/** Returns the {@link LinkMVCBundle}. */
+	private LinkMVCBundle getLinkBundle() {
+		return (LinkMVCBundle)getMVCBundle();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public Rectangle2D getCurrentBounds() {
+		// connections do not need bounds
+		return Rectangle2D.EMPTY;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public int getBendPointIndex(Node node) {
+		int superResult = super.getBendPointIndex(node);
+		if(superResult != NO_BEND_POINT_INDEX) {
+			return superResult;
+		}
+		for(int i = 0; i < segments.size(); i++) {
+			LineSegment ls = segments.get(i);
+			if(node == ls.getFeedbackHandle() || node == ls.getClickableLine() ||
+					node == ls.getVisibleLine()) {
+				return i;
+			}
+		}
+		return NO_BEND_POINT_INDEX;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean isBendPointHandle(Node node) {
+		for(LineSegment ls : segments) {
+			if(node == ls.getFeedbackHandle()) {
+				return true;
+			}
+			if(node == ls.getClickableLine() || node == ls.getVisibleLine()) {
+				return false;
+			}
+		}
+		return super.isBendPointHandle(node);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void requestFocus() {
+		if(!segments.isEmpty()) {
+			// segments list tested for emptiness => get(0) works
+			segments.get(0).getVisibleLine().requestFocus();
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public EDragGesture getDragGesture(Node node, DiagramCoordinate diagramLocation) {
+		if(node == startFeedbackHandle || node == endFeedbackHandle) {
+			return EDragGesture.RECONNECT_LINK;
+		}
+		for(LineSegment ls : segments) {
+			if(node == ls.getFeedbackHandle()) {
+				return EDragGesture.MOVE_BENDPOINT;
+			}
+			if(node == ls.getClickableLine()) {
+				return EDragGesture.NEW_BENDPOINT;
+			}
+		}
+		return EDragGesture.NONE;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void enableFeedback() {
+		super.enableFeedback();
+		for(LineSegment ls : segments) {
+			ls.setMouseTransparent(false);
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void disableFeedback() {
+		super.disableFeedback();
+		for(LineSegment ls : segments) {
+			ls.setMouseTransparent(true);
+		}
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/LineSegment.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/LineSegment.java
new file mode 100644
index 0000000000000000000000000000000000000000..a8658ec5bcd930d417a148861831b9ebb70bb37d
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/LineSegment.java
@@ -0,0 +1,214 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular;
+
+import static javafx.scene.paint.Color.TRANSPARENT;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers.ILayer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.widgets.LinkArrowWidget;
+
+import javafx.geometry.Point2D;
+import javafx.geometry.VPos;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Line;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.text.Text;
+import javafx.scene.text.TextAlignment;
+
+/**
+ * This class encapsulates {@link Line}s usually needed for every segment: a visible one, a
+ * bigger and invisible one for mouse pointer hit tests. It also provides access to the
+ * selection feedback handle of the segment, which is {@code null} for the last segment, since
+ * this one ends in an interface node.
+ */
+final class LineSegment {
+	/**
+	 * The bend-point model this segments originates from or {@code null} if it is the first
+	 * segment.
+	 */
+	private final Object bendPointModel;
+	/** The visible line. */
+	private final Line visibleLine;
+	/** The invisible line. */
+	private final Line clickableLine;
+	/** The selection feedback handle rectangle. */
+	private final Rectangle feedbackHandle;
+	/** The arrow head widget. */
+	private final LinkArrowWidget arrowWidget;
+	/** Stores the label. */
+	private Text label;
+
+	/** Constructor. */
+	public LineSegment(double sx, double sy, double ex, double ey, double clickableWidth,
+			double markerSize, boolean showArrow, boolean useLineArrow, double arrowLength,
+			String labelText) {
+		this(null, sx, sy, ex, ey, clickableWidth, markerSize, showArrow, useLineArrow, arrowLength,
+				labelText);
+	}
+
+	/** Constructor. */
+	public LineSegment(Object bendPointModel, double sx, double sy, double ex, double ey,
+			double clickableWidth, double feedbackSize, boolean showArrow, boolean useLineArrow,
+			double arrowLength, String labelText) {
+		this.bendPointModel = bendPointModel;
+
+		this.visibleLine = new Line(sx, sy, ex, ey);
+
+		this.clickableLine = new Line(sx, sy, ex, ey);
+		clickableLine.setStroke(TRANSPARENT);
+		clickableLine.setStrokeWidth(clickableWidth);
+
+		double fs2 = feedbackSize / 2;
+		this.feedbackHandle = new Rectangle(ex - fs2, ey - fs2, feedbackSize, feedbackSize);
+		feedbackHandle.setFill(TRANSPARENT);
+		feedbackHandle.setStroke(Color.RED);
+
+		if(showArrow) {
+			arrowWidget = new LinkArrowWidget(useLineArrow, ex, ey, sx, sy, arrowLength);
+		} else {
+			arrowWidget = null;
+		}
+		if(labelText != null) {
+			label = new Text(labelText);
+			label.setTextAlignment(TextAlignment.CENTER);
+			label.setTextOrigin(VPos.TOP);
+		} else {
+			label = null;
+		}
+	}
+
+	/** Returns the bend point model. */
+	public Object getBendPointModel() {
+		return bendPointModel;
+	}
+
+	/** Returns the visible line. */
+	public Line getVisibleLine() {
+		return visibleLine;
+	}
+
+	/** Returns the invisible, clickable line. */
+	public Line getClickableLine() {
+		return clickableLine;
+	}
+
+	/** Returns the feedback handle. */
+	public Rectangle getFeedbackHandle() {
+		return feedbackHandle;
+	}
+
+	/** Adds the link nodes of this segment to the link layer node. */
+	public void addLinkNodes(DiagramLayers layers, ILinkMVCBundle bundle, String labelText) {
+		ILayer visualLayer = layers.getLinkLayer();
+		visualLayer.add(visibleLine, bundle);
+		if(arrowWidget != null) {
+			visualLayer.add(arrowWidget, bundle);
+		}
+		if(label != null) {
+			label.setText(labelText);
+			double lx = (visibleLine.getStartX() + visibleLine.getEndX() -
+					label.getBoundsInLocal().getWidth()) / 2;
+			double ly = (visibleLine.getStartY() + visibleLine.getEndY()) / 2;
+			label.setX(lx);
+			label.setY(ly);
+			layers.getTextLayer().add(label, bundle);
+		}
+		ILayer interactionLayer = layers.getLinkInteractionLayer();
+		interactionLayer.add(clickableLine, bundle);
+	}
+
+	/** Removes the link nodes of this segment from the link layer node. */
+	public void removeLinkNodes(DiagramLayers layers) {
+		ILayer visualLayer = layers.getLinkLayer();
+		visualLayer.remove(visibleLine);
+		if(arrowWidget != null) {
+			visualLayer.remove(arrowWidget);
+		}
+		if(label != null) {
+			layers.getTextLayer().remove(label);
+		}
+		ILayer interactionLayer = layers.getLinkInteractionLayer();
+		interactionLayer.remove(clickableLine);
+	}
+
+	/** Adds the feedback nodes of this segment to the feedback layer node. */
+	public void addFeedbackNodes(DiagramLayers layers, ILinkMVCBundle bundle) {
+		layers.getLinkInteractionLayer().add(feedbackHandle, bundle);
+	}
+
+	/** Removes the feedback nodes of this segment from the feedback layer node. */
+	public void removeFeedbackNodes(DiagramLayers layers) {
+		layers.getLinkInteractionLayer().remove(feedbackHandle);
+	}
+
+	/** Sets the color of the visible lines. */
+	public void setStrokeColor(Paint color) {
+		visibleLine.setStroke(color);
+		if(arrowWidget != null) {
+			arrowWidget.setColor(color);
+		}
+	}
+
+	/** Sets the color of the invisible, clickable lines. */
+	public void setClickableLinesColor(Paint color) {
+		clickableLine.setStroke(color);
+	}
+
+	/** Updates the line segment. */
+	public void update(Point2D sp, Point2D ep, double feedbackSize, double arrowLength,
+			String labelText) {
+		double sx = sp.getX();
+		double sy = sp.getY();
+		visibleLine.setStartX(sx);
+		visibleLine.setStartY(sy);
+		clickableLine.setStartX(sx);
+		clickableLine.setStartY(sy);
+
+		double ex = ep.getX();
+		double ey = ep.getY();
+		visibleLine.setEndX(ex);
+		visibleLine.setEndY(ey);
+		clickableLine.setEndX(ex);
+		clickableLine.setEndY(ey);
+
+		double fs2 = feedbackSize / 2;
+		feedbackHandle.setX(ex - fs2);
+		feedbackHandle.setY(ey - fs2);
+		feedbackHandle.setWidth(feedbackSize);
+		feedbackHandle.setHeight(feedbackSize);
+
+		if(arrowWidget != null) {
+			arrowWidget.update(ex, ey, sx, sy, arrowLength);
+		}
+		if(label != null) {
+			label.setText(labelText);
+			double lx = (sx + ex - label.getBoundsInLocal().getWidth()) / 2;
+			double ly = (sy + ey) / 2;
+			label.setX(lx);
+			label.setY(ly);
+		}
+	}
+
+	/** Sets all elements to the given mouse transparency. */
+	public void setMouseTransparent(boolean transparent) {
+		visibleLine.setMouseTransparent(transparent);
+		clickableLine.setMouseTransparent(transparent);
+		feedbackHandle.setMouseTransparent(transparent);
+		if(arrowWidget != null) {
+			arrowWidget.setMouseTransparent(transparent);
+		}
+		if(label != null) {
+			label.setMouseTransparent(transparent);
+		}
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/RectangularBorderLocation.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/RectangularBorderLocation.java
new file mode 100644
index 0000000000000000000000000000000000000000..824472c353534d1094ae4f735a30a231b885f050
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/RectangularBorderLocation.java
@@ -0,0 +1,221 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular;
+
+import static java.lang.Math.hypot;
+import static java.util.Objects.requireNonNull;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+
+import javafx.geometry.Dimension2D;
+import javafx.geometry.Point2D;
+import javafx.geometry.Rectangle2D;
+import javafx.geometry.Side;
+
+/**
+ * This class encapsulates locations on the border of a rectangle defined by the side and offset
+ * value. It can be used to compute x-y-locations relative to the upper-left corner of the
+ * rectangle.
+ * <P>
+ * The {@link #getClosestLocationOnBounds(Point2D, Rectangle2D, Dimension2D, Dimension2D)} method
+ * allows to construct a {@link RectangularBorderLocation} given a rectangle and a location. The
+ * computed location will be on the rectangle and it will be the closest of those locations to the
+ * given point.
+ */
+public final class RectangularBorderLocation {
+	/** The side value. */
+	private final Side side;
+	/** The offset value. */
+	private final double offset;
+	/** The rectangle used for the computation. */
+	private final Rectangle2D rectangle;
+	/**
+	 * The correction values used to compute the final location (usually half the size of the object
+	 * located on the border so that it looks centered.
+	 */
+	private final Dimension2D anchorageSize;
+
+	/** Constructor. */
+	public RectangularBorderLocation(Side side, double offset, Rectangle2D rectangle,
+			Dimension2D anchorageSize) {
+		this.side = requireNonNull(side);
+		this.offset = offset;
+		this.rectangle = requireNonNull(rectangle);
+		this.anchorageSize = requireNonNull(anchorageSize);
+	}
+
+	/** Returns the side. */
+	public Side getSide() {
+		return side;
+	}
+
+	/** Returns the offset. */
+	public double getOffset() {
+		return offset;
+	}
+
+	/**
+	 * Returns the location computed from this {@link RectangularBorderLocation} relative to the
+	 * upper-left corner of the {@link #rectangle}.
+	 */
+	public DiagramCoordinate getLocation() {
+		if(side == Side.TOP) {
+			return new DiagramCoordinate(offset, -anchorageSize.getHeight() / 2);
+		}
+		if(side == Side.LEFT) {
+			return new DiagramCoordinate(-anchorageSize.getWidth() / 2, offset);
+		}
+		if(side == Side.BOTTOM) {
+			return new DiagramCoordinate(offset,
+					rectangle.getHeight() - anchorageSize.getHeight() / 2);
+		}
+		// Side.RIGHT
+		return new DiagramCoordinate(rectangle.getWidth() - anchorageSize.getWidth() / 2, offset);
+	}
+
+	/**
+	 * Returns the closest point on the rectangle bounds when pointing at the given location. Only
+	 * locations with distances {@code horizontalSpan} and {@code verticalSpan} are considered.
+	 */
+	@Deprecated
+	public static final RectangularBorderLocation getClosestLocationOnBounds(Point2D p,
+			Rectangle2D rectangle, Dimension2D span) {
+		LocationOnBorderComputation comp =
+				new LocationOnBorderComputation(p, rectangle, span, span);
+		RectangularBorderLocation result = new RectangularBorderLocation(comp.getResultSide(),
+				comp.getResultOffset(), rectangle, span);
+		return result;
+	}
+
+	/**
+	 * Returns the closest point on the rectangle bounds when pointing at the given location.
+	 */
+	public static final RectangularBorderLocation getClosestLocationOnBounds(Point2D p,
+			Rectangle2D rectangle, Dimension2D anchorageSize, Dimension2D span) {
+		LocationOnBorderComputation comp =
+				new LocationOnBorderComputation(p, rectangle, anchorageSize, span);
+		RectangularBorderLocation result = new RectangularBorderLocation(comp.getResultSide(),
+				comp.getResultOffset(), rectangle, anchorageSize);
+		return result;
+	}
+
+	/** Class for efficiently computing the closest viable position on the parent bounds. */
+	private static class LocationOnBorderComputation {
+		/** The left-right result offset. */
+		private double leftRightOffset;
+		/** The left-right result side. */
+		private Side leftRightSide;
+		/** The left-right distance. */
+		private double leftRightDistance;
+
+		/** The top-bottom result offset. */
+		private double topBottomOffset;
+		/** The top-bottom result side. */
+		private Side topBottomSide;
+		/** The top-bottom distance. */
+		private double topBottomDistance;
+
+		/**
+		 * Constructor, which computes the closest location on the given border to the given point.
+		 */
+		public LocationOnBorderComputation(Point2D pt, Rectangle2D rectangle,
+				Dimension2D anchorageSize, Dimension2D snap) {
+			// divide the problem by reducing to horizontal and vertical sub-problems
+			computeLeftRight(pt, rectangle, anchorageSize.getHeight(), snap.getHeight());
+			computeTopBottom(pt, rectangle, anchorageSize.getWidth(), snap.getWidth());
+		}
+
+		/** Computes the minimal location on the top or bottom border. */
+		private void computeTopBottom(Point2D pt, Rectangle2D rect, double anchorageWidth,
+				double snapWidth) {
+			// determine top or bottom
+			topBottomSide = Side.TOP;
+			double borderY = 0;
+			if(pt.getY() > rect.getHeight() / 2) {
+				topBottomSide = Side.BOTTOM;
+				borderY = rect.getHeight();
+			}
+			// setup helper variables
+			double borderWidth = rect.getWidth();
+			double correctedX = pt.getX() - anchorageWidth / 2;
+			double distanceY = pt.getY() - borderY;
+
+			// scan for closest border location
+			double xOffset = 0;
+			double currentDistance = hypot(correctedX, distanceY);
+			for(double nextOffset = snapWidth; nextOffset < borderWidth; nextOffset += snapWidth) {
+				double nextDistance = hypot(correctedX - nextOffset, distanceY);
+				if(nextDistance > currentDistance) {
+					// distance increased => solution found
+					break;
+				}
+				xOffset = nextOffset;
+				currentDistance = nextDistance;
+			}
+			// record result
+			topBottomOffset = xOffset;
+			topBottomDistance = currentDistance;
+		}
+
+		/** Computes the minimal location on the left or right border. */
+		private void computeLeftRight(Point2D pt, Rectangle2D rect, double anchorageHeight,
+				double snapHeight) {
+			// determine left or right
+			leftRightSide = Side.LEFT;
+			double borderX = 0;
+			if(pt.getX() > rect.getWidth() / 2) {
+				leftRightSide = Side.RIGHT;
+				borderX = rect.getWidth();
+			}
+			// setup helper variables
+			double borderHeight = rect.getHeight();
+			double correctedY = pt.getY() - anchorageHeight / 2;
+			double distanceX = pt.getX() - borderX;
+			// scan for closest location
+			double yOffset = 0;
+			double currentDistance = hypot(distanceX, correctedY);
+			for(double nextOffset = snapHeight; nextOffset < borderHeight; nextOffset +=
+					snapHeight) {
+				double nextDistance = hypot(distanceX, correctedY - nextOffset);
+				if(nextDistance > currentDistance) {
+					// distance increased => solution found
+					break;
+				}
+				yOffset = nextOffset;
+				currentDistance = nextDistance;
+			}
+			// record result
+			leftRightOffset = yOffset;
+			leftRightDistance = currentDistance;
+		}
+
+		/** Returns the side of the result. */
+		public Side getResultSide() {
+			if(leftRightDistance <= topBottomDistance) {
+				return leftRightSide;
+			}
+			return topBottomSide;
+		}
+
+		/** Returns the offset of the result. */
+		public double getResultOffset() {
+			if(leftRightDistance <= topBottomDistance) {
+				return leftRightOffset;
+			}
+			return topBottomOffset;
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public String toString() {
+		return side.toString() + ":" + offset;
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/RectangularContentAnchorageVisualBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/RectangularContentAnchorageVisualBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..39981dc29cac42d77c6ffe855ecc8ccad1689230
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/RectangularContentAnchorageVisualBase.java
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.HIGHLIGHT_INCOMING_LINK_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.HIGHLIGHT_OUTGOING_LINK_TAG;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentAnchorageVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base.ContentAnchorageVisualBase;
+
+import javafx.geometry.Insets;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Node;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
+
+/** Base class for {@link IContentAnchorageVisual}s depicted as rectangles (or squares). */
+public abstract class RectangularContentAnchorageVisualBase extends ContentAnchorageVisualBase {
+	/** Constructor. */
+	public RectangularContentAnchorageVisualBase(IContentAnchorageMVCBundle mvcb) {
+		super(mvcb, new Rectangle(), new Rectangle());
+	}
+
+	/** Returns the visual rectangle. */
+	protected final Rectangle getVisualRectangle() {
+		// wild cast works: see constructor
+		return (Rectangle)getVisualShape();
+	}
+
+	/** Returns the hit area rectangle. */
+	protected final Rectangle getHitAreaRectangle() {
+		// wild cast works: see constructor
+		return (Rectangle)getHitAreaShape();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void updateNodes(DiagramLayers layers) {
+		super.updateNodes(layers);
+
+		Rectangle2D bounds = getCurrentBounds();
+		if(enableVisual()) {
+			Insets i = getInsets();
+			Rectangle rectangle = getVisualRectangle();
+			rectangle.setX(bounds.getMinX() + i.getLeft());
+			rectangle.setY(bounds.getMinY() + i.getTop());
+			double w = bounds.getWidth() - i.getLeft() - i.getRight();
+			rectangle.setWidth(w);
+			double h = bounds.getHeight() - i.getTop() - i.getBottom();
+			rectangle.setHeight(h);
+			rectangle.setOpacity(getOpacity());
+			rectangle.setStrokeWidth(getBorderWidth());
+			rectangle.setStrokeType(getBorderType());
+			if(getMVCBundle().hasTag(HIGHLIGHT_INCOMING_LINK_TAG)) {
+				rectangle.setFill(getHighlightIncomingLinkColor());
+				rectangle.setStroke(getHighlightIncomingLinkBorderColor());
+			} else if(getMVCBundle().hasTag(HIGHLIGHT_OUTGOING_LINK_TAG)) {
+				rectangle.setFill(getHighlightOutgoingLinkColor());
+				rectangle.setStroke(getHighlightOutgoingLinkBorderColor());
+			} else {
+				rectangle.setFill(getFillColor());
+				rectangle.setStroke(getBorderColor());
+			}
+		}
+
+		if(enableInteraction()) {
+			// wild cast works: see addNodes(...)
+			Rectangle ha = getHitAreaRectangle();
+			double hitAreaOutSet = getHitAreaOutset();
+			ha.setX(bounds.getMinX() - hitAreaOutSet);
+			ha.setY(bounds.getMinY() - hitAreaOutSet);
+			ha.setWidth(bounds.getWidth() + 2 * hitAreaOutSet);
+			ha.setHeight(bounds.getHeight() + 2 * hitAreaOutSet);
+			Color interactionColor = getInteractionColor();
+			ha.setStroke(interactionColor);
+			ha.setFill(interactionColor);
+		}
+	}
+
+	/** Returns the insets for the filled rectangle subtracted from {@link #getDimensions()}. */
+	protected Insets getInsets() {
+		return new Insets(2);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected EDragGesture dragGestureHitTest(Node n, DiagramCoordinate diagramLocation) {
+		if(n == getHitAreaRectangle()) {
+			// wild cast works: see addNodes(...)
+			Rectangle ha = getHitAreaRectangle();
+			double x = diagramLocation.getLocalX(ha);
+			double y = diagramLocation.getLocalY(ha);
+			double outset = getHitAreaOutset();
+			if(x < outset || x > ha.getWidth() - outset || y < outset ||
+					y > ha.getHeight() - outset) {
+				return EDragGesture.NEW_LINK;
+			}
+			return EDragGesture.MOVE;
+		}
+		if(n == getVisualRectangle()) {
+			return EDragGesture.MOVE;
+		}
+		return EDragGesture.NONE;
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/RectangularContentVisualBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/RectangularContentVisualBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..ccd23400a7ac47573127a39bc68c55c82baa44ad
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/RectangularContentVisualBase.java
@@ -0,0 +1,165 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular.RectangularBorderLocation.getClosestLocationOnBounds;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout.IOffsetLayout;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout.ISideLayout;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentAnchorageVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base.ContentVisualBase;
+
+import javafx.geometry.Bounds;
+import javafx.geometry.Dimension2D;
+import javafx.geometry.Rectangle2D;
+import javafx.geometry.Side;
+import javafx.scene.Node;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
+
+/** Base class for {@link ContentVisualBase content visuals} depicted by rectangles. */
+public abstract class RectangularContentVisualBase extends ContentVisualBase {
+	/** Constructor. */
+	public RectangularContentVisualBase(IContentMVCBundle mvcb) {
+		super(mvcb, new Rectangle(), new Rectangle());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void updateNodes(DiagramLayers layers) {
+		super.updateNodes(layers);
+
+		Rectangle2D bounds = getCurrentBounds();
+		double leftX = bounds.getMinX();
+		double upperY = bounds.getMinY();
+		double width = bounds.getWidth();
+		double height = bounds.getHeight();
+		if(enableVisual()) {
+			Rectangle rectangle = getVisualRectangleShape();
+			rectangle.setX(leftX);
+			rectangle.setY(upperY);
+			rectangle.setWidth(width);
+			rectangle.setHeight(height);
+			Dimension2D cornerArc = getCornerArcDimensions();
+			rectangle.setArcWidth(cornerArc.getWidth());
+			rectangle.setArcHeight(cornerArc.getHeight());
+			rectangle.setOpacity(getOpacity());
+			rectangle.setStroke(getBorderColor());
+			rectangle.setStrokeWidth(getBorderWidth());
+			rectangle.setStrokeType(getBorderType());
+			rectangle.setFill(getFillColor());
+		}
+
+		if(enableInteraction()) {
+			Rectangle ha = getHitAreaRectangleShape();
+			double lnk = getHitAreaStartLinkSize();
+			double resize = getHitAreaResizeSize();
+			ha.setX(leftX - lnk - resize / 2);
+			ha.setY(upperY - lnk - resize / 2);
+			ha.setWidth(lnk + resize + width + lnk);
+			ha.setHeight(lnk + resize + height + lnk);
+			Color interactionColor = getInteractionColor();
+			ha.setStroke(interactionColor);
+			ha.setFill(interactionColor);
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate getAnchorageLocation(IContentAnchorageVisual visual) {
+		Rectangle2D pb = getCurrentBounds();
+		Side side = visual.getLayout(ISideLayout.class).getSide();
+		double offset = visual.getLayout(IOffsetLayout.class).getOffset();
+		RectangularBorderLocation rbl =
+				new RectangularBorderLocation(side, offset, pb, visual.getDimensions());
+		return rbl.getLocation();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate getLinkAnchorage(DiagramCoordinate indication) {
+		Rectangle2D b = getCurrentBounds();
+		Dimension2D dummy = new Dimension2D(1, 1);
+		RectangularBorderLocation loc = getClosestLocationOnBounds(
+				indication.add(-b.getMinX(), -b.getMinY()), b, dummy, dummy);
+		return loc.getLocation().add(b.getMinX(), b.getMinY());
+	}
+
+	/** Returns the dimensions of the corner arcs. */
+	protected Dimension2D getCornerArcDimensions() {
+		return new Dimension2D(15, 15);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected EDragGesture dragGestureHitTest(Node node, DiagramCoordinate diagramLocation) {
+		if(node == getHitAreaShape()) {
+			double x = diagramLocation.getLocalX(node);
+			double y = diagramLocation.getLocalY(node);
+			double l = getHitAreaStartLinkSize();
+			double r = getHitAreaResizeSize();
+			Bounds bounds = node.getBoundsInLocal();
+			// check for move area
+			double inset = l + r;
+			Rectangle2D moveArea = new Rectangle2D(inset, inset, bounds.getWidth() - 2 * inset,
+					bounds.getHeight() - 2 * inset);
+			if(moveArea.contains(x, y)) {
+				return EDragGesture.MOVE;
+			}
+			if(x < l || y < l || x > bounds.getWidth() - l || y > bounds.getHeight() - l) {
+				return EDragGesture.NEW_LINK;
+			}
+			if(x > bounds.getWidth() - inset && y >= l && y <= bounds.getHeight() - inset) {
+				return EDragGesture.RESIZE_H;
+			}
+			if(y > bounds.getHeight() - inset && x >= l && x <= bounds.getWidth() - inset) {
+				return EDragGesture.RESIZE_V;
+			}
+			if(x > bounds.getWidth() - inset && y > bounds.getHeight() - inset) {
+				return EDragGesture.RESIZE_VH;
+			}
+			return EDragGesture.NONE;
+		}
+		if(node == getVisualShape() || node == text) {
+			return EDragGesture.MOVE;
+		}
+		return EDragGesture.NONE;
+	}
+
+	/** Returns the visual rectangle shape. */
+	protected final Rectangle getVisualRectangleShape() {
+		// wild cast works: see constructor
+		return (Rectangle)getVisualShape();
+	}
+
+	/** Returns the hit area rectangle shape. */
+	protected final Rectangle getHitAreaRectangleShape() {
+		// wild cast works: see constructor
+		return (Rectangle)getHitAreaShape();
+	}
+
+	/**
+	 * Returns the extension of the hit area in pixel used to trigger link creation. This number
+	 * specifies how many pixels beyond the visible border are used to trigger link creation by a
+	 * mouse drag gesture.
+	 */
+	protected double getHitAreaStartLinkSize() {
+		return getViewer().getFeatures().getMaximumSpacing();
+	}
+
+	/** Returns the size of the hit area in pixel used to resize the content visual. */
+	protected double getHitAreaResizeSize() {
+		return getViewer().getFeatures().getMaximumSpacing();
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/RectangularDiagramAnchorageVisualBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/RectangularDiagramAnchorageVisualBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..1259d6d110becca9ae02c754036c6693f00de683
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/RectangularDiagramAnchorageVisualBase.java
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.HIGHLIGHT_INCOMING_LINK_TAG;
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerDefaultTags.HIGHLIGHT_OUTGOING_LINK_TAG;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base.DiagramAnchorageVisualBase;
+
+import javafx.geometry.Insets;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Node;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
+
+/**
+ * Base class for {@link DiagramAnchorageVisualBase free interface visuals} depicted as rectangles.
+ */
+public abstract class RectangularDiagramAnchorageVisualBase extends DiagramAnchorageVisualBase {
+	/** Constructor. */
+	public RectangularDiagramAnchorageVisualBase(IDiagramAnchorageMVCBundle mvcb) {
+		super(mvcb, new Rectangle(), new Rectangle());
+	}
+
+	/** Returns the visual rectangle. */
+	protected final Rectangle getVisualRectangle() {
+		// wild cast works: see constructor
+		return (Rectangle)getVisualShape();
+	}
+
+	/** Returns the hit area rectangle. */
+	protected final Rectangle getHitAreaRectangle() {
+		// wild cast works: see constructor
+		return (Rectangle)getHitAreaShape();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void updateNodes(DiagramLayers layers) {
+		super.updateNodes(layers);
+
+		Rectangle2D bounds = getCurrentBounds();
+		if(enableVisual()) {
+			Rectangle visual = getVisualRectangle();
+			Insets i = getInsets();
+			visual.setX(bounds.getMinX() + i.getLeft());
+			visual.setY(bounds.getMinY() + i.getTop());
+			visual.setWidth(bounds.getWidth() - i.getLeft() - i.getRight());
+			visual.setHeight(bounds.getHeight() - i.getTop() - i.getBottom());
+			visual.setOpacity(getOpacity());
+			visual.setStrokeWidth(getBorderWidth());
+			visual.setStrokeType(getBorderType());
+
+			if(getMVCBundle().hasTag(HIGHLIGHT_INCOMING_LINK_TAG)) {
+				visual.setFill(getHighlightIncomingLinkColor());
+				visual.setStroke(getHighlightIncomingLinkBorderColor());
+			} else if(getMVCBundle().hasTag(HIGHLIGHT_OUTGOING_LINK_TAG)) {
+				visual.setFill(getHighlightOutgoingLinkColor());
+				visual.setStroke(getHighlightOutgoingLinkBorderColor());
+			} else {
+				visual.setFill(getFillColor());
+				visual.setStroke(getBorderColor());
+			}
+		}
+
+		if(enableInteraction()) {
+			Rectangle ha = getHitAreaRectangle();
+			double hitAreaOutSet = getHitAreaOutset();
+			ha.setX(bounds.getMinX() - hitAreaOutSet);
+			ha.setY(bounds.getMinY() - hitAreaOutSet);
+			ha.setWidth(bounds.getWidth() + 2 * hitAreaOutSet);
+			ha.setHeight(bounds.getHeight() + 2 * hitAreaOutSet);
+			Color interactionColor = getInteractionColor();
+			ha.setStroke(interactionColor);
+			ha.setFill(interactionColor);
+		}
+	}
+
+	/** Returns the insets for the filled rectangle subtracted from {@link #getDimensions()}. */
+	protected Insets getInsets() {
+		return new Insets(2);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected EDragGesture dragGestureHitTest(Node n, DiagramCoordinate diagramLocation) {
+		if(n == getHitAreaRectangle()) {
+			Rectangle ha = getHitAreaRectangle();
+			double x = diagramLocation.getLocalX(n);
+			double y = diagramLocation.getLocalY(n);
+			double outset = getHitAreaOutset();
+			if(x < outset || x > ha.getWidth() - outset || y < outset ||
+					y > ha.getHeight() - outset) {
+				return EDragGesture.NEW_LINK;
+			}
+			return EDragGesture.MOVE;
+		}
+		if(n == getVisualRectangle()) {
+			return EDragGesture.MOVE;
+		}
+		return EDragGesture.NONE;
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/RhomboidContentVisualBase.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/RhomboidContentVisualBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a91e401034acc629179fb1a45416cd519f617d7
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/rectangular/RhomboidContentVisualBase.java
@@ -0,0 +1,197 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular;
+
+import static org.fortiss.tooling.common.ui.javafx.lwfxef.visual.rectangular.RectangularBorderLocation.getClosestLocationOnBounds;
+
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramCoordinate;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramLayers;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.EDragGesture;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout.IOffsetLayout;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.layout.ISideLayout;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentAnchorageVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.base.ContentVisualBase;
+
+import javafx.collections.ObservableList;
+import javafx.geometry.Bounds;
+import javafx.geometry.Dimension2D;
+import javafx.geometry.Rectangle2D;
+import javafx.geometry.Side;
+import javafx.scene.Node;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.ClosePath;
+import javafx.scene.shape.LineTo;
+import javafx.scene.shape.MoveTo;
+import javafx.scene.shape.Path;
+import javafx.scene.shape.PathElement;
+
+/** Base class for {@link ContentVisualBase content visuals} depicted by a rhomboid. */
+public abstract class RhomboidContentVisualBase extends ContentVisualBase {
+	/** Constructor. */
+	public RhomboidContentVisualBase(IContentMVCBundle mvcb) {
+		super(mvcb, new Path(), new Path());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void updateNodes(DiagramLayers layers) {
+		super.updateNodes(layers);
+
+		Rectangle2D bounds = getCurrentBounds();
+		double leftX = bounds.getMinX();
+		double upperY = bounds.getMinY();
+		double width = bounds.getWidth();
+		double height = bounds.getHeight();
+		if(enableVisual()) {
+			Path path = getVisualPathShape();
+			path.setOpacity(getOpacity());
+			path.setStroke(getBorderColor());
+			path.setStrokeWidth(getBorderWidth());
+			path.setStrokeType(getBorderType());
+			path.setFill(getFillColor());
+			makePath(leftX, upperY, width, height, path);
+		}
+
+		if(enableInteraction()) {
+			Path ha = getHitAreaPathShape();
+			double lnk = getHitAreaStartLinkSize();
+			double resize = getHitAreaResizeSize();
+			double lr = lnk + resize;
+			makePath(leftX - lr, upperY - lr, width + 2 * lr, height + 2 * lr, ha);
+			Color interactionColor = getInteractionColor();
+			ha.setStroke(interactionColor);
+			ha.setFill(interactionColor);
+		}
+	}
+
+	/** Creates the rhomboid shape. */
+	private void makePath(double x, double y, double w, double h, Path p) {
+		double offset = getOffset();
+		ObservableList<PathElement> pes = p.getElements();
+		pes.clear();
+		pes.add(new MoveTo(x, y + h));
+		pes.add(new LineTo(x + w - offset, y + h));
+		pes.add(new LineTo(x + w, y));
+		pes.add(new LineTo(x + offset, y));
+		pes.add(new ClosePath());
+	}
+
+	/** Returns the offset of the upper and lower rhomboid line (0 = rectangle). */
+	public double getOffset() {
+		return 3.0 * getViewer().getFeatures().getHorizontalSpacing();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate getAnchorageLocation(IContentAnchorageVisual visual) {
+		Rectangle2D pb = getCurrentBounds();
+		double rhomboidOffset = getOffset();
+		Dimension2D anchorageSize = visual.getDimensions();
+		Side side = visual.getLayout(ISideLayout.class).getSide();
+		double anchorageOffset = visual.getLayout(IOffsetLayout.class).getOffset();
+		if(side == Side.LEFT) {
+			double lx = rhomboidOffset / 2 - anchorageSize.getWidth() / 2;
+			double ly = pb.getHeight() / 2 - anchorageSize.getHeight() / 2;
+			return new DiagramCoordinate(lx, ly);
+		} else if(side == Side.RIGHT) {
+			double lx = pb.getWidth() - rhomboidOffset / 2 - anchorageSize.getWidth() / 2;
+			double ly = pb.getHeight() / 2 - anchorageSize.getHeight() / 2;
+			return new DiagramCoordinate(lx, ly);
+		}
+		RectangularBorderLocation rbl =
+				new RectangularBorderLocation(side, anchorageOffset, pb, anchorageSize);
+		return rbl.getLocation();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public DiagramCoordinate getLinkAnchorage(DiagramCoordinate indication) {
+		Rectangle2D b = getCurrentBounds();
+		Dimension2D dummy = new Dimension2D(1, 1);
+		RectangularBorderLocation loc = getClosestLocationOnBounds(
+				indication.add(-b.getMinX(), -b.getMinY()), b, dummy, dummy);
+		return loc.getLocation().add(b.getMinX(), b.getMinY());
+	}
+
+	/** Returns the dimensions of the corner arcs. */
+	protected Dimension2D getCornerArcDimensions() {
+		return new Dimension2D(15, 15);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected EDragGesture dragGestureHitTest(Node node, DiagramCoordinate diagramLocation) {
+		if(node == getHitAreaShape()) {
+			double x = diagramLocation.getLocalX(node);
+			double y = diagramLocation.getLocalY(node);
+			double l = getHitAreaStartLinkSize();
+			double r = getHitAreaResizeSize();
+			Bounds bounds = node.getBoundsInLocal();
+			// check for move area
+			double inset = l + r;
+			Rectangle2D moveArea = new Rectangle2D(inset, inset, bounds.getWidth() - 2 * inset,
+					bounds.getHeight() - 2 * inset);
+			if(moveArea.contains(x, y)) {
+				return EDragGesture.MOVE;
+			}
+			if(x < l || y < l || x > bounds.getWidth() - l || y > bounds.getHeight() - l) {
+				return EDragGesture.NEW_LINK;
+			}
+			if(x > bounds.getWidth() - inset && y >= l && y <= bounds.getHeight() - inset) {
+				return EDragGesture.RESIZE_H;
+			}
+			if(y > bounds.getHeight() - inset && x >= l && x <= bounds.getWidth() - inset) {
+				return EDragGesture.RESIZE_V;
+			}
+			if(x > bounds.getWidth() - inset && y > bounds.getHeight() - inset) {
+				return EDragGesture.RESIZE_VH;
+			}
+			return EDragGesture.NONE;
+		}
+		if(node == getVisualShape() || node == text) {
+			return EDragGesture.MOVE;
+		}
+		return EDragGesture.NONE;
+	}
+
+	/** Returns the visual path shape. */
+	protected final Path getVisualPathShape() {
+		// wild cast works: see constructor
+		return (Path)getVisualShape();
+	}
+
+	/** Returns the hit area path shape. */
+	protected final Path getHitAreaPathShape() {
+		// wild cast works: see constructor
+		return (Path)getHitAreaShape();
+	}
+
+	/**
+	 * Returns the extension of the hit area in pixel used to trigger link creation. This number
+	 * specifies how many pixels beyond the visible border are used to trigger link creation by a
+	 * mouse drag gesture.
+	 */
+	protected double getHitAreaStartLinkSize() {
+		return getViewer().getFeatures().getMaximumSpacing();
+	}
+
+	/** Returns the size of the hit area in pixel used to resize the content visual. */
+	protected double getHitAreaResizeSize() {
+		return getViewer().getFeatures().getMaximumSpacing();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected DiagramCoordinate getTextAnchorLocation() {
+		DiagramCoordinate textAnchorLocation = super.getTextAnchorLocation();
+		return textAnchorLocation.add(getOffset() / 2, 0);
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/widgets/.ratings b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/widgets/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..23567ab4141161407f8d322123ed4eb8e732c7f4
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/widgets/.ratings
@@ -0,0 +1,2 @@
+ExpandCollapseWidget.java f431b6a86f3ce1794c55b90f39a15cda4f92ea06 YELLOW
+LinkArrowWidget.java 5354f14ca9d53cc3df88afb1867a266dfde65199 YELLOW
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/widgets/ExpandCollapseWidget.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/widgets/ExpandCollapseWidget.java
new file mode 100644
index 0000000000000000000000000000000000000000..f431b6a86f3ce1794c55b90f39a15cda4f92ea06
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/widgets/ExpandCollapseWidget.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.widgets;
+
+import static javafx.scene.paint.Color.BLACK;
+import static javafx.scene.paint.Color.TRANSPARENT;
+
+import javafx.collections.ObservableList;
+import javafx.scene.Group;
+import javafx.scene.Node;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Line;
+import javafx.scene.shape.Rectangle;
+
+/** Widget showing a plus / minus indicator used for hierarchical content nodes. */
+public final class ExpandCollapseWidget extends Group {
+	/** The rectangle of the indicator. */
+	private final Rectangle borderRect = new Rectangle();
+	/** The horizontal line used for both plus and minus state. */
+	private final Line horizontalLine = new Line();
+	/** The vertical line used for plus state. */
+	private final Line verticalLine = new Line();
+
+	/** Constructor. */
+	public ExpandCollapseWidget(double x, double y, double w, double h, double inset) {
+		update(x, y, w, h, inset);
+
+		ObservableList<Node> c = getChildren();
+		c.add(borderRect);
+		c.add(horizontalLine);
+
+		setColor(BLACK, TRANSPARENT);
+	}
+
+	/** Updates the location, size and insets of the widget. */
+	public void update(double x, double y, double w, double h, double inset) {
+		borderRect.setX(x);
+		borderRect.setY(y);
+		borderRect.setWidth(w);
+		borderRect.setHeight(h);
+
+		horizontalLine.setStartX(x + inset);
+		horizontalLine.setEndX(x + w - inset);
+		horizontalLine.setStartY(y + h / 2);
+		horizontalLine.setEndY(y + h / 2);
+
+		verticalLine.setStartX(x + w / 2);
+		verticalLine.setEndX(x + w / 2);
+		verticalLine.setStartY(y + inset);
+		verticalLine.setEndY(y + h - inset);
+	}
+
+	/** Sets the state of the widget. */
+	public void setState(boolean expanded) {
+		if(expanded) {
+			getChildren().remove(verticalLine);
+		} else if(verticalLine.getParent() != this) {
+			// avoid duplicate add
+			getChildren().add(verticalLine);
+		}
+	}
+
+	/** Sets the fill of the rectangle and the stroke color. */
+	public void setColor(Paint stroke, Paint fill) {
+		borderRect.setFill(fill);
+		borderRect.setStroke(stroke);
+		verticalLine.setStroke(stroke);
+		horizontalLine.setStroke(stroke);
+	}
+
+	/** Determines if the given node is this widget or a part of it. */
+	public boolean isHit(Node hit) {
+		return hit == this || hit.getParent() == this;
+	}
+}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/widgets/LinkArrowWidget.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/widgets/LinkArrowWidget.java
new file mode 100644
index 0000000000000000000000000000000000000000..5354f14ca9d53cc3df88afb1867a266dfde65199
--- /dev/null
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/visual/widgets/LinkArrowWidget.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (c) 2019, 2020 fortiss GmbH. 
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+package org.fortiss.tooling.common.ui.javafx.lwfxef.visual.widgets;
+
+import static java.lang.Math.atan2;
+import static java.lang.Math.cos;
+import static java.lang.Math.sin;
+import static javafx.scene.paint.Color.BLACK;
+
+import javafx.collections.ObservableList;
+import javafx.scene.Group;
+import javafx.scene.Node;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Line;
+import javafx.scene.shape.LineTo;
+import javafx.scene.shape.MoveTo;
+import javafx.scene.shape.Path;
+import javafx.scene.shape.PathElement;
+
+/** A widget for displaying an arrow at the end of links. */
+public final class LinkArrowWidget extends Group {
+	/** The boolean flag for using line or filled triangle arrow head. */
+	private final boolean useLineArrows;
+	/** The length of the arrow head. */
+	private double arrowLength;
+
+	/** Constructor. */
+	public LinkArrowWidget(boolean useLineArrows, double anchorX, double anchorY, double directionX,
+			double directionY, double arrowLength) {
+		this.useLineArrows = useLineArrows;
+		this.arrowLength = arrowLength;
+		if(useLineArrows) {
+			addLines(anchorX, anchorY, directionX, directionY);
+		} else {
+			addFilledArrow(anchorX, anchorY, directionX, directionY);
+		}
+	}
+
+	/** Fills the group with a filled arrow widget. */
+	private void addFilledArrow(double anchorX, double anchorY, double directionX,
+			double directionY) {
+		ObservableList<Node> children = getChildren();
+		children.clear();
+		Path p = new Path();
+		p.setFill(BLACK);
+		p.setStrokeWidth(1);
+		children.add(p);
+		updatePath(p, anchorX, anchorY, directionX, directionY);
+	}
+
+	/** Updates the {@link Path} primitive for the filled arrow head. */
+	private void updatePath(Path p, double anchorX, double anchorY, double directionX,
+			double directionY) {
+		double angle = atan2(directionX - anchorX, directionY - anchorY);
+		double angleLeft = angle - Math.PI / 6;
+		double lx = anchorX + sin(angleLeft) * arrowLength;
+		double ly = anchorY + cos(angleLeft) * arrowLength;
+		double angleRight = angle + Math.PI / 6;
+		double rx = anchorX + sin(angleRight) * arrowLength;
+		double ry = anchorY + cos(angleRight) * arrowLength;
+
+		ObservableList<PathElement> l = p.getElements();
+		l.clear();
+		l.add(new MoveTo(anchorX, anchorY));
+		l.add(new LineTo(lx, ly));
+		l.add(new LineTo(rx, ry));
+		l.add(new LineTo(anchorX, anchorY));
+	}
+
+	/** Fills the group with two lines forming the arrow head. */
+	private void addLines(double anchorX, double anchorY, double directionX, double directionY) {
+		ObservableList<Node> children = getChildren();
+		children.clear();
+		Line leftLine = new Line(0, 0, 0, 0);
+		children.add(leftLine);
+		Line rightLine = new Line(0, 0, 0, 0);
+		children.add(rightLine);
+		updateLines(anchorX, anchorY, directionX, directionY);
+	}
+
+	/** Updates the locations of the widget's lines */
+	private void updateLines(double anchorX, double anchorY, double directionX, double directionY) {
+		// get(0) and cast works: see addLines(...)
+		Line left = (Line)getChildren().get(0);
+		double angle = atan2(directionX - anchorX, directionY - anchorY);
+		double angleLeft = angle - Math.PI / 4;
+		double lx = anchorX + sin(angleLeft) * arrowLength;
+		double ly = anchorY + cos(angleLeft) * arrowLength;
+		left.setStartX(anchorX);
+		left.setStartY(anchorY);
+		left.setEndX(lx);
+		left.setEndY(ly);
+
+		// get(1) and cast works: see addLines(...)
+		Line right = (Line)getChildren().get(1);
+		double angleRight = angle + Math.PI / 4;
+		double rx = anchorX + sin(angleRight) * arrowLength;
+		double ry = anchorY + cos(angleRight) * arrowLength;
+		right.setStartX(anchorX);
+		right.setStartY(anchorY);
+		right.setEndX(rx);
+		right.setEndY(ry);
+	}
+
+	/** Updates the arrow head with the new locations. */
+	public void update(double anchorX, double anchorY, double directionX, double directionY,
+			double arrowLength) {
+		this.arrowLength = arrowLength;
+		if(useLineArrows) {
+			updateLines(anchorX, anchorY, directionX, directionY);
+		} else {
+			// get(0) and cast works: see AddFilledArrow(...)
+			Path p = (Path)getChildren().get(0);
+			updatePath(p, anchorX, anchorY, directionX, directionY);
+		}
+	}
+
+	/** Sets the color of the arrow widget. */
+	public void setColor(Paint color) {
+		if(useLineArrows) {
+			// get(0) and cast works: see addLines(...)
+			((Line)getChildren().get(0)).setStroke(color);
+			// get(1) and cast works: see addLines(...)
+			((Line)getChildren().get(1)).setStroke(color);
+		} else {
+			// get(0) and cast works: see AddFilledArrow(...)
+			Path p = (Path)getChildren().get(0);
+			p.setStroke(color);
+			p.setFill(color);
+		}
+	}
+}
diff --git a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/.ratings b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/.ratings
index e3dae57d37ce91f7898d85eb9774c665040a8e9f..62c4ab516cd12fe289a932734684368e00f044be 100644
--- a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/.ratings
+++ b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/.ratings
@@ -3,7 +3,7 @@ IContextMenuContributor.java 0f09c76662c154cf52ddab61b417e82a42854162 GREEN
 IContextMenuMultiSelectionContributor.java 125b31dd38009bc2095b7e6bc860e946e39f58c4 GREEN
 IEditPartFactory.java 5729715847f553d95a5bad4a9211c7e6f458badd GREEN
 IModelEditor.java 962d7f7758abc88bbc6064c8b4eb32da00abf8e8 GREEN
-IModelEditorBinding.java d6896569cfe7eb0d7f0b8e4a71573c50e8c75329 GREEN
+IModelEditorBinding.java 844865d93252b6c4a648c23ff28bb28fd42c17aa YELLOW
 IModelElementHandler.java 21b4a96251e0267f156b4b8f2b95a97f6e81e646 GREEN
 ITutorialStepUI.java b8aee2b95857484ab6ad9ecd55b5de9f0ea158e5 GREEN
 ITutorialUIProvider.java aa0ff5db4d7ba0953e34edeb99f3e8279567e18f GREEN
diff --git a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/IModelEditorBinding.java b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/IModelEditorBinding.java
index d6896569cfe7eb0d7f0b8e4a71573c50e8c75329..844865d93252b6c4a648c23ff28bb28fd42c17aa 100644
--- a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/IModelEditorBinding.java
+++ b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/IModelEditorBinding.java
@@ -16,10 +16,10 @@
 package org.fortiss.tooling.kernel.ui.extension;
 
 import org.eclipse.emf.ecore.EObject;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.IControllerFactory;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.IModelFactory;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.IVisualFactory;
 import org.eclipse.ui.IEditorPart;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IControllerFactory;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelFactory;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisualFactory;
 import org.fortiss.tooling.kernel.service.base.IEObjectAware;
 import org.fortiss.tooling.kernel.ui.service.IModelEditorBindingService;
 
diff --git a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/.ratings b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/.ratings
index 3776fb18b863e417da1104e086324e0e0cd6c1fc..3747d405583494626be4fdece7f4d8e8214f2f49 100644
--- a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/.ratings
+++ b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/.ratings
@@ -6,8 +6,8 @@ EReferencePropertySectionBase.java 0548da6778516003257f59d0b4c2b60d458be3b6 GREE
 EditorBase.java 9c09fff92945256bb8680992ae7bb2c78f47b150 GREEN
 FXEditorBase.java 2e520be0bbae7d0aebdff70218a124dbe0896ce2 GREEN
 IListPropertySection.java 8bb00fe7959583e794ff9437b7a77404c9a9e70f GREEN
-LWFXEFEditorBase.java 2bd06235f20c18dc2e7d433700b2ad74a16664e2 GREEN
-ModelEditorBindingBase.java c258cb0ea28d74440856cd2abf367408fbbc1279 GREEN
+LWFXEFEditorBase.java 72a0735d0a516e53f34e485039471512c8ac6577 YELLOW
+ModelEditorBindingBase.java b9b1a1c5a48a6e677d1f57ad55a6126d9703c4b5 YELLOW
 ModelElementHandlerBase.java 384727748f125c9d43f19d9c0eba4ba1be5a7a26 GREEN
 MultiEObjectActionBase.java 9e237d8ea640c4194e4877af4a9cfce88698e543 GREEN
 NamedCommentedModelElementHandlerBase.java 681b98b50b362f01abb7a36f108f4f11b9e51829 GREEN
diff --git a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/LWFXEFEditorBase.java b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/LWFXEFEditorBase.java
index 2bd06235f20c18dc2e7d433700b2ad74a16664e2..72a0735d0a516e53f34e485039471512c8ac6577 100644
--- a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/LWFXEFEditorBase.java
+++ b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/LWFXEFEditorBase.java
@@ -28,14 +28,14 @@ import org.eclipse.jface.viewers.ISelectionProvider;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.jface.viewers.SelectionChangedEvent;
 import org.eclipse.jface.viewers.StructuredSelection;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramViewer;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.DiagramViewerSelection;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.change.Change;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.IControllerFactory;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.IModelFactory;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.IVisualFactory;
 import org.eclipse.ui.IEditorPart;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewer;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerSelection;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.change.Change;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IControllerFactory;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelFactory;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisualFactory;
 import org.fortiss.tooling.kernel.service.ICommandStackService;
 import org.fortiss.tooling.kernel.ui.extension.IModelEditorBinding;
 import org.fortiss.tooling.kernel.ui.extension.base.factory.DelegatingControllerFactory;
diff --git a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/ModelEditorBindingBase.java b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/ModelEditorBindingBase.java
index c258cb0ea28d74440856cd2abf367408fbbc1279..b9b1a1c5a48a6e677d1f57ad55a6126d9703c4b5 100644
--- a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/ModelEditorBindingBase.java
+++ b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/ModelEditorBindingBase.java
@@ -16,9 +16,9 @@
 package org.fortiss.tooling.kernel.ui.extension.base;
 
 import org.eclipse.emf.ecore.EObject;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.IControllerFactory;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.IModelFactory;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.IVisualFactory;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IControllerFactory;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelFactory;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisualFactory;
 import org.fortiss.tooling.kernel.service.base.IEObjectAware;
 import org.fortiss.tooling.kernel.ui.extension.IModelEditorBinding;
 import org.fortiss.tooling.kernel.ui.service.IModelEditorBindingService;
diff --git a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/factory/.ratings b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/factory/.ratings
index 75b475f75083ac3976a4740cc7fc19976f96542c..5d72fbf78d3b4ebb888b9c985348f093a1e4bc05 100644
--- a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/factory/.ratings
+++ b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/factory/.ratings
@@ -1,4 +1,4 @@
-DelegatingControllerFactory.java ad214d83b5821b39862b7c382c91a13c3dfddbd0 GREEN
+DelegatingControllerFactory.java be19395684645d79d892e5caed079d4b7f5cb5b0 YELLOW
 DelegatingFactoryBase.java f421742267610f41bb6196346026d2f239d90ed0 GREEN
-DelegatingModelFactory.java 717b706781879efe9efcb5ce4bf53723e39a3e1b GREEN
-DelegatingVisualFactory.java 7e834acd12ae4d1c2b2b32a5456dc9f2b6d4e466 GREEN
+DelegatingModelFactory.java f94ca989a5b97ad3f073040bb46b9c0a87e35d6d YELLOW
+DelegatingVisualFactory.java 8bb82e9d48b2577655e577e7e807629c6fd6f2db YELLOW
diff --git a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/factory/DelegatingControllerFactory.java b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/factory/DelegatingControllerFactory.java
index ad214d83b5821b39862b7c382c91a13c3dfddbd0..be19395684645d79d892e5caed079d4b7f5cb5b0 100644
--- a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/factory/DelegatingControllerFactory.java
+++ b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/factory/DelegatingControllerFactory.java
@@ -18,14 +18,14 @@ package org.fortiss.tooling.kernel.ui.extension.base.factory;
 import java.util.List;
 import java.util.Optional;
 
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.IController;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.controller.IControllerFactory;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IDiagramAnchorageMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IDiagramMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IController;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.controller.IControllerFactory;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
 
 /**
  * Delegates the creational calls to concrete {@link IControllerFactory}s. The first non-null
diff --git a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/factory/DelegatingModelFactory.java b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/factory/DelegatingModelFactory.java
index 717b706781879efe9efcb5ce4bf53723e39a3e1b..f94ca989a5b97ad3f073040bb46b9c0a87e35d6d 100644
--- a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/factory/DelegatingModelFactory.java
+++ b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/factory/DelegatingModelFactory.java
@@ -28,7 +28,7 @@ import java.util.List;
 import java.util.Optional;
 
 import org.eclipse.emf.ecore.EObject;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.model.IModelFactory;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.model.IModelFactory;
 import org.fortiss.tooling.kernel.ui.ToolingKernelUIActivator;
 
 /**
diff --git a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/factory/DelegatingVisualFactory.java b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/factory/DelegatingVisualFactory.java
index 7e834acd12ae4d1c2b2b32a5456dc9f2b6d4e466..8bb82e9d48b2577655e577e7e807629c6fd6f2db 100644
--- a/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/factory/DelegatingVisualFactory.java
+++ b/org.fortiss.tooling.kernel.ui/src/org/fortiss/tooling/kernel/ui/extension/base/factory/DelegatingVisualFactory.java
@@ -17,16 +17,16 @@ package org.fortiss.tooling.kernel.ui.extension.base.factory;
 
 import java.util.List;
 
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.IDiagramAnchorageMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.IContentAnchorageVisual;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.IContentVisual;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.IDiagramAnchorageVisual;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.ILinkVisual;
-import org.eclipse.systemfocus.kernel.common.ui.javafx.lwfxef.visual.IVisualFactory;
 import org.eclipse.ui.IEditorPart;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IContentMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IDiagramAnchorageMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.ILinkMVCBundle;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentAnchorageVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IContentVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IDiagramAnchorageVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.ILinkVisual;
+import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisualFactory;
 import org.fortiss.tooling.kernel.service.ITransformationService;
 import org.fortiss.tooling.kernel.ui.extension.base.LWFXEFEditorBase;