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.Container;
035import java.io.Serializable;
036import java.util.List;
037import java.util.Locale;
038
039/**
040 * An implementation of the {@link Size} interface that represents constant
041 * sizes described by a value and unit, for example:
042 * 10 pixel, 15 point or 4 dialog units.
043 * You can get instances of <code>ConstantSize</code> using
044 * the factory methods and constants in the {@link Sizes} class.
045 * Logical constant sizes that vary with the current layout style
046 * are delivered by the {@link com.jgoodies.forms.util.LayoutStyle} class.<p>
047 *
048 * This class supports different size units:
049 * <table>
050 * <caption>Size units</caption>
051 * <tr><td><b>Unit</b>&nbsp;
052 * </td><td>&nbsp;<b>Abbreviation</b>&nbsp;</td><td>&nbsp;
053 * <b>Size</b></td></tr>
054 * <tr><td>Millimeter</td><td>mm</td><td>0.1 cm</td></tr>
055 * <tr><td>Centimeter</td><td>cm</td><td>10.0 mm</td></tr>
056 * <tr><td>Inch</td><td>in</td><td>25.4 mm</td></tr>
057 * <tr><td>DTP Point</td><td>pt</td><td>1/72 in</td></tr>
058 * <tr><td>Pixel</td><td>px</td><td>1/(resolution in dpi) in</td></tr>
059 * <tr><td>Dialog Unit</td><td>dlu</td><td>honors l&amp;f, resolution, and
060 * dialog font size</td></tr>
061 * </table><p>
062 *
063 * <strong>Examples:</strong><pre>
064 * Sizes.ZERO;
065 * Sizes.DLUX9;
066 * Sizes.dluX(42);
067 * Sizes.pixel(99);
068 * </pre>
069 *
070 * @author Karsten Lentzsch
071 * @version $Revision$
072 *
073 * @see        Size
074 * @see        Sizes
075 */
076
077@SuppressWarnings("serial")
078public final class ConstantSize implements Size, Serializable {
079
080    // Public Units *********************************************************
081
082    public static final Unit PIXEL = new Unit("Pixel", "px", true);
083    public static final Unit POINT = new Unit("Point", "pt", true);
084    public static final Unit DIALOG_UNITS_X = new Unit("Dialog units X", "dluX",
085            true);
086    public static final Unit DLUX = DIALOG_UNITS_X;
087    public static final Unit DIALOG_UNITS_Y = new Unit("Dialog units Y", "dluY",
088            true);
089    public static final Unit DLUY = DIALOG_UNITS_Y;
090    public static final Unit MILLIMETER = new Unit("Millimeter", "mm", false);
091    public static final Unit MM = MILLIMETER;
092    public static final Unit CENTIMETER = new Unit("Centimeter", "cm", false);
093    public static final Unit CM = CENTIMETER;
094    public static final Unit INCH = new Unit("Inch", "in", false);
095    public static final Unit IN = INCH;
096
097    /**
098     * An array of all enumeration values used to canonicalize
099     * deserialized units.
100     */
101    private static final Unit[] VALUES = { PIXEL, POINT, DIALOG_UNITS_X,
102            DIALOG_UNITS_Y, MILLIMETER, CENTIMETER, INCH };
103
104    // Fields ***************************************************************
105
106    private final double value;
107    private final Unit unit;
108
109    // Instance Creation ****************************************************
110
111    /**
112     * Constructs an instance of <code>ConstantSize</code> from the given
113     * encoded size and unit description.
114     *
115     * @param value        the size value interpreted in the given units
116     * @param unit                the size's unit
117     */
118    ConstantSize(int value, Unit unit) {
119        this.value = value;
120        this.unit = unit;
121    }
122
123    /**
124     * Constructs an instance of <code>ConstantSize</code> from the given
125     * encoded size and unit description.
126     *
127     * @param value     the size value interpreted in the given units
128     * @param unit      the size's unit
129     */
130    ConstantSize(double value, Unit unit) {
131        this.value = value;
132        this.unit = unit;
133    }
134
135    /**
136     * Constructs an instance of <code>ConstantSize</code> from the given
137     * encoded size and unit description.
138     *
139     * @param encodedValueAndUnit  the size's value and unit as string
140     * @param horizontal                        true for horizontal, false for vertical
141     * @return a constant size for the given encoding and unit description
142     * @exception IllegalArgumentException   if the unit requires integer
143     *    but the value is not an integer
144     */
145    static ConstantSize valueOf(String encodedValueAndUnit,
146            boolean horizontal) {
147        String split[] = ConstantSize.splitValueAndUnit(encodedValueAndUnit);
148        String encodedValue = split[0];
149        String encodedUnit = split[1];
150        Unit unit = Unit.valueOf(encodedUnit, horizontal);
151        double value = Double.parseDouble(encodedValue);
152        if (unit.requiresIntegers) {
153            if (value != (int) value) {
154                throw new IllegalArgumentException(unit.toString() + " value "
155                        + encodedValue + " must be an integer.");
156            }
157        }
158        return new ConstantSize(value, unit);
159    }
160
161    /**
162     * Returns an instance of <code>Size</code> for the specified value
163     * in horizontal dialog units.
164     *
165     * @param value        size value in horizontal dialog units
166     * @return the associated Size instance
167     */
168    static ConstantSize dluX(int value) {
169        return new ConstantSize(value, DLUX);
170    }
171
172    /**
173     * Returns an instance of <code>Size</code> for the specified value
174     * in vertical dialog units.
175     *
176     * @param value    size value in vertical dialog units
177     * @return the associated Size instance
178     */
179    static ConstantSize dluY(int value) {
180        return new ConstantSize(value, DLUY);
181    }
182
183    // Accessing the Value **************************************************
184
185    /**
186     * Converts the size if necessary and returns the value in pixels.
187     *
188     * @param component  the associated component
189     * @return the size in pixels
190     */
191    public int getPixelSize(Component component) {
192        if (unit == PIXEL) {
193            return intValue();
194        } else if (unit == POINT) {
195            return Sizes.pointAsPixel(intValue(), component);
196        } else if (unit == INCH) {
197            return Sizes.inchAsPixel(value, component);
198        } else if (unit == MILLIMETER) {
199            return Sizes.millimeterAsPixel(value, component);
200        } else if (unit == CENTIMETER) {
201            return Sizes.centimeterAsPixel(value, component);
202        } else if (unit == DIALOG_UNITS_X) {
203            return Sizes.dialogUnitXAsPixel(intValue(), component);
204        } else if (unit == DIALOG_UNITS_Y) {
205            return Sizes.dialogUnitYAsPixel(intValue(), component);
206        } else {
207            throw new IllegalStateException("Invalid unit " + unit);
208        }
209    }
210
211    // Implementing the Size Interface **************************************
212
213    /**
214     * Returns this size as pixel size. Neither requires the component
215     * list nor the specified measures.<p>
216     *
217     * Invoked by {@link com.jgoodies.forms.layout.FormSpec} to determine
218     * the size of a column or row.
219     *
220     * @param container       the layout container
221     * @param components      the list of components used to compute the size
222     * @param minMeasure      the measure that determines the minimum sizes
223     * @param prefMeasure     the measure that determines the preferred sizes
224     * @param defaultMeasure  the measure that determines the default sizes
225     * @return the computed maximum size in pixel
226     */
227    @Override
228    public int maximumSize(Container container, List components,
229            FormLayout.Measure minMeasure, FormLayout.Measure prefMeasure,
230            FormLayout.Measure defaultMeasure) {
231        return getPixelSize(container);
232    }
233
234    // Overriding Object Behavior *******************************************
235
236    /**
237     * Indicates whether some other ConstantSize is "equal to" this one.
238     *
239     * @param o   the Object with which to compare
240     * @return <code>true</code> if this object is the same as the obj
241     * argument; <code>false</code> otherwise.
242     * @see     java.lang.Object#hashCode()
243     * @see     java.util.Hashtable
244     */
245    @Override
246    public boolean equals(Object o) {
247        if (this == o) {
248            return true;
249        }
250        if (!(o instanceof ConstantSize)) {
251            return false;
252        }
253        ConstantSize size = (ConstantSize) o;
254        return this.value == size.value && this.unit == size.unit;
255    }
256
257    /**
258     * Returns a hash code value for the object. This method is
259     * supported for the benefit of hashtables such as those provided by
260     * <code>java.util.Hashtable</code>.
261     *
262     * @return  a hash code value for this object.
263     * @see     java.lang.Object#equals(java.lang.Object)
264     * @see     java.util.Hashtable
265     */
266    @Override
267    public int hashCode() {
268        return Double.valueOf(value).hashCode() + 37 * unit.hashCode();
269    }
270
271    /**
272     * Returns a string representation of this size object.
273     *
274     * <strong>Note:</strong> The string representation may change
275     * at any time. It is strongly recommended to not use this string
276     * for parsing purposes.
277     *
278     * @return  a string representation of the constant size
279     */
280    @Override
281    public String toString() {
282        return value == intValue()
283                ? Integer.toString(intValue()) + unit.abbreviation()
284                : Double.toString(value) + unit.abbreviation();
285    }
286
287    // Helper Code **********************************************************
288
289    private int intValue() {
290        return (int) Math.round(value);
291    }
292
293    /**
294     * Splits a string that encodes size with unit into the size and unit
295     * substrings. Returns an array of two strings.
296     *
297     * @param encodedValueAndUnit  a strings that represents a size with unit
298     * @return the first element is size, the second is unit
299     */
300    static String[] splitValueAndUnit(String encodedValueAndUnit) {
301        String[] result = new String[2];
302        int len = encodedValueAndUnit.length();
303        int firstLetterIndex = len;
304        while (firstLetterIndex > 0 && Character
305                .isLetter(encodedValueAndUnit.charAt(firstLetterIndex - 1))) {
306            firstLetterIndex--;
307        }
308        result[0] = encodedValueAndUnit.substring(0, firstLetterIndex);
309        result[1] = encodedValueAndUnit.substring(firstLetterIndex);
310        return result;
311    }
312
313    // Helper Class *********************************************************
314
315    /**
316     * An ordinal-based serializable typesafe enumeration for units
317     * as used in instances of {@link ConstantSize}.
318     */
319    public static final class Unit implements Serializable {
320
321        private final transient String name;
322        private final transient String abbreviation;
323        final transient boolean requiresIntegers;
324
325        private Unit(String name, String abbreviation,
326                boolean requiresIntegers) {
327            this.name = name;
328            this.abbreviation = abbreviation;
329            this.requiresIntegers = requiresIntegers;
330        }
331
332        /**
333         * Returns an instance of <code>Unit</code> that corresponds to the
334         * specified string.
335         *
336         * @param str   the encoded unit
337         * @param horizontal  true for a horizontal unit, false for vertical
338         * @return the corresponding Unit
339         * @exception IllegalArgumentException if no Unit exists for the string
340         */
341        static Unit valueOf(String str, boolean horizontal) {
342            String lowerCase = str.toLowerCase(Locale.ENGLISH);
343            if (lowerCase.equals("px") || lowerCase.length() == 0) {
344                return PIXEL;
345            } else if (lowerCase.equals("dlu")) {
346                return horizontal ? DIALOG_UNITS_X : DIALOG_UNITS_Y;
347            } else if (lowerCase.equals("pt")) {
348                return POINT;
349            } else if (lowerCase.equals("in")) {
350                return INCH;
351            } else if (lowerCase.equals("mm")) {
352                return MILLIMETER;
353            } else if (lowerCase.equals("cm")) {
354                return CENTIMETER;
355            } else {
356                throw new IllegalArgumentException("Invalid unit name '" + str
357                        + "'. Must be one of: " + "px, dlu, pt, mm, cm, in");
358            }
359        }
360
361        @Override
362        public String toString() {
363            return name;
364        }
365
366        /**
367         * Returns the first character of this Unit's name.
368         * Used to identify it in short format strings.
369         *
370         * @return the first character of this Unit's name.
371         */
372        public String abbreviation() {
373            return abbreviation;
374        }
375
376        // Serialization *****************************************************
377
378        private static int nextOrdinal = 0;
379
380        private final int ordinal = nextOrdinal++;
381
382        private Object readResolve() {
383            return VALUES[ordinal]; // Canonicalize
384        }
385
386    }
387
388}