/* * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o 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. * * o Neither the name of JGoodies Karsten Lentzsch 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 SUBSTITUTE 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. */ package com.jgoodies.forms.layout; import java.awt.Container; import java.io.Serializable; import java.util.List; import java.util.Locale; import java.util.StringTokenizer; /** * An abstract class that specifies columns and rows in FormLayout * by their default alignment, start size and resizing behavior. * API users will use the subclasses {@link ColumnSpec} and {@link RowSpec}. * * @author Karsten Lentzsch * @version $Revision$ * * @see ColumnSpec * @see RowSpec * @see FormLayout * @see CellConstraints */ public abstract class FormSpec implements Serializable { // Horizontal and Vertical Default Alignments *************************** /** * By default put components in the left. */ static final DefaultAlignment LEFT_ALIGN = new DefaultAlignment("left"); /** * By default put components in the right. */ static final DefaultAlignment RIGHT_ALIGN = new DefaultAlignment("right"); /** * By default put the components in the top. */ static final DefaultAlignment TOP_ALIGN = new DefaultAlignment("top"); /** * By default put the components in the bottom. */ static final DefaultAlignment BOTTOM_ALIGN = new DefaultAlignment("bottom"); /** * By default put the components in the center. */ static final DefaultAlignment CENTER_ALIGN = new DefaultAlignment("center"); /** * By default fill the column or row. */ static final DefaultAlignment FILL_ALIGN = new DefaultAlignment("fill"); /** * An array of all enumeration values used to canonicalize * deserialized default alignments. */ private static final DefaultAlignment[] VALUES = { LEFT_ALIGN, RIGHT_ALIGN, TOP_ALIGN, BOTTOM_ALIGN, CENTER_ALIGN, FILL_ALIGN }; // Resizing Weights ***************************************************** /** * Gives a column or row a fixed size. */ public static final double NO_GROW = 0.0d; /** * The default resize weight. */ public static final double DEFAULT_GROW = 1.0d; // Fields *************************************************************** /** * Holds the default alignment that will be used if a cell does not * override this default. */ private DefaultAlignment defaultAlignment; /** * Holds the size that describes how to size this column or row. */ private Size size; /** * Holds the resize weight; is 0 if not used. */ private double resizeWeight; // Instance Creation **************************************************** /** * Constructs a <code>FormSpec</code> for the given default alignment, * size, and resize weight. The resize weight must be a non-negative * double; you can use <code>NONE</code> as a convenience value for no * resize. * * @param defaultAlignment the spec's default alignment * @param size a constant, component or bounded size * @param resizeWeight the spec resize weight * @exception IllegalArgumentException if the resize weight is negative */ protected FormSpec(DefaultAlignment defaultAlignment, Size size, double resizeWeight) { this.defaultAlignment = defaultAlignment; this.size = size; this.resizeWeight = resizeWeight; if (resizeWeight < 0) { throw new IllegalArgumentException( "The resize weight must be non-negative."); } } /** * Constructs a <code>FormSpec</code> from the specified encoded * description. The description will be parsed to set initial values. * * @param defaultAlignment the default alignment * @param encodedDescription the encoded description */ protected FormSpec(DefaultAlignment defaultAlignment, String encodedDescription) { this(defaultAlignment, Sizes.DEFAULT, NO_GROW); parseAndInitValues(encodedDescription.toLowerCase(Locale.ENGLISH)); } // Public API *********************************************************** /** * Returns the default alignment. * * @return the default alignment */ public final DefaultAlignment getDefaultAlignment() { return defaultAlignment; } /** * Returns the size. * * @return the size */ public final Size getSize() { return size; } /** * Returns the current resize weight. * * @return the resize weight. */ public final double getResizeWeight() { return resizeWeight; } /** * Checks and answers whether this spec can grow or not. * That is the case if and only if the resize weight is * != <code>NO_GROW</code>. * * @return true if it can grow, false if it can't grow */ final boolean canGrow() { return getResizeWeight() != NO_GROW; } // Parsing ************************************************************** /** * Parses an encoded form spec and initializes all required fields. * The encoded description must be in lower case. * * @param encodedDescription the FormSpec in an encoded format * @exception IllegalArgumentException if the string is empty, has no size, * or is otherwise invalid */ private void parseAndInitValues(String encodedDescription) { StringTokenizer tokenizer = new StringTokenizer(encodedDescription, ":"); if (!tokenizer.hasMoreTokens()) { throw new IllegalArgumentException( "The form spec must not be empty."); } String token = tokenizer.nextToken(); // Check if the first token is an orientation. DefaultAlignment alignment = DefaultAlignment.valueOf(token, isHorizontal()); if (alignment != null) { defaultAlignment = alignment; if (!tokenizer.hasMoreTokens()) { throw new IllegalArgumentException( "The form spec must provide a size."); } token = tokenizer.nextToken(); } parseAndInitSize(token); if (tokenizer.hasMoreTokens()) { resizeWeight = decodeResize(tokenizer.nextToken()); } } /** * Parses an encoded size spec and initializes the size fields. * * @param token a token that represents a size, either bounded or plain */ private void parseAndInitSize(String token) { if (token.startsWith("max(") && token.endsWith(")")) { size = parseAndInitBoundedSize(token, false); return; } if (token.startsWith("min(") && token.endsWith(")")) { size = parseAndInitBoundedSize(token, true); return; } size = decodeAtomicSize(token); } /** * Parses an encoded compound size and sets the size fields. * The compound size has format: * max(<atomic size>;<atomic size2>) | min(<atomic size1>;<atomic size2>) * One of the two atomic sizes must be a logical size, the other must * be a size constant. * * @param token a token for a bounded size, e.g. "max(50dlu; pref)" * @param setMax if true we set a maximum size, otherwise a minimum size * @return a Size that represents the parse result */ private Size parseAndInitBoundedSize(String token, boolean setMax) { int semicolonIndex = token.indexOf(';'); String sizeToken1 = token.substring(4, semicolonIndex); String sizeToken2 = token.substring(semicolonIndex + 1, token.length() - 1); Size size1 = decodeAtomicSize(sizeToken1); Size size2 = decodeAtomicSize(sizeToken2); // Check valid combinations and set min or max. if (size1 instanceof ConstantSize) { if (size2 instanceof Sizes.ComponentSize) { return new BoundedSize(size2, setMax ? null : size1, setMax ? size1 : null); } throw new IllegalArgumentException( "Bounded sizes must not be both constants."); } if (size2 instanceof ConstantSize) { return new BoundedSize(size1, setMax ? null : size2, setMax ? size2 : null); } throw new IllegalArgumentException( "Bounded sizes must not be both logical."); } /** * Decodes and returns an atomic size that is either a constant size or a * component size. * * @param token the encoded size * @return the decoded size either a constant or component size */ private Size decodeAtomicSize(String token) { Sizes.ComponentSize componentSize = Sizes.ComponentSize.valueOf(token); if (componentSize != null) { return componentSize; } return ConstantSize.valueOf(token, isHorizontal()); } /** * Decodes an encoded resize mode and resize weight and answers * the resize weight. * * @param token the encoded resize weight * @return the decoded resize weight * @exception IllegalArgumentException if the string description is an * invalid string representation */ private double decodeResize(String token) { if (token.equals("g") || token.equals("grow")) { return DEFAULT_GROW; } if (token.equals("n") || token.equals("nogrow") || token.equals("none")) { return NO_GROW; } // Must have format: grow(<double>) if ((token.startsWith("grow(") || token.startsWith("g(")) && token.endsWith(")")) { int leftParen = token.indexOf('('); int rightParen = token.indexOf(')'); String substring = token.substring(leftParen + 1, rightParen); return Double.parseDouble(substring); } throw new IllegalArgumentException( "The resize argument '" + token + "' is invalid. " + " Must be one of: grow, g, none, n, grow(<double>), g(<double>)"); } // Misc ***************************************************************** /** * Returns a string representation of this form specification. * The string representation consists of three elements separated by * a colon (<tt>":"</tt>), first the alignment, second the size, * and third the resize spec.<p> * * This method does <em>not</em> return a decoded version * of this object; the contrary is the case. Many instances * will return a string that cannot be parsed.<p> * * <strong>Note:</strong> The string representation may change * at any time. It is strongly recommended to not use this string * for parsing purposes. * * @return a string representation of the form specification. */ public final String toString() { StringBuffer buffer = new StringBuffer(); buffer.append(defaultAlignment); buffer.append(":"); buffer.append(size.toString()); buffer.append(':'); if (resizeWeight == NO_GROW) { buffer.append("noGrow"); } else if (resizeWeight == DEFAULT_GROW) { buffer.append("grow"); } else { buffer.append("grow("); buffer.append(resizeWeight); buffer.append(')'); } return buffer.toString(); } /** * Returns a string representation of this form specification. * The string representation consists of three elements separated by * a colon (<tt>":"</tt>), first the alignment, second the size, * and third the resize spec.<p> * * This method does <em>not</em> return a decoded version * of this object; the contrary is the case. Many instances * will return a string that cannot be parsed.<p> * * <strong>Note:</strong> The string representation may change * at any time. It is strongly recommended to not use this string * for parsing purposes. * * @return a string representation of the form specification. */ public final String toShortString() { StringBuffer buffer = new StringBuffer(); buffer.append(defaultAlignment.abbreviation()); buffer.append(":"); buffer.append(size.toString()); buffer.append(':'); if (resizeWeight == NO_GROW) { buffer.append("n"); } else if (resizeWeight == DEFAULT_GROW) { buffer.append("g"); } else { buffer.append("g("); buffer.append(resizeWeight); buffer.append(')'); } return buffer.toString(); } // Abstract Behavior **************************************************** /** * Returns if this is a horizontal specification (vs. vertical). * Used to distinct between horizontal and vertical dialog units, * which have different conversion factors. * @return true for horizontal, false for vertical */ abstract boolean isHorizontal(); // Helper Code ********************************************************** /** * Computes the maximum size for the given list of components, using * this form spec and the specified measure.<p> * * Invoked by FormLayout to determine the size of one of my elements * * @param container the layout container * @param components the list of components to measure * @param minMeasure the measure used to determine the minimum size * @param prefMeasure the measure used to determine the preferred size * @param defaultMeasure the measure used to determine the default size * @return the maximum size in pixels */ final int maximumSize(Container container, List components, FormLayout.Measure minMeasure, FormLayout.Measure prefMeasure, FormLayout.Measure defaultMeasure) { return size.maximumSize(container, components, minMeasure, prefMeasure, defaultMeasure); } /** * An ordinal-based serializable typesafe enumeration for the * column and row default alignment types. */ public static final class DefaultAlignment implements Serializable { private final transient String name; private DefaultAlignment(String name) { this.name = name; } /** * Returns a DefaultAlignment that corresponds to the specified * string, null if no such aignment exists. * * @param str the encoded alignment * @param isHorizontal indicates the values orientation * @return the corresponding DefaultAlignment or null */ private static DefaultAlignment valueOf(String str, boolean isHorizontal) { if (str.equals("f") || str.equals("fill")) { return FILL_ALIGN; } else if (str.equals("c") || str.equals("center")) { return CENTER_ALIGN; } else if (isHorizontal) { if (str.equals("r") || str.equals("right")) { return RIGHT_ALIGN; } else if (str.equals("l") || str.equals("left")) { return LEFT_ALIGN; } else { return null; } } else { if (str.equals("t") || str.equals("top")) { return TOP_ALIGN; } else if (str.equals("b") || str.equals("bottom")) { return BOTTOM_ALIGN; } else { return null; } } } /** * Returns this Alignment's name. * * @return this alignment's name. */ public String toString() { return name; } /** * Returns the first character of this Alignment's name. * Used to identify it in short format strings. * * @return the name's first character. */ public char abbreviation() { return name.charAt(0); } // Serialization ***************************************************** private static int nextOrdinal = 0; private final int ordinal = nextOrdinal++; private Object readResolve() { return VALUES[ordinal]; // Canonicalize } } }