/* * Copyright (c) 2004- michael lawley and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation * which accompanies this distribution, and is available by writing to * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * Contributors: * michael lawley * * */ package com.dstc.emf.view; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.HashMap; import org.eclipse.draw2d.AbstractLayout; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Insets; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.Rectangle; /** * @author lawley * */ public class RadialTreeLayout extends AbstractLayout { private static final double TWO_PI = Math.PI * 2.0; /** * Map from Child to Parent */ private Map constraints = new HashMap(); private Map layoutThreads = new HashMap(); public RadialTreeLayout() { } /* (non-Javadoc) * @see org.eclipse.draw2d.AbstractLayout#calculatePreferredSize(org.eclipse.draw2d.IFigure, int, int) */ protected Dimension calculatePreferredSize(IFigure fig, int wHint, int hHint) { Rectangle rect = new Rectangle(); for (Iterator children = fig.getChildren().iterator(); children.hasNext(); ) { IFigure child = (IFigure) children.next(); Rectangle r = (Rectangle) constraints.get(child); if (r == null) { continue; } if (r.width == -1 || r.height == -1) { Dimension preferredSize = child.getPreferredSize(r.width, r.height); r = r.getCopy(); if (r.width == -1) { r.width = preferredSize.width; } if (r.height == -1) { r.height = preferredSize.height; } } rect.union(r); } Dimension d = rect.getSize(); Insets insets = fig.getInsets(); return new Dimension(d.width + insets.getWidth(), d.height + insets.getHeight()).union(getBorderPreferredSize(fig)); } private void buildNodeList(List parents, List allNodes) { for (Iterator itr = parents.iterator(); itr.hasNext(); ) { NodeFigure node = (NodeFigure) itr.next(); List children = node.getFiguresBelow(); allNodes.addAll(children); buildNodeList(children, allNodes); } } /* (non-Javadoc) * @see org.eclipse.draw2d.LayoutManager#layout(org.eclipse.draw2d.IFigure) */ public void layout(IFigure parent) { LayoutThread thread = (LayoutThread) layoutThreads.get(parent); if (null == thread) { thread = new LayoutThread(parent); layoutThreads.put(parent, thread); } thread.doLayout(); // FIXME - should call layout() but it needs to get SWT stuff on yet another thread(!) } class LayoutThread extends Thread { private boolean requested = false; private IFigure parent; LayoutThread(IFigure parent) { this.parent = parent; } final public synchronized void layout() { requested = true; if (!isAlive()) { start(); } notifyAll(); } final public void run() { boolean doWork = false; while (true) { synchronized (this) { if (requested) { doWork = true; requested = false; } else { doWork = false; try { wait(); } catch (InterruptedException e) { } } } if (doWork) { try { doLayout(); try { Thread.sleep(500); // throttle the layout } catch (InterruptedException e) { } } catch (Throwable t) { t.printStackTrace(); } } } } final void doLayout() { List roots = new ArrayList(); for (Iterator itr = parent.getChildren().iterator(); itr.hasNext(); ) { Object obj = itr.next(); if (obj instanceof NodeFigure && null == ((NodeFigure) obj).getFigureAbove()) { roots.add(obj); } } List sortedNodes = new ArrayList(); sortedNodes.addAll(roots); buildNodeList(roots, sortedNodes); List leaves = new ArrayList(sortedNodes); leaves.removeAll(constraints.values()); // Note how the number of "Edges" increases as Show/Hide Trace is toggled // ...there's a bug // TODO delete next line // System.out.println("Nodes: " + sortedNodes.size() + " Roots: " + roots.size() + " Leaves: " + leaves.size() + " Constraints: " + constraints.size()); if (leaves.size() < 1) { return; } double arc = TWO_PI / leaves.size(); double angle = 0; int maxRank = 1; // Set the angle of each leaf node for (Iterator itr = leaves.iterator(); itr.hasNext(); ) { NodeFigure leaf = (NodeFigure) itr.next(); leaf.setAngle(angle); angle += arc; if (leaf.getRank() > maxRank) { maxRank = leaf.getRank(); } } List nodes = new ArrayList(leaves); while (nodes.size() > 0) { NodeFigure node = (NodeFigure) nodes.remove(0); NodeFigure nodeAbove = node.getFigureAbove(); if (null != nodeAbove) { List siblings = nodeAbove.getFiguresBelow(); NodeFigure firstChild = (NodeFigure) siblings.get(0); NodeFigure lastChild = (NodeFigure) siblings.get(siblings.size() - 1); nodeAbove.setAngle((firstChild.getAngle() + lastChild.getAngle()) / 2.0); nodes.removeAll(siblings); nodes.add(nodeAbove); } } Rectangle bounds = parent.getClientArea(); double rad_x = bounds.width / (maxRank + maxRank + 1); double rad_y = bounds.height / (maxRank + maxRank + 1); Point centre = bounds.getLocation().getTranslated(bounds.width / 2, bounds.height / 2); for (int i = sortedNodes.size() - 1; i >= 0; i--) { NodeFigure fig = (NodeFigure) sortedNodes.get(i); Dimension preferredSize = fig.getPreferredSize(-1, -1); int x = (int) ((fig.getRank() * rad_x) * Math.cos(fig.getAngle())) - preferredSize.width/2; int y = (int) ((fig.getRank() * rad_y) * Math.sin(fig.getAngle())) - preferredSize.height/2; Rectangle figBounds = new Rectangle(x, y, preferredSize.width, preferredSize.height); figBounds = figBounds.getTranslated(centre); fig.setBounds(figBounds); } } } /** * <code>obj</code> is the parent figure of <code>fig</code> * * @see org.eclipse.draw2d.LayoutManager#setConstraint(org.eclipse.draw2d.IFigure, java.lang.Object) */ public void setConstraint(IFigure fig, Object obj) { if (fig instanceof NodeFigure) { if (obj instanceof NodeFigure) { constraints.put(fig, obj); ((NodeFigure) fig).setFigureAbove((NodeFigure) obj); } else if (null == obj) { constraints.remove(fig); ((NodeFigure) fig).setFigureAbove(null); } } super.setConstraint(fig, obj); } }