From b21a83d8581a2607bd34ef6ff9c64ca63b774625 Mon Sep 17 00:00:00 2001
From: Simon Barner <barner@fortiss.org>
Date: Mon, 11 Jun 2018 13:13:38 +0200
Subject: [PATCH] Automatic Layout: Consider size of element and connection
 labels

* Add IAutoLayouterTapeMeasure interface to estimate size of
  elements, connectors, and connections
* Add DiagramTapeMeasure (SWT-based implementation for diagrams)
 * Ensures that layouted element is opened in diagram editor
 * Uses SWT GraphicsContext (GC) to estime size requirements of
   text labels

Issue-Ref: 3433
Issue-Url: https://af3-developer.fortiss.org/issues/3433

Signed-off-by: Simon Barner <barner@fortiss.org>
---
 .../tooling/base/ui/layout/auto/.ratings      |   4 +-
 .../ui/layout/auto/DiagramTapeMeasure.java    | 145 ++++++++++++++++++
 .../layout/auto/IAutoLayouterTapeMeasure.java |  43 ++++++
 .../ui/layout/auto/KielerAutoLayouter.java    |  72 +++++++--
 4 files changed, 246 insertions(+), 18 deletions(-)
 create mode 100644 org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/layout/auto/DiagramTapeMeasure.java
 create mode 100644 org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/layout/auto/IAutoLayouterTapeMeasure.java

diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/layout/auto/.ratings b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/layout/auto/.ratings
index 740f31903..ace0e86f8 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/layout/auto/.ratings
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/layout/auto/.ratings
@@ -1,3 +1,5 @@
 AutoLayoutMenu.java ef3b897cc2fd99cf9ce201f03cffea036555e3ac GREEN
+DiagramTapeMeasure.java 72454e6fe5225dab11d3d691baad93aab7a171c0 YELLOW
 IAutoLayouter.java de1b11d9e202c7e23352ad85684dbf8a3fd17c7d GREEN
-KielerAutoLayouter.java fae8692ca0131a6d14cff768aba446b7b35512d6 GREEN
+IAutoLayouterTapeMeasure.java df186e0ba505e0ecda211b1df76cf12f3245b47e YELLOW
+KielerAutoLayouter.java 2fe3bf63213245d6900c42be172347f863f2f198 YELLOW
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/layout/auto/DiagramTapeMeasure.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/layout/auto/DiagramTapeMeasure.java
new file mode 100644
index 000000000..72454e6fe
--- /dev/null
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/layout/auto/DiagramTapeMeasure.java
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------+
+| Copyright 2018 fortiss GmbH                                              |
+|                                                                          |
+| Licensed under the Apache License, Version 2.0 (the "License");          |
+| you may not use this file except in compliance with the License.         |
+| You may obtain a copy of the License at                                  |
+|                                                                          |
+|    http://www.apache.org/licenses/LICENSE-2.0                            |
+|                                                                          |
+| Unless required by applicable law or agreed to in writing, software      |
+| distributed under the License is distributed on an "AS IS" BASIS,        |
+| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+| See the License for the specific language governing permissions and      |
+| limitations under the License.                                           |
++--------------------------------------------------------------------------*/
+package org.fortiss.tooling.base.ui.layout.auto;
+
+import static java.lang.Math.max;
+import static org.fortiss.tooling.base.layout.DefaultLayoutConstants.DEFAULT_CONNECTOR_SIZE;
+import static org.fortiss.tooling.base.layout.DefaultLayoutConstants.DEFAULT_GRID_SIZE;
+import static org.fortiss.tooling.base.layout.DefaultLayoutConstants.DEFAULT_SHAPE_MINIMUM_WIDTH;
+import static org.fortiss.tooling.base.utils.LayoutDataUtils.getNodeSize;
+import static org.fortiss.tooling.kernel.ui.util.KernelUIUtils.getName;
+import static org.fortiss.tooling.kernel.utils.EcoreUtils.pickInstanceOf;
+
+import org.eclipse.draw2d.IFigure;
+import org.eclipse.draw2d.Label;
+import org.eclipse.gef.EditPartViewer;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.fortiss.tooling.base.model.base.EntryConnectorBase;
+import org.fortiss.tooling.base.model.element.IConnection;
+import org.fortiss.tooling.base.model.element.IConnector;
+import org.fortiss.tooling.base.model.element.IHierarchicElement;
+import org.fortiss.tooling.base.model.element.IModelElement;
+import org.fortiss.tooling.base.model.layout.Dimension;
+import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
+import org.fortiss.tooling.base.ui.editor.DiagramEditorBase;
+import org.fortiss.tooling.base.ui.editpart.ConnectionEditPartBase;
+import org.fortiss.tooling.base.ui.editpart.ElementEditPartBase;
+
+/**
+ * {@link IAutoLayouterTapeMeasure} specialization to for automatic layout of models to be displayed
+ * in {@link DiagramEditorBase}-based editors.
+ * 
+ * @author barner
+ */
+public class DiagramTapeMeasure implements IAutoLayouterTapeMeasure {
+
+	/** {@link EditPartViewer} used to display the underlying diagram. */
+	private EditPartViewer diagramEpv;
+
+	/** Underlying GC. */
+	private GC gc;
+
+	/** Constructor. */
+	public DiagramTapeMeasure(EditPartViewer epv, GC gc) {
+		this.diagramEpv = epv;
+		this.gc = gc;
+	}
+
+	/** Returns the {@code Font} used by the given {@code element} (may be {@code null}). */
+	private Font getFont(IModelElement element) {
+		if(diagramEpv == null) {
+			return null;
+		}
+		Object ep = diagramEpv.getEditPartRegistry().get(element);
+		if(ep instanceof ElementEditPartBase) {
+			Label label = ((ElementEditPartBase<?>)ep).createLabelFigure();
+			if(label != null) {
+				return label.getFont();
+			}
+		} else if(ep instanceof ConnectionEditPartBase) {
+			IFigure figure = ((ConnectionEditPartBase<?>)ep).getFigure();
+			if(figure != null) {
+				return figure.getFont();
+			}
+		}
+
+		return null;
+	}
+
+	/** Returns the text extend of the given {@code element}'s label (may be {@code null}). */
+	private Point getTextExtent(IModelElement element) {
+		if(gc == null) {
+			return null;
+		}
+
+		String name = getName(element);
+		if(name == null) {
+			return null;
+		}
+
+		gc.setFont(getFont(element));
+		Point rval = gc.textExtent(name);
+
+		return rval;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public int getElementWidth(IHierarchicElement element) {
+		Dimension dimension = getNodeSize((ILayoutedModelElement)element);
+
+		int width = max(DEFAULT_SHAPE_MINIMUM_WIDTH, dimension.getWidth());
+		Point textExtend = getTextExtent(element);
+		if(textExtend != null) {
+			width = max(width, textExtend.x + 6 * DEFAULT_GRID_SIZE);
+		}
+
+		return width;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public int getElementHeight(IHierarchicElement element) {
+		Dimension dimension = getNodeSize((ILayoutedModelElement)element);
+		int numElemConnectors = element.getConnectors().size();
+		int numElemInputConnectors =
+				pickInstanceOf(EntryConnectorBase.class, element.getConnectors()).size();
+		int numConnectors = max(numElemInputConnectors, numElemConnectors - numElemInputConnectors);
+
+		return max((1 + 2 * numConnectors) * DEFAULT_CONNECTOR_SIZE, dimension.getHeight());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public int getConnectorWidth(IConnector connector) {
+		return 2 * DEFAULT_CONNECTOR_SIZE;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public int getConnectorHeight(IConnector connector) {
+		return 2 * DEFAULT_CONNECTOR_SIZE;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public int getConnectionWidth(IConnection connection) {
+		Point textExtend = getTextExtent(connection);
+		return textExtend != null ? textExtend.x : 0;
+	}
+}
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/layout/auto/IAutoLayouterTapeMeasure.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/layout/auto/IAutoLayouterTapeMeasure.java
new file mode 100644
index 000000000..df186e0ba
--- /dev/null
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/layout/auto/IAutoLayouterTapeMeasure.java
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------+
+| Copyright 2018 fortiss GmbH                                              |
+|                                                                          |
+| Licensed under the Apache License, Version 2.0 (the "License");          |
+| you may not use this file except in compliance with the License.         |
+| You may obtain a copy of the License at                                  |
+|                                                                          |
+|    http://www.apache.org/licenses/LICENSE-2.0                            |
+|                                                                          |
+| Unless required by applicable law or agreed to in writing, software      |
+| distributed under the License is distributed on an "AS IS" BASIS,        |
+| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+| See the License for the specific language governing permissions and      |
+| limitations under the License.                                           |
++--------------------------------------------------------------------------*/
+package org.fortiss.tooling.base.ui.layout.auto;
+
+import org.fortiss.tooling.base.model.element.IConnection;
+import org.fortiss.tooling.base.model.element.IConnector;
+import org.fortiss.tooling.base.model.element.IHierarchicElement;
+
+/**
+ * Interface used by {@link IAutoLayouter} to estimate dimensions requirements of elements (for the
+ * respective editor for which the automatic layout is performed).
+ * 
+ * @author barner
+ */
+public interface IAutoLayouterTapeMeasure {
+	/** Estimates the width requirements of the given element. */
+	public int getElementWidth(IHierarchicElement element);
+
+	/** Estimates the height requirements of the given element. */
+	public int getElementHeight(IHierarchicElement element);
+
+	/** Estimates the width requirements of the given connector. */
+	public int getConnectorWidth(IConnector connector);
+
+	/** Estimates the height requirements of the given connector. */
+	public int getConnectorHeight(IConnector connector);
+
+	/** Estimates the width requirements of the given connection. */
+	public int getConnectionWidth(IConnection connection);
+}
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/layout/auto/KielerAutoLayouter.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/layout/auto/KielerAutoLayouter.java
index fae8692ca..2fe3bf632 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/layout/auto/KielerAutoLayouter.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/layout/auto/KielerAutoLayouter.java
@@ -20,22 +20,22 @@ import static de.cau.cs.kieler.kiml.options.LayoutOptions.DIRECTION;
 import static de.cau.cs.kieler.kiml.options.LayoutOptions.INTERACTIVE;
 import static de.cau.cs.kieler.kiml.options.LayoutOptions.PORT_SIDE;
 import static de.cau.cs.kieler.kiml.util.KimlUtil.createInitializedEdge;
+import static de.cau.cs.kieler.kiml.util.KimlUtil.createInitializedLabel;
 import static de.cau.cs.kieler.kiml.util.KimlUtil.createInitializedNode;
 import static de.cau.cs.kieler.kiml.util.KimlUtil.createInitializedPort;
 import static de.cau.cs.kieler.klay.layered.properties.InteractiveReferencePoint.TOP_LEFT;
 import static de.cau.cs.kieler.klay.layered.properties.Properties.FEEDBACK_EDGES;
 import static de.cau.cs.kieler.klay.layered.properties.Properties.INTERACTIVE_REFERENCE_POINT;
-import static java.lang.Math.max;
+import static java.lang.Thread.sleep;
 import static org.fortiss.tooling.base.layout.DefaultLayoutConstants.DEFAULT_CONNECTOR_SIZE;
 import static org.fortiss.tooling.base.layout.DefaultLayoutConstants.DEFAULT_GRID_SIZE;
-import static org.fortiss.tooling.base.layout.DefaultLayoutConstants.DEFAULT_SHAPE_MINIMUM_WIDTH;
 import static org.fortiss.tooling.base.ui.annotation.view.AnnotationViewPartBase.isUpdateEnabled;
 import static org.fortiss.tooling.base.ui.annotation.view.AnnotationViewPartBase.setUpdateEnabled;
 import static org.fortiss.tooling.base.ui.utils.LayoutDataUIUtils.addBendPointToConnection;
-import static org.fortiss.tooling.base.utils.LayoutDataUtils.getNodeSize;
 import static org.fortiss.tooling.base.utils.LayoutDataUtils.setNodeLayoutData;
 import static org.fortiss.tooling.base.utils.LayoutDataUtils.setNodePosition;
 import static org.fortiss.tooling.base.utils.LayoutDataUtils.setStickyConnectorLayoutData;
+import static org.fortiss.tooling.kernel.utils.LoggingUtils.error;
 
 import java.util.Map.Entry;
 
@@ -43,23 +43,30 @@ import org.eclipse.emf.common.util.BasicEList;
 import org.eclipse.emf.common.util.BasicEMap;
 import org.eclipse.emf.common.util.EList;
 import org.eclipse.emf.common.util.EMap;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.gef.EditPartViewer;
+import org.eclipse.swt.graphics.GC;
 import org.fortiss.tooling.base.model.base.EntryConnectorBase;
 import org.fortiss.tooling.base.model.base.ExitConnectorBase;
 import org.fortiss.tooling.base.model.element.IConnection;
 import org.fortiss.tooling.base.model.element.IConnector;
 import org.fortiss.tooling.base.model.element.IHierarchicElement;
-import org.fortiss.tooling.base.model.layout.Dimension;
 import org.fortiss.tooling.base.model.layout.EOrientation;
 import org.fortiss.tooling.base.model.layout.ILayoutData;
 import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
 import org.fortiss.tooling.base.model.layout.Points;
 import org.fortiss.tooling.base.model.layout.impl.PointsImpl;
+import org.fortiss.tooling.base.ui.ToolingBaseUIActivator;
+import org.fortiss.tooling.base.ui.editor.DiagramEditorBase;
 import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
 import org.fortiss.tooling.kernel.service.IPersistencyService;
+import org.fortiss.tooling.kernel.ui.extension.IModelEditor;
+import org.fortiss.tooling.kernel.ui.service.IModelEditorBindingService;
 
 import de.cau.cs.kieler.core.alg.BasicProgressMonitor;
 import de.cau.cs.kieler.core.alg.IKielerProgressMonitor;
 import de.cau.cs.kieler.core.kgraph.KEdge;
+import de.cau.cs.kieler.core.kgraph.KLabel;
 import de.cau.cs.kieler.core.kgraph.KNode;
 import de.cau.cs.kieler.core.kgraph.KPort;
 import de.cau.cs.kieler.kiml.AbstractLayoutProvider;
@@ -278,6 +285,28 @@ public class KielerAutoLayouter implements IAutoLayouter {
 		}
 	}
 
+	/**
+	 * Opens the given {@link IHierarchicElement} in an editor. Returns the editor in case it is a
+	 * {@link DiagramEditorBase}-based, {@code null} otherwise.
+	 */
+	private EditPartViewer getDiagramEditPartViewer(IHierarchicElement element) {
+		IModelEditorBindingService es = IModelEditorBindingService.getInstance();
+		es.openInEditor(element);
+		while(!es.isOpen(element)) {
+			try {
+				sleep(100);
+			} catch(Exception e) {
+				error(ToolingBaseUIActivator.getDefault(), "Error opening editor.", e);
+				break;
+			}
+		}
+
+		IModelEditor<EObject> editor = es.getActiveEditor();
+		return editor instanceof DiagramEditorBase ? ((DiagramEditorBase<?>)editor).getViewer()
+				: null;
+
+	}
+
 	/**
 	 * Creates a KIELER {@link KNode} graph with the same structure as the given
 	 * {@link IHierarchicElement}.
@@ -298,21 +327,22 @@ public class KielerAutoLayouter implements IAutoLayouter {
 		connectionsToKEdges = new BasicEMap<IConnection, KEdge>();
 		undirectedConnectorsToKPorts = new BasicEMap<IConnector, KPort>();
 
+		EditPartViewer viewer = getDiagramEditPartViewer(hierarchicElement);
+		GC gc = viewer != null ? new GC(viewer.getControl()) : null;
+		IAutoLayouterTapeMeasure tapeMeasure = new DiagramTapeMeasure(viewer, gc);
+
 		// Create nodes
 		for(IHierarchicElement currentChild : hierarchicElement.getContainedElements()) {
 
 			if(!(currentChild instanceof ILayoutedModelElement)) {
 				continue;
 			}
-
 			KNode kNode = createInitializedNode();
-
 			KShapeLayout kNodeLayout = kNode.getData(KShapeLayout.class);
 
 			EList<IConnector> inputConnectors = new BasicEList<IConnector>();
 			EList<IConnector> outputConnectors = new BasicEList<IConnector>();
 			EList<IConnector> undirectedConnectors = new BasicEList<IConnector>();
-
 			for(IConnector connector : currentChild.getConnectors()) {
 				if(connector instanceof EntryConnectorBase) {
 					inputConnectors.add(connector);
@@ -324,16 +354,14 @@ public class KielerAutoLayouter implements IAutoLayouter {
 				}
 			}
 
-			// Adjust size of node to number of connectors
-			Dimension dimension = getNodeSize((ILayoutedModelElement)currentChild);
-			int numConnectors = max(inputConnectors.size(), outputConnectors.size());
-			kNodeLayout.setHeight(max((1 + 2 * numConnectors) * DEFAULT_CONNECTOR_SIZE,
-					truncateSnap2Grid(dimension.getHeight(), true)));
-			kNodeLayout.setWidth(max(DEFAULT_SHAPE_MINIMUM_WIDTH,
-					truncateSnap2Grid(dimension.getWidth(), true)));
-
+			// Adjust size of node to number of connectors and label size
+			int width = tapeMeasure.getElementWidth(currentChild);
+			kNodeLayout.setWidth(truncateSnap2Grid(width, true));
+			int height = tapeMeasure.getElementHeight(currentChild);
+			kNodeLayout.setHeight(truncateSnap2Grid(height, true));
 			kNode.setParent(rootNode);
 			modelElementsToKNodes.put(currentChild, kNode);
+
 			for(IConnector connector : outputConnectors) {
 				KPort k = createKPortFromIConnector(connector, kNode, outboundConnectorsToKPorts);
 				if(undirectedConnectors.contains(connector)) {
@@ -352,8 +380,8 @@ public class KielerAutoLayouter implements IAutoLayouter {
 		for(IConnector connector : hierarchicElement.getConnectors()) {
 			KNode kNodeVirtual = createInitializedNode();
 			KShapeLayout kNodeVirtualLayout = kNodeVirtual.getData(KShapeLayout.class);
-			kNodeVirtualLayout.setHeight(DEFAULT_CONNECTOR_SIZE);
-			kNodeVirtualLayout.setWidth(2 * DEFAULT_CONNECTOR_SIZE);
+			kNodeVirtualLayout.setWidth(tapeMeasure.getConnectorWidth(connector));
+			kNodeVirtualLayout.setHeight(tapeMeasure.getConnectorHeight(connector));
 			kNodeVirtual.setParent(rootNode);
 
 			KPort kPort = createInitializedPort();
@@ -374,6 +402,12 @@ public class KielerAutoLayouter implements IAutoLayouter {
 
 				kEdge.setSource(kSourcePort.getNode());
 				kEdge.setSourcePort(kSourcePort);
+
+				KLabel kLabel = createInitializedLabel(kEdge);
+				KShapeLayout kLabelLayout = kLabel.getData(KShapeLayout.class);
+				int width = tapeMeasure.getConnectionWidth(connection);
+				kLabelLayout.setWidth(truncateSnap2Grid(width, true));
+
 				kSourcePort.getEdges().add(kEdge);
 				KPort kTargetPort = inboundConnectorsToKPorts.get(connection.getTarget());
 				if(kTargetPort == null) {
@@ -388,6 +422,10 @@ public class KielerAutoLayouter implements IAutoLayouter {
 				connectionsToKEdges.put(connection, kEdge);
 			}
 		}
+
+		if(gc != null) {
+			gc.dispose();
+		}
 		return rootNode;
 	}
 
-- 
GitLab