All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jgoodies.forms.layout.FormSpec Maven / Gradle / Ivy

Go to download

The JGoodies Forms framework helps you lay out and implement elegant Swing panels quickly and consistently. It makes simple things easy and the hard stuff possible, the good design easy and the bad difficult.

There is a newer version: 1.9.0
Show newest version
/*
 * Copyright (c) 2002-2014 JGoodies Software GmbH. 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 Software GmbH 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 static com.jgoodies.common.base.Preconditions.checkArgument;
import static com.jgoodies.common.base.Preconditions.checkNotBlank;
import static com.jgoodies.common.base.Preconditions.checkNotNull;

import java.awt.Container;
import java.io.Serializable;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;


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

* * Also implements the parser for encoded column and row specifications * and provides parser convenience behavior for its subclasses ColumnSpec * and RowSpec.

* * TODO: Consider extracting the parser role to a separate class. * * @author Karsten Lentzsch * @version $Revision: 1.25 $ * * @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"); /** * A special alignment intended for table columns only, * where some cell renderers are not aligned. */ static final DefaultAlignment NO_ALIGN = new DefaultAlignment("none"); /** * 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, NO_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; // Parser Patterns ****************************************************** private static final Pattern TOKEN_SEPARATOR_PATTERN = Pattern.compile(":"); private static final Pattern BOUNDS_SEPARATOR_PATTERN = Pattern.compile("\\s*,\\s*"); // Fields *************************************************************** /** * Holds the default alignment that will be used if a cell does not * override this default. */ private DefaultAlignment defaultAlignment; /** * Describes whether the default alignment has been explictly set. * * @see #getDefaultAlignmentExplictlySet() * */ private boolean defaultAlignmentExplicitlySet; /** * 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} for the given default alignment, * size, and resize weight. The resize weight must be a non-negative * double; you can use {@code NONE} 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 * * @throws NullPointerException if the {@code size} is {@code null} * @throws IllegalArgumentException if the {@code resizeWeight} is negative */ protected FormSpec(DefaultAlignment defaultAlignment, Size size, double resizeWeight) { checkNotNull(size, "The size must not be null."); checkArgument(resizeWeight >= 0, "The resize weight must be non-negative."); this.defaultAlignment = defaultAlignment; this.size = size; this.resizeWeight = resizeWeight; } /** * Constructs a FormSpec 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 whether the default alignment has been explicitly set or not. * * @return {@code true} if the default alignment has been provided * during the parse process, {@code false} if the default alignment * has been set by the constructor at the instance creation time */ public final boolean getDefaultAlignmentExplictlySet() { return defaultAlignmentExplicitlySet; } /** * 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}. * * @return true if it can grow, false if it can't grow */ final boolean canGrow() { return getResizeWeight() != NO_GROW; } // 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(); // Setting Values ********************************************************* void setDefaultAlignment(DefaultAlignment defaultAlignment) { this.defaultAlignment = defaultAlignment; this.defaultAlignmentExplicitlySet = true; } void setSize(Size size) { this.size = size; } void setResizeWeight(double resizeWeight) { this.resizeWeight = resizeWeight; } // Parsing ************************************************************** /** * Parses an encoded form specification and initializes all required fields. * The encoded description must be in lower case. * * @param encodedDescription the FormSpec in an encoded format * * @throws NullPointerException if {@code encodedDescription} is {@code null} * @throws IllegalArgumentException if {@code encodedDescription} * is empty, whitespace, has no size, or is otherwise invalid */ private void parseAndInitValues(String encodedDescription) { checkNotBlank(encodedDescription, "The encoded form specification must not be null, empty or whitespace."); String[] token = TOKEN_SEPARATOR_PATTERN.split(encodedDescription); checkArgument(token.length > 0, "The form spec must not be empty."); int nextIndex = 0; String next = token[nextIndex++]; // Check if the first token is an orientation. DefaultAlignment alignment = DefaultAlignment.valueOf(next, isHorizontal()); if (alignment != null) { setDefaultAlignment(alignment); checkArgument(token.length > 1, "The form spec must provide a size."); next = token[nextIndex++]; } setSize(parseSize(next)); if (nextIndex < token.length) { setResizeWeight(parseResizeWeight(token[nextIndex])); } } /** * Parses an encoded size spec and returns the size. * * @param token a token that represents a size, either bounded or plain * @return the decoded Size */ private Size parseSize(String token) { if (token.startsWith("[") && token.endsWith("]")) { return parseBoundedSize(token); } if (token.startsWith("max(") && token.endsWith(")")) { return parseOldBoundedSize(token, false); } if (token.startsWith("min(") && token.endsWith(")")) { return parseOldBoundedSize(token, true); } return parseAtomicSize(token); } private Size parseBoundedSize(String token) { String content = token.substring(1, token.length()-1); String[] subtoken = BOUNDS_SEPARATOR_PATTERN.split(content); Size basis = null; Size lower = null; Size upper = null; if (subtoken.length == 2) { Size size1 = parseAtomicSize(subtoken[0]); Size size2 = parseAtomicSize(subtoken[1]); if (isConstant(size1)) { if (isConstant(size2)) { lower = size1; basis = size2; upper = size2; } else { lower = size1; basis = size2; } } else { basis = size1; upper = size2; } } else if (subtoken.length == 3) { lower = parseAtomicSize(subtoken[0]); basis = parseAtomicSize(subtoken[1]); upper = parseAtomicSize(subtoken[2]); } if ( (lower == null || isConstant(lower)) && (upper == null || isConstant(upper))) { return new BoundedSize(basis, lower, upper); } throw new IllegalArgumentException( "Illegal bounded size '" + token + "'. Must be one of:" + "\n[,] // lower bound" + "\n[,] // upper bound" + "\n[,,] // lower and upper bound." + "\nExamples:" + "\n[50dlu,pref] // lower bound" + "\n[pref,200dlu] // upper bound" + "\n[50dlu,pref,200dlu] // lower and upper bound." ); } /** * 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 parseOldBoundedSize(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 = parseAtomicSize(sizeToken1); Size size2 = parseAtomicSize(sizeToken2); // Check valid combinations and set min or max. if (isConstant(size1)) { 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 (isConstant(size2)) { 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 parseAtomicSize(String token) { String trimmedToken = token.trim(); if ( trimmedToken.startsWith("'") && trimmedToken.endsWith("'")) { int length = trimmedToken.length(); if (length < 2) { throw new IllegalArgumentException("Missing closing \"'\" for prototype."); } return new PrototypeSize(trimmedToken.substring(1, length - 1)); } Sizes.ComponentSize componentSize = Sizes.ComponentSize.valueOf(trimmedToken); if (componentSize != null) { return componentSize; } return ConstantSize.valueOf(trimmedToken, 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 * @throws IllegalArgumentException if the string description is an * invalid string representation */ private static double parseResizeWeight(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() 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(), g()"); } private static boolean isConstant(Size aSize) { return aSize instanceof ConstantSize || aSize instanceof PrototypeSize; } // Misc ***************************************************************** /** * Returns a string representation of this form specification. * The string representation consists of three elements separated by * a colon (":"), first the alignment, second the size, * and third the resize spec.

