/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004, 2005, 2006], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program is distributed * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.image.widget; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.Stroke; import java.awt.image.BufferedImage; import java.awt.image.IndexColorModel; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.Vector; import org.hyperic.image.ImageUtil; import org.hyperic.image.Line; import org.hyperic.image.WebImage; import org.hyperic.util.data.IResourceTreeNode; import org.hyperic.util.data.ITreeNode; public class ResourceTree extends WebImage { //******** Private Static Variables ********* private static final int NODE_WHITESPACE = 10; private static final int ICON_WHITESPACE = 2; private static final int LEVEL_WHITESPACE = 14; private static final int LEVEL_DESCENT = (LEVEL_WHITESPACE / 2); private static final int LEVEL_INDENT = 0; //(NODE_WHITESPACE / 2); private static final int THIN_LINE = 1; private static final int THICK_LINE = 2; private static int ICON_HEIGHT; // Set in static constructor private static int ICON_WIDTH; // Set in static constructor private static BufferedImage IMG_RESOURCE; private static BufferedImage IMG_AUTO_GROUP; private static BufferedImage IMG_CLUSTER; //******** Protected Static Variables ******* protected static final Font NAME_FONT = new Font(DEFAULT_BOLD_TYPEFACE, Font.BOLD, 11); protected static final Font DESC_FONT = new Font(DEFAULT_PLAIN_TYPEFACE, Font.PLAIN, 8); protected static final Font SELECTED_FONT = new Font(DEFAULT_BOLD_TYPEFACE, Font.BOLD, 11); protected static final Color NAME_COLOR = new Color(0x00, 0x31, 0x9C); protected static final Color DESC_COLOR = Color.BLACK; protected static final Color LINE_COLOR = new Color(0x90, 0x90, 0x90); //Color(0x80, 0x80, 0x80); protected static final Color SOFT_LINE_COLOR = new Color(0xC0, 0xC0, 0xC0); protected static final Color SELECTED_COLOR = new Color(0xDE, 0x65, 0x2D); protected static final Color TRANSPARENT_COLOR = new Color(3, 3, 3); protected static final Stroke LINE_STROKE = new BasicStroke(THICK_LINE, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); //******** Private Instance Variables ******* private FontMetrics m_nameMetrics; // Set in preInit() private FontMetrics m_descMetrics; // Set in preInit() private Vector m_root = new Vector(); private Vector m_lines = new Vector(); private Dimension m_imageSize = new Dimension(); private int m_yDividerLine; //************** Test Variables ************** // Change the next line assigned from null to the // vector to get debug whitespace rectangles. private Vector m_testRects = null; //new Vector(); //************* Static Constructor *********** static { try { IMG_RESOURCE = ImageUtil.loadImage("images/icon_resource.gif"); IMG_AUTO_GROUP = ImageUtil.loadImage("images/icon_auto-group.gif"); IMG_CLUSTER = ImageUtil.loadImage("images/icon_cluster.gif"); ICON_HEIGHT = IMG_CLUSTER.getHeight(); ICON_WIDTH = IMG_CLUSTER.getWidth(); } catch(IOException e) { System.out.println(e); } } //*************** Constructors ************** public ResourceTree(int width) { super(width, 1); } public ResourceTree(int width, int height) { super(width, height); } //************* Public Methods ************** public void addLevel(IResourceTreeNode[] resources) { m_root.add(resources); } public Dimension getImageSize() { return m_imageSize; } public IResourceTreeNode[][] getLevels() { IResourceTreeNode[][] levels = new IResourceTreeNode[m_root.size()][]; for (int i=0; i<levels.length; ++i) { levels[i] = (IResourceTreeNode[])m_root.get(i); } return levels; } public void calculateCoordinates() { this.calcTree(); } public void reset() { this.m_imageSize.setSize(0, 0); Iterator iter = m_root.iterator(); while(iter.hasNext() == true) { ITreeNode[] nodes = (ITreeNode[])iter.next(); this.resetNodes(nodes); } this.m_lines.clear(); } private void resetNodes(ITreeNode[] nodes) { if(nodes == null) { throw new IllegalArgumentException( "The \'nodes\' argument cannot be null"); } for(int i = 0;i < nodes.length;i++) { ITreeNode node = nodes[i]; node.reset(); if(node.hasUpChildren() == true) this.resetNodes(node.getUpChildren()); if(node.hasDownChildren() == true) this.resetNodes(node.getDownChildren()); } } public String toString() { StringBuffer res = new StringBuffer(); res.append(this.getClass().getName()) .append('[') .append("Nodes=").append(m_root).append(',') .append("Lines=").append(m_lines).append(',') .append("Size=").append(m_imageSize).append(',') .append("Divider=").append(m_yDividerLine) .append(']'); return res.toString(); } //*********** Protected Methods ************* protected void addTestRect(Rectangle rect) { if(m_testRects != null) m_testRects.add( new Rectangle(rect) ); } protected void draw(Graphics2D g) { super.draw(g); // g.setPaint(TRANSPARENT_COLOR); // g.fillRect(0, 0, this.getWidth(), this.getHeight()); this.drawLines(g); this.drawNodes(g); } protected void postInit(Graphics2D g) { this.font = NAME_FONT; this.textColor = NAME_COLOR; } protected void preInit() { this.frameImage = true; this.setBorder(7); BufferedImage im = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); Graphics2D g = (Graphics2D)im.getGraphics(); m_nameMetrics = g.getFontMetrics(NAME_FONT); m_descMetrics = g.getFontMetrics(DESC_FONT); g.dispose(); im.flush(); Rectangle imageSize = this.calcTree(); if(imageSize != null) { // if the tree is non-existent, let's avoid an IllegalArgumentException if (imageSize.width <= 0) { imageSize.width = 1; } if (imageSize.height <= 0) { imageSize.height = 1; } this.width = imageSize.width; this.height = imageSize.height; } } //*********** Private Methods *************** private Rectangle calcTree() { if(m_imageSize.width != 0) return null; int xLevelLine = 0; int yLevelLine = this.topBorder; int yFirstLevel = 0; int cyLastLevel = 0; int cxBorder = this.leftBorder + this.rightBorder; int[] x = null, y = null; int level; CalcResult childResult = null; IResourceTreeNode selected = null; // Width and height of the entire image Rectangle result = new Rectangle(0, 0, cxBorder, this.topBorder + this.bottomBorder); m_lines.clear(); boolean topImage = m_root.size() > 1; if(topImage == false && m_root.size() > 0) { ITreeNode[] nodes = (ITreeNode[])m_root.get(0); topImage = ( nodes.length > 1 || (nodes[0].hasDownChildren() == false && nodes[0].hasUpChildren() == false) ); } // Draw top image if(topImage == true) { Iterator iter = m_root.iterator(); for(level = 0;iter.hasNext() == true;level++) { IResourceTreeNode[] nodes = (IResourceTreeNode[])iter.next(); xLevelLine = this.leftBorder + (LEVEL_INDENT * level); yLevelLine = yLevelLine + cyLastLevel; childResult = this.calcDownChildren(nodes, new Rectangle(xLevelLine, yLevelLine, this.width, 0) ); this.addTestRect(childResult.bounding); if(selected == null) selected = childResult.selected; cyLastLevel = childResult.bounding.height + LEVEL_DESCENT;; result.height += cyLastLevel; result.width = Math.max(result.width, childResult.bounding.width + cxBorder); if(level == 0) yFirstLevel = childResult.yLevelLine; } if(level > 1) { m_lines.add( new Line(this.leftBorder, yFirstLevel - 1, xLevelLine, childResult.yLevelLine + 1) ); } } else if(m_root.size() > 0) { selected = ((IResourceTreeNode[])m_root.get(0))[0]; } // Draw bottom image if(selected != null && (selected.hasDownChildren() == true || selected.hasUpChildren() == true) ) { Rectangle boundry = new Rectangle(); boundry.x = this.leftBorder; boundry.y = this.topBorder + ((topImage == true) ? yLevelLine + cyLastLevel : 0); boundry.width = this.width; // Add horizontal line if(topImage == true) { m_yDividerLine = boundry.y - LEVEL_DESCENT; result.height += LEVEL_DESCENT; } childResult = this.calcNodeTree(selected, boundry); this.addTestRect(childResult.bounding); result.height += childResult.bounding.height; result.width = Math.max(result.width, childResult.bounding.width + cxBorder); } else { // Remove the level that was added for whitespace between the top // and bottom image. result.height -= LEVEL_DESCENT;; } // Get rid of the bottom line descent so that the top and bottom borders // look the same size. result.height -= this.m_descMetrics.getDescent(); m_imageSize.width = result.width; m_imageSize.height = result.height; return result; } private CalcResult calcNodeTree(IResourceTreeNode node, Rectangle boundry) { CalcResult result = null; Rectangle nodeRect; int x, y; int type = node.getType(); if(type == IResourceTreeNode.AUTO_GROUP || type == IResourceTreeNode.CLUSTER) { Rectangle parentRect = null; // Draw the parent node for the group if we have it if(node.hasUpChildren() == true) { parentRect = this.calcNode((IResourceTreeNode)node.getUpChildren()[0], boundry.x, boundry.y); boundry.y += (parentRect.height); boundry.grow(0, -LEVEL_DESCENT); // result.width += nodeRect.width; } // Draw the selected node nodeRect = this.calcNode(node, boundry.x, boundry.y); this.addTestRect(nodeRect); x = boundry.x + (ICON_WIDTH / 2); y = boundry.y + (ICON_HEIGHT / 2); // Add the line from the parent node to this node if there is a // parent node if(parentRect != null) { m_lines.add( new Line(parentRect.x + (ICON_WIDTH / 2), parentRect.y + (ICON_HEIGHT / 2), x, y) ); } // Add the lines for this node m_lines.add( new Line(x, y, x, boundry.y + (LEVEL_WHITESPACE * 2) + 12) ); y = nodeRect.y + nodeRect.height + 3; m_lines.add( new Line(x, y, x + (NODE_WHITESPACE * 2), y) ); boundry.grow(-NODE_WHITESPACE, 0); boundry.y = nodeRect.y + nodeRect.height + LEVEL_DESCENT; // Draw the down children result = this.calcChildren((IResourceTreeNode[])node.getDownChildren(), boundry, true, false, true); this.addTestRect(result.bounding); result.bounding.width = Math.max(result.bounding.width + ICON_WIDTH, nodeRect.width); result.bounding.height += nodeRect.height + LEVEL_DESCENT; if(parentRect != null) result.bounding.height += (parentRect.height + LEVEL_DESCENT); } else { IResourceTreeNode[] children; // Calculate Up Children children = (IResourceTreeNode[])node.getUpChildren(); if(children != null && children.length > 0) { boundry.grow(-NODE_WHITESPACE, 0); result = this.calcUpChildren(children, boundry); this.addTestRect(result.bounding); result.bounding.height += LEVEL_DESCENT; boundry.x -= NODE_WHITESPACE; boundry.y += result.bounding.height; result.bounding.x -= NODE_WHITESPACE; result.bounding.width += NODE_WHITESPACE; } // Calc Node nodeRect = this.calcNode(node, boundry.x, boundry.y); this.addTestRect(nodeRect); if(result != null) { if(nodeRect.width > result.bounding.width) { result.bounding.width += (nodeRect.width - result.bounding.width); } m_lines.add( new Line(nodeRect.x + (ICON_WIDTH / 2) + 1, nodeRect.y + (ICON_HEIGHT / 2), nodeRect.x + NODE_WHITESPACE + 1, result.yLevelLine) ); } else { result = new CalcResult(); result.bounding = new Rectangle(); result.bounding.width += nodeRect.width; result.selected = node; } // Calculate Down Children children = (IResourceTreeNode[])node.getDownChildren(); if(children != null && children.length > 0) { boundry.grow(-NODE_WHITESPACE, 0); boundry.y += nodeRect.height + LEVEL_DESCENT; CalcResult downResult = this.calcDownChildren(children, boundry); this.addTestRect(downResult.bounding); m_lines.add( new Line(nodeRect.x + (ICON_WIDTH / 2) + 1, nodeRect.y + (ICON_HEIGHT / 2), nodeRect.x + NODE_WHITESPACE + 1, downResult.yLevelLine) ); if(result != null) { result.bounding.height += downResult.bounding.height; if(downResult.bounding.width > result.bounding.width) { result.bounding.width = downResult.bounding.width + downResult.bounding.x - nodeRect.x; } // Look for children attached to children if there is only // one child at the initial level. if(children.length == 1) { IResourceTreeNode[] down = (IResourceTreeNode[])children[0].getDownChildren(); if(down != null && down.length > 0) { boundry.grow(-(NODE_WHITESPACE * 2), -(downResult.bounding.height + LEVEL_DESCENT)); downResult = this.calcDownChildren( (IResourceTreeNode[])down, boundry); this.addTestRect(downResult.bounding); Rectangle[] rects = children[0].getRectangles(); nodeRect = rects[rects.length - 1]; m_lines.add( new Line(nodeRect.x + (ICON_WIDTH / 2) + 1, nodeRect.y + (ICON_HEIGHT / 2) + 1, nodeRect.x + NODE_WHITESPACE + 1, downResult.yLevelLine + 1) ); result.bounding.height += downResult.bounding.height + LEVEL_DESCENT; if(downResult.bounding.width > result.bounding.width) { result.bounding.width = downResult.bounding.width + (NODE_WHITESPACE * 3); } } } } else { result = downResult; } result.bounding.height += LEVEL_DESCENT; } // Add height of selected node to the result height result.bounding.height += nodeRect.height; } return result; } private CalcResult calcChildren(IResourceTreeNode[] nodes, Rectangle boundry, boolean wrap, boolean calcLines, boolean down) { int yHorzLine; int xNode=0, yNode=0; int cxLastNode=0; int cyTallestNode=0; int xLevelLine=boundry.x, yLevelLine=boundry.y; int wrapLevel; int xFirstNode=0, yFirstNode=0; int xLastNode=0, yLastNode=0; int nodeIndex=0; int lastWrapIndex=0; Rectangle nodeRect = null; IResourceTreeNode node = null; CalcResult result = new CalcResult(); result.bounding = new Rectangle(boundry.x, boundry.y, 0, 0); FontMetrics nameMetrics = this.getFontMetrics(); Rectangle[] rects = new Rectangle[nodes.length]; Vector levelLines = new Vector(); for(wrapLevel = ((wrap == true) ? 1 : 0); nodeIndex < nodes.length; wrapLevel ++) { if(down == true) yHorzLine = (wrapLevel > 1) ? yLastNode + cyTallestNode + LEVEL_DESCENT : yLevelLine; else yHorzLine = (wrapLevel > 1) ? yLastNode - cyTallestNode - LEVEL_WHITESPACE - LEVEL_DESCENT : yLevelLine; xNode = xLevelLine; if(calcLines == true) xNode += NODE_WHITESPACE; // Calc Level and Node Position if(nodes.length == 1) { yNode = yHorzLine; if(down == false) yNode += LEVEL_DESCENT; yHorzLine += ICON_HEIGHT / 2; if(wrapLevel == 0) { xNode -= ICON_WHITESPACE; yLevelLine = yHorzLine; } } else { yNode = yHorzLine + ((calcLines == true) ? LEVEL_DESCENT : 0); } // Reset the last node height for the level to zero cyTallestNode = 0; int levelStart; for(levelStart = nodeIndex;nodeIndex < nodes.length;nodeIndex++) { node = nodes[nodeIndex]; // Set the selected parameter if(node.isSelected() == true) { if(result.selected == null) result.selected = node; } nodeRect = this.calcNode(node, xNode, yNode, false); //this.addTestRect(nodeRect); // Add the rect to an array to set after the loop. This allows // us to set the node rects transactionally. rects[nodeIndex] = nodeRect; // Determine if the node can fit in the image width. If not, // we should wrap the node to the next line, unless it also // won't fit on the next line. If it can't fit on this line // or the next line then the right side is cutoff. if( (nodeRect.x + nodeRect.width) > (boundry.x + boundry.width) && nodeIndex != lastWrapIndex) { // If we didn't already know we need to wrap, then we must // start all over again because the wrapping has a differnt // layout. if(wrap == false) { boundry.grow(-NODE_WHITESPACE, 0); result = this.calcChildren(nodes, boundry, true, true, down); boundry.grow(NODE_WHITESPACE, 0); return result; } // If we knew then we calculate this node again on a new // line. The break will automatically start a new line. lastWrapIndex = nodeIndex; break; } // Keep track of the height of the talest node cxLastNode = nodeRect.width; cyTallestNode = Math.max(cyTallestNode, nodeRect.height); // Setup for the next node if(nodeIndex == 0) { xFirstNode = xNode; yFirstNode = yNode; } xLastNode = xNode; yLastNode = yNode; xNode += (nodeRect.width + NODE_WHITESPACE); } // Swap the nodes left to right if(down == false) this.swapNodes(rects, levelStart, nodeIndex - 1); // Add Horizontal Level Line if(calcLines == true) { Line line = new Line(); line.x1 = xLevelLine - ((wrapLevel == 1) ? NODE_WHITESPACE : 0); line.y1 = yHorzLine; if(nodes.length > 1) line.x2 = ((down == true) ? xLastNode : rects[levelStart].x) - (NODE_WHITESPACE / 2) + (ICON_WIDTH / 2) + 1; else line.x2 = xLastNode + (ICON_WIDTH / 2); levelLines.add(line); } // Update the tree width result.bounding.width = Math.max(result.bounding.width, xLastNode - xLevelLine + cxLastNode + ((wrapLevel == 1) ? NODE_WHITESPACE : 0) ); result.bounding.height += (nodeIndex <= 1 && wrap == false) ? nodeRect.height : nodeRect.height + ((calcLines == true) ? LEVEL_DESCENT : 0); } if(--wrapLevel == 0) wrapLevel = 1; // Set all the node rectangles, transactionally for(int i = 0;i < nodes.length;i++) { Rectangle rect = rects[i]; if(down == false) { if(wrap == true) rect.y += ((cyTallestNode + LEVEL_WHITESPACE) * (wrapLevel - 1)) - LEVEL_DESCENT; else rect.y -= LEVEL_DESCENT; } nodes[i].addRectangle(rect.x, rect.y, rect.width, rect.height); this.addTestRect(rect); } // Set all the level lines Line line = null; Iterator iter = levelLines.iterator(); for(int i = 0;iter.hasNext() == true;i++) { line = (Line)iter.next(); if(down == false && nodes.length > 1) line.y1 += (((cyTallestNode + LEVEL_WHITESPACE) * wrapLevel) - LEVEL_DESCENT); line.y2 = line.y1; m_lines.add(line); if(i == 0) result.yLevelLine = line.y1; } // Add wrap line if necessary if(calcLines == true && wrapLevel >= 1) { m_lines.add(new Line(xLevelLine + 1, result.yLevelLine, xLevelLine + 1, line.y1)); } // Add Node Lines if(calcLines == true && nodes.length > 1) { for(int i = 0;i < rects.length;i++) { Rectangle rect = rects[i]; line = new Line(); line.x1 = rect.x; line.y2 = rect.y + 2; if(down == true) { line.y1 = rect.y - LEVEL_DESCENT; line.x2 = rect.x + (ICON_WIDTH / 2) - 1; } else { line.y1 = rect.y + LEVEL_DESCENT + rect.height; line.x2 = rect.x + (ICON_WIDTH / 2) + 1; } m_lines.add(line); } } // Update the tree width and height if(calcLines == false) result.bounding.width -= NODE_WHITESPACE; if(wrapLevel > 0) result.bounding.height += (wrapLevel - 1) * LEVEL_DESCENT; return result; } private CalcResult calcDownChildren(IResourceTreeNode[] nodes, Rectangle boundry) { return this.calcChildren(nodes, boundry, false, true, true); } private CalcResult calcUpChildren(IResourceTreeNode[] nodes, Rectangle boundry) { return this.calcChildren(nodes, boundry, false, true, false); } private Rectangle calcNode(IResourceTreeNode node, int x, int y) { return this.calcNode(node, x, y, true); } private Rectangle calcNode(IResourceTreeNode node, int x, int y, boolean set) { int cxImg, cyImg; int cxNode, cyNode; String name = node.getName(); String desc = node.getDescription(); BufferedImage img = getIcon(node.getType()); cxImg = (img != null) ? ICON_WIDTH : 0; cyImg = (img != null) ? ICON_HEIGHT : 0; // Calc Node Width int cxName = (name != null) ? m_nameMetrics.stringWidth(name) : 0; int cxDesc = (desc != null) ? m_descMetrics.stringWidth(desc) : 0; cxNode = Math.max(cxName, cxDesc); cxNode += (cxImg + ICON_WHITESPACE); // Calc Node Height cyNode = (name != null) ? m_nameMetrics.getAscent() : 0; cyNode += (desc != null) ? m_descMetrics.getAscent() : 0; // Save the Node Rectangle for anyone interested in creating // an image map on top of this image if(set == true) node.addRectangle(x, y, cxNode, cyNode); return new Rectangle(x, y, cxNode, cyNode); } private void drawLines(Graphics2D g) { Stroke strokeOrig = g.getStroke(); g.setStroke(LINE_STROKE); g.setColor(LINE_COLOR); Iterator iter = m_lines.iterator(); while(iter.hasNext() == true) { Line line = (Line)iter.next(); g.drawLine(line.x1, line.y1, line.x2, line.y2); } g.setStroke(strokeOrig); } private void drawNodes(Graphics2D g) { Iterator iter = m_root.iterator(); while(iter.hasNext() == true) { IResourceTreeNode[] nodes = (IResourceTreeNode[])iter.next(); this.drawNodes(g, nodes, NAME_FONT); } } private void drawNodes(Graphics2D g, IResourceTreeNode[] nodes, Font nameFont) { int cyAscent = g.getFontMetrics(nameFont).getAscent(); for(int i = 0;i < nodes.length;i++) { IResourceTreeNode node = nodes[i]; String name = node.getName(); String desc = node.getDescription(); BufferedImage img = getIcon(node.getType()); Rectangle[] rects = node.getRectangles(); if(rects == null) continue; for(int r = 0;r < rects.length;r++) { Rectangle rect = rects[r]; int xText = rect.x + ICON_WHITESPACE; int yText = rect.y + cyAscent - 3; if(img != null) { g.drawImage(img, rect.x, rect.y, null); xText += img.getWidth(); } if(name != null) { g.setColor( (node.isSelected() == true) ? SELECTED_COLOR : this.textColor ); g.setFont( (node.isSelected() == true) ? SELECTED_FONT : nameFont ); g.drawString(name, xText, yText); } if(desc != null) { g.setColor(DESC_COLOR); g.setFont(DESC_FONT); g.drawString(desc, xText, yText + m_descMetrics.getAscent()); } } if(m_yDividerLine > 0) { g.setColor(DEFAULT_BORDER_COLOR); g.drawLine(0, m_yDividerLine, this.width - 1, m_yDividerLine); } if(node.hasUpChildren() == true) this.drawNodes(g, (IResourceTreeNode[])node.getUpChildren(), NAME_FONT); if(node.hasDownChildren() == true) this.drawNodes(g, (IResourceTreeNode[])node.getDownChildren(), NAME_FONT); // Draw test rectangles if they're turned on if(m_testRects != null) { g.setColor(DEFAULT_BORDER_COLOR); Iterator iter = m_testRects.iterator(); while(iter.hasNext() == true) { Rectangle rect = (Rectangle)iter.next(); g.drawRect(rect.x, rect.y, rect.width, rect.height); } } } } private void swapNodes(Rectangle[] rects, int start, int end) { if( (end - start) < 1 || rects.length <= 1 ) return; int x = rects[start].x; int space = rects[start + 1].x - (x + rects[start].width); for(int i = end;i >= start;i--) { rects[i].x = x; x += (rects[i].width + space); } } public static BufferedImage getIcon(int type) { BufferedImage result; switch(type) { case IResourceTreeNode.RESOURCE: result = IMG_RESOURCE; break; case IResourceTreeNode.AUTO_GROUP: result = IMG_AUTO_GROUP; break; case IResourceTreeNode.CLUSTER: result = IMG_CLUSTER; break; default: result = null; } return result; } private class CalcResult { private Rectangle bounding; private IResourceTreeNode selected; private int yLevelLine; } }