/* Copyright (c) 2007 John C. Gunther. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of the JComponentBreadboard Consortium nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITURE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Note: the license conditions above were copied from the BSD open source license template available at http://www.opensource.org.licenses/bsd-license.php. */ package net.sourceforge.jcomponentbreadboard; import javax.swing.*; import javax.swing.text.*; import javax.swing.border.Border; import javax.swing.border.TitledBorder; import javax.swing.event.*; import java.awt.*; import java.awt.event.*; import java.util.*; import java.lang.reflect.*; import java.beans.*; /** * * Represents all aspects of a Java/Swing form--from layout management to how the * form is connected to the objects it views and controls. * For more information including detailed examples, see the * {@link net.sourceforge.jcomponentbreadboard * package description} and the * JComponentBreadboard User's Guide. * * @author John C. Gunther * @version 1.0.1 (requires JDK 1.5 or higher) * */ @SuppressWarnings("serial") public class JComponentBreadboard extends JPanel { // When a single JComponent ref, possibly with a longish name, is mapped // into a large sub-block of the breadboard array, the resulting // verbiage makes it hard to read the breadboard array. Use of "" to // refer to the object ref in the cell immediately above, and __ for the // ref immediately to the left (the "ColumnarDitto"), can often increase // the readability of tbe breadboard array, as well as making it easier // both to enter and edit such arrays (name changes, for example, // require 1 replacement, not 100) private final static class ColumnarDitto { public static final ColumnarDitto COLUMNAR_DITTO = new ColumnarDitto(); private ColumnarDitto() {} }; /** A directive that, when placed into the cell of a breadboard array, ** instructs JComponentBreadboard to use the object reference immediately ** to the left of this cell for this cell, too. In other words, a kind ** of across columns ditto mark. **

** ** The odd name for this keyword ("__") is meant to suggest an ** ellipsis or "fill in the blank", to save space, and to make the ** breadboard array easier to read. **

** ** @see #setBreadboard setBreadboard ** @see #ROWWISE_DITTO ROWWISE_DITTO ** **/ public static final ColumnarDitto __ = ColumnarDitto.COLUMNAR_DITTO; /** A directive that, when placed into the cell of a breadboard array, ** instructs JComponentBreadboard to use the object reference immediately ** above this cell for this cell, too. In other words, a ** conventional ditto mark. This constant is equal to the String literal "", and the ** intention is that the "" String literal, ** rather than this constant, will be placed onto actual breadboard arrays both ** to save space and also for its mnemonic significance (when interpreted as a ditto mark). **

** @see #setBreadboard setBreadboard ** @see #__ "columnar ditto" **/ public final String ROWWISE_DITTO = ""; private final static String JCB_PROPERTY_PREFIX = "JCB_"; // This randomly generated name suffix assures no one will choose // the same client property names, or keyword strings, as we do. private final static String JCB_PROPERTY_SUFFIX = "_wVs0Xe1oH7Ou5juhuf9w"; private static String jcbPropertyName(String sName) { return JCB_PROPERTY_PREFIX + sName + JCB_PROPERTY_SUFFIX; } // special keyword strings that can be returned from getXXXInvalidDataMessage() methods /** ** Special keyword returned by data validation methods to indicate that the data ** String passed to the method contains valid data. See the section titled ** Data Validation Method Signatures within the {@link ** #jbConnect(JComponent,String) jbConnect} method's javadocs for more ** information. For an example that uses DATA_IS_VALID within a data validation ** method that validates a numeric entry field, see the NumericInputDialog ** application of the JComponentBreadboard User's Guide. ** ** @see #REVERT_QUIETLY REVERT_QUIETLY ** @see #REVERT_AND_BEEP REVERT_AND_BEEP ** @see #DEFAULT_INVALID_DATA_MESSAGE_TITLE DEFAULT_INVALID_DATA_MESSAGE_TITLE ** @see #jbConnect(JComponent,String) jbConnect ** **/ public final static String DATA_IS_VALID = jcbPropertyName("DATA_IS_VALID"); /** Special keyword returned by data validation methods to indicate that ** the data String passed to the method contains invalid data, and ** that you want the field to reject this input and quietly revert back ** to the last validated entry.

** ** @see #DATA_IS_VALID DATA_IS_VALID ** @see #REVERT_AND_BEEP REVERT_AND_BEEP ** @see #DEFAULT_INVALID_DATA_MESSAGE_TITLE DEFAULT_INVALID_DATA_MESSAGE_TITLE ** @see #jbConnect(JComponent,String) jbConnect ** **/ public final static String REVERT_QUIETLY = jcbPropertyName("REVERT_QUIETLY"); /** Special keyword returned by data validation methods to indicate that ** the data String passed to the method contains invalid data, and ** that you want the field to reject this input and, after beeping for ** feedback, revert the data back to the last validated entry.

** ** @see #DATA_IS_VALID DATA_IS_VALID ** @see #REVERT_QUIETLY REVERT_QUIETLY ** @see #DEFAULT_INVALID_DATA_MESSAGE_TITLE DEFAULT_INVALID_DATA_MESSAGE_TITLE ** @see #jbConnect(JComponent,String) jbConnect ** **/ public final static String REVERT_AND_BEEP = jcbPropertyName("REVERT_AND BEEP"); // ScalingDirective defines the four constants used to define how each // row and column of a JComponentBreadboard get scaled up or down in // response to surplus or deficit space in the parent container. private static final class ScalingDirective { private boolean shrinks; private boolean expands; private ScalingDirective(boolean shrinks, boolean expands) { this.shrinks = shrinks; this.expands = expands; } public final static ScalingDirective NOSCALE = new ScalingDirective(false, false); public final static ScalingDirective EXPANDS = new ScalingDirective(false, true); public final static ScalingDirective SHRINKS = new ScalingDirective(true, false); public final static ScalingDirective BISCALE = new ScalingDirective(true, true); // Does the column (row) width (height) shrink down uniformly from its // "tightest fitting to preferred sizes grid" width (height) in order to fit // the component grid into a too-small parent container? public boolean shrinks() { return shrinks; } // Does the column (row) width (height) expand up uniformly from its // "tightest fitting to preferred sizes grid" width (height) in order to use up // any extra space available in the parent container? public boolean expands() { return expands; } }; /** ** Columns (rows) of the breadboard array whose headers contain this ** directive do not uniformly scale their preferred-size-based ** widths (heights) up when extra width (height) is available in the ** parent container, and also do not uniformly scale down when ** the parent container is too small.

** ** @see #setBreadboard setBreadboard ** @see #SHRINKS SHRINKS ** @see #EXPANDS EXPANDS ** @see #BISCALE BISCALE ** **/ public final static ScalingDirective NOSCALE = ScalingDirective.NOSCALE; /** ** Columns (rows) of the breadboard array whose headers contain this ** directive uniformly scale their preferred-size-based widths (heights) ** up when extra width (height) is available in the parent container, ** but do not uniformly scale down when the parent container ** is too small.

** ** @see #setBreadboard setBreadboard ** @see #NOSCALE NOSCALE ** @see #SHRINKS SHRINKS ** @see #BISCALE BISCALE ** **/ public final static ScalingDirective EXPANDS = ScalingDirective.EXPANDS; /** ** Columns (rows) of the breadboard array whose headers contain this ** directive do not scale their preferred-size-based widths ** (heights) up when extra width (height) is available, but do scale ** down uniformly when the parent container is too small. ** **

** @see #setBreadboard setBreadboard ** @see #NOSCALE NOSCALE ** @see #EXPANDS EXPANDS ** @see #BISCALE BISCALE **/ public final static ScalingDirective SHRINKS = ScalingDirective.SHRINKS; /** ** Columns (rows) of the breadboard array whose headers contain this ** directive both uniformly scale their preferred-size-based widths ** (heights) up when extra width (height) is available in the parent ** container, and also uniformly scale down when the parent container ** is too small.

** ** @see #setBreadboard setBreadboard ** @see #NOSCALE NOSCALE ** @see #SHRINKS SHRINKS ** @see #EXPANDS EXPANDS ** **/ public final static ScalingDirective BISCALE = ScalingDirective.BISCALE; // default scaling directive when all scaling directive headers are elided private final static ScalingDirective DEFAULT_SCALING_DIRECTIVE = NOSCALE; // These hold references to the constants that define scaling directives // (see above) for each row and column in the breadboard array. private ScalingDirective[] rowScale = null; private ScalingDirective[] colScale = null; // returns the component-type based default for the fill directive private static final int XFILL = 0; private static final int YFILL = 1; private static boolean[] defaultFill(JComponent jc) { boolean[] result = new boolean[YFILL+1]; result[XFILL] = false; result[YFILL] = false; if (hasMethod(jc, "getOrientation", int.class, null, 0)) { int orientation = ((Integer) getMethodValue(jc,"getOrientation", null)).intValue(); if (SwingConstants.VERTICAL == orientation) result[YFILL] = true; else if (SwingConstants.HORIZONTAL == orientation) result[XFILL] = true; } return result; } private static boolean defaultXFill(JComponent jc) { boolean result = defaultFill(jc)[XFILL]; return result; } private static boolean defaultYFill(JComponent jc) { boolean result = defaultFill(jc)[YFILL]; return result; } // name for the "fill", etc. client property recognized by JComponentBreadboard // (other properties, like xAlign, are stored in existing JComponent properties) private final static String X_FILL_PROPNAME = jcbPropertyName("xFill"); private final static String Y_FILL_PROPNAME = jcbPropertyName("yFill"); private final static String X_UPSIZE_PROPNAME = jcbPropertyName("xUpsize"); private final static String Y_UPSIZE_PROPNAME = jcbPropertyName("yUpsize"); /** ** Defines whether or not the component should always expand to fill out any ** extra horizontal space that may be available in the ** breadboard grid cell(s) that it occupies. **

** ** For most JComponents, the default if not specified is not to fill. ** The exception is for horizontally oriented JComponents (horizontally ** oriented JScrollbars, JSliders, etc.--any ** JComponent with a getOrientation method that returns ** SwingConstants.HORIZONTAL) which fill by default.

** ** @param isXFilled true if horizontal filling is to be enabled, false if not. ** @param jc the JComponent whose horizontal fill attribute is to be specified. ** @return the JComponent whose fill attribute was specified (jc). ** ** @see #yFill(boolean, JComponent) yFill(boolean, JComponent) ** @see #setBreadboard setBreadboard ** **/ public static JComponent xFill(boolean isXFilled, JComponent jc) { if (null != jc) jc.putClientProperty(X_FILL_PROPNAME, new Boolean(isXFilled)); return jc; } /** ** Convenience method equivalent to xFill(true, jc) **

** @param jc the JComponent whose horizontal fill attribute is set to true. ** @return the JComponent reference passed in as its argument. ** ** @see #xFill(boolean, JComponent) xFill(boolean, JComponent) ** **/ public static JComponent xFill(JComponent jc) { return xFill(true, jc); } /** ** Convenience method equivalent to applying xFill(isXFilled, jca[i]) ** to every non-null element of the given 1-D array. **

** @param isXFilled true if horizontal filling is to be enabled, false if not. ** @param jca the JComponent array containing the elements operated upon. ** @return the array reference passed in as its second argument ** ** @see #xFill(boolean, JComponent) xFill(boolean, JComponent) ** **/ public static JComponent[] xFill(boolean isXFilled, JComponent[] jca) { for (int i = 0; jca != null && i < jca.length; i++) xFill(isXFilled, jca[i]); return jca; } /** ** Convenience method equivalent to applying xFill(true, jca[i]) ** to every non-null element of the given 1-D array. **

** @param jca the JComponent array containing the elements operated upon. ** @return the array reference passed in as its argument ** ** @see #xFill(boolean, JComponent) xFill(boolean, JComponent) ** **/ public static JComponent[] xFill(JComponent[] jca) { return xFill(true, jca); } /** ** Convenience method equivalent to applying xFill(isXFilled, jca[i][j]) ** to every non-null element of the given 2-D array. **

** @param isXFilled true if horizontal filling is to be enabled, false if not. ** @param jca the JComponent array containing the elements operated upon. ** @return the array reference passed in as its second argument. ** ** @see #xFill(boolean, JComponent) xFill(boolean, JComponent) ** **/ public static JComponent[][] xFill(boolean isXFilled, JComponent[][] jca) { for (int i = 0; jca != null && i < jca.length; i++) xFill(isXFilled, jca[i]); return jca; } /** ** Convenience method equivalent to applying xFill(true, jca[i][j]) ** to every non-null element of the given 2-D array. **

** @param jca the JComponent array containing the elements operated upon. ** @return the array reference passed in as its argument ** ** @see #xFill(boolean, JComponent) xFill(boolean, JComponent) ** **/ public static JComponent[][] xFill(JComponent[][] jca) { return xFill(true, jca); } /** ** Defines whether or not the component should always expand to fill out any ** extra vertical space that may be available in the ** breadboard grid cell(s) that it occupies. **

** For most JComponents, the default if not specified is not to fill. ** The exception is for vertically oriented JComponents (vertically ** oriented JScrollbars, JSliders, etc.--any JComponent with ** a getOrientation method that returns SwingConstants.VERTICAL) ** which fill by default. **

** @param isYFilled true if vertical filling is to be enabled, false if not. ** @param jc the JComponent whose vertical fill attribute is to be specified. ** @return the JComponent whose fill attribute was specified (jc). ** ** @see #xFill(boolean, JComponent) xFill(boolean, JComponent) ** @see #setBreadboard setBreadboard ** **/ public static JComponent yFill(boolean isYFilled, JComponent jc) { if (null != jc) jc.putClientProperty(Y_FILL_PROPNAME, isYFilled); return jc; } /** ** Convenience method equivalent to yFill(true, jc) **

** @param jc the JComponent whose vertical fill attribute is to be specified. ** @return the JComponent whose fill attribute was specified (jc). ** @see #yFill(boolean, JComponent) yFill(boolean, JComponent) ** **/ public static JComponent yFill(JComponent jc) { return yFill(true,jc); } /** ** Convenience method equivalent to applying yFill(isYFilled, jca[i]) ** to every non-null element of the given 1-D array. **

** @param isYFilled true if vertical filling is to be enabled, false if not. ** @param jca the JComponent array whose elements are operated upon ** @return the JComponent array whose elements were operated upon ** @see #yFill(boolean, JComponent) yFill(boolean, JComponent) ** **/ public static JComponent[] yFill(boolean isYFilled, JComponent[] jca) { for (int i = 0; jca != null && i < jca.length; i++) yFill(isYFilled, jca[i]); return jca; } /** ** Convenience method equivalent to applying yFill(true, jca[i]) ** to every non-null element of the given 1-D array. **

** @param jca the JComponent array whose elements are operated upon ** @return the JComponent array whose elements were operated upon ** @see #yFill(boolean, JComponent) yFill(boolean, JComponent) ** **/ public static JComponent[] yFill(JComponent[] jca) { return yFill(true, jca); } /** ** Convenience method equivalent to applying yFill(isYFilled, jca[i][j]) ** to every non-null element of the given 2-D array. **

** @param isYFilled true if vertical filling is to be enabled, false if not. ** @param jca the JComponent array whose elements are operated upon ** @return the JComponent array whose elements were operated upon ** @see #yFill(boolean, JComponent) yFill(boolean, JComponent) ** **/ public static JComponent[][] yFill(boolean isYFilled, JComponent[][] jca) { for (int i = 0; jca != null && i < jca.length; i++) yFill(isYFilled, jca[i]); return jca; } /** ** Convenience method equivalent to applying yFill(true, jca[i][j]) ** to every non-null element of the given 2-D array. **

** @param jca the JComponent array whose elements are operated upon ** @return the JComponent array whose elements were operated upon ** @see #yFill(boolean, JComponent) yFill(boolean, JComponent) ** **/ public static JComponent[][] yFill(JComponent[][] jca) { return yFill(true, jca); } /** ** Convenience method equivalent to ** xFill(isXFilled,yFill(isYFilled, jc)) **

** @param isXFilled true if horizontal filling is to be enabled, false if not. ** @param isYFilled true if vertical filling is to be enabled, false if not. ** @param jc the JComponent operated upon ** @return the JComponent operated upon ** @see #xFill(boolean, JComponent) xFill(boolean, JComponent) ** @see #yFill(boolean, JComponent) yFill(boolean, JComponent) ** **/ public static JComponent xyFill(boolean isXFilled, boolean isYFilled, JComponent jc) { return xFill(isXFilled, yFill(isYFilled, jc)); } /** ** Convenience method equivalent to ** xFill(true,yFill(true, jc)) **

** @param jc the JComponent operated upon ** @return the JComponent operated upon ** @see #xFill(boolean, JComponent) xFill(boolean, JComponent) ** @see #yFill(boolean, JComponent) yFill(boolean, JComponent) ** **/ public static JComponent xyFill(JComponent jc) { return xyFill(true, true, jc); } /** ** Convenience method equivalent to applying ** xyFill(isXFilled,isYFilled, jca[i]) to every non-null element of the ** given 1-D array. **

** @param isXFilled true if horizontal filling is to be enabled, false if not. ** @param isYFilled true if vertical filling is to be enabled, false if not. ** @param jca the JComponent array whose elements are operated upon ** @return the JComponent array whose elements were operated upon ** @see #xyFill(boolean, boolean, JComponent) xyFill(boolean, boolean, JComponent) ** **/ public static JComponent[] xyFill(boolean isXFilled, boolean isYFilled, JComponent[] jca) { for (int i = 0; jca != null && i < jca.length; i++) xyFill(isXFilled, isYFilled, jca[i]); return jca; } /** ** Convenience method equivalent to ** xyFill(true, true, jca) **

** @param jca the JComponent array whose elements are operated upon ** @return the JComponent array whose elements were operated upon ** @see #xyFill(boolean, boolean, JComponent[]) xyFill(boolean, boolean, JComponent[]) ** **/ public static JComponent[] xyFill(JComponent[] jca) { return xyFill(true, true, jca); } /** ** Convenience method equivalent to applying ** xyFill(isXFilled, isYFilled, jca[i][j]) to every non-null element of the ** given 2-D array **

** @param isXFilled true if horizontal filling is to be enabled, false if not. ** @param isYFilled true if vertical filling is to be enabled, false if not. ** @param jca the JComponent array whose elements are operated upon ** @return the JComponent array whose elements were operated upon ** @see #xyFill(boolean, boolean, JComponent) xyFill(boolean, boolean, JComponent) ** **/ public static JComponent[][] xyFill(boolean isXFilled, boolean isYFilled, JComponent[][] jca) { for (int i = 0; jca != null && i < jca.length; i++) xyFill(isXFilled, isYFilled, jca[i]); return jca; } /** ** Convenience method equivalent to xyFill(true, true, jca). **

** @param jca the JComponent array whose elements are operated upon ** @return the JComponent array whose elements were operated upon ** @see #xyFill(boolean, boolean, JComponent[][]) xyFill(boolean, boolean, JComponent[][]) ** **/ public static JComponent[][] xyFill(JComponent[][] jca) { return xyFill(true, true, jca); } /** ** Returns true if the an attribute to expand the JComponent's width to fill ** out the grid cells that contain it horizontally has been attached to the ** JComponent, false otherwise. **

** Such attributes are attached via the xFill method. **

** @param jc the JComponent whose xFill attribute is to be returned ** @return true if horizontal filling is enabled for the component, else false. ** @see #xFill(boolean, JComponent) xFill(boolean, JComponent) ** **/ public static boolean isXFilled(JComponent jc) { Boolean result = (Boolean) jc.getClientProperty(X_FILL_PROPNAME); if (null == result) result = defaultXFill(jc); return result; } /** ** Returns true if an attribute to expand the JComponent's height to fill ** out the grid cells that contain it vertically has been attached to the ** JComponent, false otherwise. **

** Such attributes are attached via the yFill method. **

** @param jc the JComponent whose yFill attribute is to be returned ** @return true if vertical filling is enabled for the component, else false. ** @see #yFill(boolean, JComponent) yFill(boolean, JComponent) ** **/ public static boolean isYFilled(JComponent jc) { Boolean result = (Boolean) jc.getClientProperty(Y_FILL_PROPNAME); if (null == result) result = defaultYFill(jc); return result; } /** ** Declares that, in situations in which the JComponent's (possibly ** xUpsize adjusted) preferred width ** is smaller than the width of the breadboard grid-cell-block that contains it, ** the JComponent should be positioned the specified fraction of the way ** between the extreme flush left and extreme flush right positions. **

** For example, a fraction of 0 means flush left, a fraction of 1 flush right, ** and a fraction of 0.5 means horizontally centered. **

** Note: this method changes the JComponent's xAlignment property ** via a call to jc.setXAlignment(fraction). **

** ** @param fraction the fraction of the way between the extreme left and extreme ** right positions within the breadboard grid-cell-block that contains the ** component at which to position the component horizontally. ** @param jc the JComponent to receive this horizontal alignment attribute ** @return the JComponent passed in as the last argument (jc), whose ** x alignment has been modified. ** ** @see #setBreadboard setBreadboard ** @see #xUpsize(int,JComponent) xUpsize ** @see #yAlign(double,JComponent) yAlign ** **/ public static JComponent xAlign(double fraction, JComponent jc) { if (fraction < 0) throw new IllegalArgumentException( "fraction = " + fraction + "; fraction arg cannot be negative."); else if (fraction > 1) throw new IllegalArgumentException( "fraction = " + fraction + "; fraction arg cannot be greater than 1."); else if (null != jc) { jc.setAlignmentX((float) fraction); } return jc; } /** ** Convenience method that issues xAlign(fraction, jca[i]) ** to set the xAlign fraction on every non-null element of a 1-D array of ** JComponents. ** ** @param fraction the fraction of the way between the extreme left and extreme ** right positions within the breadboard grid-cell-blocks that contain the ** components at which to position the components horizontally. ** @param jca contains the JComponents to receive this horizontal alignment attribute ** @return the array reference passed in as its second argument ** ** @see #xAlign(double, JComponent) xAlign(double, JComponent) ** **/ public static JComponent[] xAlign(double fraction, JComponent[] jca) { for (int i = 0; jca != null && i < jca.length; i++) xAlign(fraction, jca[i]); return jca; } /** ** Convenience method that issues xAlign(fraction, jca[i][j]) ** to set the xAlign fraction on every non-null element of a 2-D array of ** JComponents. ** ** @param fraction the fraction of the way between the extreme left and extreme ** right positions within the breadboard grid-cell-blocks that contain the ** components at which to position the components horizontally. ** @param jca contains the JComponents to receive this horizontal alignment attribute ** @return the array reference passed in as its second argument. ** ** @see #xAlign(double, JComponent) xAlign(double, JComponent) ** **/ public static JComponent[][] xAlign(double fraction, JComponent[][] jca) { for (int i = 0; jca != null && i < jca.length; i++) xAlign(fraction, jca[i]); return jca; } /** ** Declares that, in situations in which the JComponent's (possibly ** yUpsize adjusted) preferred height ** is smaller than the height of the breadboard grid-cell-block that contains it, ** the JComponent should be positioned the specified fraction of the way ** between the extreme top and extreme bottom positions. **

** For example, a fraction of 0 means flush against the top edge, ** a fraction of 1 flush against the bottom edge, ** and a fraction of 0.5 means vertically centered. **

** Note: this method changes the JComponent's yAlignment property ** via a call to jc.setYAlignment(fraction). **

** @see #setBreadboard setBreadboard ** @see #yUpsize(int,JComponent) yUpsize ** @see #xAlign(double,JComponent) xAlign ** ** @param fraction of the way between the extreme top and extreme ** bottom positions (within the breadboard grid-cell-block that contains the ** component) at which to position the component vertically. ** @param jc the JComponent to receive this vertical alignment attribute ** @return the JComponent passed in as the last argument, whose ** y alignment has been modified. ** ** ** **/ public static JComponent yAlign(double fraction, JComponent jc) { if (fraction < 0) throw new IllegalArgumentException( "fraction = " + fraction + "; fraction arg cannot be negative."); else if (fraction > 1) throw new IllegalArgumentException( "fraction = " + fraction + "; fraction arg cannot be greater than 1."); else if (null != jc) { jc.setAlignmentY((float) fraction); } return jc; } /** ** Convenience method that issues yAlign(fraction, jca[i]) ** to set the yAlignment of every non-null element of a 1-D array of ** JComponents. ** ** @param fraction of the way between the extreme top and extreme ** bottom positions (within the breadboard grid-cell-blocks that contain the ** components) at which to position the components vertically. ** @param jca contains the JComponents to receive this vertical alignment attribute ** @return the array reference passed in as its second argument ** ** @see #yAlign(double, JComponent) yAlign(double, JComponent) ** **/ public static JComponent[] yAlign(double fraction, JComponent[] jca) { for (int i = 0; jca != null && i < jca.length; i++) yAlign(fraction, jca[i]); return jca; } /** ** Convenience method that issues yAlign(fraction, jca[i][j]) ** to set the yAlignment of every non-null element of a 2-D array of ** JComponents. ** ** @param fraction of the way between the extreme top and extreme ** bottom positions (within the breadboard grid-cell-blocks that contain the ** components) at which to position the components vertically. ** @param jca contains the JComponents to receive this vertical alignment attribute ** @return the array reference passed in as its second argument ** ** @see #yAlign(double, JComponent) yAlign(double, JComponent) ** **/ public static JComponent[][] yAlign(double fraction, JComponent[][] jca) { for (int i = 0; jca != null && i < jca.length; i++) yAlign(fraction, jca[i]); return jca; } /** ** Convenience method equivalent to xAlign(xFraction, yAlign(yFraction, jc)) ** ** @param xFraction the fraction of the way between the extreme left and extreme ** right positions (within the breadboard grid-cell-block that contains the ** component) at which to position the component horizontally. ** @param yFraction the fraction of the way between the extreme top and extreme ** bottom positions (within the breadboard grid-cell-block that contains the ** component) at which to position the components vertically. ** @param jc the JComponent to receive these alignment attributes ** @return the JComponent reference passed in as its third argument ** ** @see #xAlign(double, JComponent) xAlign(double, JComponent) ** @see #yAlign(double, JComponent) yAlign(double, JComponent) ** **/ public static JComponent xyAlign(double xFraction, double yFraction, JComponent jc) { return xAlign(xFraction, yAlign(yFraction, jc)); } /** ** Convenience method equivalent to applying xyAlign(widthIncreaseInPixels, ** heightIncreaseInPixels, jca[i])) to every non-null element in the 1-D array. ** ** @param xFraction the fraction of the way between the extreme left and extreme ** right positions (within the breadboard grid-cell-blocks that contain the ** components) at which to position the components horizontally. ** @param yFraction the fraction of the way between the extreme top and extreme ** bottom positions (within the breadboard grid-cell-blocks that contain the ** components) at which to position the components vertically. ** @param jca contains the JComponents to receive these alignment attributes ** @return the array passed in as the third parameter ** ** @see #xyAlign(double, double, JComponent) xyAlign(double, double, JComponent) ** **/ public static JComponent[] xyAlign(double xFraction, double yFraction, JComponent[] jca) { return xAlign(xFraction, yAlign(yFraction, jca)); } /** ** Convenience method equivalent to applying xyAlign(widthIncreaseInPixels, ** heightIncreaseInPixels, jca[i][j])) to every non-null element in the 2-D array. ** ** @param xFraction the fraction of the way between the extreme left and extreme ** right positions (within the breadboard grid-cell-blocks that contain the ** components) at which to position the components horizontally. ** @param yFraction the fraction of the way between the extreme top and extreme ** bottom positions (within the breadboard grid-cell-blocks that contain the ** components) at which to position the components vertically. ** @param jca contains the JComponents to receive these alignment attributes ** @return the array passed in as the third parameter ** ** @see #xyAlign(double, double, JComponent) xyAlign(double, double, JComponent) ** **/ public static JComponent[][] xyAlign(double xFraction, double yFraction, JComponent[][] jca) { return xAlign(xFraction, yAlign(yFraction, jca)); } /** ** The maximum allowed xUpsize width increase, or yUpsize ** height increase. ** **

** With screen coordinates of around 10 times larger than this, Swing ** starts producing a variety of strange error messages. Moreover, it is ** unlikely increases larger than this -- far more than a whole screen of pixels ** on today's monitors--will be needed for the fine-tuning adjustments of ** xUpsize and yUpsize. **

** ** An IllegalArgumentException is thrown if the first arg of xUpsize ** or yUpsize exceeds this limit. **

** ** @see #xUpsize(int, JComponent) xUpsize ** @see #yUpsize(int, JComponent) yUpsize ** **/ public final static int MAX_UPSIZE = 10000; /** ** ** Declares that, for JComponentBreadboard layout purposes, the specified ** JComponent should be considered to have a preferred width the specified ** number of pixels larger than the JComponent's actual preferred width. **

** Use this method to increase or decrease the width of a JComponent ** from what it would normally be (e.g. if you want a slightly wider JButton ** than Swing would normally provide). **

** ** @param widthIncreaseInPixels the number of pixels wider that you want ** the width to be. Value must not exceed MAX_UPSIZE. Negative ** values can be used to decrease the width. However, if the sum of ** the width adjustment and original preferred width is ever less than ** 0, the adjusted preferred width will be exactly 0, not negative. ** @param jc the JComponent whose preferred width is to be adjusted ** @return the JComponent whose preferred width was adjusted (jc). ** **/ public static JComponent xUpsize(int widthIncreaseInPixels, JComponent jc) { if (MAX_UPSIZE < widthIncreaseInPixels) throw new IllegalArgumentException( "widthIncreaseInPixels > MAX_UPSIZE (" + widthIncreaseInPixels + " > " + MAX_UPSIZE + ")"); else if (0 == widthIncreaseInPixels) jc.putClientProperty(X_UPSIZE_PROPNAME, null); else if (null != jc) jc.putClientProperty(X_UPSIZE_PROPNAME, new Integer(widthIncreaseInPixels)); return jc; } /** ** Convenience method equivalent to issuing a ** xUpsize(widthIncreaseInPixels, jc[i])) on every non-null element ** of a 1-D JComponent array. **

** ** @param widthIncreaseInPixels the width increase attribute to be set on each JComponent in the array ** @param jca the 1-D array of JComponents operated upon. ** @return the 1-D array reference, jca, passed into it. ** ** @see #xUpsize(int, JComponent) xUpsize(int, JComponent) ** **/ public static JComponent[] xUpsize(int widthIncreaseInPixels, JComponent[] jca) { for (int i = 0; jca != null && i < jca.length; i++) xUpsize(widthIncreaseInPixels, jca[i]); return jca; } /** ** Convenience method equivalent to issuing a ** xUpsize(widthIncreaseInPixels, jc[i][j])) on every non-null element ** of a 2-D JComponent array. **

** ** @param widthIncreaseInPixels the width increase attribute to be set on each JComponent in the array ** @param jca the 2-D array of JComponents operated upon. ** @return the 2-D array reference, jca, passed into it. ** ** @see #xUpsize(int, JComponent) xUpsize(int, JComponent) ** **/ public static JComponent[][] xUpsize(int widthIncreaseInPixels, JComponent[][] jca) { for (int i = 0; jca != null && i < jca.length; i++) xUpsize(widthIncreaseInPixels, jca[i]); return jca; } /** ** ** Declares that, for JComponentBreadboard layout purposes, the specified ** JComponent should be considered to have a preferred height the specified ** number of pixels larger than the JComponent's actual preferred height. **

** Use this method to increase or decrease the height of a JComponent ** from what it would normally be (e.g. if you want a slightly taller JButton ** than Swing would normally provide) **

** ** @param heightIncreaseInPixels the number of pixels taller that you want ** the height to be. Value must not exceed MAX_UPSIZE. Negative ** values can be used to decrease the height. However, if the sum of ** this height adjustment and the original preferred height is ever less than ** 0, the adjusted preferred height will be exactly 0, not negative. ** @param jc the JComponent whose preferred height is to be adjusted ** @return the JComponent whose preferred height was adjusted (jc). ** **/ public static JComponent yUpsize(int heightIncreaseInPixels, JComponent jc) { if (MAX_UPSIZE < heightIncreaseInPixels) throw new IllegalArgumentException( "heightIncreaseInPixels > MAX_UPSIZE (" + heightIncreaseInPixels + " > " + MAX_UPSIZE + ")"); else if (0 == heightIncreaseInPixels) jc.putClientProperty(Y_UPSIZE_PROPNAME, null); else jc.putClientProperty(Y_UPSIZE_PROPNAME, new Integer(heightIncreaseInPixels)); return jc; } /** ** Convenience method equivalent to issuing a ** yUpsize(heightIncreaseInPixels, jc[i])) on every non-null element ** of a 1-D JComponent array. **

** ** @param heightIncreaseInPixels the height increase to be set on each JComponent in the array ** @param jca the 1-D array of JComponents operated upon. ** @return the 1-D array reference, jca, passed into it. ** ** @see #yUpsize(int, JComponent) yUpsize(int, JComponent) ** **/ public static JComponent[] yUpsize(int heightIncreaseInPixels, JComponent[] jca) { for (int i = 0; jca != null && i < jca.length; i++) yUpsize(heightIncreaseInPixels, jca[i]); return jca; } /** ** Convenience method equivalent to issuing a ** yUpsize(heightIncreaseInPixels, jc[i][j])) on every non-null element ** of a 2-D JComponent array. **

** ** @param heightIncreaseInPixels the height increase attribute to be set on each JComponent in the array ** @param jca the 2-D array of JComponents operated upon. ** @return the 2-D array reference, jca, passed into it. ** ** @see #xUpsize(int, JComponent) xUpsize(int, JComponent) ** **/ public static JComponent[][] yUpsize(int heightIncreaseInPixels, JComponent[][] jca) { for (int i = 0; jca != null && i < jca.length; i++) yUpsize(heightIncreaseInPixels, jca[i]); return jca; } /** ** Convenience method equivalent to xUpsize(widthIncreaseInPixels, ** yUpsize(heightIncreaseInPixels, jc)). ** ** @param widthIncreaseInPixels the number of pixels wider that you want ** the width to be. Value must not exceed MAX_UPSIZE. Negative ** values can be used to decrease the width. However, if the sum of ** the width adjustment and original preferred width is ever less than ** 0, the adjusted preferred width will be exactly 0, not negative. ** @param heightIncreaseInPixels the number of pixels taller that you want ** the height to be. Value must not exceed MAX_UPSIZE. Negative ** values can be used to decrease the height. However, if the sum of ** this height adjustment and the original preferred height is ever less than ** 0, the adjusted preferred height will be exactly 0, not negative. ** @param jc the JComponent whose preferred height is to be adjusted ** @return the JComponent whose preferred height was adjusted (jc). ** ** @see #xUpsize(int, JComponent) xUpsize(int, JComponent) ** @see #yUpsize(int, JComponent) yUpsize(int, JComponent) ** **/ public static JComponent xyUpsize( int widthIncreaseInPixels, int heightIncreaseInPixels, JComponent jc) { return xUpsize(widthIncreaseInPixels, yUpsize(heightIncreaseInPixels, jc)); } /** ** Convenience method equivalent to applying xyUpsize(widthIncreaseInPixels, ** heightIncreaseInPixels, jca[i]) to every non-null element in the 1-D array. ** ** @param widthIncreaseInPixels the width increase attribute to be set on each JComponent in the array ** @param heightIncreaseInPixels the height increase attribute to be set on each JComponent in the array ** @param jca the 1-D array of JComponents operated upon. ** @return the 1-D array reference, jca, passed into it. ** ** @return the array passed in as the third parameter ** ** @see #xyUpsize(int, int, JComponent) xyUpsize(int, int, JComponent) ** **/ public static JComponent[] xyUpsize( int widthIncreaseInPixels, int heightIncreaseInPixels, JComponent[] jca) { return xUpsize(widthIncreaseInPixels, yUpsize(heightIncreaseInPixels, jca)); } /** ** Convenience method equivalent to applying ** xyUpsize(widthIncreaseInPixels, heightIncreaseInPixels, ** jca[i][j])) to every non-null element in the 2-D array. ** ** @param widthIncreaseInPixels the width increase attribute to be set on each JComponent in the array ** @param heightIncreaseInPixels the height increase attribute to be set on each JComponent in the array ** @param jca the 2-D array of JComponents operated upon. ** @return the 2-D array reference, jca, passed into it. ** ** @see #xyUpsize(int, int, JComponent) xyUpsize(int, int, JComponent) ** **/ public static JComponent[][] xyUpsize( int widthIncreaseInPixels, int heightIncreaseInPixels, JComponent[][] jca) { return xUpsize(widthIncreaseInPixels, yUpsize(heightIncreaseInPixels, jca)); } /** ** Returns the number of pixels that JComponentBreadboard will add to ** the JComponent's preferred width to form the "adjusted preferred width" ** used by JComponentBreadboard's layout manager. ** ** @param jc the component whose xUpsize setting is to be returned ** @return the number of pixels the given component's width has been increased ** above the preferred width via the xUpsize method. If xUpsize has ** not been applied to the component, returns 0. ** ** @see #xUpsize(int, JComponent) xUpsize(int, JComponent) ** **/ public static int getXUpsize(JComponent jc) { int result = 0; Integer prop = (Integer) jc.getClientProperty(X_UPSIZE_PROPNAME); if (null != prop) result = prop.intValue(); return result; } /** ** Returns the number of pixels that JComponentBreadboard will add to ** the JComponent's preferred height to form the "adjusted preferred height" ** used by JComponentBreadboard's layout manager. **

** ** @param jc the component whose yUpsize setting is to be returned ** @return the number of pixels the given component's height has been increased ** above its preferred height via the yUpsize method. If yUpsize has ** not been applied to the component, returns 0. ** ** @see #yUpsize(int, JComponent) yUpsize(int, JComponent) ** **/ public static int getYUpsize(JComponent jc) { int result = 0; Integer prop = (Integer) jc.getClientProperty(Y_UPSIZE_PROPNAME); if (null != prop) result = prop.intValue(); return result; } /** ** Convenience method that returns a zero height JPanel whose preferred width ** is as specified. ** **

This method can be used to introduce small amounts of ** horizontal space between JComponents within the breadboard array. ** **

** ** @param widthInPixels width of zero height JPanel to be returned, in pixels ** @return a zero height JPanel of the specified width ** ** @see #ySpace(int) ySpace ** @see #xySpace(int, int) xySpace ** @see #setBreadboard setBreadboard ** **/ public static JPanel xSpace(int widthInPixels) { JPanel result = new JPanel(); result.setPreferredSize(new Dimension(widthInPixels, 0)); return result; } /** ** Convenience method that returns a zero width JPanel whose preferred height ** is as specified. ** **

This method can be used to introduce small amounts of ** vertical space between JComponents within the breadboard array. ** **

** ** @param heightInPixels height of zero width JPanel to be returned, in pixels ** @return a zero width JPanel of the specified height ** ** @see #xSpace(int) xSpace ** @see #xySpace(int, int) xySpace ** @see #setBreadboard setBreadboard ** **/ public static JPanel ySpace(int heightInPixels) { JPanel result = new JPanel(); result.setPreferredSize(new Dimension(0, heightInPixels)); return result; } /** ** Convenience method that returns an empty JPanel whose preferred width and ** height are as specified. ** **

This method can be used to introduce small amounts of ** vertical and horizontal space within the breadboard array. ** **

** ** @param widthInPixels width JPanel to be returned, in pixels ** @param heightInPixels height of JPanel to be returned, in pixels ** @return an empty JPanel of the specified dimensions ** ** @see #xSpace(int) xSpace ** @see #ySpace(int) ySpace ** @see #setBreadboard setBreadboard ** **/ public static JPanel xySpace(int widthInPixels, int heightInPixels) { JPanel result = new JPanel(); result.setPreferredSize(new Dimension(widthInPixels, heightInPixels)); return result; } /** ** Convenience method that returns a new horizontal, flush top, flush left JSeparator **

** This method can be used to more easily introduce horizontal separator ** lines into a JComponentBreadboard's breadboard array. **

** @return the new JSeparator ** ** @see #ySep ySep ** @see #setBreadboard setBreadboard ** **/ public static JSeparator xSep() { JSeparator result = (JSeparator) xyAlign(0,0,new JSeparator(SwingConstants.HORIZONTAL)); return result; } /** ** Same as xSep() except returns a new vertically oriented JSeparator, and ** is used to introduce vertical lines into the breadboard array. ** ** @see #xSep xSep ** **/ public static JSeparator ySep() { JSeparator result = (JSeparator) xyAlign(0,0,new JSeparator(SwingConstants.VERTICAL)); return result; } // is the JComponent at the specified grid position the same as the // JComponent immediately to it's left within the grid? private boolean sameAsLeft(Object[][] breadboard, int iRow, int iCol) { return iCol > 0 && (breadboard[iRow][iCol-1] == breadboard[iRow][iCol]); } // is the JComponent at the specified grid position the same as the // JComponent immediately to it's right within the grid? private boolean sameAsRight(Object[][] breadboard, int iRow, int iCol) { return (iCol+1 < breadboard[iRow].length && breadboard[iRow][iCol+1] == breadboard[iRow][iCol]); } // is the JComponent at the specified grid position the same as the // JComponent immediately above it within the grid? private boolean sameAsAbove(Object[][] breadboard, int iRow, int iCol) { return iRow > 0 && breadboard[iRow-1][iCol] == breadboard[iRow][iCol]; } // is the JComponent at the specified grid position the same as the // JComponent immediately below it within the grid? private boolean sameAsBelow(Object[][] breadboard, int iRow, int iCol) { return iRow+1 < breadboard.length && breadboard[iRow+1][iCol] == breadboard[iRow][iCol]; } // is the specified grid position in the upper left corner of a // rectangular region of the grid containing references to the same // JComponent? private boolean isUpperLeft(Object[][] breadboard, int iRow, int iCol) { return null!= breadboard[iRow][iCol] && !sameAsLeft(breadboard, iRow, iCol) && !sameAsAbove(breadboard, iRow, iCol); } // same as isUpperLeft method, only for lower left corner private boolean isLowerLeft(Object[][] breadboard, int iRow, int iCol) { return null!= breadboard[iRow][iCol] && !sameAsLeft(breadboard, iRow, iCol) && !sameAsBelow(breadboard, iRow, iCol); } // same as isUpperLeft method, only for upper right corner private boolean isUpperRight(Object[][] breadboard, int iRow, int iCol) { return null!= breadboard[iRow][iCol] && !sameAsRight(breadboard, iRow, iCol) && !sameAsAbove(breadboard, iRow, iCol); } // same as isUpperLeft method, only for lower right corner @SuppressWarnings("unused") private boolean isLowerRight(Object[][] breadboard, int iRow, int iCol) { return null!= breadboard[iRow][iCol] && !sameAsRight(breadboard, iRow, iCol) && !sameAsBelow(breadboard, iRow, iCol); } private boolean isOneElement1DArray(Object array) { return array instanceof JComponent[] && ((JComponent[]) array).length == 1; } private boolean isOneElement2DArray(Object array) { return array instanceof JComponent[][] && ((JComponent[][]) array).length == 1 && isOneElement1DArray(((JComponent[][]) array)[0]); } // Does the object represent a 1D array of JComponents with 2 or more elements? private boolean isJComponentArray1D(Object obj) { return obj instanceof JComponent[] && ((JComponent[]) obj).length > 1; } // Does the object represent a 2D array of JComponents with more than // 1 row and more than 1 column ? private boolean isJComponentArray2D(Object obj) { return obj instanceof JComponent[][] && ((JComponent[][]) obj).length > 1 && isJComponentArray1D(((JComponent[][]) obj)[0]); } // Does the object represent a single row of JComponents with 2 or // more columns, as a 1-Row 2D array ? private boolean isJComponentRow(Object obj) { return obj instanceof JComponent[][] && ((JComponent[][]) obj).length == 1 && ((JComponent[][]) obj)[0] instanceof JComponent[] && ((JComponent[][]) obj)[0].length > 1; } // Does the object represent a single column of JComponents with 2 or // more rows, as a 1-Column 2D array ? private boolean isJComponentCol(Object obj) { return obj instanceof JComponent[][] && ((JComponent[][]) obj).length > 1 && ((JComponent[][]) obj)[0] instanceof JComponent[] && ((JComponent[][]) obj)[0].length == 1; } // returns height of rectangular sub-block within the breadboard whose upper // left corner is in the specified breadboard row and column. private int jbRowCount(Object[][] breadboard, int iRow, int iCol) { int result = 1; while (sameAsBelow(breadboard, iRow+result-1, iCol)) result++; return result; } // returns width of rectangular sub-block within the breadboard whose upper // left corner cell is in the specified breadboard row and column. private int jbColCount(Object[][] breadboard, int iRow, int iCol) { int result = 1; while (sameAsRight(breadboard, iRow, iCol+result-1)) result++; return result; } // JComponentBreadboard arrays are required to contain only // non-overlapping rectangular blocks of cells that all contain the // same object reference (or null).

// // This function returns the difference between the given row index // (iRow), and the row index of the first row in the block of cells // that contains the object reference at the given cell (iRow, iCol). //

// For example, returns 0 for cells in the first row of the block, // 1 for cells in the second row, etc. // private int jbRowOffset(Object[][] breadboard, int iRow, int iCol) { int result = 0; while (sameAsAbove(breadboard, iRow-result, iCol)) result++; return result; } // analogous to jbRowOffset, but for column offsets. private int jbColOffset(Object[][] breadboard, int iRow, int iCol) { int result = 0; while (sameAsLeft(breadboard, iRow, iCol-result)) result++; return result; } // Assuming the given element is mapped into a single column or // single row block in the breadboard array, returns the offset from // the topmost or leftmost element of that block. private int jbOffset(Object[][] breadboard, int iRow, int iCol) { int rowOffset = jbRowOffset(breadboard, iRow, iCol); int colOffset = jbColOffset(breadboard, iRow, iCol); int result = Math.max(rowOffset, colOffset); if (colOffset > 0 && rowOffset > 0) // if this method is used properly, I think this should never be thrown throw new IllegalStateException("colOffset="+colOffset + " rowOffset="+rowOffset + " for a 1-D array at breadboard[" + iRow + "][" + iCol + "]. " + "1-D arrays must be mapped to a " + "single row or column of the breadboard so " + "either colOffset or rowOffset must be 0."); return result; } // returns the JComponent that occupies a given breadboard cell private JComponent getComponentAtCell(Object[][] breadboard, int iRow, int iCol) { JComponent result = null; if (isJComponentArray1D(breadboard[iRow][iCol])) { int offset = jbOffset(breadboard, iRow, iCol); result = ((JComponent[]) breadboard[iRow][iCol])[offset]; } else if (isJComponentArray2D(breadboard[iRow][iCol])) { int colOffset = jbColOffset(breadboard, iRow, iCol); int rowOffset = jbRowOffset(breadboard, iRow, iCol); result = ((JComponent[][]) breadboard[iRow][iCol])[rowOffset][colOffset]; } else if (isJComponentRow(breadboard[iRow][iCol])) { // Special "fill down" rule for 2-D arrays representing a single row: // // Whenever a single row array occupies multiple rows in the breadboard, the // single JComponent in each column of that array occupies every row // of the associated breadboard column into which it is mapped: int colOffset = jbColOffset(breadboard, iRow, iCol); int rowOffset = 0; result = ((JComponent[][]) breadboard[iRow][iCol])[rowOffset][colOffset]; } else if (isJComponentCol(breadboard[iRow][iCol])) { // Special "fill across" rule for 2-D arrays representing a single column: // // Whenever a single column array occupies multiple columns in the breadboard, the // single JComponent in each row of that array occupies every column // of the associated breadboard row into which it is mapped: int colOffset = 0; int rowOffset = jbRowOffset(breadboard, iRow, iCol); result = ((JComponent[][]) breadboard[iRow][iCol])[rowOffset][colOffset]; } // one element arrays are simply dereferenced and treated like ordinary // JComponent references...simpler to allow them than to make them illegal else if (isOneElement2DArray(breadboard[iRow][iCol])) result = ((JComponent[][]) (breadboard[iRow][iCol]))[0][0]; else if (isOneElement1DArray(breadboard[iRow][iCol])) result = ((JComponent[]) (breadboard[iRow][iCol]))[0]; else if (breadboard[iRow][iCol] instanceof JComponent) result = (JComponent) (breadboard[iRow][iCol]); else if (breadboard[iRow][iCol] != null ) // should have caught this earlier, but you never know. throw new IllegalStateException("Array element that could not be interpreted encountered at " + "breadboard[" + iRow + "][" + iCol + "]"); return result; } // Requires that the 2-D array contains at least one row, and the // each of these rows has at least one element (simplifies later // tests if we can assume this) private void requireNonEmptyRows(Object[][] array, String arrayName) { if (null == array) throw new IllegalArgumentException("The " + arrayName + " cannot be null."); else if (array.length < 1) throw new IllegalArgumentException("The " + arrayName + " must have at least one row."); else { for (int i=0; i < array.length; i++) { if (null == array[i]) throw new IllegalArgumentException("Row " + i + " of the " + arrayName + " is null. Rows cannot be null."); else if (array[i].length < 1) throw new IllegalArgumentException("Row " + i + " of the " + arrayName + " has zero elements. Rows must have at least 1 element."); } } } // returns index of first row whose number of elements differs from that of // the first row, or 0 if no two rows have a different number of elements. // Assumes a non-null 2-D array all of whose rows are non-null. private int firstNonRectangularRow(Object[][] array) { int result = 0; for (int iRow = 1; iRow < array.length && 0 == result; iRow++) { if (array[iRow].length != array[0].length) result = iRow; } return result; } // Requires that the breadboard have a perfectly rectangular shape // (we can rely on this requirement to simplify the logic elsewhere) private void requireRectangularBreadboardArray(Object [][] breadboard) { requireNonEmptyRows(breadboard, "breadboard array"); int iRow = firstNonRectangularRow(breadboard); if (iRow > 0) { throw new IllegalArgumentException("Row " + iRow + " of your breadboard array," + " has " + breadboard[iRow].length + " elements. This differs from row 0," + " which has " + breadboard[0].length + " elements. All rows of the breadboard array" + " must have the same number of elements."); } } // Requires that any 2-D JComponent arrays placed on the breadboard have // a perfectly rectangular shape. The breadboard cell reference is // assumed to be to the lower right corner of the breadboard cell-block // into which the array is mapped. private void requireRectangular2DArray(Object[][] breadboard, int iRow, int iCol) { requireNonEmptyRows((Object[][]) breadboard[iRow][iCol], "array at breadboard["+iRow+"]["+iCol+"]"); int iBad = firstNonRectangularRow((JComponent[][]) breadboard[iRow][iCol]); if (iBad > 0) { int row0 = iRow - jbRowOffset(breadboard, iRow, iCol); int col0 = iCol - jbColOffset(breadboard, iRow, iCol); throw new IllegalArgumentException("Invalid JComponent[][] array found in " + "rows " + row0 + " to " + iRow + " of " + "columns " + col0 + " to " + iCol + " of your breadboard. " + " Row " + iBad + " of this array has " + ((JComponent[][]) (breadboard[iRow][iCol]))[iBad].length + " elements, which " + " differs from row 0," + " which has " + ((JComponent[][]) (breadboard[iRow][iCol]))[0].length + " elements. All 2-D arrays placed on the breadboard " + " must be rectangular in shape."); } } // Checks that 1-D and 2-D arrays are plugged into same-shaped // sub-blocks in breadboard. Assumes that all breadboard sub-blocks // associated with a single reference are rectangularly shaped, and // that all arrays have at least element, and are also rectangular // shaped (these are validated in earlier validation methods). private void requireArraysFitInTheirSubblocks(Object [][] breadboard) { for (int iRow = 0; iRow < breadboard.length; iRow++) { for (int iCol =0; iCol < breadboard[iRow].length; iCol++) { if (isUpperLeft(breadboard, iRow, iCol)) { int nRows = jbRowCount(breadboard, iRow, iCol); int nCols = jbColCount(breadboard, iRow, iCol); if (isJComponentArray1D(breadboard[iRow][iCol])) { int len = ((JComponent[]) (breadboard[iRow][iCol])).length; if (nRows > 1 && nCols > 1 || Math.max(nRows, nCols) != len) { throw new IllegalArgumentException( "A 1-D array has been incorrectly placed into rows " + iRow + " to " + (iRow + nRows - 1) + " of columns " + iCol + " to " + (iCol + nCols - 1) + " of your breadboard. " + "Since this 1-D array has " + len + " elements, " + "it must be placed into either a " + "1 row, " + len + " column block of breadboard " + "cells or into a 1 column, " + len + " row block. "); } } else if (isJComponentArray2D(breadboard[iRow][iCol])) { int rows = ((JComponent[][]) (breadboard[iRow][iCol])).length; int cols = ((JComponent[][]) (breadboard[iRow][iCol]))[0].length; if (nRows != rows || nCols != cols) { throw new IllegalArgumentException( "The 2-D array placed in rows " + iRow + " to " + (iRow + nRows - 1) + " of columns " + iCol + " to " + (iCol + nCols - 1) + " of your breadboard " + "has " + rows + " rows and " + cols + " columns which differs from the " + nRows + " rows and " + nCols + " columns of the block of breadboard " + "cells it has been placed into. " + "2-D arrays with more than 1 row and more than 1 column " + "must fit exactly into the breadboard cells that they occupy."); } } else if (isJComponentRow(breadboard[iRow][iCol])) { int cols = ((JComponent[][]) (breadboard[iRow][iCol]))[0].length; if (nCols != cols) { throw new IllegalArgumentException( "The one row, 2-D array, placed in rows " + iRow + " to " + (iRow + nRows - 1) + " of columns " + iCol + " to " + (iCol + nCols - 1) + " of your breadboard, " + "has " + cols + " columns which differs from the " + nCols + " columns of the block of breadboard " + "cells it has been placed into. " + "One row, 2-D arrays " + "must have exactly the same number of columns as " + "the block of cells in the breadboard that they occupy."); } } else if (isJComponentCol(breadboard[iRow][iCol])) { int rows = ((JComponent[][]) (breadboard[iRow][iCol])).length; if (nRows != rows) { throw new IllegalArgumentException( "The one column, 2-D array, placed in rows " + iRow + " to " + (iRow + nRows - 1) + " of columns " + iCol + " to " + (iCol + nCols - 1) + " of your breadboard, " + "has " + rows + " rows which differs from the " + nRows + " rows of the block of breadboard " + "cells it has been placed into. " + "One column, 2-D arrays " + "must have exactly the same number of rows as " + "the block of cells in the breadboard that they occupy."); } } } } } } // Requires non-null cells of the breadboard array to contain square // blocks of JComponent, JComponent[], or JComponent[][] references. private void requireAcceptableJComponentReferences(Object[][] breadboard) { for (int iRow = 0; iRow < breadboard.length; iRow++) { for (int iCol =0; iCol < breadboard[iRow].length; iCol++) { if (null == breadboard[iRow][iCol]) continue; else if (!(breadboard[iRow][iCol] instanceof JComponent) && !(breadboard[iRow][iCol] instanceof JComponent[]) && !(breadboard[iRow][iCol] instanceof JComponent[][])) throw new IllegalArgumentException("The breadboard array, after removing " + "headers and expanding dittos, must contain only null or " + "JComponent, JComponent[], or JComponent[][] object references. " + "But, breadboard["+iRow+"]["+iCol+"] contains a reference of type " + breadboard[iRow][iCol].getClass().getSimpleName()); else if (breadboard[iRow][iCol] instanceof JComponent[] && ((JComponent[]) breadboard[iRow][iCol]).length < 1) throw new IllegalArgumentException( "breadboard[" + iRow + "][" + iCol + "] " + "contains a 1-D array with 0 elements. At least 1 element is required."); else if (breadboard[iRow][iCol] instanceof JComponent[][]) requireRectangular2DArray(breadboard, iRow, iCol); // else reference is OK, but still need to breaboard positions it occupies // (these positions must form a square block) // if each upper left corner anchors a square block, the entire breadboard // consists of such square blocks. if (isUpperLeft(breadboard, iRow, iCol)) { int nRows = jbRowCount(breadboard, iRow, iCol); int nCols = jbColCount(breadboard, iRow, iCol); // check across each row that all have same number of columns for (int i = 1; i < nRows; i++) if (jbColCount(breadboard, iRow+i, iCol) != nCols) throw new IllegalArgumentException( "Object references must form square subblocks. " + "But, the block beginning at breadboard[" + iRow + "][" + iCol + "] " + "has " + nCols + " columns on row 0 but " + jbColCount(breadboard, iRow+i, iCol) + " columns on " + "row " + (iRow+i) + "."); // check down each column that all columns have same number of rows for (int j = 0; j < nCols; j++) if (jbRowCount(breadboard, iRow, iCol+j) != nRows) throw new IllegalArgumentException( "Object references must form square subblocks. " + "However, the block beginning at breadboard[" + iRow + "][" + iCol + "] " + "has " + nRows + " rows in column 0 but " + jbRowCount(breadboard, iRow, iCol+j) + " rows in " + "column " + (iCol+j) + "."); } } } } // Require that each JComponent appears in exactly one rectangular // sub-block within the breadboard. Assumes breadboard consists of // rectangular blocks of the same object ref, and that any array refs // fit properly into these blocks. private void requireSingleSubblockPerJComponent(Object[][] breadboard) { Object obj = null; // The String in the Hashmap is used in the exception messages HashMap distinctComponents = new HashMap(); for (int iRow = 0; iRow < breadboard.length; iRow++) { for (int iCol =0; iCol < breadboard[iRow].length; iCol++) { obj = null; if (isJComponentArray1D(breadboard[iRow][iCol])) { obj = getComponentAtCell(breadboard, iRow, iCol); } else if (isJComponentArray2D(breadboard[iRow][iCol])) { obj = getComponentAtCell(breadboard, iRow, iCol); } else if (isJComponentRow(breadboard[iRow][iCol]) && 0==jbRowOffset(breadboard, iRow, iCol)) { obj = getComponentAtCell(breadboard, iRow, iCol); } else if (isJComponentCol(breadboard[iRow][iCol]) && 0==jbColOffset(breadboard, iRow, iCol)) { obj = getComponentAtCell(breadboard, iRow, iCol); } else if (isUpperLeft(breadboard, iRow, iCol)) { // one element arrays are dereferenced but otherwise act like ordinary JComponent refs if (isOneElement2DArray(breadboard[iRow][iCol])) obj = ((JComponent[][]) (breadboard[iRow][iCol]))[0][0]; else if (isOneElement1DArray(breadboard[iRow][iCol])) obj = ((JComponent[]) (breadboard[iRow][iCol]))[0]; else if (breadboard[iRow][iCol] instanceof JComponent[] || breadboard[iRow][iCol] instanceof JComponent[][]) obj = null; else obj = breadboard[iRow][iCol]; } if (null != obj) { if (!(obj instanceof JComponent)) { throw new IllegalArgumentException( "The object that appears at (after removing any headers and expanding arrays) " + "breadboard[" + iRow + "][" + iCol +"] is of type \"" + obj.getClass().getName() + "\". " + "Either null, or an object of type JComponent is required."); } else if (!distinctComponents.containsKey(obj)) { distinctComponents.put(obj, iRow + "][" + iCol); } else { throw new IllegalArgumentException( "The JComponent that appears at (after removing any headers and expanding arrays) breadboard["+iRow+ "][" + iCol + "] " + "also occupies an earlier block at " + "breadboard[" + distinctComponents.get(obj) + "]. " + "Separate JComponents must appear in exactly one " + "rectangular block of breadboard-grid cells. Similarly, each JComponent within " + "any arrays placed on the breadboard must map into a single breadboard grid cell-block."); } } // else one of the situations where repeated JComponents are valid, or null. } } } // Check that a breadboard array has the required format: private void validateBreadboard(Object[][] breadboard) { requireRectangularBreadboardArray(breadboard); requireAcceptableJComponentReferences(breadboard); requireArraysFitInTheirSubblocks(breadboard); requireSingleSubblockPerJComponent(breadboard); } // add the components (ignoring those repeated within rectangular // regions) to this JComponentBreadboard (a JPanel). private void addBreadboardJComponents(Object[][] breadboard) { for (int iRow = 0; iRow < breadboard.length; iRow++) { for (int iCol = 0; iCol < breadboard[iRow].length; iCol++) { if (isJComponentArray1D(breadboard[iRow][iCol])) { int offset = jbOffset(breadboard, iRow, iCol); JComponent jc = ((JComponent[]) breadboard[iRow][iCol])[offset]; if (null != jc) add(jc); } else if (isJComponentArray2D(breadboard[iRow][iCol]) || isJComponentCol(breadboard[iRow][iCol]) || isJComponentRow(breadboard[iRow][iCol])) { int rowOffset = jbRowOffset(breadboard, iRow, iCol); int colOffset = jbColOffset(breadboard, iRow, iCol); if (isJComponentArray2D(breadboard[iRow][iCol]) || isJComponentCol(breadboard[iRow][iCol]) && 0 == colOffset || isJComponentRow(breadboard[iRow][iCol]) && 0 == rowOffset) { JComponent jc = ((JComponent[][]) breadboard[iRow][iCol])[rowOffset][colOffset]; if (null != jc) add(jc); } } else if (isUpperLeft(breadboard, iRow, iCol)) { JComponent jc = null; if (isOneElement2DArray(breadboard[iRow][iCol])) jc = ((JComponent[][]) (breadboard[iRow][iCol]))[0][0]; else if (isOneElement1DArray(breadboard[iRow][iCol])) jc = ((JComponent[]) (breadboard[iRow][iCol]))[0]; else if (breadboard[iRow][iCol] instanceof JComponent) jc = (JComponent) (breadboard[iRow][iCol]); if (null != jc) add(jc); } } } } /** ** This class provides the layout manager for JComponentBreadboard. **

** ** For more information, see the discussion of the layout algorithm ** within the javadocs for the {@link #setBreadboard setBreadboard} ** method. ** **/ private class JComponentBreadboardLayout implements LayoutManager2 { // a 2-D array of components in the breadboard. Note that the original // breadboard array can contain component array references, and these // are expanded out into the underlying individual components in this array JComponent[][] comp; // row heights, col width for "tightest fitting to preferred sizes, pushed closest // to upper left corner, grid", layout that is always our starting point. int[] preferredRowHeights; int[] preferredColWidths; // This constructor relies upon there being appropriately, // previously, defined rowScale and colScale arrays in outter // class parent, and it also assumes that the breadboard array // conforms to the requirements imposed by the outter class' // validateBreadboard method. public JComponentBreadboardLayout(Object[][] breadboard) { super(); if (breadboard.length != rowScale.length) throw new IllegalArgumentException("breadboard.length must equal rowScale.length"); if ((breadboard.length==0 && colScale.length!=0) || (breadboard.length > 0 && breadboard[0].length != colScale.length)) throw new IllegalArgumentException("breadboard[0].length must equal colScale.length"); comp = new JComponent[rowScale.length][colScale.length]; for (int iRow = 0; iRow < rowScale.length; iRow++) { for (int iCol = 0; iCol < colScale.length; iCol++) { comp[iRow][iCol] = getComponentAtCell(breadboard, iRow, iCol); } } // these are computed on-the-fly during layout, just allocate them for now preferredRowHeights = new int[rowScale.length]; preferredColWidths = new int[colScale.length]; } // Returns the preferred height, resized for the component's Y-upsize setting. private int getResizedPreferredHeight(JComponent jc) { int result = jc.getPreferredSize().height + getYUpsize(jc); // neg upsizes are allowed, but final resized size must be at least 0. if (result < 0) result = 0; return result; } // Given a cell reference at the bottom of a single component mapped cell block, // and assuming preferred heights of preceeding rows are known, returns // the excess height, beyond that already available in preceeding rows, // needed to fit the component in at its preferred height. Negative // excesses mean that the previous rows already have more than enough space. private int excessHeight(JComponent[][] comp, int iRow, int iCol) { int result = getResizedPreferredHeight(comp[iRow][iCol]); for (int i = 0; sameAsAbove(comp, iRow-i, iCol); i++) result -= preferredRowHeights[iRow-i-1]; return result; } // updates the preferred row heights to reflect the current preferred heights // of all components in the grid. Note that preferred sizes can change // (for example, if user enters more text into a JTextField) private void updatePreferredRowHeights() { for (int iRow = 0; iRow < rowScale.length; iRow++) { int maxHeight = 0; for (int iCol = 0; iCol < colScale.length; iCol++) { if (isLowerLeft(comp, iRow, iCol) && comp[iRow][iCol].isVisible()) maxHeight = Math.max(maxHeight, excessHeight(comp, iRow, iCol)); } preferredRowHeights[iRow] = maxHeight; } } // Returns the preferred height, resized for the component's Y-upsize setting. private int getResizedPreferredWidth(JComponent jc) { int result = jc.getPreferredSize().width + getXUpsize(jc); // neg upsizes are allowed, but final resized size must be at least 0. if (result < 0) result = 0; return result; } // horizontal analogue of the excessHeight method. private int excessWidth(JComponent[][] comp, int iRow, int iCol) { int result = getResizedPreferredWidth(comp[iRow][iCol]); for (int i = 0; sameAsLeft(comp, iRow, iCol-i); i++) result -= preferredColWidths[iCol-i-1]; return result; } // horizontal analogue of the updatePreferredRowHeights method private void updatePreferredColWidths() { for (int iCol = 0; iCol < colScale.length; iCol++) { int maxWidth = 0; for (int iRow = 0; iRow < rowScale.length; iRow++) { if (isUpperRight(comp, iRow, iCol) && comp[iRow][iCol].isVisible()) maxWidth = Math.max(maxWidth, excessWidth(comp, iRow, iCol)); } preferredColWidths[iCol] = maxWidth; } } // returns sum of all, or shrinks() or expands() tagged, row or column ints private int arraySum(int[] a, ScalingDirective[] opt, boolean countShrinks, boolean countExpands) { int result = 0; for (int i=0; i < a.length; i++) { if (null == opt || // null scale directives means "count everything" (countShrinks && opt[i].shrinks()) || (countExpands && opt[i].expands())) result += a[i]; } return result; } // sums integers associated with shrinkable rows or columns private int shrinkableArraySum(int a[], ScalingDirective[] opt) { return arraySum(a, opt, true, false); } // sums integers associated with expandable rows or columns private int expandableArraySum(int a[], ScalingDirective[] opt) { return arraySum(a, opt, false, true); } // sums all of the integers in an array associated with any row or column private int arraySum(int[] a) { return arraySum(a, null, true, true); } /** ** Returns preferred size of the container. **

** Parent argument must be same as the outer class parent that ** holds the JCBreadboard layout manager. **/ public Dimension preferredLayoutSize(Container parent) { if (parent != JComponentBreadboard.this) throw new IllegalArgumentException("JComponentBreadboardLayout layout " + "managers can only manage the " + "layout of the parent " + "JComponentBreadboard that contains " + "them."); // I'm not exactly sure why this is needed, but // GridBagLayout.java synchronizes it's getPreferredSize calls // on components with such a line, so, fearing a dead-lock, I // synchronize them too. synchronized (parent.getTreeLock()) { Dimension result = new Dimension(); updatePreferredRowHeights(); result.height = arraySum(preferredRowHeights); updatePreferredColWidths(); result.width = arraySum(preferredColWidths); Border border = ((JComponent) parent).getBorder(); if (border instanceof TitledBorder) { Dimension borderSize = ((TitledBorder) border).getMinimumSize(parent); // Any extra height or width needed for the title label is added to // last row or column. Behavior is the same as if a zero-height // JPanel with a width equal to the minimum required for the title // label were mapped into an extra row of the breadboard, and a second // zero-width JPanel with a height equal to the minimum height // required for the title label were mapped into an extra column of // the breadboard. // // Without this code, longer title labels can get clipped. // preferredRowHeights[rowScale.length-1] += Math.max(0, borderSize.height - result.height); preferredColWidths[colScale.length-1] += Math.max(0, borderSize.width - result.width); result.height = Math.max(result.height, borderSize.height); result.width = Math.max(result.width, borderSize.width); } Insets insets = ((JComponent) parent).getInsets(); result.height += insets.top + insets.bottom; result.width += insets.left + insets.right; return result; } } // Sets the component's bounds only if they have changed (idea was // to make it faster but I was not able to measure any appreciable // speed increase). private void setBoundsIfTheyChanged(JComponent comp, int x, int y, int width, int height) { Rectangle r = comp.getBounds(); if (r.x != x || r.y != y || r.width != width || r.height != height) comp.setBounds(x, y, width, height); } /** ** Lays out the components in the breadboard within the parent. ** ** Parent argument must be same as the outer class parent that ** holds the JCBreadboard layout manager. **/ public void layoutContainer(Container parent) { if (parent != JComponentBreadboard.this) throw new IllegalArgumentException("JComponentBreadboardLayout layout managers can only manage the layout of the parent JComponentBreadboard that contains them."); Insets insets = ((JComponent) parent).getInsets(); Dimension prefParentSize = preferredLayoutSize(parent); int prefHeight = prefParentSize.height; int prefWidth = prefParentSize.width; Dimension parentSize = parent.getSize(); int parentHeight = parentSize.height; int parentWidth = parentSize.width; // height of rows, width of columns that are shrinkable; expandable int shrinksHeight = shrinkableArraySum(preferredRowHeights, rowScale); int shrinksWidth = shrinkableArraySum(preferredColWidths, colScale); int expandsHeight = expandableArraySum(preferredRowHeights, rowScale); int expandsWidth = expandableArraySum(preferredColWidths, colScale); // estimate the y-grid row dividing line locations, taking into account any // expansion or shrinkage of the rows required to fit in the parent int[] y = new int[rowScale.length+1]; // 1 more row divider than # of rows y[0] = insets.top; // we use double rather than int to prevent accumulation of roundoff errors: double yNext = y[0]; for (int iRow = 0; iRow < rowScale.length; iRow++) { yNext += preferredRowHeights[iRow]; // applies various row scaling directives to make rows fit vertically in parent if (rowScale[iRow].expands() && parentHeight > prefHeight && expandsHeight > 0) { // extra vertical space, and at least one non-zero, expandable, row yNext += preferredRowHeights[iRow] * (parentHeight - prefHeight)/((double) expandsHeight); } else if (rowScale[iRow].shrinks() && parentHeight < prefHeight && shrinksHeight > 0) { // deficit vertical space and at least 1 non-zero shrinkable col // scale down to fit, or, if that's not possible, just zero it out. yNext += preferredRowHeights[iRow] * Math.max(-1.0, (parentHeight - prefHeight)/((double) shrinksHeight)); } y[iRow+1] = (int) Math.rint(yNext); } // analogously, estimate the x-grid col divider locations, taking into account any // expansion or shrinkage of the columns required to fit into the parent int[] x = new int[colScale.length+1]; x[0] = insets.left; double xNext = x[0]; for (int iCol = 0; iCol < colScale.length; iCol++) { xNext += preferredColWidths[iCol]; // applies col scaling directives to make cols fit horizontally in parent if (colScale[iCol].expands() && parentWidth > prefWidth && expandsWidth > 0) { // extra horizontal space, and at least 1 non-zero, expandable, col xNext += preferredColWidths[iCol] * (parentWidth - prefWidth)/((double) expandsWidth); } else if (colScale[iCol].shrinks() && parentWidth < prefWidth && shrinksWidth > 0) { // deficit horizontal space and at least 1 non-zero shrinkable col // scale down to fit, or, if that's not possible, just zero it out. xNext += preferredColWidths[iCol] * Math.max(-1.0, (parentWidth - prefWidth)/((double) shrinksWidth)); } x[iCol+1] = (int) Math.rint(xNext); } // With row and column cell divider locations in hand, we next // use them to determine the size/location of each cell or // block of cells that contains a component, and, using various // component specific layout attributes such as "fill", the // exact size and location within each such cell block of the // component it contains: for (int iRow = 0; iRow < rowScale.length; iRow++) { for (int iCol = 0; iCol < colScale.length; iCol++) { if (isUpperLeft(comp, iRow, iCol) && comp[iRow][iCol].isVisible()) { int cellWidth = (x[iCol + jbColCount(comp, iRow, iCol)] - x[iCol]); int cellHeight = (y[iRow + jbRowCount(comp, iRow, iCol)] - y[iRow]); int resizedCompWidth = getResizedPreferredWidth(comp[iRow][iCol]); int resizedCompHeight = getResizedPreferredHeight(comp[iRow][iCol]); // note that cells can shrink, making cellWidth < resizedCompWidth int width = isXFilled(comp[iRow][iCol]) ? cellWidth : Math.min(cellWidth, resizedCompWidth); int height = isYFilled(comp[iRow][iCol]) ? cellHeight : Math.min(cellHeight, resizedCompHeight); int deltaX = cellWidth - width; int deltaY = cellHeight - height; float xAlign = comp[iRow][iCol].getAlignmentX(); float yAlign = comp[iRow][iCol].getAlignmentY(); setBoundsIfTheyChanged(comp[iRow][iCol], x[iCol]+(int) Math.rint(xAlign*deltaX), y[iRow]+ (int) Math.rint(yAlign*deltaY), width, height); } } } } // more or less do-nothing methods required to fill out the LayoutManager2 interface // begin here. /** Unsupported method required by LayoutManager2 interface. ** Calling will raise an exception. (components can only be ** added in the constructor, and can never be removed) */ public void addLayoutComponent(Component comp, Object constraints) { throw new IllegalStateException("Components can only be added via the constructor, and can never be removed."); } /** method required by LayoutManager2 interface (returns Container.LEFT_ALIGNMENT)*/ public float getLayoutAlignmentX(Container target) { return Container.LEFT_ALIGNMENT; } /** method required by LayoutManager2 interface (returns Container.TOP_ALIGNMENT)*/ public float getLayoutAlignmentY(Container target) { return Container.TOP_ALIGNMENT; } /** do-nothing method required by LayoutManager2 interface */ public void invalidateLayout(Container target) { } /** ** Method required by LayoutManager2 interface (returns ** Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE) -- ** essentially, there is no maximum size ** **/ public Dimension maximumLayoutSize(Container target) { return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); } /** Unsupported method required by LayoutManager2 interface. ** Calling will raise an exception. (components can only be ** added in the constructor, and can never be removed) */ public void addLayoutComponent(String name, Component comp) { throw new IllegalStateException("Components can only be added via the constructor, and can never be removed."); } /** ** Method required by LayoutManager2 interface (returns ** Dimension(0, 0) -- essentially, there is no minimum size ** **/ public Dimension minimumLayoutSize(Container parent) { return new Dimension(0,0); } /** Do-nothing method required by LayoutManager2 interface. */ public void removeLayoutComponent(Component comp) { // this is called by Swing, so it cannot throw an exception, // but it does not do anything, because the only way to add and // remove components is via setBreadboard. } } // hidden (but responsive to key strokes) JMenuBar used to // implement ability to jbConnect to a KeyStroke ("hot keys") private JMenuBar hotKeyMenuBar = new JMenuBar(); // Sets the "plain" (that is, after any row/column ScalingDirective headers have been // stripped off) breadboard array as the active breadboard array. private void setPlainBreadboard(Object[][] breadboard) { validateBreadboard(breadboard); setLayout(null); // expect adding/removing is faster if no layout present removeAll(); add(hotKeyMenuBar); // hidden menu for implementing KeyStroke-connections addBreadboardJComponents(breadboard); JComponentBreadboardLayout layout = new JComponentBreadboardLayout(breadboard); setLayout(layout); repaint(); // without this line, busier forms can fail to refresh correctly // (I don't know why--my reading of JDK says it should not be needed) } // allocates appropriately sized row and column scaling directive vectors, and sets // all of their elements to the specified row and column scaling directives private void setAllScales(Object[][] breadboard, ScalingDirective theRowScale, ScalingDirective theColScale) { rowScale = new ScalingDirective[breadboard.length]; colScale = new ScalingDirective[breadboard.length==0 ? 0 : breadboard[0].length]; for (int i = 0; i < rowScale.length; i++) rowScale[i] = theRowScale; for (int i = 0; i < colScale.length; i++) colScale[i] = theColScale; } // copies ids associated with scaling directive factors from the breadboard array private void setScales(Object[][] breadboard) { rowScale = new ScalingDirective[breadboard.length-1]; colScale = new ScalingDirective[breadboard[0].length-1]; for (int i = 0; i < rowScale.length; i++) { if (null == breadboard[i+1][0]) { throw new IllegalArgumentException( "breadboard["+(i+1)+"][0] is null. " + "This cell is a row header, and thus must be one of the four possible " + "allowed ScalingDirective values: NOSCALE, SHRINKS, EXPANDS, or BISCALE"); } rowScale[i] = (ScalingDirective) breadboard[i+1][0]; } for (int i = 0; i < colScale.length; i++) { if (null == breadboard[0][i+1]) { throw new IllegalArgumentException( "breadboard[0]["+(i+1)+"] is null. " + "This cell is a column header, and thus must be one of the four possible " + "allowed ScalingDirective values: NOSCALE, SHRINKS, EXPANDS, or BISCALE"); } colScale[i] = (ScalingDirective) breadboard[0][i+1]; } } // Does the breadboard lack row/column scaling prefixes (= "plain") // or does it have them. Throws an exception in ambiguous situations. private boolean isPlainBreadboard(Object[][] breadboard) { requireRectangularBreadboardArray(breadboard); boolean result = false; int rowCount = 0; // # of rows prefixed by scaling directive int colCount = 0; // # of columns prefixed by a scaling directive int firstNonDirective = 0; // ignore upper left cell, which isn't used with scaled header formats for (int i = 1; i < breadboard.length; i++) { if (breadboard[i][0] instanceof JComponentBreadboard.ScalingDirective) rowCount++; else if (firstNonDirective == 0) firstNonDirective = i; } if (rowCount > 0 && firstNonDirective > 0) throw new IllegalArgumentException( "Column #" + firstNonDirective + " of the first row of your breadboard " + "array is not a scaling directive, " + "but other columns contain scaling directives. " + "Either the first row of your breadboard array must contain only the " + "NOSCALE, SHRINKS, EXPANDS, or BISCALE scaling directives (which would " + "make it a header), or else it must contain none of these keywords."); firstNonDirective = 0; for (int i=1; breadboard.length > 0 && i < breadboard[0].length; i++) { if (breadboard[0][i] instanceof JComponentBreadboard.ScalingDirective) colCount++; else if (firstNonDirective == 0) firstNonDirective = i; } if (colCount > 0 && firstNonDirective > 0) throw new IllegalArgumentException( "Row #" + firstNonDirective + " of the first column of your breadboard " + "array is not a scaling directive, " + "but other rows contain scaling directives. " + "Either the first column of your breadboard array must contain only the " + "NOSCALE, SHRINKS, EXPANDS, or BISCALE scaling directives (which would " + "make it a header), or else it must contain none of these keywords."); if (rowCount == 0 && colCount == 0) result = true; else result = false; return result; } // Returns an array in which all columnar (left) and row (above) // dittos have been expanded into explicit object references // // Ditto expansion occurs in a naive manner that works independently // of if the plain or header-ed breadboard format is being used; errors // that result from ditto-ing a header into a non-header area will // be caught later when the non-header area is validated. private Object[][] decodeDittos(Object[][] breadboard) { requireRectangularBreadboardArray(breadboard); Object[][] result = new Object[breadboard.length][breadboard[0].length]; for (int i = 0; i < breadboard.length; i++) { for (int j = 0; j < breadboard[0].length; j++) { if (breadboard[i][j] instanceof String && ((String) breadboard[i][j]).equals(ROWWISE_DITTO)) { if (0 == i) throw new IllegalArgumentException( "Error at breadboard[" + i + "][" + j + "]: " + "the \"copy of reference immediately above\" ditto keyword (\"\") " + "cannot be used in row 0, since there is no row above row 0."); else result[i][j] = result[i-1][j]; } else if (breadboard[i][j]==ColumnarDitto.COLUMNAR_DITTO) { if (0 == j) throw new IllegalArgumentException( "Error at breadboard[" + i + "][" + j + "]: " + "the \"copy of reference immediately to the left\" ditto " + "keyword ( __ ) cannot be used in column 0, since " + " there is no column to the left of column 0."); else result[i][j] = result[i][j-1]; } else { result[i][j] = breadboard[i][j]; } } } return result; } /*********************************************************************** Defines this parent container's child components and their layout. The breadboard array argument defines the rectangular block of cells occupied by each child component within a flexible rectilinear grid, as well as the conditions under which each row and column of this grid scales up and/or scales down in response to the availability of space in the parent container. This available space is determined, ultimately, by the size of the top level Dialog or Frame container, which can typically be altered via the sizing border by the end user.

The breadboard array must be rectangular (all rows must have the same number of elements) and must have the following general format:

nullscalingDirectivescalingDirectivescalingDirective...
scalingDirectivejComponentRefjComponentRefjComponentRef...
scalingDirectivejComponentRefjComponentRefjComponentRef...
scalingDirectivejComponentRefjComponentRefjComponentRef...
...............

In the above:

A special no-header format is also supported, in which the row and column headers are completely eliminated. In this case each row and column is given, by default, the NOSCALE scaling directive.

Rules governing the placement of jComponentRefs in the breadboard array:

  1. The breadboard array must have at least one non-header cell (to represent an empty breadboard, you can place null into this single cell).
  2. The special keyword "" (conventional ditto) is allowed in any row except the first. When present, for every such cell, the nearest non-"" reference above the cell but in the same column is copied into the cell, and the rules below are then applied to the so-transformed breadboard array.
  3. The special keyword __ (columnar ditto) is allowed in any column except the first. When present, for every such cell, the nearest non-__ reference to the left of the cell in the same row is copied into the cell, and the rules below are then applied to the so-transformed breadboard array.
  4. Individual JComponent references must be placed into a single rectangular sub-block within the breadboard array. The single component occupies the corresponding position within the breadboard grid, which determines the relative placement of this component on the form.
  5. All 2-D array references must refer to rectangular arrays (arrays all of whose rows have the same number of elements).
  6. All 2-D array references that reference arrays with more than one row and more than one column must be placed into a single rectangular sub-block of the breadboard array that has exactly the same number of rows and columns as the 2-D array (the JComponents in the 2-D array will be placed into the corresponding cells in the breadboard array).
  7. All 2-D arrays references that reference an array with only one row and more than one column must be placed into a rectangular sub-block of the breadboard with the same number of columns and one or more rows. The single JComponent in each column of the 2-D array is placed into the entire corresponding column of the breadboard sub-block.
  8. All 2-D arrays references that reference an array with only one column and more than one row must be placed into a rectangular sub-block of the breadboard with the same number of rows and one or more columns. The single JComponent in each row of the 2-D array is placed into the entire corresponding row of the breadboard sub-block.
  9. All 1-D array references that reference arrays with more than one element must be placed into either a single row, or a single column, that has exactly the same number of elements as the 1-D array. The JComponents in the array will be placed into the corresponding cells in the breadboard array.
  10. All 2-D and 1-D array references that contain a single JComponent element, are replaced with a reference to that single element, and then follow the same rules as for individual JComponent references.
  11. No JComponent, regardless of if it is placed on the breadboard grid directly, or indirectly via an array reference, can be mapped into more than one rectangular sub-block of the breadboard array.
  12. Array elements may also be null, which indicates unoccupied space in the grid.
  13. Any references (for example, a reference to a 2-D array with three zero-element rows) not explicitly mentioned above are illegal.

Failure to comply with the above rules will cause this method to throw an IllegalArgumentException that is informative enough so that you don't need to refer back to this list.

How component placement and sizing are determined (the layout algorithm)

Note: The code that implements the layout manager is so simple that you might prefer to consult it directly (c.f. the layoutContainer method in JComponentBreadboard.java) rather than relying on the more intuitive description below.

There are three basic steps in the determination of component placement and sizing:

  1. The preferred size determined height of each row, and the preferred size determined width of each column of the breadboard grid are calculated.
  2. If the parent container's actual size is different than the sum of these preferred size determined heights and widths, certain rows and columns (as indicated by the row and column scalingDirectives) are either uniformly scaled up or uniformly scaled down such that (if possible) the so-rescaled grid exactly fits within the parent container.
  3. Components are positioned and sized within the rectangular block of grid cells that they occupy.

As a simple example of this process, consider a grid in which each grid cell is occupied by just a single JComponent (no multi-cell sub-blocks), and where every row and column has the BISCALE scaling directive:

  1. The preferred size determined height of each row is the maximum preferred height of all the JComponents within that row. Similarly, the preferred size determined width of each column is the maximum preferred width of all JComponents within that column.
  2. Because each row and column contains the BISCALE directive, each preferred size determined row height is scaled up or down uniformly such that the sum of all so-rescaled row heights exactly equals the parent's height. Similarly, each preferred size determined column width is scaled up or down uniformly such that the sum of all so-adjusted column widths equals the parent container's actual width.
  3. With these adjusted grid-sizes in hand, the exact location of the grid cell that contains each JComponent is determined. Although the JComponent will always be placed within that cell, the exact location and sizing within that grid-cell will be determined by consulting additional layout attributes (such as those governing alignment) described below.

The key role of preferredSize. The built-in preferred size estimates provided by Swing are central to how JComponentBreadboard places your components on the form. The key reason preferred sizes work so well to define basic grid-sizes is that Swing, by default, computes its preferred size estimates as the smallest size each JComponent requires to display itself well. For example, with the default font size, a JButton with the label "OK" will have a smaller preferred width than one with the label "Cancel", because part of the preferred width computation takes into account the amount of space required to display the button's label without truncating it.

Determining the preferred size determined row and column heights

For each JComponent on the breadboard array we manufacture a rigid steel bar whose length is equal to the (possibly yUpsize-adjusted) preferred height of that JComponent.

We construct a series of parallel rigid steel rectangular shelves (each of negligible thickness), and hang them from the ceiling via a special system of pulleys that allows them to move up and down while remaining parallel to the ceiling. These dividers correspond to the bottom edges of each of the rows in the breadboard array (with the ceiling representing the top edge of the first row) so there will be N such dividers, where N is the number of rows in the breadboard array.

We take each rigid bar and weld it to the bottom edge of the steel divider (or ceiling) that represents the top edge of the grid-cell-block that contains the corresponding JComponent, so that the bar is perpendicular to the row dividers, pointing down. We then drill holes immediately under the spot where the bar was welded in every row divider that is inside that component's grid-cell-block, but not in the row divider that represents the bottom edge of this JComponent's grid-cell block. (note that this implies that JComponents that occupy only a single row in the grid don't require any drilling). The bar is threaded through these holes, so that the inner row dividers can move freely around the bar.

We repeat this process for each JComponent on the grid, adding appropriately sized bars and drilling holes for intermediary row dividers where needed, until there is one bar in place for each JComponent on the breadboard grid.

The pulleys are pulled as tight as possible, until every row-divider-shelf, while still parallel to the ceiling, is as close to the ceiling as possible. Namely, each divider is either flush against the bottom tip of one or more of the steel bars, or else flush against the row divider immediately above it (or against the ceiling for the first row divider). The final vertical distances between each metal divider equals the preferred sized determined height of each row. Note that if we give each breadboard grid row these heights, the rigid bars assure us that it will always be possible to fit each JComponent into it's corresponding grid-cell-block at it's preferred height. Simultaneously, the tight pulleys assure us that the sum of all these heights is as small as it can possibly be.

This entire process is repeated, in an exactly analogous manner, to determine the preferred size determined column widths. Specifically, we use the same apparatus, except that the number of dividers is equal to the number of columns in the breadboard array, and the bar lengths are equal to the widths of each component, and the welded-to dividers are those corresponding to the left-edge of the grid-cell block containing the component, and holes are drilled in the shelves corresponding to the inner column dividers of the each component's grid-cell-block. (Or, if you prefer, just imagine rotating the breadboard array clockwise through a right angle, so that all the column dividers become row dividers).

The preferred height and width of the JComponentBreadboard as a whole (which in turn impacts its own placement within its own parent container) equals the sum of all of their preferred-height-determined row heights and the sum of all of their preferred-width-determined column widths.

Note that in the above algorithm, the row heights and column widths are determined independently. For example, changes to the preferred height of a component can never have any impact on the preferred size determined column widths. This generates more easily predicted layout decisions than layout algorithms (such as that used by GridBagLayout) that lack such independence.

Scaling up and down to fit within the actual size of the parent container

The sum of all the "preferred row heights" as determined by the above algorithm may either be:

  1. Equal to the actual height of the parent container
  2. Less than the actual height of the parent container (space surplus in parent)
  3. Greater than the actual height of the parent container (space deficit in parent)
For a top-level JComponentBreadboard occupying the entire content pane, the size of its parent container changes whenever the user resizes the Dialog or Frame via it's sizing border.

In the first case, no further rescaling of the row heights is required, and they simply remain at their preferred-size-determined values.

In the second case, every row with a non-zero preferred height whose row header contains either the EXPANDS or BISCALE scaling directive is uniformly scaled up so as to use up the extra space in the parent (if there are no such "up-scaleable" rows, no changes are made to the row heights from their preferred size determined values).

In the third case, every row whose row header contains either the SHRINKS or BISCALE scalingDirective is scaled down uniformly until the so-rescaled grid has a height that exactly equals the height of the parent container, or until all "down-scalable" rows have zero height, whichever comes first.

Note that the above rules imply that in some cases it will not be possible for the grid to be scaled up or down to exactly equal the height of the parent container. In such cases, the grid will be positioned as determined by the layout manager of its own parent container.

Exactly analogous rules are applied to distribute any horizontal surplus or deficit space in the parent container across the columns of the grid.

Placement of each JComponent within it's grid-cell block

The final (possibly rescaled) row heights and column widths determine exactly the position (within this container's x-y coordinate system) of the grid-cell-block that contains each JComponent. Although the algorithm assures that the initial preferred-size-determined grid produces grid-cell-blocks that are large enough to contain every JComponent at their preferred sizes, due to the presence of larger components in nearby cells, as well as the impact of any "scaling up" that may have been done, the final grid-cell-block could be larger than the JComponent it contains. Furthermore, due to the impact of any "scaling down" that may have been done, the final grid-cell-block could also be too small to contain the JComponent at it's preferred size.

In light of the above, JComponentBreadboard uses the following rules to determine the exact size and placement of each component within it's grid-cell block:

  1. If the grid-cell-block and the JComponent are exactly the same height, (an exact fit) no adjustment is required and the JComponent is simply placed into the grid-cell block at it's preferred height.
  2. If the block is shorter than the preferred height of the JComponent it contains, the JComponent's height is simply set equal to the smaller, grid-cell-block's height.
  3. If the block is taller than the JComponent it contains, and if the JComponent's yFill attribute is true, then the JComponent's height is also set equal to the height of the grid-cell-block that contains it.
  4. If the block is taller and the yFill attribute is false, then the JComponent remains at it's preferred height, and will be flush against the top edge of its grid-cell-block if it's alignmentY property equals 0, flush against the bottom edge if it's alignmentY property equals 1, and a fraction "f" of the way between these two extremes if the alignmentY property equals a number "f" between 0 and 1.

Exactly analogous rules (using xFill instead of yFill, alignmentX instead of alignmentY, etc.) apply independently to the horizontal placement and sizing of the JComponent relative to the width of it's containing grid-cell-block.

For an example of how these rules work themselves out in defining the placement of components within a specific form, including examples of each of the four allowed scaling directives, see the CelsiusFahrenheitConverter application in the JComponentBreadboard User's Guide.

@param breadboard an array that defines the relative positions of all components on this form within a flexible, rectilinear grid. @see #xFill(boolean, JComponent) xFill @see #yFill(boolean, JComponent) yFill @see #xAlign(double, JComponent) xAlign @see #yAlign(double, JComponent) yAlign @see #xUpsize(int, JComponent) xUpsize @see #yUpsize(int, JComponent) yUpsize @see #NOSCALE NOSCALE @see #SHRINKS SHRINKS @see #EXPANDS EXPANDS @see #BISCALE BISCALE *********************************************************************/ public void setBreadboard(Object[][] breadboard) { Object[][] dittoDecoded = decodeDittos(breadboard); if (isPlainBreadboard(dittoDecoded)) { setAllScales(dittoDecoded, DEFAULT_SCALING_DIRECTIVE, DEFAULT_SCALING_DIRECTIVE); setPlainBreadboard(dittoDecoded); } else { setScales(dittoDecoded); Object[][] plainBreadboard = new Object[dittoDecoded.length-1][dittoDecoded[0].length-1]; for (int i = 0; i < plainBreadboard.length; i++) for (int j = 0; j < plainBreadboard[0].length; j++) plainBreadboard[i][j] = dittoDecoded[i+1][j+1]; setPlainBreadboard(plainBreadboard); } } // Code below this point manages a simplified "signals and // slots"-like, "Qt connect" facility. // This list stores the JComponent <==> {object, "getter/setter convention" property name} // connections created via calls to jbConnect private ArrayList jbConnections = new ArrayList(); // Does the plugged into object have a public method with the specified // name and type signature? These signatures are the specific method // signatures of the various special "getter/setter" methods that are // recognized and exploited whenever you use jbConnect on one of the // breadboard's JComponents. // // For example: // // hasMethod(theObj, "aMethod",int.class,String.class, 0) // // would return true if and only if the object "theObj" had a method with the signature: // // public int aMethod(String s) // // if the last, nArrayIndexes, arg is > 0, looks for a method with a similar signature, // but with up to two extra int "array indexing" args. For example: // // hasMethod(theObj, "aMethod",long.class,String.class, 2) // // would return true if and only if the object "theObj" had a method with the signature: // // public long aMethod(int iRow, int iCol, String s) // private static boolean hasMethod(Object theObj, String methodName, Class retType, Class argType, int nArrayIndexes) { Method[] theMethods = theObj.getClass().getMethods(); boolean result = false; int nValidatedArgs = 0; if (argType != null && argType != Void.TYPE) nValidatedArgs++; nValidatedArgs += nArrayIndexes; for (int i = 0; i < theMethods.length && !result; i++) { if (theMethods[i].getName().equals(methodName) && retType.equals(theMethods[i].getReturnType())) { // method with same name and return type found... int iArgs = theMethods[i].getParameterTypes().length; if (nValidatedArgs == iArgs) { // # of args must match if ((nArrayIndexes < 1 || int.class.equals(theMethods[i].getParameterTypes()[0])) && (nArrayIndexes < 2 || int.class.equals(theMethods[i].getParameterTypes()[1])) && (argType == null || argType == Void.TYPE || argType.equals(theMethods[i].getParameterTypes()[iArgs-1]))) result = true; } } } return result; } // represent a possibly null object reference as a string private static String objRefToString(Object obj) { String result = "null"; if (obj != null) result = obj.toString(); return result; } // represents an argument list as a string for error reporting purposes private static String argsToString(Object[] args) { String result = ""; for (int i = 0; args != null && i < args.length; i++) { result += objRefToString(args[i]); if (i < args.length-1) result += ", "; } return result; } // Returns copy of given string whose first character is in uppercase. private static String firstToUpper(String s) { String result = s.substring(0,1).toUpperCase() + s.substring(1, s.length()); return result; } // Turns out code below makes refreshes of forms with lost of // connections an order of magnitude faster, simply by keeping track of // the object/method pairs that don't exist, and not trying to access // them via Java reflection more than once. Empirically, the overhead // of using Java's reflection to determine non-existence is much higher // than just looking it up on a HashSet. Exploits the fact that most // methods that COULD be plugged into a connected component (optional, // auxiliary, methods) are not used. // Can be shown that memory used by the hash has an upper bound equal // to a small constant times the number of connectable properties on // all JComponentBreadboard derived classes (usually, it will be much // less than this--layout only forms have no such costs, for // example)--so it can be viewed as a (reasonable) fixed overhead // memory cost per form. private static HashSet noSuchMethodHash = new HashSet(); private static Object[] tmpHolder = new Object[3]; // static to avoid "heap-stress" private static int methodHashCode(Object obj, String methodName, Object[] args) { tmpHolder[0] = obj.getClass(); tmpHolder[1] = methodName; // Hidden dependency alert: the only possible args that matter for a // unique hash code (given other checks/requirements specific to how // this method is used within JComponentBreadboard) are integer indexing // arguments, of which there can be 0, 1, or 2, so the number of args is // sufficient for uniqueness. Watch out for bugs here if you make major // method signature related changes (e.g. introducing string-indexed array support). tmpHolder[2] = (null == args) ? 0 : args.length; int result = Arrays.hashCode(tmpHolder); return result; } private static void addNoSuchMethodMethod(Object obj, String methodName, Object[] args) { int thisMethodsCode = methodHashCode(obj, methodName, args); noSuchMethodHash.add(thisMethodsCode); } private static boolean methodMayExist(Object obj, String methodName, Object[] args) { boolean result = true; int thisMethodsCode = methodHashCode(obj, methodName, args); if (noSuchMethodHash.contains(thisMethodsCode)) result = false; return result; } // Gets value of the property with a given name in the given object. // Returns null if property does not exist. private static Object getMethodValue(Object obj, String methodName, Object[] args) { Object result = null; if (methodMayExist(obj, methodName, args)) { Expression getExpr = new Expression(obj, methodName, args); try { result = getExpr.getValue(); } catch(NoSuchMethodException e) { addNoSuchMethodMethod(obj, methodName, args); result = null; // null used to indicate "no such property" } catch (Exception e) { throw new IllegalStateException(objRefToString(obj) + "." + methodName + "(" + argsToString(args) + ") failed unexpectedly.",e); } } return result; } /* // Gets value of the property with a given name in the given object. // Returns null if property does not exist. private static Object getMethodValue(Object obj, String methodName, Object[] args) { Object result = null; Expression getExpr = new Expression(obj, methodName, args); try { result = getExpr.getValue(); } catch(NoSuchMethodException e) { result = null; // null used to indicate "no s