001: /*
002: * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package com.jgoodies.forms.layout;
032:
033: import java.awt.Container;
034: import java.io.Serializable;
035: import java.util.List;
036: import java.util.Locale;
037: import java.util.StringTokenizer;
038:
039: /**
040: * An abstract class that specifies columns and rows in FormLayout
041: * by their default alignment, start size and resizing behavior.
042: * API users will use the subclasses {@link ColumnSpec} and {@link RowSpec}.
043: *
044: * @author Karsten Lentzsch
045: * @version $Revision: 1.4 $
046: *
047: * @see ColumnSpec
048: * @see RowSpec
049: * @see FormLayout
050: * @see CellConstraints
051: */
052: public abstract class FormSpec implements Serializable {
053:
054: // Horizontal and Vertical Default Alignments ***************************
055:
056: /**
057: * By default put components in the left.
058: */
059: static final DefaultAlignment LEFT_ALIGN = new DefaultAlignment(
060: "left");
061:
062: /**
063: * By default put components in the right.
064: */
065: static final DefaultAlignment RIGHT_ALIGN = new DefaultAlignment(
066: "right");
067:
068: /**
069: * By default put the components in the top.
070: */
071: static final DefaultAlignment TOP_ALIGN = new DefaultAlignment(
072: "top");
073:
074: /**
075: * By default put the components in the bottom.
076: */
077: static final DefaultAlignment BOTTOM_ALIGN = new DefaultAlignment(
078: "bottom");
079:
080: /**
081: * By default put the components in the center.
082: */
083: static final DefaultAlignment CENTER_ALIGN = new DefaultAlignment(
084: "center");
085:
086: /**
087: * By default fill the column or row.
088: */
089: static final DefaultAlignment FILL_ALIGN = new DefaultAlignment(
090: "fill");
091:
092: /**
093: * An array of all enumeration values used to canonicalize
094: * deserialized default alignments.
095: */
096: private static final DefaultAlignment[] VALUES = { LEFT_ALIGN,
097: RIGHT_ALIGN, TOP_ALIGN, BOTTOM_ALIGN, CENTER_ALIGN,
098: FILL_ALIGN };
099:
100: // Resizing Weights *****************************************************
101:
102: /**
103: * Gives a column or row a fixed size.
104: */
105: public static final double NO_GROW = 0.0d;
106:
107: /**
108: * The default resize weight.
109: */
110: public static final double DEFAULT_GROW = 1.0d;
111:
112: // Fields ***************************************************************
113:
114: /**
115: * Holds the default alignment that will be used if a cell does not
116: * override this default.
117: */
118: private DefaultAlignment defaultAlignment;
119:
120: /**
121: * Holds the size that describes how to size this column or row.
122: */
123: private Size size;
124:
125: /**
126: * Holds the resize weight; is 0 if not used.
127: */
128: private double resizeWeight;
129:
130: // Instance Creation ****************************************************
131:
132: /**
133: * Constructs a <code>FormSpec</code> for the given default alignment,
134: * size, and resize weight. The resize weight must be a non-negative
135: * double; you can use <code>NONE</code> as a convenience value for no
136: * resize.
137: *
138: * @param defaultAlignment the spec's default alignment
139: * @param size a constant, component or bounded size
140: * @param resizeWeight the spec resize weight
141: * @throws IllegalArgumentException if the resize weight is negative
142: */
143: protected FormSpec(DefaultAlignment defaultAlignment, Size size,
144: double resizeWeight) {
145: this .defaultAlignment = defaultAlignment;
146: this .size = size;
147: this .resizeWeight = resizeWeight;
148: if (resizeWeight < 0)
149: throw new IllegalArgumentException(
150: "The resize weight must be non-negative.");
151: }
152:
153: /**
154: * Constructs a <code>FormSpec</code> from the specified encoded
155: * description. The description will be parsed to set initial values.
156: *
157: * @param defaultAlignment the default alignment
158: * @param encodedDescription the encoded description
159: */
160: protected FormSpec(DefaultAlignment defaultAlignment,
161: String encodedDescription) {
162: this (defaultAlignment, Sizes.DEFAULT, NO_GROW);
163: parseAndInitValues(encodedDescription
164: .toLowerCase(Locale.ENGLISH));
165: }
166:
167: // Public API ***********************************************************
168:
169: /**
170: * Returns the default alignment.
171: *
172: * @return the default alignment
173: */
174: public final DefaultAlignment getDefaultAlignment() {
175: return defaultAlignment;
176: }
177:
178: /**
179: * Returns the size.
180: *
181: * @return the size
182: */
183: public final Size getSize() {
184: return size;
185: }
186:
187: /**
188: * Returns the current resize weight.
189: *
190: * @return the resize weight.
191: */
192: public final double getResizeWeight() {
193: return resizeWeight;
194: }
195:
196: /**
197: * Checks and answers whether this spec can grow or not.
198: * That is the case if and only if the resize weight is
199: * != <code>NO_GROW</code>.
200: *
201: * @return true if it can grow, false if it can't grow
202: */
203: final boolean canGrow() {
204: return getResizeWeight() != NO_GROW;
205: }
206:
207: // Parsing **************************************************************
208:
209: /**
210: * Parses an encoded form spec and initializes all required fields.
211: * The encoded description must be in lower case.
212: *
213: * @param encodedDescription the FormSpec in an encoded format
214: * @throws IllegalArgumentException if the string is empty, has no size,
215: * or is otherwise invalid
216: */
217: private void parseAndInitValues(String encodedDescription) {
218: StringTokenizer tokenizer = new StringTokenizer(
219: encodedDescription, ":");
220: if (!tokenizer.hasMoreTokens()) {
221: throw new IllegalArgumentException(
222: "The form spec must not be empty.");
223: }
224: String token = tokenizer.nextToken();
225:
226: // Check if the first token is an orientation.
227: DefaultAlignment alignment = DefaultAlignment.valueOf(token,
228: isHorizontal());
229: if (alignment != null) {
230: defaultAlignment = alignment;
231: if (!tokenizer.hasMoreTokens()) {
232: throw new IllegalArgumentException(
233: "The form spec must provide a size.");
234: }
235: token = tokenizer.nextToken();
236: }
237:
238: parseAndInitSize(token);
239:
240: if (tokenizer.hasMoreTokens()) {
241: resizeWeight = decodeResize(tokenizer.nextToken());
242: }
243: }
244:
245: /**
246: * Parses an encoded size spec and initializes the size fields.
247: *
248: * @param token a token that represents a size, either bounded or plain
249: */
250: private void parseAndInitSize(String token) {
251: if (token.startsWith("max(") && token.endsWith(")")) {
252: size = parseAndInitBoundedSize(token, false);
253: return;
254: }
255: if (token.startsWith("min(") && token.endsWith(")")) {
256: size = parseAndInitBoundedSize(token, true);
257: return;
258: }
259: size = decodeAtomicSize(token);
260: }
261:
262: /**
263: * Parses an encoded compound size and sets the size fields.
264: * The compound size has format:
265: * max(<atomic size>;<atomic size2>) | min(<atomic size1>;<atomic size2>)
266: * One of the two atomic sizes must be a logical size, the other must
267: * be a size constant.
268: *
269: * @param token a token for a bounded size, e.g. "max(50dlu; pref)"
270: * @param setMax if true we set a maximum size, otherwise a minimum size
271: * @return a Size that represents the parse result
272: */
273: private Size parseAndInitBoundedSize(String token, boolean setMax) {
274: int semicolonIndex = token.indexOf(';');
275: String sizeToken1 = token.substring(4, semicolonIndex);
276: String sizeToken2 = token.substring(semicolonIndex + 1, token
277: .length() - 1);
278:
279: Size size1 = decodeAtomicSize(sizeToken1);
280: Size size2 = decodeAtomicSize(sizeToken2);
281:
282: // Check valid combinations and set min or max.
283: if (size1 instanceof ConstantSize) {
284: if (size2 instanceof Sizes.ComponentSize) {
285: return new BoundedSize(size2, setMax ? null : size1,
286: setMax ? size1 : null);
287: }
288: throw new IllegalArgumentException(
289: "Bounded sizes must not be both constants.");
290: }
291: if (size2 instanceof ConstantSize) {
292: return new BoundedSize(size1, setMax ? null : size2,
293: setMax ? size2 : null);
294: }
295: throw new IllegalArgumentException(
296: "Bounded sizes must not be both logical.");
297: }
298:
299: /**
300: * Decodes and returns an atomic size that is either a constant size or a
301: * component size.
302: *
303: * @param token the encoded size
304: * @return the decoded size either a constant or component size
305: */
306: private Size decodeAtomicSize(String token) {
307: Sizes.ComponentSize componentSize = Sizes.ComponentSize
308: .valueOf(token);
309: if (componentSize != null)
310: return componentSize;
311: return ConstantSize.valueOf(token, isHorizontal());
312: }
313:
314: /**
315: * Decodes an encoded resize mode and resize weight and answers
316: * the resize weight.
317: *
318: * @param token the encoded resize weight
319: * @return the decoded resize weight
320: * @throws IllegalArgumentException if the string description is an
321: * invalid string representation
322: */
323: private double decodeResize(String token) {
324: if (token.equals("g") || token.equals("grow")) {
325: return DEFAULT_GROW;
326: }
327: if (token.equals("n") || token.equals("nogrow")
328: || token.equals("none")) {
329: return NO_GROW;
330: }
331: // Must have format: grow(<double>)
332: if ((token.startsWith("grow(") || token.startsWith("g("))
333: && token.endsWith(")")) {
334: int leftParen = token.indexOf('(');
335: int rightParen = token.indexOf(')');
336: String substring = token.substring(leftParen + 1,
337: rightParen);
338: return Double.parseDouble(substring);
339: }
340: throw new IllegalArgumentException(
341: "The resize argument '"
342: + token
343: + "' is invalid. "
344: + " Must be one of: grow, g, none, n, grow(<double>), g(<double>)");
345: }
346:
347: // Misc *****************************************************************
348:
349: /**
350: * Returns a string representation of this form specification.
351: * The string representation consists of three elements separated by
352: * a colon (<tt>":"</tt>), first the alignment, second the size,
353: * and third the resize spec.<p>
354: *
355: * This method does <em>not</em> return a decoded version
356: * of this object; the contrary is the case. Many instances
357: * will return a string that cannot be parsed.<p>
358: *
359: * <strong>Note:</strong> The string representation may change
360: * at any time. It is strongly recommended to not use this string
361: * for parsing purposes.
362: *
363: * @return a string representation of the form specification.
364: */
365: public final String toString() {
366: StringBuffer buffer = new StringBuffer();
367: buffer.append(defaultAlignment);
368:
369: buffer.append(":");
370: buffer.append(size.toString());
371: buffer.append(':');
372: if (resizeWeight == NO_GROW) {
373: buffer.append("noGrow");
374: } else if (resizeWeight == DEFAULT_GROW) {
375: buffer.append("grow");
376: } else {
377: buffer.append("grow(");
378: buffer.append(resizeWeight);
379: buffer.append(')');
380: }
381: return buffer.toString();
382: }
383:
384: /**
385: * Returns a string representation of this form specification.
386: * The string representation consists of three elements separated by
387: * a colon (<tt>":"</tt>), first the alignment, second the size,
388: * and third the resize spec.<p>
389: *
390: * This method does <em>not</em> return a decoded version
391: * of this object; the contrary is the case. Many instances
392: * will return a string that cannot be parsed.<p>
393: *
394: * <strong>Note:</strong> The string representation may change
395: * at any time. It is strongly recommended to not use this string
396: * for parsing purposes.
397: *
398: * @return a string representation of the form specification.
399: */
400: public final String toShortString() {
401: StringBuffer buffer = new StringBuffer();
402: buffer.append(defaultAlignment.abbreviation());
403:
404: buffer.append(":");
405: buffer.append(size.toString());
406: buffer.append(':');
407: if (resizeWeight == NO_GROW) {
408: buffer.append("n");
409: } else if (resizeWeight == DEFAULT_GROW) {
410: buffer.append("g");
411: } else {
412: buffer.append("g(");
413: buffer.append(resizeWeight);
414: buffer.append(')');
415: }
416: return buffer.toString();
417: }
418:
419: // Abstract Behavior ****************************************************
420:
421: /**
422: * Returns if this is a horizontal specification (vs. vertical).
423: * Used to distinct between horizontal and vertical dialog units,
424: * which have different conversion factors.
425: * @return true for horizontal, false for vertical
426: */
427: abstract boolean isHorizontal();
428:
429: // Helper Code **********************************************************
430:
431: /**
432: * Computes the maximum size for the given list of components, using
433: * this form spec and the specified measure.<p>
434: *
435: * Invoked by FormLayout to determine the size of one of my elements
436: *
437: * @param container the layout container
438: * @param components the list of components to measure
439: * @param minMeasure the measure used to determine the minimum size
440: * @param prefMeasure the measure used to determine the preferred size
441: * @param defaultMeasure the measure used to determine the default size
442: * @return the maximum size in pixels
443: */
444: final int maximumSize(Container container, List components,
445: FormLayout.Measure minMeasure,
446: FormLayout.Measure prefMeasure,
447: FormLayout.Measure defaultMeasure) {
448: return size.maximumSize(container, components, minMeasure,
449: prefMeasure, defaultMeasure);
450: }
451:
452: /**
453: * An ordinal-based serializable typesafe enumeration for the
454: * column and row default alignment types.
455: */
456: public static final class DefaultAlignment implements Serializable {
457:
458: private final transient String name;
459:
460: private DefaultAlignment(String name) {
461: this .name = name;
462: }
463:
464: /**
465: * Returns a DefaultAlignment that corresponds to the specified
466: * string, null if no such aignment exists.
467: *
468: * @param str the encoded alignment
469: * @param isHorizontal indicates the values orientation
470: * @return the corresponding DefaultAlignment or null
471: */
472: private static DefaultAlignment valueOf(String str,
473: boolean isHorizontal) {
474: if (str.equals("f") || str.equals("fill"))
475: return FILL_ALIGN;
476: else if (str.equals("c") || str.equals("center"))
477: return CENTER_ALIGN;
478: else if (isHorizontal) {
479: if (str.equals("r") || str.equals("right"))
480: return RIGHT_ALIGN;
481: else if (str.equals("l") || str.equals("left"))
482: return LEFT_ALIGN;
483: else
484: return null;
485: } else {
486: if (str.equals("t") || str.equals("top"))
487: return TOP_ALIGN;
488: else if (str.equals("b") || str.equals("bottom"))
489: return BOTTOM_ALIGN;
490: else
491: return null;
492: }
493: }
494:
495: /**
496: * Returns this Alignment's name.
497: *
498: * @return this alignment's name.
499: */
500: public String toString() {
501: return name;
502: }
503:
504: /**
505: * Returns the first character of this Alignment's name.
506: * Used to identify it in short format strings.
507: *
508: * @return the name's first character.
509: */
510: public char abbreviation() {
511: return name.charAt(0);
512: }
513:
514: // Serialization *****************************************************
515:
516: private static int nextOrdinal = 0;
517:
518: private final int ordinal = nextOrdinal++;
519:
520: private Object readResolve() {
521: return VALUES[ordinal]; // Canonicalize
522: }
523:
524: }
525:
526: }
|