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