* * This method does not return an encoded version * of this object; the contrary is the case. Many instances * will return a string that cannot be parsed.

* * Note: The string representation may change at any time. * For parsing use {@link #encode()} instead. * * @return a string representation of the form specification. */ @Override 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 (":"), first the alignment, second the size, * and third the resize spec.

* * This method does not return an encoded version * of this object; the contrary is the case. Many instances * will return a string that cannot be parsed.

* * Note: The string representation may change at any time. * For parsing use {@link #encode()} instead. * * @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(); } /** * Returns a short and parseable string representation of this * form specification. The string will omit the alignment and resize * specifications if these are the default values.

* * @return a string representation of the form specification. * * @see #toShortString() for a more verbose string representation * * @since 1.2 */ public final String encode() { StringBuffer buffer = new StringBuffer(); DefaultAlignment alignmentDefault = isHorizontal() ? ColumnSpec.DEFAULT : RowSpec.DEFAULT; if (!alignmentDefault.equals(defaultAlignment)) { buffer.append(defaultAlignment.abbreviation()); buffer.append(":"); } buffer.append(size.encode()); if (resizeWeight == NO_GROW) { // Omit the resize part } else if (resizeWeight == DEFAULT_GROW) { buffer.append(':'); buffer.append("g"); } else { buffer.append(':'); buffer.append("g("); buffer.append(resizeWeight); buffer.append(')'); } return buffer.toString(); } // Helper Code ********************************************************** /** * Computes the maximum size for the given list of components, using * this form spec and the specified measure.

* * 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 alignment 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 if (str.equals("none")) { return NO_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. */ @Override 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 } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy