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