/**
* Copyright (c) 2012 committers of YAKINDU 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
* Contributors:
* Markus Muehlbrandt - initial API and implementation
*
*/
package org.yakindu.base.gmf.runtime.treelayout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.ConnectionLayer;
import org.eclipse.draw2d.FreeformLayout;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.LayoutManager;
import org.eclipse.draw2d.RectangleFigure;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
/**
*
* @author Markus Muehlbrandt
*
*/
public class TreeLayout extends FreeformLayout {
private final int rankSpacing;
private final ConnectionLayer connectionLayer;
private Point parentOffset;
private Dimension graphSize;
private int maxTreeLevel;
private int leafSpacing;
private boolean isVertical;
private boolean isTopLeftAlignment;
// Represents the minimal needed level width or height.
private int[] levelOffset;
protected class TreeNode {
private final TreeNode parent;
private List<TreeNode> children;
private final IFigure figure;
private int weight;
private int level;
private int cell;
private boolean isLayouted;
public TreeNode(TreeNode parent, IFigure figure) {
super();
this.parent = parent;
this.figure = figure;
children = new LinkedList<TreeNode>();
weight = 0;
level = -1;
cell = -1;
isLayouted = false;
}
public IFigure getFigure() {
return figure;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public int getCell() {
return cell;
}
public void setCell(int cell) {
this.cell = cell;
}
public TreeNode getParent() {
return parent;
}
public List<TreeNode> getChildren() {
return children;
}
public boolean isLayouted() {
return isLayouted;
}
public void setLayouted(boolean isLayouted) {
this.isLayouted = isLayouted;
}
}
public TreeLayout(int rankSpacing, ConnectionLayer connectionLayer) {
super();
this.rankSpacing = rankSpacing;
this.connectionLayer = connectionLayer;
isVertical = true;
isTopLeftAlignment = true;
leafSpacing = 5;
maxTreeLevel = 0;
graphSize = new Dimension();
}
public boolean isVertical() {
return isVertical;
}
public void setVertical(boolean isVertical) {
this.isVertical = isVertical;
}
public boolean isTopLeftAlignment() {
return isTopLeftAlignment;
}
public void setTopLeftAlignment(boolean isTopLeftAlignment) {
this.isTopLeftAlignment = isTopLeftAlignment;
}
public int getLeafSpacing() {
return leafSpacing;
}
public void setLeafSpacing(int leafSpacing) {
this.leafSpacing = leafSpacing;
}
@Override
public void layout(IFigure figure) {
parentOffset = getOrigin(figure);
final TreeNode rootTreeNode = createNodeTree(figure);
Dimension parentSize = figure.getBounds().getSize().getCopy();
if (graphSize.width > parentSize.width) {
parentSize.width = graphSize.width;
}
if (graphSize.height > parentSize.height) {
parentSize.height = graphSize.height;
}
if (rootTreeNode != null) {
final int start = 0;
final double share = isVertical ? parentSize.width
/ rootTreeNode.weight : parentSize.height
/ rootTreeNode.weight;
layoutTree(rootTreeNode, start, share);
reduceLeafSpacing(rootTreeNode);
}
}
private void layoutTree(TreeNode treeNode, int start, double share) {
final Rectangle constraint = (Rectangle) getConstraint(treeNode.figure);
if (constraint != null) {
Rectangle bounds = constraint.getCopy();
if (bounds.width <= 0 || bounds.height <= 0) {
final Dimension preferredSize = treeNode.figure
.getPreferredSize(bounds.width, bounds.height);
if (bounds.width <= 0) {
bounds.width = preferredSize.width;
}
if (bounds.height <= 0) {
bounds.height = preferredSize.height;
}
}
// Calculation for tree level distance assumes that all figures have
// the same width and/or height;
final int rankPos = isTopLeftAlignment ? treeNode.level
: maxTreeLevel - treeNode.level;
// final int levelPos = (rankPos * (bounds.width + rankSpacing))
// + (rankSpacing / 2);
final int levelPos = getlevelPos(rankPos) + (rankSpacing / 2) ;
final int cellPos = (int) (start + treeNode.weight * share / 2);
bounds.x = isVertical ? cellPos - (bounds.width / 2) : levelPos;
bounds.y = isVertical ? levelPos : cellPos - (bounds.height / 2);
for (final TreeNode childNode : treeNode.children) {
layoutTree(childNode, start, share);
start += share * childNode.weight;
}
// System.out.println("******************************************");
// //System.out.println("Parent Size: " +
// treeNode.parent.figure.getSize());
// System.out.println("Figure " + treeNode.figure + " Tree Level: "
// + treeNode.level + " Weight: " + treeNode.weight);
// System.out.println(" X: " + bounds.x + " Y: " + bounds.y
// + " Width : " + bounds.width + " Height: " + bounds.height);
// System.out.println("******************************************");
bounds = bounds.getTranslated(parentOffset);
treeNode.figure.setBounds(bounds);
treeNode.setLayouted(true);
}
}
private int getlevelPos(int rankPos) {
int pos = 0;
for (int i = levelOffset.length - rankPos; i < levelOffset.length; i++) {
//Rankspacing is already included.
pos += levelOffset[i];
}
return pos;
}
/**
* @see LayoutManager#getConstraint(IFigure) Transforms a Rectangle
* constraint to a TreeLayoutConstraint if necessary
*/
@Override
public TreeLayoutConstraint getConstraint(IFigure figure) {
Rectangle constraint = (Rectangle) super.getConstraint(figure);
if (constraint != null && !(constraint instanceof TreeLayoutConstraint)) {
setConstraint(figure, new TreeLayoutConstraint(
constraint.getCopy(), false, -1));
}
return (TreeLayoutConstraint) super.getConstraint(figure);
}
private TreeNode createNodeTree(IFigure figure) {
final TreeNode rootTreeNode = createRootTreeNode(figure);
if (rootTreeNode != null) {
graphSize = new Dimension();
final List<List<TreeNode>> treeLevelList = getTreeLevels(
Collections.singletonList(rootTreeNode), 0);
maxTreeLevel = treeLevelList.size() - 1;
// Algorithm to calculate node weight, level and canvas size. Starts
// with the leafs and walks up to the root element.
int requiredSize = 0;
int largestRankSize = 0;
int rankSize = 0;
int rowSize = 0;
levelOffset = new int[treeLevelList.size()];
for (int rankIndex = treeLevelList.size() - 1; rankIndex > -1; rankIndex--) {
rankSize = 0;
rowSize = 0;
for (int cellIndex = 0; cellIndex < treeLevelList.get(rankIndex)
.size(); cellIndex++) {
final TreeNode treeNode = treeLevelList.get(rankIndex).get(
cellIndex);
treeNode.setCell(cellIndex);
// set node weight
// Case 1: TreeNode is leaf
if (treeNode.children.isEmpty()) {
treeNode.weight = 1;
}
// Case 2: TreeNode has only one child.
else if (treeNode.children.size() == 1) {
final TreeNode childGraphNode = treeNode.children
.get(0);
treeNode.weight = childGraphNode.weight;
}
// Case 3: TreeNode has many children.
else {
treeNode.weight = 0;
for (final TreeNode childGraphNode : treeNode.children) {
treeNode.weight += childGraphNode.weight;
}
}
// calculate rank size
rankSize += isVertical ? treeNode.getFigure().getSize().width
+ leafSpacing
: treeNode.getFigure().getSize().height
+ leafSpacing;
// get largest row figure
if (isVertical) {
if (rowSize < treeNode.getFigure().getSize().height
+ rankSpacing) {
rowSize = treeNode.getFigure().getSize().height
+ rankSpacing;
}
} else {
if (rowSize < treeNode.getFigure().getSize().width
+ rankSpacing) {
rowSize = treeNode.getFigure().getSize().width
+ rankSpacing;
}
}
}
if (largestRankSize < rankSize) {
// System.out.println("Set new Ranksize for Rank " + rankIndex
// + " Size : " + rankSize);
largestRankSize = rankSize;
}
levelOffset[rankIndex] = rowSize;
requiredSize += rowSize;
}
// set row and rank size
if (isVertical) {
graphSize.width = (int) (largestRankSize * 3);
graphSize.height = requiredSize;
} else {
graphSize.height = (int) (largestRankSize * 3);
graphSize.width = requiredSize;
}
}
return rootTreeNode;
}
protected TreeNode createRootTreeNode(IFigure parent) {
final List<IFigure> rootFigures = getGraphRootFigures(parent);
if (rootFigures.size() > 1) {
// initializes a "virtual" root
// tree level (needed for correct size calculations)
RectangleFigure rootFigure = new RectangleFigure();
rootFigure.setPreferredSize(1, 1);
rootFigure.setSize(1, 1);
setConstraint(rootFigure, new Rectangle(0, 0, 0, 0));
TreeNode rootTreeNode = new TreeNode(null, rootFigure);
for (IFigure figure : rootFigures) {
rootTreeNode.children.add(new TreeNode(null, figure));
}
return rootTreeNode;
} else if (rootFigures.size() == 1) {
return new TreeNode(null, rootFigures.get(0));
}
return null;
}
// Returns the GraphRootFigure
@SuppressWarnings("unchecked")
private List<IFigure> getGraphRootFigures(IFigure parent) {
List<IFigure> figures = new LinkedList<IFigure>();
final Iterator<IFigure> children = parent.getChildren().iterator();
while (children.hasNext()) {
final IFigure figure = children.next();
final Object constraint = getConstraint(figure);
if (constraint instanceof TreeLayoutConstraint) {
final TreeLayoutConstraint bounds = (TreeLayoutConstraint) constraint;
if (bounds.isRoot()) {
figures.add(figure);
}
}
}
return figures;
}
protected List<List<TreeNode>> getTreeLevels(List<TreeNode> treeNodeList,
int level) {
final List<List<TreeNode>> levelElements = new ArrayList<List<TreeNode>>();
final List<TreeNode> nextRankElements = new ArrayList<TreeNode>();
levelElements.add(new ArrayList<TreeNode>());
levelElements.get(0).addAll(treeNodeList);
for (final TreeNode treeNode : treeNodeList) {
// Set level of the treeNode
treeNode.level = level;
if (treeNode.children.isEmpty()) {
// calculate child GraphNodes in next Rank
final List<Connection> connectionList = TreeLayoutUtil
.getTreeFigureIncomingConnections(connectionLayer,
treeNode.getFigure());
for (int i = 0; i < connectionList.size(); i++) {
final Connection connection = connectionList.get(i);
if (connection.getSourceAnchor().getOwner() != null) {
final IFigure childFig = connection.getSourceAnchor()
.getOwner();
final TreeNode childTreeNode = new TreeNode(treeNode,
childFig);
treeNode.children.add(childTreeNode);
}
}
}
treeNode.children = sortTreeNodeList(treeNode.children);
nextRankElements.addAll(treeNode.children);
}
// add ranks for childTreeNodeList
if (!nextRankElements.isEmpty()) {
levelElements.addAll(getTreeLevels(nextRankElements, ++level));
}
return levelElements;
}
private List<TreeNode> sortTreeNodeList(List<TreeNode> treeNodeList) {
final TreeNode[] sortedTreeNodeList = new TreeNode[treeNodeList.size()];
final List<TreeNode> notConstrained = new ArrayList<TreeNode>();
for (int index = 0; index < treeNodeList.size(); index++) {
final TreeNode treeNode = treeNodeList.get(index);
final TreeLayoutConstraint constraint = getConstraint(treeNode.figure);
if (constraint.getTreeInnerRankIndex() != -1
&& constraint.getTreeInnerRankIndex() < sortedTreeNodeList.length
&& sortedTreeNodeList[constraint.getTreeInnerRankIndex()] == null) {
sortedTreeNodeList[constraint.getTreeInnerRankIndex()] = treeNode;
} else {
notConstrained.add(treeNode);
}
}
for (final TreeNode treeNode : notConstrained) {
final int nextEmptyIndex = getNextEmptyIndex(sortedTreeNodeList);
final TreeLayoutConstraint constraint = getConstraint(treeNode.figure);
constraint.setTreeInnerRankIndex(nextEmptyIndex);
sortedTreeNodeList[nextEmptyIndex] = treeNode;
}
return Arrays.asList(sortedTreeNodeList);
}
private int getNextEmptyIndex(TreeNode[] list) {
for (int index = 0; index < list.length; index++) {
if (list[index] == null) {
return index;
}
}
return -1;
}
private void reduceLeafSpacing(TreeNode treeNode) {
if (areLeafs(treeNode.children)
&& treeNode.children.size() > 1) {
final TreeNode firstNode = treeNode.children.get(0);
final TreeNode lastNode = treeNode.children
.get(treeNode.children.size() - 1);
final int delta = lastNode.figure.getBounds().y
- firstNode.figure.getBounds().y;
final int newDelta = (firstNode.getFigure().getBounds().height + leafSpacing)
* (treeNode.children.size() - 1);
if (delta - newDelta > 0) {
final int offset = delta - newDelta;
final int start = firstNode.figure.getBounds().y + offset / 2;
for (final TreeNode childTreeNode : treeNode.children) {
final int index = treeNode.children
.indexOf(childTreeNode);
final Rectangle bounds = childTreeNode.getFigure()
.getBounds().getCopy();
bounds.y = start
+ (childTreeNode.figure.getSize().height + leafSpacing)
* index;
childTreeNode.getFigure().setBounds(bounds);
}
}
} else {
for (final TreeNode childTreeNode : treeNode.children) {
reduceLeafSpacing(childTreeNode);
}
}
}
private boolean areLeafs(List<TreeNode> childTreeNodeList) {
for (final TreeNode treeNode : childTreeNodeList) {
if (!treeNode.children.isEmpty()) {
return false;
}
}
return true;
}
}