/*******************************************************************************
* Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
*******************************************************************************/
package com.cisco.yangide.ext.model.editor.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.common.util.EList;
import org.eclipse.graphiti.datatypes.IDimension;
import org.eclipse.graphiti.features.IFeatureProvider;
import org.eclipse.graphiti.features.context.impl.AddBendpointContext;
import org.eclipse.graphiti.features.context.impl.LayoutContext;
import org.eclipse.graphiti.mm.algorithms.GraphicsAlgorithm;
import org.eclipse.graphiti.mm.algorithms.Polyline;
import org.eclipse.graphiti.mm.algorithms.Text;
import org.eclipse.graphiti.mm.algorithms.styles.Point;
import org.eclipse.graphiti.mm.pictograms.Connection;
import org.eclipse.graphiti.mm.pictograms.ContainerShape;
import org.eclipse.graphiti.mm.pictograms.FreeFormConnection;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.mm.pictograms.Shape;
import org.eclipse.graphiti.services.Graphiti;
import org.eclipse.graphiti.ui.services.GraphitiUi;
import com.cisco.yangide.ext.model.editor.diagram.EditorFeatureProvider;
import com.cisco.yangide.ext.model.editor.util.connection.Position;
import com.cisco.yangide.ext.model.editor.util.connection.RectilinearAvoidObstaclesPathFinder;
import com.cisco.yangide.ext.model.editor.util.connection.RoutePath;
public class LayoutUtil {
private LayoutUtil() {
super();
}
public static final double DEFAULT_DIAGRAM_LAYOUT_V_SHIFT = 10;
protected static final Comparator<YangDiagramNode> COMPARATOR = new LayoutEntityOrderComparator();
private static void updateGraphCoordinates(List<YangDiagramNode> nodes) {
for (YangDiagramNode node : nodes) {
node.updateRealObject();
}
}
private static List<YangDiagramNode> getLayoutEntities(IFeatureProvider fp) {
EList<Shape> children = fp.getDiagramTypeProvider().getDiagram().getChildren();
List<YangDiagramNode> result = new ArrayList<YangDiagramNode>();
for (Shape shape : children) {
Object bo = fp.getBusinessObjectForPictogramElement(shape);
if (null != bo) {
int pos = YangModelUtil.getPositionInParent(
fp.getBusinessObjectForPictogramElement(fp.getDiagramTypeProvider().getDiagram()), bo);
YangDiagramNode currentEntity = new YangDiagramNode(shape, pos);
result.add(currentEntity);
}
}
return result;
}
private static double[] getAreaSizeAndArrangeLayout(List<YangDiagramNode> entities,
YangDiagramLayoutAlgorithm layout, int width, int height) {
double maxW = 0D;
double maxH = 0D;
double fullW = 0D;
double fullH = 0D;
for (YangDiagramNode node : entities) {
fullW += node.getWidth();
if (maxW < node.getWidth()) {
maxW = node.getWidth();
}
fullH += node.getHeight();
if (maxH < node.getHeight()) {
maxH = node.getHeight();
}
}
layout.setMaxElementSizes(maxW, maxH);
layout.setFullElementSizes(fullW, fullH);
double h = Math.max(1, Math.ceil(entities.size() / (width / maxW))) * maxH;
return new double[] { width, h };
}
public static class YangDiagramNode {
protected int pos;
protected Shape realObject;
protected double x, y, width, height;
public YangDiagramNode(Shape realObject, int pos) {
this.realObject = realObject;
this.x = realObject.getGraphicsAlgorithm().getX();
this.y = realObject.getGraphicsAlgorithm().getY();
this.height = realObject.getGraphicsAlgorithm().getHeight();
this.width = realObject.getGraphicsAlgorithm().getWidth();
this.pos = pos;
}
public int getPositionInParent() {
return pos;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public void setLocation(double x, double y) {
this.x = x;
this.y = y;
}
public double getWidth() {
return width;
}
public double getHeight() {
return height;
}
public void updateRealObject() {
realObject.getGraphicsAlgorithm().setX((int) x);
realObject.getGraphicsAlgorithm().setY((int) y);
}
}
public static class YangCompositeSimpleNode extends YangDiagramNode {
private List<YangCompositeSimpleNode> children;
public YangCompositeSimpleNode(ContainerShape realObject, int pos) {
super(realObject, pos);
}
@Override
public int getPositionInParent() {
return pos;
}
public List<YangCompositeSimpleNode> getChildren() {
if (null == children) {
children = new ArrayList<YangCompositeSimpleNode>();
}
return children;
}
public void addChild(YangCompositeSimpleNode child) {
getChildren().add(child);
}
public void setSize(int width, int height) {
this.width = width;
this.height = height;
}
public void updateRealObject(IFeatureProvider fp) {
realObject.getGraphicsAlgorithm().setX((int) x);
realObject.getGraphicsAlgorithm().setY((int) y);
realObject.getGraphicsAlgorithm().setHeight((int) height);
realObject.getGraphicsAlgorithm().setWidth((int) width);
layoutContainerShapeHeader((ContainerShape) realObject, fp);
for (YangCompositeSimpleNode child : getChildren()) {
child.updateRealObject(fp);
}
}
}
private static class LayoutEntityOrderComparator implements Comparator<YangDiagramNode> {
@Override
public int compare(YangDiagramNode o1, YangDiagramNode o2) {
if (null == o1 && null == o2) {
return 0;
}
if (null != o1 && null == o2) {
return 1;
}
if (null == o1 && null != o2) {
return -1;
}
return o1.getPositionInParent() > o2.getPositionInParent() ? 1
: o1.getPositionInParent() == o2.getPositionInParent() ? 0 : -1;
}
}
protected static int layoutCompositeSimpleNode(YangCompositeSimpleNode element, int boundsWidth) {
int y = YangModelUIUtil.DEFAULT_TEXT_HEIGHT;
for (YangCompositeSimpleNode sn : element.getChildren()) {
int elementHeight = layoutCompositeSimpleNode(sn,
Math.max(0, boundsWidth - 2 * YangModelUIUtil.DEFAULT_V_ALIGN))
+ 2 * YangModelUIUtil.DEFAULT_H_ALIGN;
int xmove = YangModelUIUtil.DEFAULT_V_ALIGN;
int ymove = y + YangModelUIUtil.DEFAULT_H_ALIGN;
sn.setLocation(xmove, ymove);
sn.setSize(Math.max(0, boundsWidth - 2 * YangModelUIUtil.DEFAULT_V_ALIGN),
(int) Math.max(elementHeight, sn.getHeight()));
y = (int) (y + sn.getHeight() + YangModelUIUtil.DEFAULT_H_ALIGN);
}
element.setSize(boundsWidth, (int) Math.max(y + 2 * YangModelUIUtil.DEFAULT_H_ALIGN, element.getHeight()));
return y;
}
private static class YangDiagramLayoutAlgorithm {
protected double maxW = YangModelUIUtil.DEFAULT_WIDTH;
@SuppressWarnings("unused")
protected double maxH = YangModelUIUtil.DEFAULT_COMPOSITE_HEIGHT;
protected int cols;
protected int rows;
@SuppressWarnings("unused")
protected double fullW = YangModelUIUtil.DEFAULT_WIDTH;
protected double fullH = YangModelUIUtil.DEFAULT_COMPOSITE_HEIGHT;
public static final int OFFSET = 40;
protected int[] calculateNumberOfRowsAndCols(int numChildren, double boundX, double boundY, double boundWidth,
double boundHeight) {
cols = Math.max(1, (int) Math.min(numChildren, boundWidth / (getMaxW() + OFFSET)));
rows = Math.max(1, (int) Math.ceil(numChildren / cols)) + 1;
return new int[] { cols, rows };
}
public void applyLayout(List<YangDiagramNode> entitiesToLayout, double boundX, double boundY, double boundWidth,
double boundHeight) {
Collections.sort(entitiesToLayout, COMPARATOR);
calculateNumberOfRowsAndCols(entitiesToLayout.size(), boundX, boundY, boundWidth, boundHeight);
int index = 0;
double averageH = fullH / cols;
double leftH = fullH;
if (cols >= entitiesToLayout.size()) {
leftH = 0;
}
for (int j = 0; j < cols; j++) {
double y = 0;
do { // set at least one element in the row
if (index < entitiesToLayout.size()) {
YangDiagramNode sn = entitiesToLayout.get(index++);
double xmove = boundX + j * (getMaxW() + OFFSET) + OFFSET;
double ymove = boundY + y + OFFSET;
sn.setLocation(xmove, ymove);
y += sn.getHeight() + OFFSET;
leftH -= sn.getHeight();
}
} while ((j == cols - 1
|| (leftH / (cols - j - 1) > averageH && cols - j - 1 < entitiesToLayout.size() - index))
&& index < entitiesToLayout.size());
}
}
public double getMaxW() {
return maxW;
}
public void setMaxElementSizes(double maxW, double maxH) {
this.maxW = maxW;
this.maxH = maxH;
}
public void setFullElementSizes(double fullW, double fullH) {
this.fullW = fullW;
this.fullH = fullH;
}
}
private static void setShapes(Shape cs, Rectangle r, Map<GraphicsAlgorithm, Rectangle> map) {
map.put(cs.getGraphicsAlgorithm(), r);
if (cs instanceof ContainerShape) { // connect inner shapes with outer rectangles on diagram
for (Shape shape : ((ContainerShape) cs).getChildren()) {
setShapes(shape, r, map);
}
}
}
public static void layoutDiagramConnections(IFeatureProvider fp) {
RectilinearAvoidObstaclesPathFinder finder = new RectilinearAvoidObstaclesPathFinder();
Map<GraphicsAlgorithm, Rectangle> map = new HashMap<GraphicsAlgorithm, Rectangle>();
for (Shape shape : fp.getDiagramTypeProvider().getDiagram().getChildren()) {
Rectangle r = new Rectangle(shape.getGraphicsAlgorithm().getX(), shape.getGraphicsAlgorithm().getY(),
shape.getGraphicsAlgorithm().getWidth(), shape.getGraphicsAlgorithm().getHeight());
finder.addObstacle(r);
setShapes(shape, r, map);
}
for (Connection connection : fp.getDiagramTypeProvider().getDiagram().getConnections()) {
// remove all bendpoints
((FreeFormConnection) connection).getBendpoints().clear();
org.eclipse.draw2d.geometry.Point start, end;
Rectangle source = map.get(connection.getStart().getReferencedGraphicsAlgorithm());
Rectangle target = map.get(connection.getEnd().getReferencedGraphicsAlgorithm());
// start always with right side because of box relative anchor
start = new org.eclipse.draw2d.geometry.Point(source.x + source.width,
Graphiti.getLayoutService().getLocationRelativeToDiagram(connection.getStart()).getY() - 3);
if (source.x < target.x) {
end = new org.eclipse.draw2d.geometry.Point(target.x, target.y + target.height / 2);
} else {
end = new org.eclipse.draw2d.geometry.Point(target.x + target.width, target.y + target.height / 2);
}
RoutePath route = finder.find(
Position.create(map.get(connection.getStart().getReferencedGraphicsAlgorithm()), start),
Position.create(map.get(connection.getEnd().getReferencedGraphicsAlgorithm()), end), false);
if (null != route && null != route.path) {
for (int i = 0; i < route.path.size(); i++) {
AddBendpointContext context = new AddBendpointContext((FreeFormConnection) connection,
route.path.getPoint(i).x, route.path.getPoint(i).y, i);
fp.getAddBendpointFeature(context).addBendpoint(context);
}
}
}
}
public static void layoutDiagram(IFeatureProvider fp) {
// get the chosen LayoutAlgorithmn instance
YangDiagramLayoutAlgorithm layoutAlgorithm = new YangDiagramLayoutAlgorithm();
// Setup the array of Shape LayoutEntity
List<YangDiagramNode> diagramEntities = getLayoutEntities(fp);
int diagramWidth = ((EditorFeatureProvider) fp).getDiagramWidth();
int diagramHeight = ((EditorFeatureProvider) fp).getDiagramHeight();
double[] preferedSize = getAreaSizeAndArrangeLayout(diagramEntities, layoutAlgorithm, diagramWidth,
diagramHeight);
// Apply the LayoutAlgorithmn
layoutAlgorithm.applyLayout(diagramEntities, 0D, 0D, preferedSize[0], preferedSize[1]);
updateGraphCoordinates(diagramEntities);
layoutDiagramConnections(fp);
}
public static void layoutContainerShapeHeader(ContainerShape cs, IFeatureProvider fp) {
GraphicsAlgorithm text = YangModelUIUtil.getObjectPropGA(cs, PropertyUtil.OBJECT_HEADER_TEXT_SHAPE_KEY);
GraphicsAlgorithm type = YangModelUIUtil.getObjectPropGA(cs, PropertyUtil.BUSINESS_OBJECT_TYPE_SHAPE_KEY);
GraphicsAlgorithm number = YangModelUIUtil.getObjectPropGA(cs, PropertyUtil.OBJECT_NUMBER_SHAPE_KEY);
int textWidth = Math.max(0, cs.getGraphicsAlgorithm().getWidth() - YangModelUIUtil.DEFAULT_TEXT_HEIGHT);
if (null != number) {
IDimension dim = GraphitiUi.getUiLayoutService().calculateTextSize(((Text) number).getValue(),
number.getStyle().getFont());
number.setX(Math.max(0,
cs.getGraphicsAlgorithm().getWidth() - YangModelUIUtil.DEFAULT_OBJECT_NUMBER_IND - dim.getWidth()));
textWidth = Math.max(0, textWidth - number.getWidth());
}
int typeWidth = 0;
if (null != type && type instanceof Text) {
IDimension size = GraphitiUi.getUiLayoutService().calculateTextSize(((Text) type).getValue(),
type.getStyle().getFont());
if (size != null) {
typeWidth = size.getWidth();
textWidth += YangModelUIUtil.DEFAULT_TEXT_HEIGHT;
}
}
int nameWidth = 0;
if (null != text && text instanceof Text) {
nameWidth = GraphitiUi.getUiLayoutService()
.calculateTextSize(((Text) text).getValue(), text.getStyle().getFont()).getWidth();
if (0 != typeWidth && typeWidth + nameWidth + YangModelUIUtil.DEFAULT_H_ALIGN > textWidth) {
nameWidth = (int) Math.min(nameWidth, (0.5 * Math.max(0, textWidth)));
}
text.setWidth(Math.min(nameWidth, textWidth));
}
if (null != type && type instanceof Text) {
Text ga = (Text) type;
ga.setX(nameWidth + YangModelUIUtil.DEFAULT_TEXT_HEIGHT + YangModelUIUtil.DEFAULT_H_ALIGN);
ga.setWidth(Math.max(0, textWidth - ga.getX()));
}
Polyline line = YangModelUIUtil.getPolyline(cs);
if (null != line) {
EList<Point> points = line.getPoints();
if (1 < points.size()) {
points.get(1).setX(cs.getGraphicsAlgorithm().getWidth());
}
}
}
protected static YangCompositeSimpleNode createCompositeSimpleNode(ContainerShape cs, IFeatureProvider fp) {
Object bo = fp.getBusinessObjectForPictogramElement(cs);
YangCompositeSimpleNode result = new YangCompositeSimpleNode(cs,
YangModelUtil.getPositionInParent(fp.getBusinessObjectForPictogramElement(cs.getContainer()), bo));
for (Shape shape : YangModelUIUtil.filterBusinessObjectShapes(cs.getChildren())) {
if (shape instanceof ContainerShape) {
result.addChild(createCompositeSimpleNode((ContainerShape) shape, fp));
}
}
Collections.sort(result.getChildren(), COMPARATOR);
return result;
}
public static void layoutContainerShape(ContainerShape cs, IFeatureProvider fp) {
YangCompositeSimpleNode node = createCompositeSimpleNode(cs, fp);
layoutCompositeSimpleNode(node, cs.getGraphicsAlgorithm().getWidth());
node.updateRealObject(fp);
layoutDiagramConnections(fp);
}
/**
* calls layout feature for {@link PictogramElement}
*
* @param pe
* @param fp
*/
public static void layoutPictogramElement(PictogramElement pe, IFeatureProvider fp) {
LayoutContext context = new LayoutContext(pe);
fp.layoutIfPossible(context);
}
}