Skip to content
Snippets Groups Projects
Commit b21a83d8 authored by Simon Barner's avatar Simon Barner
Browse files

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: default avatarSimon Barner <barner@fortiss.org>
parent 591ca881
Branches cbmd
No related tags found
1 merge request!63433 auto layouter label size
AutoLayoutMenu.java ef3b897cc2fd99cf9ce201f03cffea036555e3ac GREEN AutoLayoutMenu.java ef3b897cc2fd99cf9ce201f03cffea036555e3ac GREEN
DiagramTapeMeasure.java 72454e6fe5225dab11d3d691baad93aab7a171c0 YELLOW
IAutoLayouter.java de1b11d9e202c7e23352ad85684dbf8a3fd17c7d GREEN IAutoLayouter.java de1b11d9e202c7e23352ad85684dbf8a3fd17c7d GREEN
KielerAutoLayouter.java fae8692ca0131a6d14cff768aba446b7b35512d6 GREEN IAutoLayouterTapeMeasure.java df186e0ba505e0ecda211b1df76cf12f3245b47e YELLOW
KielerAutoLayouter.java 2fe3bf63213245d6900c42be172347f863f2f198 YELLOW
/*-------------------------------------------------------------------------+
| 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;
}
}
/*-------------------------------------------------------------------------+
| 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);
}
...@@ -20,22 +20,22 @@ import static de.cau.cs.kieler.kiml.options.LayoutOptions.DIRECTION; ...@@ -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.INTERACTIVE;
import static de.cau.cs.kieler.kiml.options.LayoutOptions.PORT_SIDE; 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.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.createInitializedNode;
import static de.cau.cs.kieler.kiml.util.KimlUtil.createInitializedPort; 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.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.FEEDBACK_EDGES;
import static de.cau.cs.kieler.klay.layered.properties.Properties.INTERACTIVE_REFERENCE_POINT; 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_CONNECTOR_SIZE;
import static org.fortiss.tooling.base.layout.DefaultLayoutConstants.DEFAULT_GRID_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.isUpdateEnabled;
import static org.fortiss.tooling.base.ui.annotation.view.AnnotationViewPartBase.setUpdateEnabled; 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.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.setNodeLayoutData;
import static org.fortiss.tooling.base.utils.LayoutDataUtils.setNodePosition; import static org.fortiss.tooling.base.utils.LayoutDataUtils.setNodePosition;
import static org.fortiss.tooling.base.utils.LayoutDataUtils.setStickyConnectorLayoutData; import static org.fortiss.tooling.base.utils.LayoutDataUtils.setStickyConnectorLayoutData;
import static org.fortiss.tooling.kernel.utils.LoggingUtils.error;
import java.util.Map.Entry; import java.util.Map.Entry;
...@@ -43,23 +43,30 @@ import org.eclipse.emf.common.util.BasicEList; ...@@ -43,23 +43,30 @@ import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.BasicEMap; import org.eclipse.emf.common.util.BasicEMap;
import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.EMap; 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.EntryConnectorBase;
import org.fortiss.tooling.base.model.base.ExitConnectorBase; import org.fortiss.tooling.base.model.base.ExitConnectorBase;
import org.fortiss.tooling.base.model.element.IConnection; import org.fortiss.tooling.base.model.element.IConnection;
import org.fortiss.tooling.base.model.element.IConnector; import org.fortiss.tooling.base.model.element.IConnector;
import org.fortiss.tooling.base.model.element.IHierarchicElement; 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.EOrientation;
import org.fortiss.tooling.base.model.layout.ILayoutData; import org.fortiss.tooling.base.model.layout.ILayoutData;
import org.fortiss.tooling.base.model.layout.ILayoutedModelElement; import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
import org.fortiss.tooling.base.model.layout.Points; import org.fortiss.tooling.base.model.layout.Points;
import org.fortiss.tooling.base.model.layout.impl.PointsImpl; 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.extension.data.ITopLevelElement;
import org.fortiss.tooling.kernel.service.IPersistencyService; 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.BasicProgressMonitor;
import de.cau.cs.kieler.core.alg.IKielerProgressMonitor; import de.cau.cs.kieler.core.alg.IKielerProgressMonitor;
import de.cau.cs.kieler.core.kgraph.KEdge; 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.KNode;
import de.cau.cs.kieler.core.kgraph.KPort; import de.cau.cs.kieler.core.kgraph.KPort;
import de.cau.cs.kieler.kiml.AbstractLayoutProvider; import de.cau.cs.kieler.kiml.AbstractLayoutProvider;
...@@ -278,6 +285,28 @@ public class KielerAutoLayouter implements IAutoLayouter { ...@@ -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 * Creates a KIELER {@link KNode} graph with the same structure as the given
* {@link IHierarchicElement}. * {@link IHierarchicElement}.
...@@ -298,21 +327,22 @@ public class KielerAutoLayouter implements IAutoLayouter { ...@@ -298,21 +327,22 @@ public class KielerAutoLayouter implements IAutoLayouter {
connectionsToKEdges = new BasicEMap<IConnection, KEdge>(); connectionsToKEdges = new BasicEMap<IConnection, KEdge>();
undirectedConnectorsToKPorts = new BasicEMap<IConnector, KPort>(); 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 // Create nodes
for(IHierarchicElement currentChild : hierarchicElement.getContainedElements()) { for(IHierarchicElement currentChild : hierarchicElement.getContainedElements()) {
if(!(currentChild instanceof ILayoutedModelElement)) { if(!(currentChild instanceof ILayoutedModelElement)) {
continue; continue;
} }
KNode kNode = createInitializedNode(); KNode kNode = createInitializedNode();
KShapeLayout kNodeLayout = kNode.getData(KShapeLayout.class); KShapeLayout kNodeLayout = kNode.getData(KShapeLayout.class);
EList<IConnector> inputConnectors = new BasicEList<IConnector>(); EList<IConnector> inputConnectors = new BasicEList<IConnector>();
EList<IConnector> outputConnectors = new BasicEList<IConnector>(); EList<IConnector> outputConnectors = new BasicEList<IConnector>();
EList<IConnector> undirectedConnectors = new BasicEList<IConnector>(); EList<IConnector> undirectedConnectors = new BasicEList<IConnector>();
for(IConnector connector : currentChild.getConnectors()) { for(IConnector connector : currentChild.getConnectors()) {
if(connector instanceof EntryConnectorBase) { if(connector instanceof EntryConnectorBase) {
inputConnectors.add(connector); inputConnectors.add(connector);
...@@ -324,16 +354,14 @@ public class KielerAutoLayouter implements IAutoLayouter { ...@@ -324,16 +354,14 @@ public class KielerAutoLayouter implements IAutoLayouter {
} }
} }
// Adjust size of node to number of connectors // Adjust size of node to number of connectors and label size
Dimension dimension = getNodeSize((ILayoutedModelElement)currentChild); int width = tapeMeasure.getElementWidth(currentChild);
int numConnectors = max(inputConnectors.size(), outputConnectors.size()); kNodeLayout.setWidth(truncateSnap2Grid(width, true));
kNodeLayout.setHeight(max((1 + 2 * numConnectors) * DEFAULT_CONNECTOR_SIZE, int height = tapeMeasure.getElementHeight(currentChild);
truncateSnap2Grid(dimension.getHeight(), true))); kNodeLayout.setHeight(truncateSnap2Grid(height, true));
kNodeLayout.setWidth(max(DEFAULT_SHAPE_MINIMUM_WIDTH,
truncateSnap2Grid(dimension.getWidth(), true)));
kNode.setParent(rootNode); kNode.setParent(rootNode);
modelElementsToKNodes.put(currentChild, kNode); modelElementsToKNodes.put(currentChild, kNode);
for(IConnector connector : outputConnectors) { for(IConnector connector : outputConnectors) {
KPort k = createKPortFromIConnector(connector, kNode, outboundConnectorsToKPorts); KPort k = createKPortFromIConnector(connector, kNode, outboundConnectorsToKPorts);
if(undirectedConnectors.contains(connector)) { if(undirectedConnectors.contains(connector)) {
...@@ -352,8 +380,8 @@ public class KielerAutoLayouter implements IAutoLayouter { ...@@ -352,8 +380,8 @@ public class KielerAutoLayouter implements IAutoLayouter {
for(IConnector connector : hierarchicElement.getConnectors()) { for(IConnector connector : hierarchicElement.getConnectors()) {
KNode kNodeVirtual = createInitializedNode(); KNode kNodeVirtual = createInitializedNode();
KShapeLayout kNodeVirtualLayout = kNodeVirtual.getData(KShapeLayout.class); KShapeLayout kNodeVirtualLayout = kNodeVirtual.getData(KShapeLayout.class);
kNodeVirtualLayout.setHeight(DEFAULT_CONNECTOR_SIZE); kNodeVirtualLayout.setWidth(tapeMeasure.getConnectorWidth(connector));
kNodeVirtualLayout.setWidth(2 * DEFAULT_CONNECTOR_SIZE); kNodeVirtualLayout.setHeight(tapeMeasure.getConnectorHeight(connector));
kNodeVirtual.setParent(rootNode); kNodeVirtual.setParent(rootNode);
KPort kPort = createInitializedPort(); KPort kPort = createInitializedPort();
...@@ -374,6 +402,12 @@ public class KielerAutoLayouter implements IAutoLayouter { ...@@ -374,6 +402,12 @@ public class KielerAutoLayouter implements IAutoLayouter {
kEdge.setSource(kSourcePort.getNode()); kEdge.setSource(kSourcePort.getNode());
kEdge.setSourcePort(kSourcePort); 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); kSourcePort.getEdges().add(kEdge);
KPort kTargetPort = inboundConnectorsToKPorts.get(connection.getTarget()); KPort kTargetPort = inboundConnectorsToKPorts.get(connection.getTarget());
if(kTargetPort == null) { if(kTargetPort == null) {
...@@ -388,6 +422,10 @@ public class KielerAutoLayouter implements IAutoLayouter { ...@@ -388,6 +422,10 @@ public class KielerAutoLayouter implements IAutoLayouter {
connectionsToKEdges.put(connection, kEdge); connectionsToKEdges.put(connection, kEdge);
} }
} }
if(gc != null) {
gc.dispose();
}
return rootNode; return rootNode;
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment