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
031package com.jgoodies.forms.layout;
032
033import java.awt.Component;
034import java.awt.Insets;
035import java.awt.Rectangle;
036import java.io.Serializable;
037import java.util.Locale;
038import java.util.StringTokenizer;
039
040/**
041 * Defines constraints for components that are layed out with the FormLayout.
042 * Defines the components display area: grid x, grid y,
043 * grid width (column span), grid height (row span), horizontal alignment
044 * and vertical alignment.<p>
045 *
046 * Most methods return <em>this</em> object to enable method chaining.<p>
047 *
048 * You can set optional insets in a constructor. This is useful if you
049 * need to use a pixel-size insets to align perceived component bounds
050 * with pixel data, for example an icon. Anyway, this is rarely used.
051 * The insets don't affect the size computation for columns and rows.
052 * I consider renaming the insets to offsets to better indicate the
053 * motivation for this option.<p>
054 *
055 * <strong>Examples</strong>:<br>
056 * The following cell constraints locate a component in the third
057 * column of the fifth row; column and row span are 1; the component
058 * will be aligned with the column's right-hand side and the row's
059 * bottom.
060 * <pre>
061 * CellConstraints cc = new CellConstraints();
062 * cc.xy  (3, 5);
063 * cc.xy  (3, 5, CellConstraints.RIGHT, CellConstraints.BOTTOM);
064 * cc.xy  (3, 5, "right, bottom");
065 *
066 * cc.xyw (3, 5, 1);
067 * cc.xyw (3, 5, 1, CellConstraints.RIGHT, CellConstraints.BOTTOM);
068 * cc.xyw (3, 5, 1, "right, bottom");
069 *
070 * cc.xywh(3, 5, 1, 1);
071 * cc.xywh(3, 5, 1, 1, CellConstraints.RIGHT, CellConstraints.BOTTOM);
072 * cc.xywh(3, 5, 1, 1, "right, bottom");
073 * </pre>
074 * See also the examples in the {@link FormLayout} class comment.<p>
075 *
076 * TODO: Explain in the JavaDocs that the insets are actually offsets.
077 * And describe that these offsets are not taken into account when
078 * FormLayout computes the column and row sizes.<p>
079 *
080 * TODO: Rename the inset to offsets.<p>
081 *
082 * TODO: In the Forms 1.0.x invisible components are not taken into account
083 * when the FormLayout lays out the container. Add an optional setting for
084 * this on both the container-level and component-level. So one can specify
085 * that invisible components shall be taken into account, but may exclude
086 * individual components. Or the other way round, exclude invisible components,
087 * and include individual components. The API of both the FormLayout and
088 * CellConstraints classes shall be extended to support this option.
089 * This feature is planned for the Forms version 1.1 and is described in
090 * <a href="https://forms.dev.java.net/issues/show_bug.cgi?id=28">issue #28</a>
091 * of the Forms' issue tracker where you can track the progress.
092 *
093 * @author        Karsten Lentzsch
094 * @version $Revision$
095 */
096@SuppressWarnings("serial")
097public final class CellConstraints implements Cloneable, Serializable {
098
099    // Alignment Constants *************************************************
100
101    /*
102     * Implementation Note: Do not change the order of the following constants.
103     * The serialization of class Alignment is ordinal-based and relies on it.
104     */
105
106    /**
107     * Use the column's or row's default alignment.
108     */
109    public static final Alignment DEFAULT = new Alignment("default",
110            Alignment.BOTH);
111
112    /**
113     * Fill the cell either horizontally or vertically.
114     */
115    public static final Alignment FILL = new Alignment("fill", Alignment.BOTH);
116
117    /**
118     * Put the component in the left.
119     */
120    public static final Alignment LEFT = new Alignment("left",
121            Alignment.HORIZONTAL);
122
123    /**
124     * Put the component in the right.
125     */
126    public static final Alignment RIGHT = new Alignment("right",
127            Alignment.HORIZONTAL);
128
129    /**
130     * Put the component in the center.
131     */
132    public static final Alignment CENTER = new Alignment("center",
133            Alignment.BOTH);
134
135    /**
136     * Put the component in the top.
137     */
138    public static final Alignment TOP = new Alignment("top",
139            Alignment.VERTICAL);
140
141    /**
142     * Put the component in the bottom.
143     */
144    public static final Alignment BOTTOM = new Alignment("bottom",
145            Alignment.VERTICAL);
146
147    /**
148     * An array of all enumeration values used to canonicalize
149     * deserialized alignments.
150     */
151    private static final Alignment[] VALUES = { DEFAULT, FILL, LEFT, RIGHT,
152            CENTER, TOP, BOTTOM };
153
154    /**
155     * A reusable <code>Insets</code> object to reduce object instantiation.
156     */
157    private static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
158
159    // Fields ***************************************************************
160
161    /**
162     * Describes the component's horizontal grid origin (starts at 1).
163     */
164    public int gridX;
165
166    /**
167     * Describes the component's vertical grid origin (starts at 1).
168     */
169    public int gridY;
170
171    /**
172     * Describes the component's horizontal grid extend (number of cells).
173     */
174    public int gridWidth;
175
176    /**
177     * Describes the component's vertical grid extent (number of cells).
178     */
179    public int gridHeight;
180
181    /**
182     * Describes the component's horizontal alignment.
183     */
184    public Alignment hAlign;
185
186    /**
187     * Describes the component's vertical alignment.
188     */
189    public Alignment vAlign;
190
191    /**
192     * Describes the component's <code>Insets</code> in it's display area.
193     */
194    public Insets insets;
195
196    // Instance Creation ****************************************************
197
198    /**
199     * Constructs a default instance of <code>CellConstraints</code>.
200     */
201    public CellConstraints() {
202        this(1, 1);
203    }
204
205    /**
206     * Constructs an instance of <code>CellConstraints</code> for the given
207     * cell position.<p>
208     *
209     * <strong>Examples:</strong><pre>
210     * new CellConstraints(1, 3);
211     * new CellConstraints(1, 3);
212     * </pre>
213     *
214     * @param gridX        the component's horizontal grid origin
215     * @param gridY        the component's vertical grid origin
216     */
217    public CellConstraints(int gridX, int gridY) {
218        this(gridX, gridY, 1, 1);
219    }
220
221    /**
222     * Constructs an instance of <code>CellConstraints</code> for the given
223     * cell position, anchor, and fill.<p>
224     *
225     * <strong>Examples:</strong><pre>
226     * new CellConstraints(1, 3, CellConstraints.LEFT,   CellConstraints.BOTTOM);
227     * new CellConstraints(1, 3, CellConstraints.CENTER, CellConstraints.FILL);
228     * </pre>
229     *
230     * @param gridX        the component's horizontal grid origin
231     * @param gridY        the component's vertical grid origin
232     * @param hAlign        the component's horizontal alignment
233     * @param vAlign        the component's vertical alignment
234     */
235    public CellConstraints(int gridX, int gridY, Alignment hAlign,
236            Alignment vAlign) {
237        this(gridX, gridY, 1, 1, hAlign, vAlign, EMPTY_INSETS);
238    }
239
240    /**
241     * Constructs an instance of <code>CellConstraints</code> for the given
242     * cell position and size.<p>
243     *
244     * <strong>Examples:</strong><pre>
245     * new CellConstraints(1, 3, 2, 1);
246     * new CellConstraints(1, 3, 7, 3);
247     * </pre>
248     *
249     * @param gridX                the component's horizontal grid origin
250     * @param gridY                the component's vertical grid origin
251     * @param gridWidth        the component's horizontal extent
252     * @param gridHeight        the component's vertical extent
253     */
254    public CellConstraints(int gridX, int gridY, int gridWidth,
255            int gridHeight) {
256        this(gridX, gridY, gridWidth, gridHeight, DEFAULT, DEFAULT);
257    }
258
259    /**
260     * Constructs an instance of <code>CellConstraints</code> for the given
261     * cell position and size, anchor, and fill.<p>
262     *
263     * <strong>Examples:</strong><pre>
264     * new CellConstraints(1, 3, 2, 1, CellConstraints.LEFT,   CellConstraints.BOTTOM);
265     * new CellConstraints(1, 3, 7, 3, CellConstraints.CENTER, CellConstraints.FILL);
266     * </pre>
267     *
268     * @param gridX                the component's horizontal grid origin
269     * @param gridY                the component's vertical grid origin
270     * @param gridWidth        the component's horizontal extent
271     * @param gridHeight        the component's vertical extent
272     * @param hAlign            the component's horizontal alignment
273     * @param vAlign            the component's vertical alignment
274     */
275    public CellConstraints(int gridX, int gridY, int gridWidth, int gridHeight,
276            Alignment hAlign, Alignment vAlign) {
277        this(gridX, gridY, gridWidth, gridHeight, hAlign, vAlign, EMPTY_INSETS);
278    }
279
280    /**
281     * Constructs an instance of <code>CellConstraints</code> for
282     * the complete set of available properties.<p>
283     *
284     * <strong>Examples:</strong><pre>
285     * new CellConstraints(1, 3, 2, 1, CellConstraints.LEFT,   CellConstraints.BOTTOM, new Insets(0, 1, 0, 3));
286     * new CellConstraints(1, 3, 7, 3, CellConstraints.CENTER, CellConstraints.FILL,   new Insets(0, 1, 0, 0));
287     * </pre>
288     *
289     * @param gridX             the component's horizontal grid origin
290     * @param gridY             the component's vertical grid origin
291     * @param gridWidth         the component's horizontal extent
292     * @param gridHeight        the component's vertical extent
293     * @param hAlign                the component's horizontal alignment
294     * @param vAlign                the component's vertical alignment
295     * @param insets                the component's display area <code>Insets</code>
296     * @exception IndexOutOfBoundsException if the grid origin or extent is negative
297     * @exception NullPointerException if the horizontal or vertical alignment is null
298     * @exception IllegalArgumentException if an alignment orientation is invalid
299     */
300    public CellConstraints(int gridX, int gridY, int gridWidth, int gridHeight,
301            Alignment hAlign, Alignment vAlign, Insets insets) {
302        this.gridX = gridX;
303        this.gridY = gridY;
304        this.gridWidth = gridWidth;
305        this.gridHeight = gridHeight;
306        this.hAlign = hAlign;
307        this.vAlign = vAlign;
308        this.insets = insets;
309        if (gridX <= 0) {
310            throw new IndexOutOfBoundsException(
311                    "The grid x must be a positive number.");
312        }
313        if (gridY <= 0) {
314            throw new IndexOutOfBoundsException(
315                    "The grid y must be a positive number.");
316        }
317        if (gridWidth <= 0) {
318            throw new IndexOutOfBoundsException(
319                    "The grid width must be a positive number.");
320        }
321        if (gridHeight <= 0) {
322            throw new IndexOutOfBoundsException(
323                    "The grid height must be a positive number.");
324        }
325        if (hAlign == null) {
326            throw new NullPointerException(
327                    "The horizontal alignment must not be null.");
328        }
329        if (vAlign == null) {
330            throw new NullPointerException(
331                    "The vertical alignment must not be null.");
332        }
333        ensureValidOrientations(hAlign, vAlign);
334    }
335
336    /**
337     * Constructs an instance of <code>CellConstraints</code> from
338     * the given encoded string properties.<p>
339     *
340     * <strong>Examples:</strong><pre>
341     * new CellConstraints("1, 3");
342     * new CellConstraints("1, 3, left, bottom");
343     * new CellConstraints("1, 3, 2, 1, left, bottom");
344     * new CellConstraints("1, 3, 2, 1, l, b");
345     * </pre>
346     *
347     * @param encodedConstraints        the constraints encoded as string
348     */
349    public CellConstraints(String encodedConstraints) {
350        this();
351        initFromConstraints(encodedConstraints);
352    }
353
354    // Setters **************************************************************
355
356    /**
357     * Sets row and column origins; sets width and height to 1;
358     * uses the default alignments.<p>
359     *
360     * <strong>Examples:</strong><pre>
361     * cc.xy(1, 1);
362     * cc.xy(1, 3);
363     * </pre>
364     *
365     * @param col     the new column index
366     * @param row     the new row index
367     * @return this
368     */
369    public CellConstraints xy(int col, int row) {
370        return xywh(col, row, 1, 1);
371    }
372
373    /**
374     * Sets row and column origins; sets width and height to 1;
375     * decodes horizontal and vertical alignments from the given string.<p>
376     *
377     * <strong>Examples:</strong><pre>
378     * cc.xy(1, 3, "left, bottom");
379     * cc.xy(1, 3, "l, b");
380     * cc.xy(1, 3, "center, fill");
381     * cc.xy(1, 3, "c, f");
382     * </pre>
383     *
384     * @param col                the new column index
385     * @param row                the new row index
386     * @param encodedAlignments  describes the horizontal and vertical alignments
387     * @return this
388     * @exception IllegalArgumentException if an alignment orientation is invalid
389     */
390    public CellConstraints xy(int col, int row, String encodedAlignments) {
391        return xywh(col, row, 1, 1, encodedAlignments);
392    }
393
394    /**
395     * Sets the row and column origins; sets width and height to 1;
396     * set horizontal and vertical alignment using the specified objects.<p>
397     *
398     * <strong>Examples:</strong><pre>
399     * cc.xy(1, 3, CellConstraints.LEFT,   CellConstraints.BOTTOM);
400     * cc.xy(1, 3, CellConstraints.CENTER, CellConstraints.FILL);
401     * </pre>
402     *
403     * @param col       the new column index
404     * @param row       the new row index
405     * @param colAlign  horizontal component alignment
406     * @param rowAlign  vertical component alignment
407     * @return this
408     */
409    public CellConstraints xy(int col, int row, Alignment colAlign,
410            Alignment rowAlign) {
411        return xywh(col, row, 1, 1, colAlign, rowAlign);
412    }
413
414    /**
415     * Sets the row, column, width, and height; uses a height (row span) of 1
416     * and the horizontal and vertical default alignments.<p>
417     *
418     * <strong>Examples:</strong><pre>
419     * cc.xyw(1, 3, 7);
420     * cc.xyw(1, 3, 2);
421     * </pre>
422     *
423     * @param col      the new column index
424     * @param row      the new row index
425     * @param colSpan  the column span or grid width
426     * @return this
427     */
428    public CellConstraints xyw(int col, int row, int colSpan) {
429        return xywh(col, row, colSpan, 1, DEFAULT, DEFAULT);
430    }
431
432    /**
433     * Sets the row, column, width, and height;
434     * decodes the horizontal and vertical alignments from the given string.
435     * The row span (height) is set to 1.<p>
436     *
437     * <strong>Examples:</strong><pre>
438     * cc.xyw(1, 3, 7, "left, bottom");
439     * cc.xyw(1, 3, 7, "l, b");
440     * cc.xyw(1, 3, 2, "center, fill");
441     * cc.xyw(1, 3, 2, "c, f");
442     * </pre>
443     *
444     * @param col                the new column index
445     * @param row                the new row index
446     * @param colSpan            the column span or grid width
447     * @param encodedAlignments  describes the horizontal and vertical alignments
448     * @return this
449     * @exception IllegalArgumentException if an alignment orientation is invalid
450     */
451    public CellConstraints xyw(int col, int row, int colSpan,
452            String encodedAlignments) {
453        return xywh(col, row, colSpan, 1, encodedAlignments);
454    }
455
456    /**
457     * Sets the row, column, width, and height; sets the horizontal
458     * and vertical alignment using the specified alignment objects.
459     * The row span (height) is set to 1.<p>
460     *
461     * <strong>Examples:</strong><pre>
462     * cc.xyw(1, 3, 2, CellConstraints.LEFT,   CellConstraints.BOTTOM);
463     * cc.xyw(1, 3, 7, CellConstraints.CENTER, CellConstraints.FILL);
464     * </pre>
465     *
466     * @param col       the new column index
467     * @param row       the new row index
468     * @param colSpan   the column span or grid width
469     * @param colAlign  horizontal component alignment
470     * @param rowAlign  vertical component alignment
471     * @return this
472     * @exception IllegalArgumentException if an alignment orientation is invalid
473     */
474    public CellConstraints xyw(int col, int row, int colSpan,
475            Alignment colAlign, Alignment rowAlign) {
476        return xywh(col, row, colSpan, 1, colAlign, rowAlign);
477    }
478
479    /**
480     * Sets the row, column, width, and height; uses default alignments.<p>
481     *
482     * <strong>Examples:</strong><pre>
483     * cc.xywh(1, 3, 2, 1);
484     * cc.xywh(1, 3, 7, 3);
485     * </pre>
486     *
487     * @param col      the new column index
488     * @param row      the new row index
489     * @param colSpan  the column span or grid width
490     * @param rowSpan  the row span or grid height
491     * @return this
492     */
493    public CellConstraints xywh(int col, int row, int colSpan, int rowSpan) {
494        return xywh(col, row, colSpan, rowSpan, DEFAULT, DEFAULT);
495    }
496
497    /**
498     * Sets the row, column, width, and height;
499     * decodes the horizontal and vertical alignments from the given string.<p>
500     *
501     * <strong>Examples:</strong><pre>
502     * cc.xywh(1, 3, 2, 1, "left, bottom");
503     * cc.xywh(1, 3, 2, 1, "l, b");
504     * cc.xywh(1, 3, 7, 3, "center, fill");
505     * cc.xywh(1, 3, 7, 3, "c, f");
506     * </pre>
507     *
508     * @param col                the new column index
509     * @param row                the new row index
510     * @param colSpan            the column span or grid width
511     * @param rowSpan            the row span or grid height
512     * @param encodedAlignments  describes the horizontal and vertical alignments
513     * @return this
514     * @exception IllegalArgumentException if an alignment orientation is invalid
515     */
516    public CellConstraints xywh(int col, int row, int colSpan, int rowSpan,
517            String encodedAlignments) {
518        CellConstraints result = xywh(col, row, colSpan, rowSpan);
519        result.setAlignments(encodedAlignments);
520        return result;
521    }
522
523    /**
524     * Sets the row, column, width, and height; sets the horizontal
525     * and vertical alignment using the specified alignment objects.<p>
526     *
527     * <strong>Examples:</strong><pre>
528     * cc.xywh(1, 3, 2, 1, CellConstraints.LEFT,   CellConstraints.BOTTOM);
529     * cc.xywh(1, 3, 7, 3, CellConstraints.CENTER, CellConstraints.FILL);
530     * </pre>
531     *
532     * @param col       the new column index
533     * @param row       the new row index
534     * @param colSpan   the column span or grid width
535     * @param rowSpan   the row span or grid height
536     * @param colAlign  horizontal component alignment
537     * @param rowAlign  vertical component alignment
538     * @return this
539     * @exception IllegalArgumentException if an alignment orientation is invalid
540     */
541    public CellConstraints xywh(int col, int row, int colSpan, int rowSpan,
542            Alignment colAlign, Alignment rowAlign) {
543        this.gridX = col;
544        this.gridY = row;
545        this.gridWidth = colSpan;
546        this.gridHeight = rowSpan;
547        this.hAlign = colAlign;
548        this.vAlign = rowAlign;
549        ensureValidOrientations(hAlign, vAlign);
550        return this;
551    }
552
553    // Parsing and Decoding String Descriptions *****************************
554
555    /**
556     * Decodes and returns the grid bounds and alignments for this
557     * constraints as an array of six integers. The string representation
558     * is a comma separated sequence, one of
559     * <pre>
560     * "x, y"
561     * "x, y, w, h"
562     * "x, y, hAlign, vAlign"
563     * "x, y, w, h, hAlign, vAlign"
564     * </pre>
565     *
566     * @param encodedConstraints represents horizontal and vertical alignment
567     * @exception IllegalArgumentException if the encoded constraints do not
568     *     follow the constraint syntax
569     */
570    private void initFromConstraints(String encodedConstraints) {
571        StringTokenizer tokenizer = new StringTokenizer(encodedConstraints,
572                " ,");
573        int argCount = tokenizer.countTokens();
574        if (!(argCount == 2 || argCount == 4 || argCount == 6)) {
575            throw new IllegalArgumentException(
576                    "You must provide 2, 4 or 6 arguments.");
577        }
578
579        Integer nextInt = decodeInt(tokenizer.nextToken());
580        if (nextInt == null) {
581            throw new IllegalArgumentException(
582                    "First cell constraint element must be a number.");
583        }
584        gridX = nextInt.intValue();
585        if (gridX <= 0) {
586            throw new IndexOutOfBoundsException(
587                    "The grid x must be a positive number.");
588        }
589
590        nextInt = decodeInt(tokenizer.nextToken());
591        if (nextInt == null) {
592            throw new IllegalArgumentException(
593                    "Second cell constraint element must be a number.");
594        }
595        gridY = nextInt.intValue();
596        if (gridY <= 0) {
597            throw new IndexOutOfBoundsException(
598                    "The grid y must be a positive number.");
599        }
600
601        if (!tokenizer.hasMoreTokens()) {
602            return;
603        }
604
605        String token = tokenizer.nextToken();
606        nextInt = decodeInt(token);
607        if (nextInt != null) {
608            // Case: "x, y, w, h" or
609            //       "x, y, w, h, hAlign, vAlign"
610            gridWidth = nextInt.intValue();
611            if (gridWidth <= 0) {
612                throw new IndexOutOfBoundsException(
613                        "The grid width must be a positive number.");
614            }
615            nextInt = decodeInt(tokenizer.nextToken());
616            if (nextInt == null) {
617                throw new IllegalArgumentException(
618                        "Fourth cell constraint element must be like third.");
619            }
620            gridHeight = nextInt.intValue();
621            if (gridHeight <= 0) {
622                throw new IndexOutOfBoundsException(
623                        "The grid height must be a positive number.");
624            }
625
626            if (!tokenizer.hasMoreTokens()) {
627                return;
628            }
629            token = tokenizer.nextToken();
630        }
631
632        hAlign = decodeAlignment(token);
633        vAlign = decodeAlignment(tokenizer.nextToken());
634        ensureValidOrientations(hAlign, vAlign);
635    }
636
637    /**
638     * Decodes a string description for the horizontal and vertical alignment
639     * and sets this CellConstraints' alignment values.<p>
640     *
641     * Valid horizontal alignments are: left, middle, right, default, and fill.
642     * Valid vertical alignments are: top, center, bottom, default, and fill.
643     * The anchor's string representation abbreviates the alignment:
644     * l, m, r, d, f, t, c, and b.<p>
645     *
646     * Anchor examples:
647     * "m, c" is centered, "l, t" is northwest, "m, t" is north, "r, c" east.
648     * "m, d" is horizontally centered and uses the row's default alignment.
649     * "d, t" is on top of the cell and uses the column's default alignment.<p>
650     *
651     * @param encodedAlignments represents horizontal and vertical alignment
652     * @exception IllegalArgumentException if an alignment orientation is invalid
653     */
654    private void setAlignments(String encodedAlignments) {
655        StringTokenizer tokenizer = new StringTokenizer(encodedAlignments,
656                " ,");
657        hAlign = decodeAlignment(tokenizer.nextToken());
658        vAlign = decodeAlignment(tokenizer.nextToken());
659        ensureValidOrientations(hAlign, vAlign);
660    }
661
662    /**
663     * Decodes an integer string representation and returns the
664     * associated Integer or null in case of an invalid number format.
665     *
666     * @param token                the encoded integer
667     * @return the decoded Integer or null
668     */
669    private Integer decodeInt(String token) {
670        try {
671            return Integer.decode(token);
672        } catch (NumberFormatException e) {
673            return null;
674        }
675    }
676
677    /**
678     * Parses an alignment string description and
679     * returns the corresponding alignment value.
680     *
681     * @param encodedAlignment        the encoded alignment
682     * @return the associated <code>Alignment</code> instance
683     */
684    private Alignment decodeAlignment(String encodedAlignment) {
685        return Alignment.valueOf(encodedAlignment);
686    }
687
688    /**
689     * Checks and verifies that this constraints object has valid grid
690     * index values, i. e. the display area cells are inside the form's grid.
691     *
692     * @param colCount  number of columns in the grid
693     * @param rowCount  number of rows in the grid
694     * @exception IndexOutOfBoundsException if the display area described
695     *     by this constraints object is not inside the grid
696     */
697    void ensureValidGridBounds(int colCount, int rowCount) {
698        if (gridX <= 0) {
699            throw new IndexOutOfBoundsException(
700                    "The column index " + gridX + " must be positive.");
701        }
702        if (gridX > colCount) {
703            throw new IndexOutOfBoundsException("The column index " + gridX
704                    + " must be less than or equal to " + colCount + ".");
705        }
706        if (gridX + gridWidth - 1 > colCount) {
707            throw new IndexOutOfBoundsException("The grid width " + gridWidth
708                    + " must be less than or equal to " + (colCount - gridX + 1)
709                    + ".");
710        }
711        if (gridY <= 0) {
712            throw new IndexOutOfBoundsException(
713                    "The row index " + gridY + " must be positive.");
714        }
715        if (gridY > rowCount) {
716            throw new IndexOutOfBoundsException("The row index " + gridY
717                    + " must be less than or equal to " + rowCount + ".");
718        }
719        if (gridY + gridHeight - 1 > rowCount) {
720            throw new IndexOutOfBoundsException("The grid height " + gridHeight
721                    + " must be less than or equal to " + (rowCount - gridY + 1)
722                    + ".");
723        }
724    }
725
726    /**
727     * Checks and verifies that the horizontal alignment is a horizontal
728     * and the vertical alignment is vertical.
729     *
730     * @param horizontalAlignment  the horizontal alignment
731     * @param verticalAlignment    the vertical alignment
732     * @exception IllegalArgumentException if an alignment is invalid
733     */
734    private void ensureValidOrientations(Alignment horizontalAlignment,
735            Alignment verticalAlignment) {
736        if (!horizontalAlignment.isHorizontal()) {
737            throw new IllegalArgumentException(
738                    "The horizontal alignment must be one of: left, center, right, fill, default.");
739        }
740        if (!verticalAlignment.isVertical()) {
741            throw new IllegalArgumentException(
742                    "The vertical alignment must be one of: top, center, botto, fill, default.");
743        }
744    }
745
746    // Settings Component Bounds ********************************************
747
748    /**
749     * Sets the component's bounds using the given component and cell bounds.
750     *
751     * @param c                                    the component to set bounds
752     * @param layout             the FormLayout instance that computes the bounds
753     * @param cellBounds                   the cell's bounds
754     * @param minWidthMeasure          measures the minimum width
755     * @param minHeightMeasure          measures the minimum height
756     * @param prefWidthMeasure          measures the preferred width
757     * @param prefHeightMeasure  measures the preferred height
758     */
759    void setBounds(Component c, FormLayout layout, Rectangle cellBounds,
760            FormLayout.Measure minWidthMeasure,
761            FormLayout.Measure minHeightMeasure,
762            FormLayout.Measure prefWidthMeasure,
763            FormLayout.Measure prefHeightMeasure) {
764        ColumnSpec colSpec = gridWidth == 1 ? layout.getColumnSpec(gridX)
765                : null;
766        RowSpec rowSpec = gridHeight == 1 ? layout.getRowSpec(gridY) : null;
767        Alignment concreteHAlign = concreteAlignment(this.hAlign, colSpec);
768        Alignment concreteVAlign = concreteAlignment(this.vAlign, rowSpec);
769        Insets concreteInsets = this.insets != null ? this.insets
770                : EMPTY_INSETS;
771        int cellX = cellBounds.x + concreteInsets.left;
772        int cellY = cellBounds.y + concreteInsets.top;
773        int cellW = cellBounds.width - concreteInsets.left
774                - concreteInsets.right;
775        int cellH = cellBounds.height - concreteInsets.top
776                - concreteInsets.bottom;
777        int compW = componentSize(c, colSpec, cellW, minWidthMeasure,
778                prefWidthMeasure);
779        int compH = componentSize(c, rowSpec, cellH, minHeightMeasure,
780                prefHeightMeasure);
781        int x = origin(concreteHAlign, cellX, cellW, compW);
782        int y = origin(concreteVAlign, cellY, cellH, compH);
783        int w = extent(concreteHAlign, cellW, compW);
784        int h = extent(concreteVAlign, cellH, compH);
785        c.setBounds(x, y, w, h);
786    }
787
788    /**
789     * Computes and returns the concrete alignment. Takes into account
790     * the cell alignment and <i>the</i> <code>FormSpec</code> if applicable.<p>
791     *
792     * If this constraints object doesn't belong to a single column or row,
793     * the <code>formSpec</code> parameter is <code>null</code>.
794     * In this case the cell alignment is answered, but <code>DEFAULT</code>
795     * is mapped to <code>FILL</code>.<p>
796     *
797     * If the cell belongs to a single column or row, we use the cell
798     * alignment, unless it is <code>DEFAULT</code>, where the alignment
799     * is inherited from the column or row resp.
800     *
801     * @param cellAlignment   this cell's alignment
802     * @param formSpec        the associated column or row specification
803     * @return the concrete alignment
804     */
805    private Alignment concreteAlignment(Alignment cellAlignment,
806            FormSpec formSpec) {
807        return formSpec == null
808                ? cellAlignment == DEFAULT ? FILL : cellAlignment
809                : usedAlignment(cellAlignment, formSpec);
810    }
811
812    /**
813     * Returns the alignment used for a given form constraints object.
814     * The cell alignment overrides the column or row default, unless
815     * it is <code>DEFAULT</code>. In the latter case, we use the
816     * column or row alignment.
817     *
818     * @param cellAlignment   this cell constraint's alignment
819     * @param formSpec        the associated column or row specification
820     * @return the alignment used
821     */
822    private Alignment usedAlignment(Alignment cellAlignment,
823            FormSpec formSpec) {
824        if (cellAlignment != CellConstraints.DEFAULT) {
825            // Cell alignments other than DEFAULT override col/row alignments
826            return cellAlignment;
827        }
828        FormSpec.DefaultAlignment defaultAlignment = formSpec
829                .getDefaultAlignment();
830        if (defaultAlignment == FormSpec.FILL_ALIGN) {
831            return FILL;
832        }
833        if (defaultAlignment == ColumnSpec.LEFT) {
834            return LEFT;
835        } else if (defaultAlignment == FormSpec.CENTER_ALIGN) {
836            return CENTER;
837        } else if (defaultAlignment == ColumnSpec.RIGHT) {
838            return RIGHT;
839        } else if (defaultAlignment == RowSpec.TOP) {
840            return TOP;
841        } else {
842            return BOTTOM;
843        }
844    }
845
846    /**
847     * Computes and returns the pixel size of the given component using the
848     * given form specification, measures, and cell size.
849     *
850     * @param component        the component to measure
851     * @param formSpec                the specification of the component's column/row
852     * @param minMeasure        the measure for the minimum size
853     * @param prefMeasure        the measure for the preferred size
854     * @param cellSize                the cell size
855     * @return the component size as measured or a constant
856     */
857    private int componentSize(Component component, FormSpec formSpec,
858            int cellSize, FormLayout.Measure minMeasure,
859            FormLayout.Measure prefMeasure) {
860        if (formSpec == null) {
861            return prefMeasure.sizeOf(component);
862        } else if (formSpec.getSize() == Sizes.MINIMUM) {
863            return minMeasure.sizeOf(component);
864        } else if (formSpec.getSize() == Sizes.PREFERRED) {
865            return prefMeasure.sizeOf(component);
866        } else { // default mode
867            return Math.min(cellSize, prefMeasure.sizeOf(component));
868        }
869    }
870
871    /**
872     * Computes and returns the component's pixel origin.
873     *
874     * @param alignment                the component's alignment
875     * @param cellOrigin                the origin of the display area
876     * @param cellSize                        the extent of the display area
877     * @param componentSize
878     * @return the component's pixel origin
879     */
880    private int origin(Alignment alignment, int cellOrigin, int cellSize,
881            int componentSize) {
882        if (alignment == RIGHT || alignment == BOTTOM) {
883            return cellOrigin + cellSize - componentSize;
884        } else if (alignment == CENTER) {
885            return cellOrigin + (cellSize - componentSize) / 2;
886        } else { // left, top, fill
887            return cellOrigin;
888        }
889    }
890
891    /**
892     * Returns the component's pixel extent.
893     *
894     * @param alignment                the component's alignment
895     * @param cellSize                        the size of the display area
896     * @param componentSize        the component's size
897     * @return the component's pixel extent
898     */
899    private int extent(Alignment alignment, int cellSize, int componentSize) {
900        return alignment == FILL ? cellSize : componentSize;
901    }
902
903    // Misc *****************************************************************
904
905    /**
906     * Creates a copy of this cell constraints object.
907     *
908     * @return                a copy of this cell constraints object
909     */
910    @Override
911    public Object clone() {
912        try {
913            CellConstraints c = (CellConstraints) super.clone();
914            c.insets = (Insets) insets.clone();
915            return c;
916        } catch (CloneNotSupportedException e) {
917            // This shouldn't happen, since we are Cloneable.
918            throw new InternalError();
919        }
920    }
921
922    /**
923     * Constructs and returns a string representation of this constraints object.
924     *
925     * @return        string representation of this constraints object
926     */
927    @Override
928    public String toString() {
929        StringBuffer buffer = new StringBuffer("CellConstraints");
930        buffer.append("[x=");
931        buffer.append(gridX);
932        buffer.append("; y=");
933        buffer.append(gridY);
934        buffer.append("; w=");
935        buffer.append(gridWidth);
936        buffer.append("; h=");
937        buffer.append(gridHeight);
938        buffer.append("; hAlign=");
939        buffer.append(hAlign);
940        buffer.append("; vAlign=");
941        buffer.append(vAlign);
942        if (!EMPTY_INSETS.equals(insets)) {
943            buffer.append("; insets=");
944            buffer.append(insets);
945        }
946
947        buffer.append(']');
948        return buffer.toString();
949    }
950
951    /**
952     * Returns a short string representation of this constraints object.
953     *
954     * @return a short string representation of this constraints object
955     */
956    public String toShortString() {
957        return toShortString(null);
958    }
959
960    /**
961     * Returns a short string representation of this constraints object.
962     * This method can use the given <code>FormLayout</code>
963     * to display extra information how default alignments
964     * are mapped to concrete alignments. Therefore it asks the
965     * related column and row as specified by this constraints object.
966     *
967     * @param layout  the layout to be presented as a string
968     * @return a short string representation of this constraints object
969     */
970    public String toShortString(FormLayout layout) {
971        StringBuffer buffer = new StringBuffer("(");
972        buffer.append(formatInt(gridX));
973        buffer.append(", ");
974        buffer.append(formatInt(gridY));
975        buffer.append(", ");
976        buffer.append(formatInt(gridWidth));
977        buffer.append(", ");
978        buffer.append(formatInt(gridHeight));
979        buffer.append(", \"");
980        buffer.append(hAlign.abbreviation());
981        if (hAlign == DEFAULT && layout != null) {
982            buffer.append('=');
983            ColumnSpec colSpec = gridWidth == 1 ? layout.getColumnSpec(gridX)
984                    : null;
985            buffer.append(concreteAlignment(hAlign, colSpec).abbreviation());
986        }
987        buffer.append(", ");
988        buffer.append(vAlign.abbreviation());
989        if (vAlign == DEFAULT && layout != null) {
990            buffer.append('=');
991            RowSpec rowSpec = gridHeight == 1 ? layout.getRowSpec(gridY) : null;
992            buffer.append(concreteAlignment(vAlign, rowSpec).abbreviation());
993        }
994        buffer.append("\"");
995        if (!EMPTY_INSETS.equals(insets)) {
996            buffer.append(", ");
997            buffer.append(insets);
998        }
999
1000        buffer.append(')');
1001        return buffer.toString();
1002    }
1003
1004    // Helper Class *********************************************************
1005
1006    /**
1007     * An ordinal-based serializable typesafe enumeration for component
1008     * alignment types as used by the {@link FormLayout}.
1009     */
1010    public static final class Alignment implements Serializable {
1011
1012        private static final int HORIZONTAL = 0;
1013        private static final int VERTICAL = 1;
1014        private static final int BOTH = 2;
1015
1016        private final transient String name;
1017        private final transient int orientation;
1018
1019        private Alignment(String name, int orientation) {
1020            this.name = name;
1021            this.orientation = orientation;
1022        }
1023
1024        static Alignment valueOf(String nameOrAbbreviation) {
1025            String str = nameOrAbbreviation.toLowerCase(Locale.ENGLISH);
1026            if (str.equals("d") || str.equals("default")) {
1027                return DEFAULT;
1028            } else if (str.equals("f") || str.equals("fill")) {
1029                return FILL;
1030            } else if (str.equals("c") || str.equals("center")) {
1031                return CENTER;
1032            } else if (str.equals("l") || str.equals("left")) {
1033                return LEFT;
1034            } else if (str.equals("r") || str.equals("right")) {
1035                return RIGHT;
1036            } else if (str.equals("t") || str.equals("top")) {
1037                return TOP;
1038            } else if (str.equals("b") || str.equals("bottom")) {
1039                return BOTTOM;
1040            } else {
1041                throw new IllegalArgumentException("Invalid alignment "
1042                        + nameOrAbbreviation
1043                        + ". Must be one of: left, center, right, top, bottom, "
1044                        + "fill, default, l, c, r, t, b, f, d.");
1045            }
1046        }
1047
1048        /**
1049         * Returns this Alignment's name.
1050         *
1051         * @return this alignment's name.
1052         */
1053        @Override
1054        public String toString() {
1055            return name;
1056        }
1057
1058        /**
1059         * Returns the first character of this Alignment's name.
1060         * Used to identify it in short format strings.
1061         *
1062         * @return the name's first character.
1063         */
1064        public char abbreviation() {
1065            return name.charAt(0);
1066        }
1067
1068        private boolean isHorizontal() {
1069            return orientation != VERTICAL;
1070        }
1071
1072        private boolean isVertical() {
1073            return orientation != HORIZONTAL;
1074        }
1075
1076        // Serialization *********************************************************
1077
1078        private static int nextOrdinal = 0;
1079
1080        private final int ordinal = nextOrdinal++;
1081
1082        private Object readResolve() {
1083            return VALUES[ordinal]; // Canonicalize
1084        }
1085
1086    }
1087
1088    /**
1089     * Returns an integer that has a minimum of two characters.
1090     *
1091     * @param number   the number to format
1092     * @return a string representation for a number with a minimum of two chars
1093     */
1094    private String formatInt(int number) {
1095        String str = Integer.toString(number);
1096        return number < 10 ? " " + str : str;
1097    }
1098
1099}