diff --git a/projects/viztool/src/java/com/samskivert/viztool/Driver.java b/projects/viztool/src/java/com/samskivert/viztool/Driver.java index fcda052a..d3bf8a4f 100644 --- a/projects/viztool/src/java/com/samskivert/viztool/Driver.java +++ b/projects/viztool/src/java/com/samskivert/viztool/Driver.java @@ -1,5 +1,5 @@ // -// $Id: Driver.java,v 1.13 2001/12/01 05:28:01 mdb Exp $ +// $Id: Driver.java,v 1.14 2001/12/01 06:22:18 mdb Exp $ // // viztool - a tool for visualizing collections of java classes // Copyright (C) 2001 Michael Bayne @@ -70,8 +70,8 @@ public class Driver // and finally generate the visualization PackageEnumerator penum = new PackageEnumerator(pkgroot, enum, true); - Visualizer viz = new HierarchyVisualizer(pkgroot, penum); - // Visualizer viz = new SummaryVisualizer(pkgroot, penum); + // Visualizer viz = new HierarchyVisualizer(pkgroot, penum); + Visualizer viz = new SummaryVisualizer(pkgroot, penum); if (print) { // we use the print system to render things diff --git a/projects/viztool/src/java/com/samskivert/viztool/summary/ClassSummary.java b/projects/viztool/src/java/com/samskivert/viztool/summary/ClassSummary.java new file mode 100644 index 00000000..e506076d --- /dev/null +++ b/projects/viztool/src/java/com/samskivert/viztool/summary/ClassSummary.java @@ -0,0 +1,382 @@ +// +// $Id: ClassSummary.java,v 1.1 2001/12/01 06:22:18 mdb Exp $ +// +// viztool - a tool for visualizing collections of java classes +// Copyright (C) 2001 Michael Bayne +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation; either version 2.1 of the License, or (at your +// option) any later version. +// +// 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 com.samskivert.viztool.summary; + +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.font.FontRenderContext; +import java.awt.font.TextLayout; +import java.awt.geom.Line2D; +import java.awt.geom.Rectangle2D; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import java.util.ArrayList; + +import com.samskivert.viztool.Log; +import com.samskivert.viztool.layout.Element; +import com.samskivert.viztool.util.FontPicker; +import com.samskivert.viztool.util.LayoutUtil; +import com.samskivert.viztool.util.RenderUtil; + +/** + * A class summary displays information about a particular class + * (specifically, the interfaces it implements, the class it extends, its + * public instances, member functions, and inner class definitions). + */ +public class ClassSummary implements Element +{ + /** + * Constructs a class summary for the specified class. + */ + public ClassSummary (Class subject, SummaryVisualizer viz) + { + _viz = viz; + _subject = subject; + _name = _viz.name(_subject); + + // obtain information on our subject class + Class parent = _subject.getSuperclass(); + if (parent != null && !parent.equals(Object.class)) { + _parentName = _viz.name(parent); + } + + // get the implemented interfaces + Class[] interfaces = _subject.getInterfaces(); + int icount = interfaces.length; + _interfaces = new String[icount]; + for (int i = 0; i < icount; i++) { + _interfaces[i] = _viz.name(interfaces[i]); + } + + // get the public fields + Field[] fields = _subject.getDeclaredFields(); + ArrayList fsigtypes = new ArrayList(); + ArrayList fsigs = new ArrayList(); + for (int i = 0; i < fields.length; i++) { + Field f = fields[i]; + if ((f.getModifiers() & Modifier.PUBLIC) != 0) { + fsigtypes.add(genFieldTypeSig(f)); + fsigs.add(f.getName()); + } + } + _fieldTypes = new String[fsigtypes.size()]; + fsigtypes.toArray(_fieldTypes); + _fields = new String[fsigs.size()]; + fsigs.toArray(_fields); + + // get the public constructors and methods + ArrayList sigrets = new ArrayList(); + ArrayList sigs = new ArrayList(); + Constructor[] ctors = _subject.getConstructors(); + for (int i = 0; i < ctors.length; i++) { + Constructor c = ctors[i]; + if ((c.getModifiers() & Modifier.PUBLIC) != 0) { + sigrets.add(" "); + sigs.add(genConstructorSig(c)); + } + } + Method[] methods = _subject.getDeclaredMethods(); + for (int i = 0; i < methods.length; i++) { + Method m = methods[i]; + if ((m.getModifiers() & Modifier.PUBLIC) != 0) { + sigrets.add(genMethodRetSig(m)); + sigs.add(genMethodSig(m)); + } + } + _methodReturns = new String[sigrets.size()]; + sigrets.toArray(_methodReturns); + _methods = new String[sigs.size()]; + sigs.toArray(_methods); + } + + /** + * Introspects on our subject class and determines how much space + * we'll need to visualize it. + * + * @param gfx the graphics context in which this summary will + * eventually be rendered. + */ + public void layout (Graphics2D gfx) + { + FontRenderContext frc = gfx.getFontRenderContext(); + + // the header will be the name of this class surrounded by N + // points of space and a box + Rectangle2D bounds = LayoutUtil.getTextBox( + _subject.isInterface() ? FontPicker.getInterfaceFont() : + FontPicker.getClassFont(), frc, _name); + double spacing = 0; + + // add our parent class if we've got one + if (_parentName != null) { + String subtext = "extends " + _parentName; + bounds = LayoutUtil.accomodate( + bounds, FontPicker.getDeclaresFont(), frc, + LayoutUtil.SUBORDINATE_INSET, subtext); + } + + // add our interfaces + bounds = LayoutUtil.accomodate( + bounds, FontPicker.getImplementsFont(), frc, + LayoutUtil.SUBORDINATE_INSET, _interfaces); + + // add our fields + bounds = LayoutUtil.accomodate( + bounds, FontPicker.getClassFont(), frc, 0, _fieldTypes, _fields); + spacing += (_fields.length > 0) ? 2*LayoutUtil.HEADER_BORDER : 0; + + // add our constructors and methods + bounds = LayoutUtil.accomodate( + bounds, FontPicker.getClassFont(), frc, 0, + _methodReturns, _methods); + spacing += (_methods.length > 0) ? 2*LayoutUtil.HEADER_BORDER : 0; + + // incorporate space for the gaps + bounds.setRect(bounds.getX(), bounds.getY(), bounds.getWidth(), + bounds.getHeight() + spacing); + + // grab the new bounds + _bounds = bounds; + } + + /** + * Renders this class summary to the specified graphics context. + */ + public void render (Graphics2D gfx) + { + // figure out where we'll be rendering + Rectangle2D bounds = getBounds(); + double x = bounds.getX() + LayoutUtil.HEADER_BORDER; + double y = bounds.getY() + LayoutUtil.HEADER_BORDER; + double maxwid = 0, sy1 = 0, sy2 = 0; + + // draw the name + FontRenderContext frc = gfx.getFontRenderContext(); + Font font = _subject.isInterface() ? + FontPicker.getInterfaceFont() : FontPicker.getClassFont(); + Rectangle2D bnds = + RenderUtil.renderString(gfx, frc, font, x, y, _name); + maxwid = Math.max(maxwid, bnds.getWidth() + + 2*LayoutUtil.HEADER_BORDER); + y += bnds.getHeight(); + + // render the parent classname + if (_parentName != null) { + bnds = RenderUtil.renderString( + gfx, frc, FontPicker.getDeclaresFont(), + x + LayoutUtil.SUBORDINATE_INSET, y, _parentName); + maxwid = Math.max(maxwid, bnds.getWidth() + + 2*LayoutUtil.HEADER_BORDER + + LayoutUtil.SUBORDINATE_INSET); + y += bnds.getHeight(); + } + + // render our implemented interfaces + bnds = RenderUtil.renderStrings( + gfx, frc, FontPicker.getImplementsFont(), + x + LayoutUtil.SUBORDINATE_INSET, y, _interfaces); + maxwid = Math.max(maxwid, bnds.getWidth() + + 2*LayoutUtil.HEADER_BORDER + + LayoutUtil.SUBORDINATE_INSET); + y += bnds.getHeight(); + + // stroke a box that contains the header + Rectangle2D outline = new Rectangle2D.Double( + bounds.getX(), bounds.getY(), + maxwid, y + LayoutUtil.HEADER_BORDER - bounds.getY()); + gfx.draw(outline); + + // leave space for a separator + if (_fields.length > 0) { + y += LayoutUtil.HEADER_BORDER; + sy1 = y; + y += LayoutUtil.HEADER_BORDER; + } + + // render our fields + bnds = RenderUtil.renderStrings( + gfx, frc, FontPicker.getClassFont(), x, y, _fieldTypes, _fields); + maxwid = Math.max(maxwid, bnds.getWidth() + + 2*LayoutUtil.HEADER_BORDER); + y += bnds.getHeight(); + + // leave space for a separator + if (_methods.length > 0) { + y += LayoutUtil.HEADER_BORDER; + sy2 = y; + y += LayoutUtil.HEADER_BORDER; + } + + // render our constructors and methods + bnds = RenderUtil.renderStrings( + gfx, frc, FontPicker.getClassFont(), x, y, + _methodReturns, _methods); + maxwid = Math.max(maxwid, bnds.getWidth() + + 2*LayoutUtil.HEADER_BORDER); + y += bnds.getHeight(); + + // draw our separators now that we know how wide things are + double x1 = bounds.getX(), x2 = x1 + maxwid; + if (sy1 > 0) { + gfx.draw(new Line2D.Double(x1, sy1, x2, sy1)); + } + if (sy2 > 0) { + gfx.draw(new Line2D.Double(x1, sy2, x2, sy2)); + } + } + + // documentation inherited + public String getName () + { + return _name; + } + + // documentation inherited + public Rectangle2D getBounds () + { + return _bounds; + } + + // documentation inherited + public void setBounds (double x, double y, double width, double height) + { + _bounds.setRect(x, y, width, height); + } + + /** + * Generates a signature for the type of the supplied field. + */ + public String genFieldTypeSig (Field field) + { + StringBuffer buf = new StringBuffer(); + if ((field.getModifiers() & Modifier.STATIC) != 0) { + buf.append("static "); + } + buf.append(_viz.name(field.getType())).append(" "); + return buf.toString(); + } + + /** + * Generates a signature for the supplied constructor. + */ + public String genConstructorSig (Constructor ctor) + { + StringBuffer buf = new StringBuffer(); + buf.append(_viz.name(ctor.getDeclaringClass())).append("("); + Class[] ptypes = ctor.getParameterTypes(); + for (int i = 0; i < ptypes.length; i++) { + if (i > 0) { + buf.append(", "); + } + buf.append(_viz.name(ptypes[i])); + } + buf.append(")"); + Class[] etypes = ctor.getExceptionTypes(); + if (etypes.length > 0) { + buf.append(" throws "); + for (int i = 0; i < etypes.length; i++) { + if (i > 0) { + buf.append(", "); + } + buf.append(_viz.name(etypes[i])); + } + } + return buf.toString(); + } + + /** + * Generates a signature for the return value of the supplied method. + */ + public String genMethodRetSig (Method method) + { + StringBuffer buf = new StringBuffer(); + if ((method.getModifiers() & Modifier.STATIC) != 0) { + buf.append("static "); + } + buf.append(_viz.name(method.getReturnType())); + return buf.toString(); + } + + /** + * Generates a signature for the supplied method (minus return type). + */ + public String genMethodSig (Method method) + { + StringBuffer buf = new StringBuffer(); + buf.append(method.getName()).append("("); + Class[] ptypes = method.getParameterTypes(); + for (int i = 0; i < ptypes.length; i++) { + if (i > 0) { + buf.append(", "); + } + buf.append(_viz.name(ptypes[i])); + } + buf.append(")"); + Class[] etypes = method.getExceptionTypes(); + if (etypes.length > 0) { + buf.append(" throws "); + for (int i = 0; i < etypes.length; i++) { + if (i > 0) { + buf.append(", "); + } + buf.append(_viz.name(etypes[i])); + } + } + return buf.toString(); + } + + /** The package we're visualizing, which we'll strip from the front of + * class names. */ + protected SummaryVisualizer _viz; + + /** The class for which we're generating a summary visualization. */ + protected Class _subject; + + /** The cleaned up name of the class we're summarizing. */ + protected String _name; + + /** The name of our parent class or null if we don't have an + * interesting parent class. */ + protected String _parentName; + + /** The names of interfaces that we implement. */ + protected String[] _interfaces; + + /** The types of our public fields. */ + protected String[] _fieldTypes; + + /** The names of our public fields. */ + protected String[] _fields; + + /** The return types of our public constructors and methods. */ + protected String[] _methodReturns; + + /** The signatures of our public constructors and methods (minus + * return type). */ + protected String[] _methods; + + /** Our bounds. */ + protected Rectangle2D _bounds = new Rectangle2D.Double(); +} diff --git a/projects/viztool/src/java/com/samskivert/viztool/summary/SummaryVisualizer.java b/projects/viztool/src/java/com/samskivert/viztool/summary/SummaryVisualizer.java new file mode 100644 index 00000000..0ed0fe82 --- /dev/null +++ b/projects/viztool/src/java/com/samskivert/viztool/summary/SummaryVisualizer.java @@ -0,0 +1,245 @@ +// +// $Id: SummaryVisualizer.java,v 1.1 2001/12/01 06:22:18 mdb Exp $ +// +// viztool - a tool for visualizing collections of java classes +// Copyright (C) 2001 Michael Bayne +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation; either version 2.1 of the License, or (at your +// option) any later version. +// +// 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 com.samskivert.viztool.summary; + +import java.awt.BasicStroke; +import java.awt.Graphics2D; +import java.awt.Graphics; +import java.awt.geom.Rectangle2D; + +import java.awt.print.PageFormat; +import java.awt.print.PrinterException; + +import java.util.ArrayList; +import java.util.Iterator; + +import com.samskivert.viztool.Log; +import com.samskivert.viztool.Visualizer; +import com.samskivert.viztool.layout.ElementLayout; +import com.samskivert.viztool.layout.PackedColumnElementLayout; + +/** + * The summary visualizer displays summaries of the enumerated classes. + */ +public class SummaryVisualizer implements Visualizer +{ + /** + * Constructs a summary visualizer with the supplied enumerator as its + * source of classes. If the summary enumerator should be limited to a + * particular set of classes (and it most likely should), a filter + * enumerator should be supplied that returns only the classes to be + * visualized. + * + * @param pkgroot The name of the top-level package in which the + * visualized classes reside. This will be used to shorten the package + * names that are displayed. + * @param iter The enumerator that will return the names of the + * classes for which visualization is desired. + */ + public SummaryVisualizer (String pkgroot, Iterator iter) + { + // keep track of the package root + _pkgroot = pkgroot; + + // create our class summaries + while (iter.hasNext()) { + // strip out inner classes, we'll catch those via their + // declaring classes + String name = (String)iter.next(); + if (name.indexOf("$") != -1) { + continue; + } + + // add a class summary instance for this class + try { + Class subject = Class.forName(name); + _summaries.add(new ClassSummary(subject, this)); + } catch (Exception e) { + Log.warning("Unable to introspect class [class=" + name + + ", error=" + e + "]."); + } + } + } + + /** + * Configures the visualizer to prefix classnames with their package + * names (when they are outside of the visualized package root) or + * not. + */ + public void setDisplayPackageNames (boolean displayPackageNames) + { + _displayPackageNames = displayPackageNames; + } + + /** + * Lays out the class summary visualizations into the specified page + * dimensions. + */ + public void layout (Graphics2D gfx, double x, double y, + double width, double height) + { + // first layout all of our summaries (giving them dimensions) + for (int i = 0; i < _summaries.size(); i++) { + ClassSummary sum = (ClassSummary)_summaries.get(i); + sum.layout(gfx); + } + + // now arrange our summaries onto pages + _pages = new ArrayList(); + ArrayList list = new ArrayList(_summaries); + ArrayList next = new ArrayList(); + ElementLayout elay = new PackedColumnElementLayout(); + + while (list.size() > 0) { + // lay out the elements that fit on this page + Log.info("Laying out " + list.size() + " summaries in " + + width + "x" + height + "+" + x + "+" + y + "."); + elay.layout(list, width, height, next); + + // remove the overflowed elements from the list for this page + list.removeAll(next); + + // append this page to the pages list + _pages.add(list); + + // move to the next page + list = next; + next = new ArrayList(); + } + + // finally adjust all of the bounds of the class summaries by the + // x and y offset of the page + for (int i = 0; i < _summaries.size(); i++) { + ClassSummary sum = (ClassSummary)_summaries.get(i); + Rectangle2D b = sum.getBounds(); + sum.setBounds(b.getX()+x, b.getY()+y, b.getWidth(), b.getHeight()); + } + } + + /** + * Lays out and renders each of the classes that make up this package + * summary visualization. + */ + public int print (Graphics g, PageFormat pf, int pageIndex) + throws PrinterException + { + Graphics2D gfx = (Graphics2D)g; + + // relay things out if the page format has changed or if we've + // never been laid out + if (!pf.equals(_format) || _pages == null) { + // keep this around + _format = pf; + + // and do the layout + layout(gfx, pf.getImageableX(), pf.getImageableY(), + pf.getImageableWidth(), pf.getImageableHeight()); + } + + // adjust the stroke + gfx.setStroke(new BasicStroke(0.1f)); + + // make sure we're rendering a page that we have + if (pageIndex < 0 || pageIndex >= _pages.size()) { + return NO_SUCH_PAGE; + } + + // render the summaries on the requested page + ArrayList list = (ArrayList)_pages.get(pageIndex); + for (int i = 0; i < list.size(); i++) { + ((ClassSummary)list.get(i)).render(gfx); + } + return PAGE_EXISTS; + } + + /** + * Renders the specified page to the supplied graphics context. + */ + public void paint (Graphics2D gfx, int pageIndex) + { + // ignore them if we haven't been laid out or if we have + // absolutely nothing to display + if (_pages == null || _pages.size() == 0) { + return; + } + + // sanity check + if (pageIndex >= _pages.size()) { + Log.info("Requested to render non-existent page " + + "[index=" + pageIndex + "]."); + return; + } + + // adjust the stroke + gfx.setStroke(new BasicStroke(0.1f)); + + // render the summaries on the requested page + ArrayList list = (ArrayList)_pages.get(pageIndex); + for (int i = 0; i < list.size(); i++) { + ((ClassSummary)list.get(i)).render(gfx); + } + } + + /** + * Returns the number of pages occupied by this visualization. This is + * only valid after a call to {@link #layout}. + * + * @return the page count or -1 if we've not yet been laid out. + */ + public int getPageCount () + { + return (_pages == null) ? -1 : _pages.size(); + } + + /** + * Cleans up a fully qualified class name according to our + * configuration and the package root. + */ + public String name (Class clazz) + { + if (clazz.isArray()) { + return name(clazz.getComponentType()) + "[]"; + } + + String name = clazz.getName(); + if (_displayPackageNames) { + if (name.startsWith(_pkgroot)) { + return "." + name.substring(_pkgroot.length()); + } else if (name.startsWith("java.lang")) { + return name.substring(10); + } else { + return name; + } + + } else { + int ldidx = name.lastIndexOf("."); + return (ldidx == -1) ? name : name.substring(ldidx+1); + } + } + + protected String _pkgroot; + protected ArrayList _summaries = new ArrayList(); + protected ArrayList _pages; + protected PageFormat _format; + protected boolean _displayPackageNames = false; + + protected static final int GAP = 72/4; +} diff --git a/projects/viztool/src/java/com/samskivert/viztool/util/LayoutUtil.java b/projects/viztool/src/java/com/samskivert/viztool/util/LayoutUtil.java index 710b26f4..eecbcf19 100644 --- a/projects/viztool/src/java/com/samskivert/viztool/util/LayoutUtil.java +++ b/projects/viztool/src/java/com/samskivert/viztool/util/LayoutUtil.java @@ -1,5 +1,5 @@ // -// $Id: LayoutUtil.java,v 1.1 2001/12/01 05:28:01 mdb Exp $ +// $Id: LayoutUtil.java,v 1.2 2001/12/01 06:22:18 mdb Exp $ // // viztool - a tool for visualizing collections of java classes // Copyright (C) 2001 Michael Bayne @@ -103,4 +103,38 @@ public class LayoutUtil maxwid, bounds.getHeight() + height); return bounds; } + + /** + * Returns a rectangle that accomodates the two specified columns of + * text at the bottom of the supplied rectangle, taking into account + * the preferred text spacing and the specified inset for the + * accomodated text. + */ + public static Rectangle2D accomodate ( + Rectangle2D bounds, Font font, FontRenderContext frc, double inset, + String[] left, String[] right) + { + double maxleft = 0, maxwid = bounds.getWidth(); + double height = 0; + + Rectangle2D[] bndl = new Rectangle2D[left.length]; + Rectangle2D[] bndr = new Rectangle2D[right.length]; + + // first compute our dimensions + for (int i = 0; i < left.length; i++) { + bndl[i] = new TextLayout(left[i], font, frc).getBounds(); + bndr[i] = new TextLayout(right[i], font, frc).getBounds(); + maxleft = Math.max(maxleft, bndl[i].getWidth()); + } + + // now that we have the maxleft width we can calculate the rest + for (int i = 0; i < left.length; i++) { + maxwid = Math.max(maxwid, maxleft+GAP+bndr[i].getWidth()+inset); + height += Math.max(bndl[i].getHeight(), bndr[i].getHeight()); + } + + bounds.setRect(bounds.getX(), bounds.getY(), + maxwid, bounds.getHeight() + height); + return bounds; + } } diff --git a/projects/viztool/src/java/com/samskivert/viztool/util/RenderUtil.java b/projects/viztool/src/java/com/samskivert/viztool/util/RenderUtil.java index 44c0add0..b3824411 100644 --- a/projects/viztool/src/java/com/samskivert/viztool/util/RenderUtil.java +++ b/projects/viztool/src/java/com/samskivert/viztool/util/RenderUtil.java @@ -1,5 +1,5 @@ // -// $Id: RenderUtil.java,v 1.1 2001/12/01 05:28:01 mdb Exp $ +// $Id: RenderUtil.java,v 1.2 2001/12/01 06:22:18 mdb Exp $ // // viztool - a tool for visualizing collections of java classes // Copyright (C) 2001 Michael Bayne @@ -61,7 +61,7 @@ public class RenderUtil Graphics2D gfx, FontRenderContext frc, Font font, double x, double y, String[] text) { - return renderStrings(gfx, frc, font, x, y, text, null); + return renderStrings(gfx, frc, font, x, y, text, (String)null); } /** @@ -104,4 +104,57 @@ public class RenderUtil // return the dimensions occupied by the rendered strings return new Rectangle2D.Double(x, y, maxwid, y-starty); } + + /** + * Renders a two column array of strings to the specified graphics + * context, in the specified font at the specified coordinates. The + * left column is right align and the right, left aligned. + * + * @return the bounds occupied by the rendered strings. + */ + public static Rectangle2D renderStrings ( + Graphics2D gfx, FontRenderContext frc, Font font, + double x, double y, String[] left, String[] right) + { + double maxleft = 0, maxwid = 0, starty = y; + double inset = 0; + + // first generate text layout instances and compute bounds for all + // entries in both columns + TextLayout[] llay = new TextLayout[left.length]; + Rectangle2D[] lbnds = new Rectangle2D[left.length]; + TextLayout[] rlay = new TextLayout[right.length]; + Rectangle2D[] rbnds = new Rectangle2D[right.length]; + + // compute the dimensions + for (int i = 0; i < left.length; i++) { + llay[i] = new TextLayout(left[i], font, frc); + lbnds[i] = llay[i].getBounds(); + rlay[i] = new TextLayout(right[i], font, frc); + rbnds[i] = rlay[i].getBounds(); + maxleft = Math.max(maxleft, lbnds[i].getWidth()); + } + + // do the rendering + for (int i = 0; i < left.length; i++) { + double lw = lbnds[i].getWidth(); + llay[i].draw(gfx, (float)(x - lbnds[i].getX() + maxleft - lw), + // we actually mean to use rbnds[i] here because + // the right hand side (usually being the method + // declaration), tends to be taller than the left + // hand side (because of the parenthesis) and + // would appear a bit lower than the left hand + // side if we didn't use it's y offset + (float)(y - rbnds[i].getY())); + rlay[i].draw(gfx, (float)(x - rbnds[i].getX() + maxleft + + LayoutUtil.GAP), + (float)(y - rbnds[i].getY())); + maxwid = Math.max(maxwid, maxleft + LayoutUtil.GAP + + rbnds[i].getWidth()); + y += Math.max(lbnds[i].getHeight(), rbnds[i].getHeight()); + } + + // return the dimensions occupied by the rendered strings + return new Rectangle2D.Double(x, y, maxwid, y-starty); + } }