/** * 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; } }