    001/* ========================================================================
    002 * JCommon : a free general purpose class library for the Java(tm) platform
    003 * ========================================================================
    004 *
    005 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
    006 *
    007 * Project Info:  http://www.jfree.org/jcommon/index.html
    008 *
    009 * This library is free software; you can redistribute it and/or modify it
    010 * under the terms of the GNU Lesser General Public License as published by
    011 * the Free Software Foundation; either version 2.1 of the License, or
    012 * (at your option) any later version.
    013 *
    014 * This library is distributed in the hope that it will be useful, but
    015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017 * License for more details.
    018 *
    019 * You should have received a copy of the GNU Lesser General Public
    020 * License along with this library; if not, write to the Free Software
    021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022 * USA.
    023 *
    024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025 * in the United States and other countries.]
    026 *
    027 * --------------------
    028 * RectangleInsets.java
    029 * --------------------
    030 * (C) Copyright 2004, 2005, 2007, by Object Refinery Limited.
    031 *
    032 * Original Author:  David Gilbert (for Object Refinery Limited);
    033 * Contributor(s):   -;
    034 *
    035 * $Id: RectangleInsets.java,v 1.15 2007/03/16 14:29:45 mungady Exp $
    036 *
    037 * Changes:
    038 * --------
    039 * 11-Feb-2004 : Version 1 (DG);
    040 * 14-Jun-2004 : Implemented Serializable (DG);
    041 * 02-Feb-2005 : Added new methods and renamed some existing methods (DG);
    042 * 22-Feb-2005 : Added a new constructor for convenience (DG);
    043 * 19-Apr-2005 : Changed order of parameters in constructors to match
    044 *               java.awt.Insets (DG);
    045 * 16-Mar-2007 : Added default constructor (DG);
    046 *
    047 */
    048
    049package org.jfree.ui;
    050
    051import java.awt.geom.Rectangle2D;
    052import java.io.Serializable;
    053
    054import org.jfree.util.UnitType;
    055
    056/**
    057 * Represents the insets for a rectangle, specified in absolute or relative
    058 * terms. This class is immutable.
    059 *
    060 * @author David Gilbert
    061 */
    062public class RectangleInsets implements Serializable {
    063
    064    /** For serialization. */
    065    private static final long serialVersionUID = 1902273207559319996L;
    066
    067    /**
    068     * A useful constant representing zero insets.
    069     */
    070    public static final RectangleInsets ZERO_INSETS = new RectangleInsets(
    071        UnitType.ABSOLUTE, 0.0, 0.0, 0.0, 0.0);
    072
    073    /** Absolute or relative units. */
    074    private UnitType unitType;
    075
    076    /** The top insets. */
    077    private double top;
    078
    079    /** The left insets. */
    080    private double left;
    081
    082    /** The bottom insets. */
    083    private double bottom;
    084
    085    /** The right insets. */
    086    private double right;
    087
    088    /**
    089     * Creates a new instance with all insets initialised to <code>1.0</code>.
    090     *
    091     * @since 1.0.9
    092     */
    093    public RectangleInsets() {
    094        this(1.0, 1.0, 1.0, 1.0);
    095    }
    096
    097    /**
    098     * Creates a new instance with the specified insets (as 'absolute' units).
    099     *
    100     * @param top  the top insets.
    101     * @param left  the left insets.
    102     * @param bottom  the bottom insets.
    103     * @param right  the right insets.
    104     */
    105    public RectangleInsets(final double top, final double left,
    106                           final double bottom, final double right) {
    107        this(UnitType.ABSOLUTE, top, left, bottom, right);
    108    }
    109
    110    /**
    111     * Creates a new instance.
    112     *
    113     * @param unitType  absolute or relative units (<code>null</code> not
    114     *                  permitted).
    115     * @param top  the top insets.
    116     * @param left  the left insets.
    117     * @param bottom  the bottom insets.
    118     * @param right  the right insets.
    119     */
    120    public RectangleInsets(final UnitType unitType,
    121                           final double top, final double left,
    122                           final double bottom, final double right) {
    123        if (unitType == null) {
    124            throw new IllegalArgumentException("Null 'unitType' argument.");
    125        }
    126        this.unitType = unitType;
    127        this.top = top;
    128        this.bottom = bottom;
    129        this.left = left;
    130        this.right = right;
    131    }
    132
    133    /**
    134     * Returns the unit type (absolute or relative).  This specifies whether
    135     * the insets are measured as Java2D units or percentages.
    136     *
    137     * @return The unit type (never <code>null</code>).
    138     */
    139    public UnitType getUnitType() {
    140        return this.unitType;
    141    }
    142
    143    /**
    144     * Returns the top insets.
    145     *
    146     * @return The top insets.
    147     */
    148    public double getTop() {
    149        return this.top;
    150    }
    151
    152    /**
    153     * Returns the bottom insets.
    154     *
    155     * @return The bottom insets.
    156     */
    157    public double getBottom() {
    158        return this.bottom;
    159    }
    160
    161    /**
    162     * Returns the left insets.
    163     *
    164     * @return The left insets.
    165     */
    166    public double getLeft() {
    167        return this.left;
    168    }
    169
    170    /**
    171     * Returns the right insets.
    172     *
    173     * @return The right insets.
    174     */
    175    public double getRight() {
    176        return this.right;
    177    }
    178
    179    /**
    180     * Tests this instance for equality with an arbitrary object.
    181     *
    182     * @param obj  the object (<code>null</code> permitted).
    183     *
    184     * @return A boolean.
    185     */
    186    public boolean equals(final Object obj) {
    187        if (obj == this) {
    188            return true;
    189        }
    190        if (!(obj instanceof RectangleInsets)) {
    191                return false;
    192        }
    193        final RectangleInsets that = (RectangleInsets) obj;
    194        if (that.unitType != this.unitType) {
    195            return false;
    196        }
    197        if (this.left != that.left) {
    198            return false;
    199        }
    200        if (this.right != that.right) {
    201            return false;
    202        }
    203        if (this.top != that.top) {
    204            return false;
    205        }
    206        if (this.bottom != that.bottom) {
    207            return false;
    208        }
    209        return true;
    210    }
    211
    212    /**
    213     * Returns a hash code for the object.
    214     *
    215     * @return A hash code.
    216     */
    217    public int hashCode() {
    218        int result;
    219        long temp;
    220        result = (this.unitType != null ? this.unitType.hashCode() : 0);
    221        temp = this.top != +0.0d ? Double.doubleToLongBits(this.top) : 0L;
    222        result = 29 * result + (int) (temp ^ (temp >>> 32));
    223        temp = this.bottom != +0.0d ? Double.doubleToLongBits(this.bottom) : 0L;
    224        result = 29 * result + (int) (temp ^ (temp >>> 32));
    225        temp = this.left != +0.0d ? Double.doubleToLongBits(this.left) : 0L;
    226        result = 29 * result + (int) (temp ^ (temp >>> 32));
    227        temp = this.right != +0.0d ? Double.doubleToLongBits(this.right) : 0L;
    228        result = 29 * result + (int) (temp ^ (temp >>> 32));
    229        return result;
    230    }
    231
    232    /**
    233     * Returns a textual representation of this instance, useful for debugging
    234     * purposes.
    235     *
    236     * @return A string representing this instance.
    237     */
    238    public String toString() {
    239        return "RectangleInsets[t=" + this.top + ",l=" + this.left
    240                + ",b=" + this.bottom + ",r=" + this.right + "]";
    241    }
    242
    243    /**
    244     * Creates an adjusted rectangle using the supplied rectangle, the insets
    245     * specified by this instance, and the horizontal and vertical
    246     * adjustment types.
    247     *
    248     * @param base  the base rectangle (<code>null</code> not permitted).
    249     * @param horizontal  the horizontal adjustment type (<code>null</code> not
    250     *                    permitted).
    251     * @param vertical  the vertical adjustment type (<code>null</code> not
    252     *                  permitted).
    253     *
    254     * @return The inset rectangle.
    255     */
    256    public Rectangle2D createAdjustedRectangle(final Rectangle2D base,
    257                                          final LengthAdjustmentType horizontal,
    258                                                  final LengthAdjustmentType vertical) {
    259        if (base == null) {
    260            throw new IllegalArgumentException("Null 'base' argument.");
    261        }
    262        double x = base.getX();
    263        double y = base.getY();
    264        double w = base.getWidth();
    265        double h = base.getHeight();
    266        if (horizontal == LengthAdjustmentType.EXPAND) {
    267            final double leftOutset = calculateLeftOutset(w);
    268            x = x - leftOutset;
    269            w = w + leftOutset + calculateRightOutset(w);
    270        }
    271        else if (horizontal == LengthAdjustmentType.CONTRACT) {
    272            final double leftMargin = calculateLeftInset(w);
    273            x = x + leftMargin;
    274            w = w - leftMargin - calculateRightInset(w);
    275        }
    276        if (vertical == LengthAdjustmentType.EXPAND) {
    277            final double topMargin = calculateTopOutset(h);
    278            y = y - topMargin;
    279            h = h + topMargin + calculateBottomOutset(h);
    280        }
    281        else if (vertical == LengthAdjustmentType.CONTRACT) {
    282            final double topMargin = calculateTopInset(h);
    283            y = y + topMargin;
    284            h = h - topMargin - calculateBottomInset(h);
    285        }
    286        return new Rectangle2D.Double(x, y, w, h);
    287    }
    288
    289    /**
    290     * Creates an 'inset' rectangle.
    291     *
    292     * @param base  the base rectangle (<code>null</code> not permitted).
    293     *
    294     * @return The inset rectangle.
    295     */
    296    public Rectangle2D createInsetRectangle(final Rectangle2D base) {
    297        return createInsetRectangle(base, true, true);
    298    }
    299
    300    /**
    301     * Creates an 'inset' rectangle.
    302     *
    303     * @param base  the base rectangle (<code>null</code> not permitted).
    304     * @param horizontal  apply horizontal insets?
    305     * @param vertical  apply vertical insets?
    306     *
    307     * @return The inset rectangle.
    308     */
    309    public Rectangle2D createInsetRectangle(final Rectangle2D base,
    310                                            final boolean horizontal,
    311                                            final boolean vertical) {
    312        if (base == null) {
    313            throw new IllegalArgumentException("Null 'base' argument.");
    314        }
    315        double topMargin = 0.0;
    316        double bottomMargin = 0.0;
    317        if (vertical) {
    318            topMargin = calculateTopInset(base.getHeight());
    319            bottomMargin = calculateBottomInset(base.getHeight());
    320        }
    321        double leftMargin = 0.0;
    322        double rightMargin = 0.0;
    323        if (horizontal) {
    324            leftMargin = calculateLeftInset(base.getWidth());
    325            rightMargin = calculateRightInset(base.getWidth());
    326        }
    327        return new Rectangle2D.Double(
    328            base.getX() + leftMargin,
    329            base.getY() + topMargin,
    330            base.getWidth() - leftMargin - rightMargin,
    331            base.getHeight() - topMargin - bottomMargin
    332        );
    333    }
    334
    335    /**
    336     * Creates an outset rectangle.
    337     *
    338     * @param base  the base rectangle (<code>null</code> not permitted).
    339     *
    340     * @return An outset rectangle.
    341     */
    342    public Rectangle2D createOutsetRectangle(final Rectangle2D base) {
    343        return createOutsetRectangle(base, true, true);
    344    }
    345
    346    /**
    347     * Creates an outset rectangle.
    348     *
    349     * @param base  the base rectangle (<code>null</code> not permitted).
    350     * @param horizontal  apply horizontal insets?
    351     * @param vertical  apply vertical insets?
    352     *
    353     * @return An outset rectangle.
    354     */
    355    public Rectangle2D createOutsetRectangle(final Rectangle2D base,
    356                                             final boolean horizontal,
    357                                             final boolean vertical) {
    358        if (base == null) {
    359            throw new IllegalArgumentException("Null 'base' argument.");
    360        }
    361        double topMargin = 0.0;
    362        double bottomMargin = 0.0;
    363        if (vertical) {
    364            topMargin = calculateTopOutset(base.getHeight());
    365            bottomMargin = calculateBottomOutset(base.getHeight());
    366        }
    367        double leftMargin = 0.0;
    368        double rightMargin = 0.0;
    369        if (horizontal) {
    370            leftMargin = calculateLeftOutset(base.getWidth());
    371            rightMargin = calculateRightOutset(base.getWidth());
    372        }
    373        return new Rectangle2D.Double(
    374            base.getX() - leftMargin,
    375            base.getY() - topMargin,
    376            base.getWidth() + leftMargin + rightMargin,
    377            base.getHeight() + topMargin + bottomMargin
    378        );
    379    }
    380
    381    /**
    382     * Returns the top margin.
    383     *
    384     * @param height  the height of the base rectangle.
    385     *
    386     * @return The top margin (in Java2D units).
    387     */
    388    public double calculateTopInset(final double height) {
    389        double result = this.top;
    390        if (this.unitType == UnitType.RELATIVE) {
    391            result = (this.top * height);
    392        }
    393        return result;
    394    }
    395
    396    /**
    397     * Returns the top margin.
    398     *
    399     * @param height  the height of the base rectangle.
    400     *
    401     * @return The top margin (in Java2D units).
    402     */
    403    public double calculateTopOutset(final double height) {
    404        double result = this.top;
    405        if (this.unitType == UnitType.RELATIVE) {
    406            result = (height / (1 - this.top - this.bottom)) * this.top;
    407        }
    408        return result;
    409    }
    410
    411    /**
    412     * Returns the bottom margin.
    413     *
    414     * @param height  the height of the base rectangle.
    415     *
    416     * @return The bottom margin (in Java2D units).
    417     */
    418    public double calculateBottomInset(final double height) {
    419        double result = this.bottom;
    420        if (this.unitType == UnitType.RELATIVE) {
    421            result = (this.bottom * height);
    422        }
    423        return result;
    424    }
    425
    426    /**
    427     * Returns the bottom margin.
    428     *
    429     * @param height  the height of the base rectangle.
    430     *
    431     * @return The bottom margin (in Java2D units).
    432     */
    433    public double calculateBottomOutset(final double height) {
    434        double result = this.bottom;
    435        if (this.unitType == UnitType.RELATIVE) {
    436            result = (height / (1 - this.top - this.bottom)) * this.bottom;
    437        }
    438        return result;
    439    }
    440
    441    /**
    442     * Returns the left margin.
    443     *
    444     * @param width  the width of the base rectangle.
    445     *
    446     * @return The left margin (in Java2D units).
    447     */
    448    public double calculateLeftInset(final double width) {
    449        double result = this.left;
    450        if (this.unitType == UnitType.RELATIVE) {
    451            result = (this.left * width);
    452        }
    453        return result;
    454    }
    455
    456    /**
    457     * Returns the left margin.
    458     *
    459     * @param width  the width of the base rectangle.
    460     *
    461     * @return The left margin (in Java2D units).
    462     */
    463    public double calculateLeftOutset(final double width) {
    464        double result = this.left;
    465        if (this.unitType == UnitType.RELATIVE) {
    466            result = (width / (1 - this.left - this.right)) * this.left;
    467        }
    468        return result;
    469    }
    470
    471    /**
    472     * Returns the right margin.
    473     *
    474     * @param width  the width of the base rectangle.
    475     *
    476     * @return The right margin (in Java2D units).
    477     */
    478    public double calculateRightInset(final double width) {
    479        double result = this.right;
    480        if (this.unitType == UnitType.RELATIVE) {
    481            result = (this.right * width);
    482        }
    483        return result;
    484    }
    485
    486    /**
    487     * Returns the right margin.
    488     *
    489     * @param width  the width of the base rectangle.
    490     *
    491     * @return The right margin (in Java2D units).
    492     */
    493    public double calculateRightOutset(final double width) {
    494        double result = this.right;
    495        if (this.unitType == UnitType.RELATIVE) {
    496            result = (width / (1 - this.left - this.right)) * this.right;
    497        }
    498        return result;
    499    }
    500
    501    /**
    502     * Trims the given width to allow for the insets.
    503     *
    504     * @param width  the width.
    505     *
    506     * @return The trimmed width.
    507     */
    508    public double trimWidth(final double width) {
    509        return width - calculateLeftInset(width) - calculateRightInset(width);
    510    }
    511
    512    /**
    513     * Extends the given width to allow for the insets.
    514     *
    515     * @param width  the width.
    516     *
    517     * @return The extended width.
    518     */
    519    public double extendWidth(final double width) {
    520        return width + calculateLeftOutset(width) + calculateRightOutset(width);
    521    }
    522
    523    /**
    524     * Trims the given height to allow for the insets.
    525     *
    526     * @param height  the height.
    527     *
    528     * @return The trimmed height.
    529     */
    530    public double trimHeight(final double height) {
    531        return height
    532               - calculateTopInset(height) - calculateBottomInset(height);
    533    }
    534
    535    /**
    536     * Extends the given height to allow for the insets.
    537     *
    538     * @param height  the height.
    539     *
    540     * @return The extended height.
    541     */
    542    public double extendHeight(final double height) {
    543        return height
    544               + calculateTopOutset(height) + calculateBottomOutset(height);
    545    }
    546
    547    /**
    548     * Shrinks the given rectangle by the amount of these insets.
    549     *
    550     * @param area  the area (<code>null</code> not permitted).
    551     */
    552    public void trim(final Rectangle2D area) {
    553        final double w = area.getWidth();
    554        final double h = area.getHeight();
    555        final double l = calculateLeftInset(w);
    556        final double r = calculateRightInset(w);
    557        final double t = calculateTopInset(h);
    558        final double b = calculateBottomInset(h);
    559        area.setRect(area.getX() + l, area.getY() + t, w - l - r, h - t - b);
    560    }
    561
    562}